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

This commit is contained in:
sean-brydon 2022-12-08 10:23:14 +00:00
commit 1a25dfcbf8
271 changed files with 3601 additions and 2152 deletions

View File

@ -1,22 +0,0 @@
name: Add bugs to Bug Hunting
on:
issues:
types:
- opened
- labeled
pull_request:
types:
- opened
- labeled
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.1.0
with:
project-url: https://github.com/orgs/calcom/projects/9
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
labeled: 🙋🏻help wanted

@ -1 +1 @@
Subproject commit 055699f612169bf71538e03b86a1074cb18b6759
Subproject commit 3d84ce68c9baa5d4ce7c85a37a9b8678f399b7a7

@ -1 +1 @@
Subproject commit 9174a0c261aa806b3feae983929900d2b677dafa
Subproject commit d2445da9f5e623e2c93248f8d43daf78a2eb3946

View File

@ -42,7 +42,7 @@ const Component = ({
const mutation = useAddAppMutation(null, {
onSuccess: (data) => {
if (data.setupPending) return;
if (data?.setupPending) return;
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
@ -195,7 +195,7 @@ const Component = ({
<h4 className="mt-8 font-semibold text-gray-900 ">{t("pricing")}</h4>
<span>
{price === 0 ? (
"Free"
t("free_to_use_apps")
) : (
<>
{Intl.NumberFormat("en-US", {

View File

@ -1,6 +1,7 @@
import { SchedulingType } from "@prisma/client";
import { FC, ReactNode, useEffect } from "react";
import dayjs from "@calcom/dayjs";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Badge } from "@calcom/ui";
@ -41,6 +42,7 @@ interface Props {
const BookingDescription: FC<Props> = (props) => {
const { profile, eventType, isBookingPage = false, children } = props;
const { date: bookingDate } = useRouterQuery("date");
const { t } = useLocale();
const { duration, setQuery: setDuration } = useRouterQuery("duration");
useEffect(() => {
@ -54,6 +56,21 @@ const BookingDescription: FC<Props> = (props) => {
}
}
}, []);
let requiresConfirmation = eventType?.requiresConfirmation;
let requiresConfirmationText = t("requires_confirmation");
const rcThreshold = eventType?.metadata?.requiresConfirmationThreshold;
if (rcThreshold) {
if (isBookingPage) {
if (dayjs(bookingDate).diff(dayjs(), rcThreshold.unit) > rcThreshold.time) {
requiresConfirmation = false;
}
} else {
requiresConfirmationText = t("requires_confirmation_threshold", {
...rcThreshold,
unit: rcThreshold.unit.slice(0, -1),
});
}
}
return (
<>
<UserAvatars
@ -89,16 +106,16 @@ const BookingDescription: FC<Props> = (props) => {
</div>
</div>
)}
{eventType?.requiresConfirmation && (
{requiresConfirmation && (
<div
className={classNames(
"flex items-center",
"items-top flex",
isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600"
)}>
<div>
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 " />
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] inline-block h-4 w-4 " />
</div>
{t("requires_confirmation")}
{requiresConfirmationText}
</div>
)}
<AvailableEventLocations

View File

@ -1,7 +1,6 @@
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 } from "react";
import { Toaster } from "react-hot-toast";
@ -19,7 +18,6 @@ import {
} from "@calcom/embed-core/embed-iframe";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { WEBSITE_URL } from "@calcom/lib/constants";
import getStripeAppData from "@calcom/lib/getStripeAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
@ -27,7 +25,6 @@ import notEmpty from "@calcom/lib/notEmpty";
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import { Icon, DatePicker } from "@calcom/ui";
@ -48,23 +45,6 @@ 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("/");
path.pop(); // Remove the last item (where we currently are)
path.shift(); // Removes first item e.g. if we were visitng "/teams/test/30mins" the array will new look like ["teams","test"]
const slug = path.join("/");
return (
<div className="flex h-full flex-col justify-end">
<button title={t("profile")} onClick={() => router.replace(`${WEBSITE_URL}/${slug}`)}>
<Icon.FiArrowLeft className="dark:text-darkgray-600 h-4 w-4 text-black transition-opacity hover:cursor-pointer" />
<p className="sr-only">Go Back</p>
</button>
</div>
);
};
const useSlots = ({
eventTypeId,
eventTypeSlug,
@ -237,7 +217,7 @@ function TimezoneDropdown({
return (
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 inline-block rounded-md px-2 py-2 text-left text-gray-600">
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 !mt-2 inline-block self-start rounded-md px-2 py-2 text-left text-gray-600">
<p className="flex items-center text-sm font-medium">
<Icon.FiGlobe className="min-h-4 min-w-4 mr-[10px] ml-[2px] -mt-[2px] inline-block" />
{timeZone}
@ -423,12 +403,6 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
{timezoneDropdown}
</BookingDescription>
{!isEmbed && (
<div className="mt-auto hidden md:block">
<GoBackToPreviousPage t={t} />
</div>
)}
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
<div>
<p
@ -468,7 +442,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
/>
</div>
</div>
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && <PoweredByCal />}
{(!restProps.isBrandingHidden || isEmbed) && <PoweredByCal />}
</div>
</main>
</div>

View File

@ -240,7 +240,7 @@ const BookingPage = ({
const bookingFormSchema = z
.object({
name: z.string().min(1),
email: z.string().email(),
email: z.string().trim().email(),
phone: z
.string()
.refine((val) => isValidPhoneNumber(val))

View File

@ -297,7 +297,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
control={locationFormMethods.control}
render={() => (
<Select<{ label: string; value: string; icon?: string }>
maxMenuHeight={150}
maxMenuHeight={300}
name="location"
defaultValue={selection}
options={locationOptions}

View File

@ -20,12 +20,13 @@ type AvailabilityOption = {
const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const { label, isDefault } = props.data;
const { t } = useLocale();
return (
<components.Option {...props}>
<span>{label}</span>
{isDefault && (
<Badge variant="blue" className="ml-2">
Default
{t("default")}
</Badge>
)}
</components.Option>
@ -34,12 +35,13 @@ const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const SingleValue = ({ ...props }: SingleValueProps<AvailabilityOption>) => {
const { label, isDefault } = props.data;
const { t } = useLocale();
return (
<components.SingleValue {...props}>
<span>{label}</span>
{isDefault && (
<Badge variant="blue" className="ml-2">
Default
{t("default")}
</Badge>
)}
</components.SingleValue>

View File

@ -1,371 +0,0 @@
import { ErrorMessage } from "@hookform/error-message";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { Trans } from "next-i18next";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { components } from "react-select";
import { z } from "zod";
import {
EventLocationType,
getEventLocationType,
getHumanReadableLocationValue,
getMessageForOrganizer,
LocationObject,
LocationType,
} from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogContent, Form, Icon, Label, PhoneInput, Select } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import { LinkText } from "@components/ui/LinkText";
import CheckboxField from "@components/ui/form/CheckboxField";
type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number];
type OptionTypeBase = {
label: string;
value: EventLocationType["type"];
disabled?: boolean;
};
interface ISetLocationDialog {
saveLocation: (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => void;
selection?: OptionTypeBase;
booking?: BookingItem;
defaultValues?: LocationObject[];
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
isOpenDialog: boolean;
setSelectedLocation?: (param: OptionTypeBase | undefined) => void;
}
const LocationInput = (props: {
eventLocationType: EventLocationType;
locationFormMethods: ReturnType<typeof useForm>;
id: string;
required: boolean;
placeholder: string;
className?: string;
defaultValue?: string;
}): JSX.Element | null => {
const { eventLocationType, locationFormMethods, ...remainingProps } = props;
if (eventLocationType?.organizerInputType === "text") {
return (
<input {...locationFormMethods.register(eventLocationType.variable)} type="text" {...remainingProps} />
);
} else if (eventLocationType?.organizerInputType === "phone") {
return (
<PhoneInput
name={eventLocationType.variable}
control={locationFormMethods.control}
{...remainingProps}
/>
);
}
return null;
};
export const EditLocationDialog = (props: ISetLocationDialog) => {
const {
saveLocation,
selection,
booking,
setShowLocationModal,
isOpenDialog,
defaultValues,
setSelectedLocation,
} = props;
const { t } = useLocale();
const locationsQuery = trpc.viewer.locationOptions.useQuery();
useEffect(() => {
if (selection) {
locationFormMethods.setValue("locationType", selection?.value);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selection]);
const locationFormSchema = z.object({
locationType: z.string(),
phone: z.string().optional(),
locationAddress: z.string().optional(),
locationLink: z
.string()
.optional()
.superRefine((val, ctx) => {
if (
eventLocationType &&
!eventLocationType.default &&
eventLocationType.linkType === "static" &&
eventLocationType.urlRegExp
) {
const valid = z
.string()
.regex(new RegExp(eventLocationType.urlRegExp || ""))
.safeParse(val).success;
if (!valid) {
const sampleUrl = eventLocationType.organizerInputPlaceholder;
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid URL for ${eventLocationType.label}. ${
sampleUrl ? "Sample URL: " + sampleUrl : ""
}`,
});
}
return;
}
const valid = z.string().url().optional().safeParse(val).success;
if (!valid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid URL`,
});
}
return;
}),
displayLocationPublicly: z.boolean().optional(),
locationPhoneNumber: z
.string()
.refine((val) => isValidPhoneNumber(val))
.optional(),
});
const locationFormMethods = useForm({
mode: "onSubmit",
resolver: zodResolver(locationFormSchema),
});
const selectedLocation = useWatch({
control: locationFormMethods.control,
name: "locationType",
});
const eventLocationType = getEventLocationType(selectedLocation);
const defaultLocation = defaultValues?.find(
(location: { type: EventLocationType["type"] }) => location.type === eventLocationType?.type
);
const LocationOptions = (() => {
if (eventLocationType && eventLocationType.organizerInputType && LocationInput) {
if (!eventLocationType.variable) {
console.error("eventLocationType.variable can't be undefined");
return null;
}
return (
<div>
<div>
<Label htmlFor="locationInput">{t(eventLocationType.messageForOrganizer || "")}</Label>
<LocationInput
locationFormMethods={locationFormMethods}
eventLocationType={eventLocationType}
id="locationInput"
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
required
className="block w-full rounded-md border-gray-300 text-sm"
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
defaultValue={
(defaultLocation && defaultLocation[eventLocationType.defaultValueVariable]) || ""
}
/>
<ErrorMessage
errors={locationFormMethods.formState.errors}
name={eventLocationType.variable}
className="mt-1 text-sm text-red-500"
as="p"
/>
</div>
{!booking && (
<div className="mt-3">
<Controller
name="displayLocationPublicly"
control={locationFormMethods.control}
render={() => (
<CheckboxField
defaultChecked={defaultLocation?.displayLocationPublicly}
description={t("display_location_label")}
onChange={(e) =>
locationFormMethods.setValue("displayLocationPublicly", e.target.checked)
}
informationIconText={t("display_location_info_badge")}
/>
)}
/>
</div>
)}
</div>
);
} else {
return <p className="text-sm">{getMessageForOrganizer(selectedLocation, t)}</p>;
}
})();
return (
<Dialog open={isOpenDialog}>
<DialogContent
type="creation"
Icon={Icon.FiMapPin}
title={t("edit_location")}
description={!booking ? t("this_input_will_shown_booking_this_event") : undefined}>
{booking && (
<>
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
<p className="mb-2 ml-1 text-sm text-black">
{getHumanReadableLocationValue(booking.location, t)}
</p>
</>
)}
<Form
form={locationFormMethods}
className="space-y-4"
handleSubmit={async (values) => {
const { locationType: newLocation, displayLocationPublicly } = values;
let details = {};
if (newLocation === LocationType.AttendeeInPerson) {
details = {
address: values.locationAddress,
};
}
if (newLocation === LocationType.InPerson) {
details = {
address: values.locationAddress,
};
}
const eventLocationType = getEventLocationType(newLocation);
// TODO: There can be a property that tells if it is to be saved in `link`
if (
newLocation === LocationType.Link ||
(!eventLocationType?.default && eventLocationType?.linkType === "static")
) {
details = { link: values.locationLink };
}
if (newLocation === LocationType.UserPhone) {
details = { hostPhoneNumber: values.locationPhoneNumber };
}
if (eventLocationType?.organizerInputType) {
details = {
...details,
displayLocationPublicly,
};
}
saveLocation(newLocation, details);
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister([
"locationType",
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
}}>
<QueryCell
query={locationsQuery}
success={({ data: locationOptions }) => {
if (!locationOptions.length) return null;
if (booking) {
locationOptions.forEach((location) => {
if (location.label === "phone") {
location.options.filter((l) => l.value !== "phone");
}
});
}
return (
<Controller
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select<{ label: string; value: string; icon?: string }>
maxMenuHeight={150}
name="location"
defaultValue={selection}
options={locationOptions}
components={{
Option: (props) => (
<components.Option {...props}>
<div className="flex items-center gap-3">
{props.data.icon && (
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" />
)}
<span
className={classNames(
"text-sm font-medium",
props.isSelected ? "text-white" : "text-gray-900"
)}>
{props.data.label}
</span>
</div>
</components.Option>
),
}}
formatOptionLabel={(e) => (
<div className="flex items-center gap-2.5">
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => <p className="text-xs font-medium text-gray-600">{e.label}</p>}
isSearchable={false}
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
)}
/>
);
}}
/>
{selectedLocation && LocationOptions}
<div className="mt-5">
<p className="text-sm text-gray-400">
<Trans i18nKey="cant_find_the_right_video_app_visit_our_app_store">
Can&apos;t find the right video app? Visit our
<LinkText href="/apps/categories/video" classNameChildren="text-blue-400" target="_blank">
App Store
</LinkText>
.
</Trans>
</p>
</div>
<div className="mt-4 flex justify-end space-x-2">
<Button
onClick={() => {
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister("locationType");
}}
type="button"
color="secondary">
{t("cancel")}
</Button>
<Button type="submit">{t("update")}</Button>
</div>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@ -27,6 +27,8 @@ import {
import CustomInputTypeForm from "@components/eventtype/CustomInputTypeForm";
import RequiresConfirmationController from "./RequiresConfirmationController";
const generateHashedLink = (id: number) => {
const translator = short();
const seed = `${id}:${new Date().getTime()}`;
@ -47,6 +49,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
);
const [selectedCustomInput, setSelectedCustomInput] = useState<CustomInputParsed | undefined>(undefined);
const [selectedCustomInputModalOpen, setSelectedCustomInputModalOpen] = useState(false);
const [requiresConfirmation, setRequiresConfirmation] = useState(eventType.requiresConfirmation);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
const seatsEnabled = formMethods.getValues("seatsPerTimeSlotEnabled");
@ -100,7 +103,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<TextField
label={t("event_name")}
type="text"
placeholder={t("meeting_with_user")}
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
addOnSuffix={
@ -159,18 +162,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
</SettingsToggle>
</div>
<hr />
<Controller
name="requiresConfirmation"
defaultValue={eventType.requiresConfirmation}
render={({ field: { value, onChange } }) => (
<SettingsToggle
title={t("requires_confirmation")}
description={t("requires_confirmation_description")}
checked={value}
onCheckedChange={(e) => onChange(e)}
disabled={seatsEnabled}
/>
)}
<RequiresConfirmationController
seatsEnabled={seatsEnabled}
metadata={eventType.metadata}
requiresConfirmation={requiresConfirmation}
onRequiresConfirmation={setRequiresConfirmation}
/>
<hr />
<Controller
@ -306,6 +302,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
if (e) {
formMethods.setValue("disableGuests", true);
formMethods.setValue("requiresConfirmation", false);
setRequiresConfirmation(false);
formMethods.setValue("seatsPerTimeSlot", 2);
} else {
formMethods.setValue("seatsPerTimeSlot", null);
@ -355,7 +352,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<TextField
label={t("event_name")}
type="text"
placeholder={t("meeting_with_user")}
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
/>

View File

@ -33,22 +33,10 @@ export const EventSetupTab = (
const [selectedLocation, setSelectedLocation] = useState<OptionTypeBase | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration);
const multipleDurationOptions = [
{ value: 5, label: t("multiple_duration_mins", { count: 5 }) },
{ value: 10, label: t("multiple_duration_mins", { count: 10 }) },
{ value: 15, label: t("multiple_duration_mins", { count: 15 }) },
{ value: 20, label: t("multiple_duration_mins", { count: 20 }) },
{ value: 25, label: t("multiple_duration_mins", { count: 25 }) },
{ value: 30, label: t("multiple_duration_mins", { count: 30 }) },
{ value: 45, label: t("multiple_duration_mins", { count: 45 }) },
{ value: 50, label: t("multiple_duration_mins", { count: 50 }) },
{ value: 60, label: t("multiple_duration_mins", { count: 60 }) },
{ value: 75, label: t("multiple_duration_mins", { count: 75 }) },
{ value: 80, label: t("multiple_duration_mins", { count: 80 }) },
{ value: 90, label: t("multiple_duration_mins", { count: 90 }) },
{ value: 120, label: t("multiple_duration_mins", { count: 120 }) },
{ value: 180, label: t("multiple_duration_mins", { count: 180 }) },
];
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({
value: mins,
label: t("multiple_duration_mins", { count: mins }),
}));
const [selectedMultipleDuration, setSelectedMultipleDuration] = useState<
MultiValue<{
@ -142,6 +130,7 @@ export const EventSetupTab = (
{validLocations.length === 0 && (
<div className="flex">
<Select
placeholder={t("select")}
options={locationOptions}
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
@ -224,7 +213,7 @@ export const EventSetupTab = (
<div className="space-y-8">
<TextField
required
label={t("Title")}
label={t("title")}
defaultValue={eventType.title}
{...formMethods.register("title")}
/>

View File

@ -67,7 +67,7 @@ function getNavigation(props: {
name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`,
icon: Icon.FiCalendar,
info: `Working Hours`, // TODO: Get this from props
info: `default_schedule_name`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",

View File

@ -0,0 +1,156 @@
import * as RadioGroup from "@radix-ui/react-radio-group";
import { UnitTypeLongPlural } from "dayjs";
import { Trans } from "next-i18next";
import type { FormValues } from "pages/event-types/[type]";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import z from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Alert, Input, Label, SettingsToggle } from "@calcom/ui";
type RequiresConfirmationControllerProps = {
metadata: z.infer<typeof EventTypeMetaDataSchema>;
requiresConfirmation: boolean;
onRequiresConfirmation: Dispatch<SetStateAction<boolean>>;
seatsEnabled: boolean;
};
export default function RequiresConfirmationController({
metadata,
requiresConfirmation,
onRequiresConfirmation,
seatsEnabled,
}: RequiresConfirmationControllerProps) {
const { t } = useLocale();
const [requiresConfirmationSetup, setRequiresConfirmationSetup] = useState(
metadata?.requiresConfirmationThreshold
);
const defaultRequiresConfirmationSetup = { time: 30, unit: "minutes" as UnitTypeLongPlural };
const formMethods = useFormContext<FormValues>();
useEffect(() => {
if (!requiresConfirmation) {
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
}
}, [requiresConfirmation]);
return (
<div className="block items-start sm:flex">
<div className={!seatsEnabled ? "w-full" : ""}>
{seatsEnabled ? (
<Alert severity="warning" title="Seats option doesn't support confirmation requirement" />
) : (
<Controller
name="requiresConfirmation"
control={formMethods.control}
render={() => (
<SettingsToggle
title={t("requires_confirmation")}
description={t("requires_confirmation_description")}
checked={requiresConfirmation}
onCheckedChange={(val) => {
formMethods.setValue("requiresConfirmation", val);
onRequiresConfirmation(val);
}}>
<RadioGroup.Root
defaultValue={
requiresConfirmation
? requiresConfirmationSetup === undefined
? "always"
: "notice"
: undefined
}
onValueChange={(val) => {
if (val === "always") {
formMethods.setValue("requiresConfirmation", true);
onRequiresConfirmation(true);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
setRequiresConfirmationSetup(undefined);
} else if (val === "notice") {
formMethods.setValue("requiresConfirmation", true);
onRequiresConfirmation(true);
formMethods.setValue(
"metadata.requiresConfirmationThreshold",
requiresConfirmationSetup || defaultRequiresConfirmationSetup
);
}
}}>
<div className="-ml-2 flex flex-col flex-wrap justify-start gap-y-2 space-y-2">
<div className="flex items-center">
<RadioGroup.Item
id="always"
value="always"
className="min-w-4 flex h-4 w-4 cursor-pointer items-center rounded-full border border-black bg-white focus:border-2 focus:outline-none ltr:mr-2 rtl:ml-2">
<RadioGroup.Indicator className="relative flex h-4 w-4 items-center justify-center after:block after:h-2 after:w-2 after:rounded-full after:bg-black" />
</RadioGroup.Item>
<Label htmlFor="always" className="!m-0 flex items-center">
{t("always_requires_confirmation")}
</Label>
</div>
<div className="flex items-center">
<RadioGroup.Item
id="notice"
value="notice"
className="min-w-4 flex h-4 w-4 cursor-pointer items-center rounded-full border border-black bg-white focus:border-2 focus:outline-none ltr:mr-2 rtl:ml-2">
<RadioGroup.Indicator className="relative flex h-4 w-4 items-center justify-center after:block after:h-2 after:w-2 after:rounded-full after:bg-black" />
</RadioGroup.Item>
<Label htmlFor="notice" className="!m-0 flex items-center">
<Trans
i18nKey="when_booked_with_less_than_notice"
defaults="When booked with less than <time></time> notice"
components={{
time: (
<div className="mx-2 flex">
<Input
type="number"
min={1}
onChange={(evt) => {
const val = Number(evt.target?.value);
setRequiresConfirmationSetup({
unit:
requiresConfirmationSetup?.unit ??
defaultRequiresConfirmationSetup.unit,
time: val,
});
formMethods.setValue("metadata.requiresConfirmationThreshold.time", val);
}}
className="!m-0 block w-16 rounded-md border-gray-300 text-sm [appearance:textfield]"
defaultValue={metadata?.requiresConfirmationThreshold?.time || 30}
/>
<select
onChange={(evt) => {
const val = evt.target.value as UnitTypeLongPlural;
setRequiresConfirmationSetup({
time:
requiresConfirmationSetup?.time ??
defaultRequiresConfirmationSetup.time,
unit: val,
});
formMethods.setValue("metadata.requiresConfirmationThreshold.unit", val);
}}
className="ml-2 block h-9 rounded-md border-gray-300 py-2 pl-3 pr-10 text-sm focus:outline-none"
defaultValue={
metadata?.requiresConfirmationThreshold?.unit ||
defaultRequiresConfirmationSetup.unit
}>
<option value="minutes">{t("minute_timeUnit")}</option>
<option value="hours">{t("hour_timeUnit")}</option>
</select>
</div>
),
}}
/>
</Label>
</div>
</div>
</RadioGroup.Root>
</SettingsToggle>
)}
/>
)}
</div>
</div>
);
}

View File

@ -1,13 +1,13 @@
import { ArrowRightIcon } from "@heroicons/react/outline";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, TimezoneSelect } from "@calcom/ui";
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
@ -24,11 +24,9 @@ const UserSettings = (props: IUserSettingsProps) => {
register,
handleSubmit,
formState: { errors },
control,
} = useForm({
defaultValues: {
name: user?.name || "",
username: user?.username || "",
},
reValidateMode: "onChange",
});
@ -42,39 +40,19 @@ const UserSettings = (props: IUserSettingsProps) => {
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: onSuccess,
});
const { data: stripeCustomer } = trpc.viewer.stripeCustomer.useQuery();
const paymentRequired = stripeCustomer?.isPremium ? !stripeCustomer?.paidForPremium : false;
const onSubmit = handleSubmit((data) => {
if (paymentRequired) {
return;
}
mutation.mutate({
name: data.name,
timeZone: selectedTimeZone,
});
});
const [currentUsername, setCurrentUsername] = useState(user.username || "");
return (
<form onSubmit={onSubmit}>
<div className="space-y-6">
{/* Username textfield */}
<Controller
control={control}
name="username"
render={({ field: { value, ref, onChange } }) => (
<UsernameAvailability
readonly={true}
currentUsername={currentUsername}
setCurrentUsername={setCurrentUsername}
inputUsernameValue={value}
usernameRef={ref}
setInputUsernameValue={onChange}
user={user}
/>
)}
/>
<UsernameAvailabilityField user={user} />
{/* Full name textfield */}
<div className="w-full">

View File

@ -0,0 +1,73 @@
import type { Attendee, Booking, User } from "@prisma/client";
import type { FC } from "react";
import { useMemo } from "react";
import { JsonLd } from "react-schemaorg";
import type { EventReservation, Person, ReservationStatusType } from "schema-dts";
type EventSchemaUser = Pick<User, "name" | "email">;
type EventSchemaAttendee = Pick<Attendee, "name" | "email">;
interface EventReservationSchemaInterface {
reservationId: Booking["uid"];
eventName: Booking["title"];
startTime: Booking["startTime"];
endTime: Booking["endTime"];
organizer: EventSchemaUser | null;
attendees: EventSchemaAttendee[];
location: Booking["location"];
description: Booking["description"];
status: Booking["status"];
}
const EventReservationSchema: FC<EventReservationSchemaInterface> = ({
reservationId,
eventName,
startTime,
endTime,
organizer,
attendees,
location,
description,
status,
}) => {
const reservationStatus = useMemo<ReservationStatusType>(() => {
switch (status) {
case "ACCEPTED":
return "ReservationConfirmed";
case "REJECTED":
case "CANCELLED":
return "ReservationCancelled";
case "PENDING":
return "ReservationPending";
default:
return "ReservationHold";
}
}, [status]);
return (
<JsonLd<EventReservation>
item={{
"@context": "https://schema.org",
"@type": "EventReservation",
reservationId,
reservationStatus,
reservationFor: {
"@type": "Event",
name: eventName,
startDate: startTime.toString(),
endDate: endTime.toString(),
organizer: organizer
? ({ "@type": "Person", name: organizer.name, email: organizer.email } as Person)
: undefined,
attendee: attendees?.map(
(person) => ({ "@type": "Person", name: person.name, email: person.email } as Person)
),
location: location || undefined,
description: description || undefined,
},
}}
/>
);
};
export default EventReservationSchema;

View File

@ -3,7 +3,7 @@ import { debounce, noop } from "lodash";
import { useRouter } from "next/router";
import { RefCallback, useEffect, useMemo, useState } from "react";
import { getPremiumPlanMode, getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
import { fetchUsername } from "@calcom/lib/fetchUsername";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -24,9 +24,7 @@ import {
} from "@calcom/ui";
export enum UsernameChangeStatusEnum {
NORMAL = "NORMAL",
UPGRADE = "UPGRADE",
DOWNGRADE = "DOWNGRADE",
}
interface ICustomUsernameProps {
@ -37,43 +35,21 @@ interface ICustomUsernameProps {
setInputUsernameValue: (value: string) => void;
onSuccessMutation?: () => void;
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
user: Pick<
User,
| "username"
| "name"
| "email"
| "bio"
| "avatar"
| "timeZone"
| "weekStart"
| "hideBranding"
| "theme"
| "plan"
| "brandColor"
| "darkBrandColor"
| "timeFormat"
| "metadata"
>;
user: Pick<User, "username" | "metadata">;
readonly?: boolean;
}
const obtainNewUsernameChangeCondition = ({
userIsPremium,
isNewUsernamePremium,
stripeCustomer,
}: {
userIsPremium: boolean;
isNewUsernamePremium: boolean;
stripeCustomer: RouterOutputs["viewer"]["stripeCustomer"] | undefined;
}) => {
if (!userIsPremium && isNewUsernamePremium && !stripeCustomer?.paidForPremium) {
if (!userIsPremium && isNewUsernamePremium) {
return UsernameChangeStatusEnum.UPGRADE;
}
if (userIsPremium && !isNewUsernamePremium && getPremiumPlanMode() === "subscription") {
return UsernameChangeStatusEnum.DOWNGRADE;
}
return UsernameChangeStatusEnum.NORMAL;
};
const PremiumTextfield = (props: ICustomUsernameProps) => {
@ -106,7 +82,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
setIsInputUsernamePremium(data.premium);
setUsernameIsAvailable(data.available);
}, 150),
[]
[currentUsername]
);
useEffect(() => {
@ -137,7 +113,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
});
// when current username isn't set - Go to stripe to check what username he wanted to buy and was it a premium and was it paid for
const paymentRequired = !currentUsername && stripeCustomer?.isPremium && !stripeCustomer?.paidForPremium;
const paymentRequired = !currentUsername && stripeCustomer?.isPremium;
const usernameChangeCondition = obtainNewUsernameChangeCondition({
userIsPremium: isCurrentUsernamePremium,
@ -194,7 +170,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
};
const saveUsername = () => {
if (usernameChangeCondition === UsernameChangeStatusEnum.NORMAL) {
if (usernameChangeCondition !== UsernameChangeStatusEnum.UPGRADE) {
updateUsername.mutate({
username: inputUsernameValue,
});
@ -279,14 +255,6 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
{paymentMsg}
{markAsError && <p className="mt-1 text-xs text-red-500">Username is already taken</p>}
{usernameIsAvailable && (
<p className={classNames("mt-1 text-xs text-gray-900")}>
{usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE && (
<>{t("premium_to_standard_username_description")}</>
)}
</p>
)}
<Dialog open={openDialogSaveUsername}>
<DialogContent>
<div className="flex flex-row">
@ -295,13 +263,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
</div>
<div className="mb-4 w-full px-4 pt-1">
<DialogHeader title={t("confirm_username_change_dialog_title")} />
{usernameChangeCondition && usernameChangeCondition !== UsernameChangeStatusEnum.NORMAL && (
<p className="-mt-4 mb-4 text-sm text-gray-800">
{usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE &&
t("change_username_standard_to_premium")}
{usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE &&
t("change_username_premium_to_standard")}
</p>
{usernameChangeCondition && usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE && (
<p className="mb-4 text-sm text-gray-800">{t("change_username_standard_to_premium")}</p>
)}
<div className="flex w-full flex-wrap rounded-sm bg-gray-100 py-3 text-sm">
@ -323,8 +286,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<div className="mt-4 flex flex-row-reverse gap-x-2">
{/* redirect to checkout */}
{(usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE ||
usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE) && (
{usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE && (
<Button
type="button"
loading={updateUsername.isLoading}
@ -336,7 +298,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
</Button>
)}
{/* Normal save */}
{usernameChangeCondition === UsernameChangeStatusEnum.NORMAL && (
{usernameChangeCondition !== UsernameChangeStatusEnum.UPGRADE && (
<Button
type="button"
loading={updateUsername.isLoading}

View File

@ -124,6 +124,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
<Input
ref={usernameRef}
name="username"
value={inputUsernameValue}
autoComplete="none"
autoCapitalize="none"
autoCorrect="none"
@ -133,7 +134,6 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none focus:ring-0"
: ""
)}
defaultValue={currentUsername}
onChange={(event) => {
event.preventDefault();
setInputUsernameValue(event.target.value);

View File

@ -1,6 +1,51 @@
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
import { User } from "@calcom/prisma/client";
import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
import { PremiumTextfield } from "./PremiumTextfield";
import { UsernameTextfield } from "./UsernameTextfield";
export const UsernameAvailability = IS_SELF_HOSTED ? UsernameTextfield : PremiumTextfield;
interface UsernameAvailabilityFieldProps {
onSuccessMutation?: () => void;
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
user: Pick<User, "username" | "metadata">;
}
export const UsernameAvailabilityField = ({
onSuccessMutation,
onErrorMutation,
user,
}: UsernameAvailabilityFieldProps) => {
const [currentUsername, setCurrentUsername] = useState(user.username ?? "");
const formMethods = useForm({
defaultValues: {
username: currentUsername,
},
});
return (
<Controller
control={formMethods.control}
name="username"
render={({ field: { ref, onChange, value } }) => {
return (
<UsernameAvailability
currentUsername={currentUsername}
setCurrentUsername={setCurrentUsername}
inputUsernameValue={value}
usernameRef={ref}
setInputUsernameValue={onChange}
onSuccessMutation={onSuccessMutation}
onErrorMutation={onErrorMutation}
user={user}
/>
);
}}
/>
);
};

View File

@ -1,5 +1,3 @@
import { User } from "@prisma/client";
export function isBrandingHidden<TUser extends Pick<User, "hideBranding" | "plan">>(user: TUser) {
return user.hideBranding && user.plan !== "FREE";
export function isBrandingHidden(hideBrandingSetting: boolean, belongsToActiveTeam: boolean) {
return belongsToActiveTeam && hideBrandingSetting;
}

View File

@ -7,6 +7,29 @@ export type EmbedProps = {
export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
return async (context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<EmbedProps>> => {
const ssrResponse = await getServerSideProps(context);
const embed = context.query.embed;
if ("redirect" in ssrResponse) {
// Use a dummy URL https://base as the fallback base URL so that URL parsing works for relative URLs as well.
const destinationUrlObj = new URL(ssrResponse.redirect.destination, "https://base");
// Make sure that redirect happens to /embed page and pass on embed query param as is for preserving Cal JS API namespace
const newDestinationUrl =
destinationUrlObj.pathname +
"/embed?" +
destinationUrlObj.searchParams.toString() +
"&embed=" +
embed;
return {
...ssrResponse,
redirect: {
...ssrResponse.redirect,
destination: newDestinationUrl,
},
};
}
if (!("props" in ssrResponse)) {
return ssrResponse;
}

View File

@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.3.5",
"version": "2.3.6",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@ -107,12 +107,14 @@
"react-live-chat-loader": "^2.7.3",
"react-multi-email": "^0.5.3",
"react-phone-number-input": "^3.2.7",
"react-schemaorg": "^2.0.0",
"react-select": "^5.4.0",
"react-timezone-select": "^1.3.2",
"react-use-intercom": "1.5.1",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.7",
"rrule": "^2.7.1",
"schema-dts": "^1.1.0",
"short-uuid": "^4.2.0",
"stripe": "^9.16.0",
"superjson": "1.9.1",

View File

@ -1,9 +1,12 @@
import Head from "next/head";
import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui";
export default function Error500() {
const { t } = useLocale();
return (
<div className="flex h-screen">
<Head>
@ -24,7 +27,7 @@ export default function Error500() {
Something went wrong on our end. Get in touch with our support team, and well get it fixed right
away for you.
</p>
<Button href={`${WEBSITE_URL}/support`}>Contact support</Button>
<Button href={`${WEBSITE_URL}/support`}>{t("contact_support")}</Button>
<Button color="secondary" href="javascript:history.back()" className="ml-2">
Go back
</Button>

View File

@ -10,8 +10,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
import prisma from "@calcom/prisma";
import { User } from "@calcom/prisma/client";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
@ -110,6 +111,11 @@ async function getUserPageProps(context: GetStaticPropsContext) {
},
],
},
teams: {
include: {
team: true,
},
},
},
});
@ -121,7 +127,7 @@ async function getUserPageProps(context: GetStaticPropsContext) {
"strikethrough",
]);
if (!user || !user.eventTypes) return { notFound: true };
if (!user || !user.eventTypes.length) return { notFound: true };
const [eventType]: (typeof user.eventTypes[number] & {
users: Pick<User, "name" | "username" | "hideBranding" | "plan" | "timeZone">[];
@ -150,6 +156,13 @@ async function getUserPageProps(context: GetStaticPropsContext) {
locations: privacyFilteredLocations(locations),
descriptionAsSafeHTML: eventType.description ? md.render(eventType.description) : null,
});
// Check if the user you are logging into has any active teams
const hasActiveTeam =
user.teams.filter((m) => {
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
if (metadata.success && metadata.data?.subscriptionId) return false;
return true;
}).length > 0;
return {
props: {
@ -167,6 +180,7 @@ async function getUserPageProps(context: GetStaticPropsContext) {
away: user?.away,
isDynamic: false,
trpcState: ssg.dehydrate(),
isBrandingHidden: isBrandingHidden(user.hideBranding, hasActiveTeam),
},
revalidate: 10, // seconds
};
@ -262,6 +276,7 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
isDynamic: true,
away: false,
trpcState: ssg.dehydrate(),
isBrandingHidden: false, // I think we should always show branding for dynamic groups - saves us checking every single user
},
revalidate: 10, // seconds
};

View File

@ -3,10 +3,9 @@ import Document, { DocumentContext, Head, Html, Main, NextScript, DocumentProps
type Props = Record<string, unknown> & DocumentProps;
function toRunBeforeReactOnClient() {
const calEmbedMode =
location.search.includes("embed=") ||
/* Iframe Name */
window.name.includes("cal-embed");
const calEmbedMode = typeof new URL(document.URL).searchParams.get("embed") === "string";
/* Iframe Name */
window.name.includes("cal-embed");
window.isEmbed = () => {
// Once an embed mode always an embed mode

View File

@ -20,6 +20,7 @@ import { defaultCookies } from "@calcom/lib/default-cookies";
import rateLimit from "@calcom/lib/rateLimit";
import { serverConfig } from "@calcom/lib/serverConfig";
import prisma from "@calcom/prisma";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import CalComAdapter from "@lib/auth/next-auth-custom-adapter";
import { randomString } from "@lib/random";
@ -63,6 +64,11 @@ const providers: Provider[] = [
password: true,
twoFactorEnabled: true,
twoFactorSecret: true,
teams: {
include: {
team: true,
},
},
},
});
@ -116,6 +122,13 @@ const providers: Provider[] = [
intervalInMs: 60 * 1000, // 1 minute
});
await limiter.check(10, user.email); // 10 requests per minute
// Check if the user you are logging into has any active teams
const hasActiveTeams =
user.teams.filter((m) => {
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
if (metadata.success && metadata.data?.subscriptionId) return false;
return true;
}).length > 0;
// authentication success- but does it meet the minimum password requirements?
if (user.role === "ADMIN" && !isPasswordValid(credentials.password, false, true)) {
@ -125,6 +138,7 @@ const providers: Provider[] = [
email: user.email,
name: user.name,
role: "USER",
belongsToActiveTeam: hasActiveTeams,
};
}
@ -134,6 +148,7 @@ const providers: Provider[] = [
email: user.email,
name: user.name,
role: user.role,
belongsToActiveTeam: hasActiveTeams,
};
},
}),
@ -272,6 +287,7 @@ export default NextAuth({
email: user.email,
role: user.role,
impersonatedByUID: user?.impersonatedByUID,
belongsToActiveTeam: user?.belongsToActiveTeam,
};
}
@ -307,6 +323,7 @@ export default NextAuth({
email: existingUser.email,
role: existingUser.role,
impersonatedByUID: token.impersonatedByUID as number,
belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
};
}
@ -324,6 +341,7 @@ export default NextAuth({
username: token.username as string,
role: token.role as UserPermissionRole,
impersonatedByUID: token.impersonatedByUID as number,
belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
},
};
return calendsoSession;

View File

@ -60,9 +60,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write(
renderEmail("AttendeeScheduledEmail", {
attendee: evt.attendees[0],
calEvent: evt,
renderEmail("DisabledAppEmail", {
appName: "Stripe",
appType: ["payment"],
t,
})
);
res.end();

View File

@ -1,68 +0,0 @@
import { MembershipRole } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import prisma from "@calcom/prisma";
import { getSession } from "@lib/auth";
import slugify from "@lib/slugify";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({ req: req });
if (!session?.user?.id) {
res.status(401).json({ message: "Not authenticated" });
return;
}
const ownerUser = await prisma.user.findFirst({
where: {
id: session.user.id,
},
select: {
id: true,
username: true,
createdDate: true,
name: true,
plan: true,
email: true,
},
});
if (req.method === "POST") {
const slug = slugify(req.body.name);
const nameCollisions = await prisma.team.count({
where: {
OR: [{ name: req.body.name }, { slug: slug }],
},
});
if (nameCollisions > 0) {
return res.status(409).json({ errorCode: "TeamNameCollision", message: "Team name already taken." });
}
const createTeam = await prisma.team.create({
data: {
name: req.body.name,
slug: slug,
},
});
await prisma.membership.create({
data: {
teamId: createTeam.id,
userId: session.user.id,
role: MembershipRole.OWNER,
accepted: true,
},
});
// Sync Services: Close.com
closeComUpsertTeamUser(createTeam, ownerUser, MembershipRole.OWNER);
return res.status(201).json({ message: "Team created" });
}
res.status(404).json({ message: "Team not found" });
}

View File

@ -1,86 +1,72 @@
import crypto from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
import prisma from "@calcom/prisma";
import { defaultAvatarSrc } from "@lib/profile";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// const username = req.url?.substring(1, req.url.lastIndexOf("/"));
const username = req.query.username as string;
const teamname = req.query.teamname as string;
let identity;
let linksToThisRoute = false;
const querySchema = z
.object({
username: z.string(),
teamname: z.string(),
})
.partial();
async function getIdentityData(req: NextApiRequest) {
const { username, teamname } = querySchema.parse(req.query);
if (username) {
const user = await prisma.user.findUnique({
where: {
username: username,
},
select: {
avatar: true,
email: true,
},
where: { username },
select: { avatar: true, email: true },
});
identity = {
return {
name: username,
email: user?.email,
avatar: user?.avatar,
};
linksToThisRoute =
identity.avatar === `${CAL_URL}/${username}/avatar.png` ||
identity.avatar === `${WEBAPP_URL}/${username}/avatar.png`;
} else if (teamname) {
const team = await prisma.team.findUnique({
where: {
slug: teamname,
},
select: {
logo: true,
},
});
identity = {
name: teamname,
shouldDefaultBeNameBased: true,
avatar: team?.logo,
};
linksToThisRoute =
identity.avatar === `${CAL_URL}/team/${teamname}/avatar.png` ||
identity.avatar === `${WEBAPP_URL}/team/${teamname}/avatar.png`;
}
const emailMd5 = crypto
.createHash("md5")
.update((identity?.email as string) || "guest@example.com")
.digest("hex");
const img = identity?.avatar;
// If image isn't set or links to this route itself, use default avatar
if (!img || linksToThisRoute) {
let defaultSrc = defaultAvatarSrc({ md5: emailMd5 });
if (identity?.shouldDefaultBeNameBased) {
defaultSrc = getPlaceholderAvatar(null, identity.name);
}
res.writeHead(302, {
Location: defaultSrc,
if (teamname) {
const team = await prisma.team.findUnique({
where: { slug: teamname },
select: { logo: true },
});
res.end();
} else if (!img.includes("data:image")) {
res.writeHead(302, {
Location: img,
});
res.end();
} else {
const decoded = img
.toString()
.replace("data:image/png;base64,", "")
.replace("data:image/jpeg;base64,", "");
const imageResp = Buffer.from(decoded, "base64");
res.writeHead(200, {
"Content-Type": "image/png",
"Content-Length": imageResp.length,
});
res.end(imageResp);
return {
name: teamname,
email: null,
avatar: team?.logo || getPlaceholderAvatar(null, teamname),
};
}
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const identity = await getIdentityData(req);
const img = identity?.avatar;
// If image isn't set or links to this route itself, use default avatar
if (!img) {
res.writeHead(302, {
Location: defaultAvatarSrc({
md5: crypto
.createHash("md5")
.update(identity?.email || "guest@example.com")
.digest("hex"),
}),
});
return res.end();
}
if (!img.includes("data:image")) {
res.writeHead(302, { Location: img });
return res.end();
}
const decoded = img.toString().replace("data:image/png;base64,", "").replace("data:image/jpeg;base64,", "");
const imageResp = Buffer.from(decoded, "base64");
res.writeHead(200, {
"Content-Type": "image/png",
"Content-Length": imageResp.length,
});
res.end(imageResp);
}

View File

@ -1,5 +1,4 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { InferGetStaticPropsType, NextPageContext } from "next";
import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import { ChangeEventHandler, useState } from "react";
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
@ -31,7 +30,10 @@ function AppsSearch({
);
}
export default function Apps({ appStore, categories }: InferGetStaticPropsType<typeof getServerSideProps>) {
export default function Apps({
categories,
appStore,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const { t } = useLocale();
const [searchText, setSearchText] = useState<string | undefined>(undefined);
@ -42,7 +44,8 @@ export default function Apps({ appStore, categories }: InferGetStaticPropsType<t
subtitle={t("app_store_description")}
actions={(className) => (
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
)}>
)}
emptyStore={!appStore.length}>
{!searchText && (
<>
<AppStoreCategories categories={categories} />
@ -54,7 +57,7 @@ export default function Apps({ appStore, categories }: InferGetStaticPropsType<t
);
}
export const getServerSideProps = async (context: NextPageContext) => {
export const getServerSideProps: GetServerSideProps = async (context) => {
const ssg = await ssgInit(context);
const session = await getSession(context);
@ -77,7 +80,6 @@ export const getServerSideProps = async (context: NextPageContext) => {
}, {} as Record<string, number>);
return {
props: {
trpcState: ssg.dehydrate(),
categories: Object.entries(categories)
.map(([name, count]): { name: AppCategories; count: number } => ({
name: name as AppCategories,
@ -87,6 +89,7 @@ export const getServerSideProps = async (context: NextPageContext) => {
return b.count - a.count;
}),
appStore,
trpcState: ssg.dehydrate(),
},
};
};

View File

@ -0,0 +1,48 @@
import { useState } from "react";
import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { WizardForm } from "@calcom/ui";
import SetupFormStep1 from "./steps/SetupFormStep1";
import StepDone from "./steps/StepDone";
export default function Setup(props: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const [isLoadingStep1, setIsLoadingStep1] = useState(false);
const shouldDisable = props.userCount !== 0;
const steps = [
{
title: t("administrator_user"),
description: t("lets_create_first_administrator_user"),
content: shouldDisable ? <StepDone /> : <SetupFormStep1 setIsLoading={setIsLoadingStep1} />,
isLoading: isLoadingStep1,
},
{
title: t("enable_apps"),
description: t("enable_apps_description"),
content: <AdminAppsList baseURL="/auth/setup" />,
isLoading: false,
},
];
return (
<>
<main className="flex items-center bg-gray-100 print:h-full">
<WizardForm href="/auth/setup" steps={steps} disableNavigation={shouldDisable} />
</main>
</>
);
}
export const getServerSideProps = async () => {
const userCount = await prisma.user.count();
return {
props: {
userCount,
},
};
};

View File

@ -1,34 +1,14 @@
import { CheckIcon } from "@heroicons/react/solid";
import { zodResolver } from "@hookform/resolvers/zod";
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import { signIn } from "next-auth/react";
import { useRouter } from "next/router";
import { useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import * as z from "zod";
import { isPasswordValid } from "@calcom/lib/auth";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { EmailField, Label, PasswordField, TextField, WizardForm } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const StepDone = () => {
const { t } = useLocale();
return (
<div className="min-h-36 my-6 flex flex-col items-center justify-center">
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white">
<CheckIcon className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
</div>
<div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
</div>
</div>
);
};
import { EmailField, Label, PasswordField, TextField } from "@calcom/ui";
const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
const router = useRouter();
@ -83,13 +63,19 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
},
});
if (response.status === 200) {
router.replace(`/auth/login?email=${data.email_address.toLowerCase()}`);
await signIn("credentials", {
redirect: false,
callbackUrl: "/",
email: data.email_address.toLowerCase(),
password: data.password,
});
router.replace(`/auth/setup?step=2&category=calendar`);
} else {
router.replace("/auth/setup");
}
}, onError);
const longWebsiteUrl = process.env.NEXT_PUBLIC_WEBSITE_URL.length > 30;
const longWebsiteUrl = WEBSITE_URL.length > 30;
return (
<FormProvider {...formMethods}>
@ -201,37 +187,4 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
);
};
export default function Setup(props: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const [isLoadingStep1, setIsLoadingStep1] = useState(false);
const steps = [
{
title: t("administrator_user"),
description: t("lets_create_first_administrator_user"),
content: props.userCount !== 0 ? <StepDone /> : <SetupFormStep1 setIsLoading={setIsLoadingStep1} />,
enabled: props.userCount === 0, // to check if the wizard should show buttons to navigate through more steps
isLoading: isLoadingStep1,
},
];
return (
<>
<main className="flex h-screen items-center bg-gray-100 print:h-full">
<WizardForm href="/auth/setup" steps={steps} containerClassname="max-w-sm" />
</main>
</>
);
}
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
const userCount = await prisma.user.count();
return {
props: {
trpcState: ssr.dehydrate(),
userCount,
},
};
};
export default SetupFormStep1;

View File

@ -0,0 +1,19 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
const StepDone = () => {
const { t } = useLocale();
return (
<div className="min-h-36 my-6 flex flex-col items-center justify-center">
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white">
<Icon.FiCheck className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
</div>
<div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
</div>
</div>
);
};
export default StepDone;

View File

@ -3,7 +3,7 @@ import { signIn } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { getPremiumPlanPrice } from "@calcom/app-store/stripepayment/lib/utils";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
import stripe from "@calcom/features/ee/payments/server/stripe";
import {
hostedCal,
@ -161,7 +161,7 @@ const getStripePremiumUsernameUrl = async ({
customer: customer.id,
line_items: [
{
price: getPremiumPlanPrice(),
price: getPremiumMonthlyPlanPriceId(),
quantity: 1,
},
],

View File

@ -29,6 +29,8 @@ import { HttpError } from "@lib/core/http/error";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
import EditableHeading from "@components/ui/EditableHeading";
import { ssgInit } from "@server/lib/ssg";
const querySchema = z.object({
schedule: stringOrNumber,
});
@ -91,7 +93,7 @@ export default function Availability({ schedule }: { schedule: number }) {
return (
<Shell
backPath="/availability"
title={data?.schedule.name && data.schedule.name + " | " + t("availability")}
title={data?.schedule.name ? data.schedule.name + " | " + t("availability") : t("availability")}
heading={
<Controller
control={form.control}
@ -209,14 +211,16 @@ export default function Availability({ schedule }: { schedule: number }) {
);
}
export const getStaticProps: GetStaticProps = (ctx) => {
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const ssg = await ssgInit(ctx);
if (!params.success) return { notFound: true };
return {
props: {
schedule: params.data.schedule,
trpcState: ssg.dehydrate(),
},
revalidate: 10, // seconds
};

View File

@ -1,4 +1,5 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { GetServerSidePropsContext } from "next";
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -10,6 +11,8 @@ import { HttpError } from "@lib/core/http/error";
import SkeletonLoader from "@components/availability/SkeletonLoader";
import { ssrInit } from "@server/lib/ssr";
export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) {
const { t } = useLocale();
const utils = trpc.useContext();
@ -45,6 +48,24 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
},
});
const updateMutation = trpc.viewer.availability.schedule.update.useMutation({
onSuccess: async ({ schedule }) => {
await utils.viewer.availability.list.invalidate();
showToast(
t("availability_updated_successfully", {
scheduleName: schedule.name,
}),
"success"
);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
},
});
// Adds smooth delete button - item fades and old item slides into place
const [animationParentRef] = useAutoAnimate<HTMLUListElement>();
@ -71,6 +92,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
}}
key={schedule.id}
schedule={schedule}
updateDefault={updateMutation.mutate}
deleteFunction={deleteMutation.mutate}
/>
))}
@ -93,3 +115,13 @@ export default function AvailabilityPage() {
</div>
);
}
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -41,6 +41,7 @@ import { timeZone } from "@lib/clock";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import CancelBooking from "@components/booking/CancelBooking";
import EventReservationSchema from "@components/schemas/EventReservationSchema";
import { HeadSeo } from "@components/seo/head-seo";
import { ssrInit } from "@server/lib/ssr";
@ -178,12 +179,16 @@ export default function Success(props: SuccessProps) {
console.error(`No location found `);
}
const name = props.bookingInfo?.user?.name;
const email = props.bookingInfo?.user?.email;
const status = props.bookingInfo?.status;
const reschedule = props.bookingInfo.status === BookingStatus.ACCEPTED;
const cancellationReason = props.bookingInfo.cancellationReason;
const attendeeName =
typeof props?.bookingInfo?.attendees?.[0]?.name === "string"
? props?.bookingInfo?.attendees?.[0]?.name
: "Nameless";
const [is24h, setIs24h] = useState(isBrowserLocale24h());
const { data: session } = useSession();
@ -205,8 +210,6 @@ export default function Success(props: SuccessProps) {
});
}
const attendeeName = typeof name === "string" ? name : "Nameless";
const eventNameObject = {
attendeeName,
eventType: props.eventType.title,
@ -319,6 +322,19 @@ export default function Success(props: SuccessProps) {
return (
<div className={isEmbed ? "" : "h-screen"} data-testid="success-page">
{!isEmbed && (
<EventReservationSchema
reservationId={bookingInfo.uid}
eventName={eventName}
startTime={bookingInfo.startTime}
endTime={bookingInfo.endTime}
organizer={bookingInfo.user}
attendees={bookingInfo.attendees}
location={locationToDisplay}
description={bookingInfo.description}
status={status}
/>
)}
{userIsOwner && !isEmbed && (
<div className="mt-2 ml-4 -mb-4">
<Link href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}>

View File

@ -15,6 +15,8 @@ import { useInViewObserver } from "@lib/hooks/useInViewObserver";
import BookingListItem from "@components/booking/BookingListItem";
import SkeletonLoader from "@components/booking/SkeletonLoader";
import { ssgInit } from "@server/lib/ssg";
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["status"];
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
@ -180,14 +182,16 @@ export default function Bookings() {
);
}
export const getStaticProps: GetStaticProps = (ctx) => {
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const ssg = await ssgInit(ctx);
if (!params.success) return { notFound: true };
return {
props: {
status: params.data.status,
trpcState: ssg.dehydrate(),
},
};
};

View File

@ -166,6 +166,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
previousPage: context.req.headers.referer ?? null,
booking,
users: [user.username],
isBrandingHidden: user.hideBranding,
},
};
};

View File

@ -8,9 +8,10 @@ import { useForm } from "react-hook-form";
import { z } from "zod";
import { StripeData } from "@calcom/app-store/stripepayment/lib/server";
import getApps, { getEventTypeAppData, getLocationOptions } from "@calcom/app-store/utils";
import { getEventTypeAppData, getLocationOptions } from "@calcom/app-store/utils";
import { EventLocationType, LocationObject } from "@calcom/core/location";
import { parseBookingLimit, parseRecurringEvent, validateBookingLimitOrder } from "@calcom/lib";
import getEnabledApps from "@calcom/lib/apps/getEnabledApps";
import { CAL_URL } from "@calcom/lib/constants";
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
@ -39,6 +40,7 @@ import { EventTypeSingleLayout } from "@components/eventtype/EventTypeSingleLayo
import EventWorkflowsTab from "@components/eventtype/EventWorkfowsTab";
import { getTranslation } from "@server/lib/i18n";
import { ssrInit } from "@server/lib/ssr";
export type FormValues = {
title: string;
@ -316,6 +318,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const { req, query } = context;
const session = await getSession({ req });
const typeParam = parseInt(asStringOrThrow(query.type));
const ssr = await ssrInit(context);
if (Number.isNaN(typeParam)) {
return {
@ -465,6 +468,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const credentials = await prisma.credential.findMany({
where: {
userId: session.user.id,
app: {
enabled: true,
},
},
select: {
id: true,
@ -472,6 +478,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
key: true,
userId: true,
appId: true,
invalid: true,
},
});
@ -524,7 +531,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
}
const currentUser = eventType.users.find((u) => u.id === session.user.id);
const t = await getTranslation(currentUser?.locale ?? "en", "common");
const integrations = getApps(credentials);
const integrations = await getEnabledApps(credentials);
const locationOptions = getLocationOptions(integrations, t);
const eventTypeObject = Object.assign({}, eventType, {
@ -553,6 +560,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
team: eventTypeObject.team || null,
teamMembers,
currentUserMembership,
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1,5 +1,5 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import Head from "next/head";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { Fragment, useEffect, useState } from "react";
@ -38,6 +38,8 @@ import SkeletonLoader from "@components/eventtype/SkeletonLoader";
import Avatar from "@components/ui/Avatar";
import AvatarGroup from "@components/ui/AvatarGroup";
import { ssrInit } from "@server/lib/ssr";
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
type EventTypeGroupProfile = EventTypeGroups[number]["profile"];
@ -531,9 +533,9 @@ const EventTypeListHeading = ({
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
<a className="font-bold">{profile?.name || ""}</a>
</Link>
{membershipCount && (
{membershipCount && teamId && (
<span className="relative -top-px text-xs text-neutral-500 ltr:ml-2 rtl:mr-2">
<Link href="/settings/teams">
<Link href={`/settings/teams/${teamId}/members`}>
<a>
<Badge variant="gray">
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
@ -579,12 +581,9 @@ const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer);
const EventTypesPage = () => {
const { t } = useLocale();
return (
<div>
<Head>
<title>Home | {APP_NAME}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell
heading={t("event_types_page_title") as string}
subtitle={t("event_types_page_subtitle") as string}
@ -622,4 +621,14 @@ const EventTypesPage = () => {
);
};
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default EventTypesPage;

View File

@ -42,6 +42,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
if (!booking) {
return {
notFound: true,
} as {
notFound: true;
};
}
@ -49,6 +51,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
// TODO: Show something in UI to let user know that this booking is not rescheduleable.
return {
notFound: true,
} as {
notFound: true;
};
}
@ -62,7 +66,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
: booking.user?.username || "rick") /* This shouldn't happen */ +
"/" +
eventType?.slug;
return {
redirect: {
destination: "/" + eventPage + "?rescheduleUid=" + context.query.uid,

View File

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

View File

@ -1,14 +0,0 @@
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
function AdminAppsView() {
return (
<>
<Meta title="Apps" description="apps_description" />
<h1>App listing</h1>
</>
);
}
AdminAppsView.getLayout = getLayout;
export default AdminAppsView;

View File

@ -0,0 +1,31 @@
import { GetServerSidePropsContext } from "next";
import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminAppsView() {
const { t } = useLocale();
return (
<>
<Meta title={t("apps")} description={t("apps_description")} />
<AdminAppsList baseURL="/settings/admin/apps" className="w-0" />
</>
);
}
AdminAppsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminAppsView;

View File

@ -1,16 +1,19 @@
import { GetServerSidePropsContext } from "next";
import { signIn } from "next-auth/react";
import { useRef } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, getAdminLayout as getLayout, Meta, TextField } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminView() {
const { t } = useLocale();
const usernameRef = useRef<HTMLInputElement>(null);
return (
<>
<Meta title="Admin" description="Impersonation" />
<Meta title={t("admin")} description={t("impersonation")} />
<form
className="mb-6 w-full sm:w-1/2"
onSubmit={(e) => {
@ -21,7 +24,7 @@ function AdminView() {
<div className="flex items-center space-x-2">
<TextField
containerClassName="w-full"
name="Impersonate User"
name={t("user_impersonation_heading")}
addOnLeading={<>{process.env.NEXT_PUBLIC_WEBSITE_URL}/</>}
ref={usernameRef}
hint={t("impersonate_user_tip")}
@ -36,4 +39,14 @@ function AdminView() {
AdminView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminView;

View File

@ -1,5 +1,9 @@
import { GetServerSidePropsContext } from "next";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminAppsView() {
return (
<>
@ -11,4 +15,14 @@ function AdminAppsView() {
AdminAppsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminAppsView;

View File

@ -1,14 +1,30 @@
import { GetServerSidePropsContext } from "next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminUsersView() {
const { t } = useLocale();
return (
<>
<Meta title="Users" description="users_description" />
<h1>Users listing</h1>
<Meta title={t("users")} description={t("users_description")} />
<h1>{t("users_listing")}</h1>
</>
);
}
AdminUsersView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminUsersView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react";
import { HelpScout, useChat } from "react-live-chat-loader";
@ -8,6 +9,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, getSettingsLayout as getLayout, Icon, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
interface CtaRowProps {
title: string;
description: string;
@ -64,7 +67,7 @@ const BillingView = () => {
<CtaRow title={t("billing_help_title")} description={t("billing_help_description")}>
<Button color="secondary" onClick={onContactSupportClick}>
{t("billing_help_cta")}
{t("contact_support")}
</Button>
</CtaRow>
{showChat && <HelpScout color="#292929" icon="message" horizontalPosition="right" zIndex="1" />}
@ -75,4 +78,14 @@ const BillingView = () => {
BillingView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default BillingView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
@ -18,6 +19,8 @@ import {
SkeletonLoader,
} from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const ApiKeysView = () => {
const { t } = useLocale();
@ -93,4 +96,14 @@ const ApiKeysView = () => {
ApiKeysView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ApiKeysView;

View File

@ -1,3 +1,5 @@
import { GetServerSidePropsContext } from "next";
import { useSession } from "next-auth/react";
import { Controller, useForm } from "react-hook-form";
import { APP_NAME } from "@calcom/lib/constants";
@ -17,9 +19,12 @@ import {
Switch,
} from "@calcom/ui";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
@ -41,7 +46,7 @@ const SkeletonLoader = () => {
const AppearanceView = () => {
const { t } = useLocale();
const session = useSession();
const utils = trpc.useContext();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
@ -68,7 +73,7 @@ const AppearanceView = () => {
},
});
if (isLoading) return <SkeletonLoader />;
if (isLoading) return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
if (!user) return null;
@ -85,7 +90,7 @@ const AppearanceView = () => {
theme: values.theme || null,
});
}}>
<Meta title="Appearance" description="Manage settings for your booking appearance" />
<Meta title={t("appearance")} description={t("appearance_description")} />
<div className="mb-6 flex items-center text-sm">
<div>
<p className="font-semibold">{t("theme")}</p>
@ -180,6 +185,7 @@ const AppearanceView = () => {
<div className="flex-none">
<Switch
id="hideBranding"
disabled={!session.data?.user.belongsToActiveTeam}
onCheckedChange={(checked) =>
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
}
@ -204,6 +210,16 @@ const AppearanceView = () => {
AppearanceView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AppearanceView;
interface ThemeLabelProps {
variant: "light" | "dark" | "system";

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
@ -22,12 +23,15 @@ import {
SkeletonButton,
SkeletonContainer,
SkeletonText,
showToast,
} from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import { CalendarSwitch } from "@components/settings/CalendarSwitch";
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = () => {
return (
<SkeletonContainer>
@ -66,15 +70,17 @@ const CalendarsView = () => {
async onSettled() {
await utils.viewer.connectedCalendars.invalidate();
},
onSuccess: async () => {
showToast(t("calendar_updated_successfully"), "success");
},
onError: () => {
showToast(t("unexpected_error_try_again"), "error");
},
});
return (
<>
<Meta
title="Calendars"
description="Configure how your event types interact with your calendars"
CTA={<AddCalendarButton />}
/>
<Meta title={t("calendars")} description={t("calendars_description")} CTA={<AddCalendarButton />} />
<QueryCell
query={query}
customLoader={<SkeletonLoader />}
@ -110,7 +116,7 @@ const CalendarsView = () => {
{t("check_for_conflicts")}
</h4>
<p className="pb-2 text-sm leading-5 text-gray-600">{t("select_calendars")}</p>
<List>
<List className="flex flex-col gap-6" noBorderTreatment>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.error && item.error.message && (
@ -137,7 +143,7 @@ const CalendarsView = () => {
/>
)}
{item?.error === undefined && item.calendars && (
<ListItem className="flex-col">
<ListItem className="flex-col rounded-md">
<div className="flex w-full flex-1 items-center space-x-3 p-4 rtl:space-x-reverse">
{
// eslint-disable-next-line @next/next/no-img-element
@ -164,7 +170,7 @@ const CalendarsView = () => {
<DisconnectIntegration
trashIcon
credentialId={item.credentialId}
buttonProps={{ size: "icon", color: "secondary" }}
buttonProps={{ className: "border border-gray-300" }}
/>
</div>
</div>
@ -172,7 +178,7 @@ const CalendarsView = () => {
<p className="px-2 pt-4 text-sm text-neutral-500">
{t("toggle_calendars_conflict")}
</p>
<ul className="space-y-2 px-2 pt-4">
<ul className="space-y-2 p-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
@ -224,4 +230,14 @@ const CalendarsView = () => {
CalendarsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default CalendarsView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -24,9 +25,12 @@ import {
SkeletonText,
} from "@calcom/ui";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
@ -60,11 +64,12 @@ const ConferencingLayout = () => {
const [deleteAppModal, setDeleteAppModal] = useState(false);
const [deleteCredentialId, setDeleteCredentialId] = useState<number>(0);
if (isLoading) return <SkeletonLoader />;
if (isLoading)
return <SkeletonLoader title={t("conferencing")} description={t("conferencing_description")} />;
return (
<div className="w-full bg-white sm:mx-0 xl:mt-0">
<Meta title="Conferencing" description="Add your favourite video conferencing apps for your meetings" />
<Meta title={t("conferencing")} description={t("conferencing_description")} />
<List>
{apps?.items &&
apps.items
@ -128,4 +133,14 @@ const ConferencingLayout = () => {
ConferencingLayout.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ConferencingLayout;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useMemo } from "react";
import { Controller, useForm } from "react-hook-form";
@ -21,9 +22,12 @@ import {
import { withQuery } from "@lib/QueryCell";
import { nameOfDay } from "@lib/core/i18n/weekday";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
@ -47,14 +51,14 @@ const GeneralQueryView = () => {
const { t } = useLocale();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
if (isLoading) return <SkeletonLoader />;
if (isLoading) return <SkeletonLoader title={t("general")} description={t("general_description")} />;
if (!user) {
throw new Error(t("something_went_wrong"));
}
return (
<WithQuery
success={({ data }) => <GeneralView user={user} localeProp={data.locale} />}
customLoader={<SkeletonLoader />}
customLoader={<SkeletonLoader title={t("general")} description={t("general_description")} />}
/>
);
};
@ -127,7 +131,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
weekStart: values.weekStart.value,
});
}}>
<Meta title="General" description="Manage settings for your language and timezone" />
<Meta title={t("general")} description={t("general_description")} />
<Controller
name="locale"
render={({ field: { value, onChange } }) => (
@ -210,4 +214,14 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
GeneralQueryView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default GeneralQueryView;

View File

@ -1,7 +1,8 @@
import { IdentityProvider } from "@prisma/client";
import crypto from "crypto";
import { GetServerSidePropsContext } from "next";
import { signOut } from "next-auth/react";
import { BaseSyntheticEvent, useEffect, useRef, useState } from "react";
import { BaseSyntheticEvent, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
@ -35,11 +36,14 @@ import {
} from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonAvatar className=" h-12 w-12 px-4" />
@ -59,14 +63,25 @@ interface DeleteAccountValues {
totpCode: string;
}
type FormValues = {
username: string;
avatar: string;
name: string;
email: string;
bio: string;
};
const ProfileView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
const { data: avatar, isLoading: isLoadingAvatar } = trpc.viewer.avatar.useQuery();
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: () => {
showToast(t("settings_updated_successfully"), "success");
utils.viewer.me.invalidate();
utils.viewer.avatar.invalidate();
setTempFormValues(null);
},
onError: () => {
showToast(t("error_updating_settings"), "error");
@ -74,18 +89,14 @@ const ProfileView = () => {
});
const [confirmPasswordOpen, setConfirmPasswordOpen] = useState(false);
const [tempFormValues, setTempFormValues] = useState<FormValues | null>(null);
const [confirmPasswordErrorMessage, setConfirmPasswordDeleteErrorMessage] = useState("");
const [deleteAccountOpen, setDeleteAccountOpen] = useState(false);
const [hasDeleteErrors, setHasDeleteErrors] = useState(false);
const [deleteErrorMessage, setDeleteErrorMessage] = useState("");
const form = useForm<DeleteAccountValues>();
const emailMd5 = crypto
.createHash("md5")
.update(user?.email || "example@example.com")
.digest("hex");
const onDeleteMeSuccessMutation = async () => {
await utils.viewer.me.invalidate();
showToast(t("Your account was deleted"), "success");
@ -100,7 +111,7 @@ const ProfileView = () => {
const confirmPasswordMutation = trpc.viewer.auth.verifyPassword.useMutation({
onSuccess() {
mutation.mutate(formMethods.getValues());
if (tempFormValues) mutation.mutate(tempFormValues);
setConfirmPasswordOpen(false);
},
onError() {
@ -146,6 +157,7 @@ const ProfileView = () => {
deleteMeWithoutPasswordMutation.mutate();
}
};
const onConfirm = ({ totpCode }: DeleteAccountValues, e: BaseSyntheticEvent | undefined) => {
e?.preventDefault();
if (isCALIdentityProviver) {
@ -156,38 +168,6 @@ const ProfileView = () => {
}
};
const formMethods = useForm({
defaultValues: {
username: "",
avatar: "",
name: "",
email: "",
bio: "",
},
});
const {
reset,
formState: { isSubmitting, isDirty },
} = formMethods;
useEffect(() => {
if (user) {
reset(
{
avatar: user?.avatar || "",
username: user?.username || "",
name: user?.name || "",
email: user?.email || "",
bio: user?.bio || "",
},
{
keepDirtyValues: true,
}
);
}
}, [reset, user]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const passwordRef = useRef<HTMLInputElement>(null!);
@ -199,143 +179,100 @@ const ProfileView = () => {
[ErrorCode.InternalServerError]: `${t("something_went_wrong")} ${t("please_try_again_and_contact_us")}`,
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
};
const onSuccessfulUsernameUpdate = async () => {
showToast(t("settings_updated_successfully"), "success");
await utils.viewer.me.invalidate();
};
const onErrorInUsernameUpdate = () => {
showToast(t("error_updating_settings"), "error");
};
if (isLoading || !user || isLoadingAvatar || !avatar)
return <SkeletonLoader title={t("profile")} description={t("profile_description")} />;
if (isLoading || !user) return <SkeletonLoader />;
const isDisabled = isSubmitting || !isDirty;
const defaultValues = {
username: user.username || "",
avatar: avatar.avatar || "",
name: user.name || "",
email: user.email || "",
bio: user.bio || "",
};
return (
<>
<Form
form={formMethods}
handleSubmit={(values) => {
if (values.email !== user?.email && isCALIdentityProviver) {
<Meta title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
<ProfileForm
key={JSON.stringify(defaultValues)}
defaultValues={defaultValues}
onSubmit={(values) => {
if (values.email !== user.email && isCALIdentityProviver) {
setTempFormValues(values);
setConfirmPasswordOpen(true);
} else {
mutation.mutate(values);
}
}}>
<Meta title="Profile" description={t("profile_description", { appName: APP_NAME })} />
<div className="flex items-center">
<Controller
control={formMethods.control}
name="avatar"
render={({ field: { value } }) => (
<>
<Avatar alt="" imageSrc={value} gravatarFallbackMd5={emailMd5} size="lg" />
<div className="ml-4">
<ImageUploader
target="avatar"
id="avatar-upload"
buttonMsg={t("change_avatar")}
handleAvatarChange={(newAvatar) => {
formMethods.setValue("avatar", newAvatar, { shouldDirty: true });
}}
imageSrc={value}
/>
</div>
</>
}}
extraField={
<div className="mt-8">
<UsernameAvailabilityField
user={user}
onSuccessMutation={async () => {
showToast(t("settings_updated_successfully"), "success");
await utils.viewer.me.invalidate();
}}
onErrorMutation={() => {
showToast(t("error_updating_settings"), "error");
}}
/>
</div>
}
/>
<hr className="my-6 border-neutral-200" />
<Label>{t("danger_zone")}</Label>
{/* Delete account Dialog */}
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
<DialogTrigger asChild>
<Button
data-testid="delete-account"
color="destructive"
className="mt-1 border-2"
StartIcon={Icon.FiTrash2}>
{t("delete_account")}
</Button>
</DialogTrigger>
<DialogContent
title={t("delete_account_modal_title")}
description={t("confirm_delete_account_modal", { appName: APP_NAME })}
type="creation"
Icon={Icon.FiAlertTriangle}>
<>
<p className="mb-7">{t("delete_account_confirmation_message", { appName: APP_NAME })}</p>
{isCALIdentityProviver && (
<PasswordField
data-testid="password"
name="password"
id="password"
autoComplete="current-password"
required
label="Password"
ref={passwordRef}
/>
)}
/>
</div>
<div className="mt-8">
<Controller
control={formMethods.control}
name="username"
render={({ field: { ref, onChange, value } }) => {
return (
<UsernameAvailability
currentUsername={user?.username || ""}
inputUsernameValue={value}
usernameRef={ref}
setInputUsernameValue={onChange}
onSuccessMutation={onSuccessfulUsernameUpdate}
onErrorMutation={onErrorInUsernameUpdate}
user={user}
/>
);
}}
/>
</div>
<div className="mt-8">
<TextField label={t("full_name")} {...formMethods.register("name")} />
</div>
<div className="mt-8">
<TextField label={t("email")} hint={t("change_email_hint")} {...formMethods.register("email")} />
</div>
<div className="mt-8">
<TextField label={t("about")} hint={t("bio_hint")} {...formMethods.register("bio")} />
</div>
<Button
disabled={isDisabled}
color="primary"
className="mt-8"
type="submit"
loading={mutation.isLoading}>
{t("update")}
</Button>
{user?.twoFactorEnabled && isCALIdentityProviver && (
<Form handleSubmit={onConfirm} className="pb-4" form={form}>
<TwoFactor center={false} />
</Form>
)}
<hr className="my-6 border-neutral-200" />
<Label>{t("danger_zone")}</Label>
{/* Delete account Dialog */}
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
<DialogTrigger asChild>
<Button
data-testid="delete-account"
color="destructive"
className="mt-1 border-2"
StartIcon={Icon.FiTrash2}>
{t("delete_account")}
</Button>
</DialogTrigger>
<DialogContent
title={t("delete_account_modal_title")}
description={t("confirm_delete_account_modal", { appName: APP_NAME })}
type="creation"
Icon={Icon.FiAlertTriangle}>
<>
<p className="mb-7">{t("delete_account_confirmation_message", { appName: APP_NAME })}</p>
{isCALIdentityProviver && (
<PasswordField
data-testid="password"
name="password"
id="password"
autoComplete="current-password"
required
label="Password"
ref={passwordRef}
/>
)}
{user?.twoFactorEnabled && isCALIdentityProviver && (
<Form handleSubmit={onConfirm} className="pb-4" form={form}>
<TwoFactor center={false} />
</Form>
)}
{hasDeleteErrors && <Alert severity="error" title={deleteErrorMessage} />}
<DialogFooter>
<Button
color="primary"
data-testid="delete-account-confirm"
onClick={(e) => onConfirmButton(e)}>
{t("delete_my_account")}
</Button>
<DialogClose />
</DialogFooter>
</>
</DialogContent>
</Dialog>
</Form>
{hasDeleteErrors && <Alert severity="error" title={deleteErrorMessage} />}
<DialogFooter>
<Button
color="primary"
data-testid="delete-account-confirm"
onClick={(e) => onConfirmButton(e)}>
{t("delete_my_account")}
</Button>
<DialogClose />
</DialogFooter>
</>
</DialogContent>
</Dialog>
{/* If changing email, confirm password */}
<Dialog open={confirmPasswordOpen} onOpenChange={setConfirmPasswordOpen}>
@ -369,6 +306,83 @@ const ProfileView = () => {
);
};
const ProfileForm = ({
defaultValues,
onSubmit,
extraField,
}: {
defaultValues: FormValues;
onSubmit: (values: FormValues) => void;
extraField?: React.ReactNode;
}) => {
const { t } = useLocale();
const emailMd5 = crypto
.createHash("md5")
.update(defaultValues.email || "example@example.com")
.digest("hex");
const formMethods = useForm<FormValues>({
defaultValues,
});
const {
formState: { isSubmitting, isDirty },
} = formMethods;
const isDisabled = isSubmitting || !isDirty;
return (
<Form form={formMethods} handleSubmit={onSubmit}>
<div className="flex items-center">
<Controller
control={formMethods.control}
name="avatar"
render={({ field: { value } }) => (
<>
<Avatar alt="" imageSrc={value} gravatarFallbackMd5={emailMd5} size="lg" />
<div className="ml-4">
<ImageUploader
target="avatar"
id="avatar-upload"
buttonMsg={t("change_avatar")}
handleAvatarChange={(newAvatar) => {
formMethods.setValue("avatar", newAvatar, { shouldDirty: true });
}}
imageSrc={value || undefined}
/>
</div>
</>
)}
/>
</div>
{extraField}
<div className="mt-8">
<TextField label={t("full_name")} {...formMethods.register("name")} />
</div>
<div className="mt-8">
<TextField label={t("email")} hint={t("change_email_hint")} {...formMethods.register("email")} />
</div>
<div className="mt-8">
<TextField label={t("about")} hint={t("bio_hint")} {...formMethods.register("bio")} />
</div>
<Button disabled={isDisabled} color="primary" className="mt-8" type="submit">
{t("update")}
</Button>
</Form>
);
};
ProfileView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ProfileView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -13,6 +14,8 @@ import {
Switch,
} from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const ProfileImpersonationView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
@ -39,7 +42,7 @@ const ProfileImpersonationView = () => {
return (
<>
<Meta title="Impersonation Settings" description="" />
<Meta title={t("impersonation")} description={t("impersonation_description")} />
<Form
form={formMethods}
handleSubmit={({ disableImpersonation }) => {
@ -74,4 +77,14 @@ const ProfileImpersonationView = () => {
ProfileImpersonationView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ProfileImpersonationView;

View File

@ -1,4 +1,5 @@
import { IdentityProvider } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import { useForm } from "react-hook-form";
@ -7,6 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, Form, getSettingsLayout as getLayout, Meta, PasswordField, showToast } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
type ChangePasswordFormValues = {
oldPassword: string;
newPassword: string;
@ -92,4 +95,14 @@ const PasswordView = () => {
PasswordView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default PasswordView;

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/sso/page/user-sso-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1,12 +1,37 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Badge, getSettingsLayout as getLayout, Loader, Meta, Switch } from "@calcom/ui";
import {
Badge,
getSettingsLayout as getLayout,
Meta,
Switch,
SkeletonButton,
SkeletonContainer,
SkeletonText,
} from "@calcom/ui";
import DisableTwoFactorModal from "@components/settings/DisableTwoFactorModal";
import EnableTwoFactorModal from "@components/settings/EnableTwoFactorModal";
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
<SkeletonText className="h-8 w-full" />
</div>
</div>
</SkeletonContainer>
);
};
const TwoFactorAuthView = () => {
const utils = trpc.useContext();
@ -16,11 +41,11 @@ const TwoFactorAuthView = () => {
const [enableModalOpen, setEnableModalOpen] = useState(false);
const [disableModalOpen, setDisableModalOpen] = useState(false);
if (isLoading) return <Loader />;
if (isLoading) return <SkeletonLoader title={t("2fa")} description={t("2fa_description")} />;
return (
<>
<Meta title="Two-Factor Authentication" description="Manage settings for your account passwords" />
<Meta title={t("2fa")} description={t("2fa_description")} />
<div className="mt-6 flex items-start space-x-4">
<Switch
checked={user?.twoFactorEnabled}
@ -35,7 +60,7 @@ const TwoFactorAuthView = () => {
{user?.twoFactorEnabled ? t("enabled") : t("disabled")}
</Badge>
</div>
<p className="text-sm text-gray-600">Add an extra layer of security to your account.</p>
<p className="text-sm text-gray-600">{t("add_an_extra_layer_of_security")}</p>
</div>
</div>
@ -68,4 +93,14 @@ const TwoFactorAuthView = () => {
TwoFactorAuthView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default TwoFactorAuthView;

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-appearance-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-billing-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-members-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-profile-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/sso/page/teams-sso-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1,14 +1,19 @@
import { GetServerSidePropsContext } from "next";
import Head from "next/head";
import { CreateANewTeamForm } from "@calcom/features/ee/teams/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getWizardLayout as getLayout } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const CreateNewTeamPage = () => {
const { t } = useLocale();
return (
<>
<Head>
<title>Create a new Team</title>
<meta name="description" content="Create a new team to ease your organisational booking" />
<title>{t("create_new_team")}</title>
<meta name="description" content={t("create_new_team_description")} />
</Head>
<CreateANewTeamForm />
</>
@ -17,4 +22,14 @@ const CreateNewTeamPage = () => {
CreateNewTeamPage.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default CreateNewTeamPage;

View File

@ -43,6 +43,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: true,
slug: true,
logo: true,
hideBranding: true,
eventTypes: {
where: {
slug: typeParam,
@ -160,6 +161,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
previousPage: context.req.headers.referer ?? null,
booking,
trpcState: ssg.dehydrate(),
isBrandingHidden: team.hideBranding,
},
};
};

View File

@ -1,8 +1,12 @@
import { GetServerSidePropsContext } from "next";
import { TeamsListing } from "@calcom/features/ee/teams/components";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Shell } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function Teams() {
const { t } = useLocale();
return (
@ -22,4 +26,14 @@ function Teams() {
Teams.requiresLicense = false;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default Teams;

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/workflows/pages/index";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1,8 +1,7 @@
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { getFreePlanPrice, getProPlanPrice } from "@calcom/app-store/stripepayment/lib/utils";
import dayjs from "@calcom/dayjs";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { WEBAPP_URL } from "@calcom/lib/constants";
@ -27,7 +26,7 @@ test.describe("Change username on settings", () => {
/** TODO: Find out why it's timing out */
test.fixme("User can change username", async ({ page, users, prisma }) => {
const user = await users.create({ plan: UserPlan.TRIAL });
const user = await users.create();
await user.login();
// Try to go homepage
@ -50,28 +49,13 @@ test.describe("Change username on settings", () => {
expect(newUpdatedUser.username).toBe("demousernamex");
});
test("User trial can update to PREMIUM username", async ({ page, users }, testInfo) => {
test("User can update to PREMIUM username", async ({ page, users }, testInfo) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_STRIPE_ENABLED, "It should only run if Stripe is installed");
test.skip(IS_SELF_HOSTED, "It shouldn't run on self hosted");
const user = await users.create({ plan: UserPlan.TRIAL });
const customer = await stripe.customers.create({ email: `${user?.username}@example.com` });
await stripe.subscriptionSchedules.create({
customer: customer.id,
start_date: "now",
end_behavior: "release",
phases: [
{
items: [{ price: getProPlanPrice() }],
trial_end: dayjs().add(14, "day").unix(),
end_date: dayjs().add(14, "day").unix(),
},
{
items: [{ price: getFreePlanPrice() }],
},
],
});
const user = await users.create();
await stripe.customers.create({ email: `${user?.username}@example.com` });
await user.login();
await page.goto("/settings/my-account/profile");
@ -97,50 +81,4 @@ test.describe("Change username on settings", () => {
await expect(page).toHaveURL(/.*checkout.stripe.com/);
});
test("User PRO can update to PREMIUM username", async ({ page, users }, testInfo) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_STRIPE_ENABLED, "It should only run if Stripe is installed");
test.skip(IS_SELF_HOSTED, "It shouldn't run on self hosted");
const user = await users.create({ plan: UserPlan.PRO });
const customer = await stripe.customers.create({ email: `${user?.username}@example.com` });
const paymentMethod = await stripe.paymentMethods.create({
type: "card",
card: {
number: "4242424242424242",
cvc: "123",
exp_month: 12,
exp_year: 2040,
},
});
await stripe.paymentMethods.attach(paymentMethod.id, { customer: customer.id });
await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: getProPlanPrice() }],
});
await user.login();
await page.goto("/settings/profile");
// Change username from normal to premium
const usernameInput = page.locator("[data-testid=username-input]");
await usernameInput.fill(`xx${testInfo.workerIndex}`);
// Click on save button
await page.click('button[type="submit"]');
// Validate modal text fields
const currentUsernameText = page.locator("[data-testid=current-username]").innerText();
const newUsernameText = page.locator("[data-testid=new-username]").innerText();
expect(currentUsernameText).not.toBe(newUsernameText);
// Click on Go to billing
await page.click("[data-testid=go-to-billing]", { timeout: 300 });
await page.waitForLoadState();
await expect(page).toHaveURL(/.*billing.stripe.com/);
});
});

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "تم إلغاء حدثك المُجَدْوَل",
"organizer": "المنظِّم",
"need_to_reschedule_or_cancel": "هل تحتاج إلى إعادة جدولة أو إلغاء؟",
"cancellation_reason": "سبب الإلغاء",
"cancellation_reason_placeholder": "ما سبب الإلغاء؟ (اختياري)",
"cancellation_reason": "سبب الإلغاء (اختياري)",
"cancellation_reason_placeholder": "لماذا تلغي؟",
"rejection_reason": "سبب الرفض",
"rejection_reason_title": "رفض طلب الحجز؟",
"rejection_reason_description": "هل أنت متأكد من رغبتك في رفض الحجز؟ سنقوم بإبلاغ الشخص الذي حاول الحجز. يمكنك تقديم سبب أدناه.",
@ -109,7 +109,7 @@
"upgrade_to_per_seat": "الترقية إلى \"كل مقعد بشكل منفرد\"",
"team_upgrade_seats_details": "من بين {{memberCount}} من الأعضاء في فريقك، لم يتم الدفع مقابل {{unpaidCount}} من المقاعد. بما أن سعر المقعد ${{seatPrice}} شهريًا، تبلغ التكلفة الإجمالية المقدرة لعضويتك ${{totalCost}} شهريًا.",
"team_upgrade_banner_description": "شكراً لك على تجربة خطة الفريق الجديدة التي نقدمها. لقد لاحظنا أن فريقك \"{{teamName}}\" بحاجة إلى الترقية.",
"team_upgrade_banner_action": "الترقية هنا",
"team_upgrade_banner_action": "قم بالترقية هنا",
"team_upgraded_successfully": "تمت ترقية فريقك بنجاح!",
"use_link_to_reset_password": "استخدم الرابط أدناه لإعادة تعيين كلمة المرور",
"hey_there": "مرحبًا،",
@ -419,7 +419,7 @@
"forgotten_secret_description": "إذا فقدت هذا السر أو نسيته، يمكنك تغييره، ولكن ضع في اعتبارك أن جميع التكاملات التي تستخدم هذا السر ستحتاج إلى تحديث",
"current_incorrect_password": "كلمة المرور الحالية غير صحيحة",
"password_hint_caplow": "مزيج من الحروف الكبيرة والحروف الصغيرة",
"password_hint_min": "7 أحرف كحد أدنى",
"password_hint_min": "8 أحرف كحد أدنى",
"password_hint_num": "يحتوي على رقم واحد على الأقل",
"invalid_password_hint": "يجب أن تتألف كلمة المرور على الأقل من 7 أحرف، ورقم واحد على الأقل، ومزيج من الحروف الكبيرة والصغيرة",
"incorrect_password": "كلمة المرور غير صحيحة.",
@ -653,7 +653,7 @@
"show_advanced_settings": "عرض الإعدادات المتقدمة",
"event_name": "اسم الحدث",
"event_name_tooltip": "الاسم الذي سيظهر في التقاويم",
"meeting_with_user": "الاجتماع مع {ATTENDEE}",
"meeting_with_user": "الاجتماع مع {{attendeeName}}",
"additional_inputs": "مدخلات إضافية",
"additional_input_description": "يتطلب من المجدول إدخال مدخلات إضافية قبل تأكيد الحجز",
"label": "العلامة",
@ -784,7 +784,7 @@
"analytics": "التحليلات",
"empty_installed_apps_headline": "لم يتم تثبيت أي تطبيق",
"empty_installed_apps_description": "تعطيك التطبيقات القدرة على تعزيز سير عملك وتحسين الجدولة في حياتك بشدة.",
"empty_installed_apps_button": "استكشف App Store أو قم بالتثبيت من التطبيقات أدناه",
"empty_installed_apps_button": "تصفح متجر التطبيقات",
"manage_your_connected_apps": "إدارة تطبيقاتك المثبتة أو تغيير الإعدادات",
"browse_apps": "استعراض التطبيقات",
"features": "الميزات",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "رمز المصادقة الثنائية غير صحيح",
"which_event_type_apply": "ما نوع الحدث الذي سينطبق عليه هذا؟",
"no_workflows_description": "تعمل سير العمل على تمكين أتمتة إرسال الإشعارات والتذكيرات التي تمكنك من بناء عمليات حول الأحداث الخاصة بك.",
"timeformat_profile_hint": "هذا إعداد داخلي، ولن يؤثر على كيفية عرض الأوقات على صفحات الحجز العامة لديك أو لدى أي شخص يقوم بحجزك.",
"create_workflow": "إنشاء سير عمل",
"do_this": "افعل هذا",
"turn_off": "إيقاف التشغيل",
@ -1129,7 +1130,7 @@
"removes_cal_branding": "إزالة أي علامات تجارية ذات صلة بـ{{appName}} ، أي 'تم تشغيلها من قبل {{appName}}.'",
"profile_picture": "صورة الملف الشخصي",
"upload": "تحميل",
"add_profile_photo": "إضافة صورة ملف تعريفي",
"add_profile_photo": "إضافة صورة الملف الشخصي",
"web3": "Web3",
"rainbow_token_gated": "يتم فتح هذا النوع من الأحداث برمز مميز.",
"rainbow_connect_wallet_gate": "قم بربط محفظتك إذا كنت تملك <1>{{name}}</1> (<3>{{symbol}}</3>).",
@ -1228,7 +1229,7 @@
"exchange_compression": "ضغط Gzip",
"routing_forms_description": "يمكنك رؤية جميع النماذج والتوجيهات التي أنشأتها هنا.",
"routing_forms_send_email_owner": "إرسال رسالة إلكترونية إلى المالك",
"routing_forms_send_email_owner_description": "يرسل رسالة إلكترونية إلى المالك عند يتم تقديم الاستمارة",
"routing_forms_send_email_owner_description": "يرسل رسالة بريد إلكتروني إلى المالك عند إرسال النموذج",
"add_new_form": "إضافة استمارة جديدة",
"form_description": "قم بإنشاء النموذج الخاص بك لتوجيه صاحب حجز",
"copy_link_to_form": "نسخ الرابط إلى النموذج",
@ -1341,15 +1342,15 @@
"limit_future_bookings_description": "الحد من عدد مرات حجز هذا الحدث في المستقبل",
"no_event_types": "لا يوجد إعداد لأنواع الأحداث",
"no_event_types_description": "لم يتم إعداد أي نوع من الأحداث لك من قِبل {{name}}.",
"billing_frequency": "تواتر الفواتير",
"monthly": "شهرياً",
"yearly": "سنوياً",
"billing_frequency": "تواتر إصدار الفواتير",
"monthly": "شهريًا",
"yearly": "سنويًا",
"checkout": "الدفع",
"your_team_disbanded_successfully": "تم حل فريقك بنجاح",
"error_creating_team": "خطأ في إنشاء فريق",
"you": "أنت",
"send_email": "إرسال رسالة إلكترونية",
"member_already_invited": "تم بالفعل دعوة العضو",
"send_email": "إرسال بريد إلكتروني",
"member_already_invited": "تمت دعوة العضو بالفعل",
"enter_email_or_username": "أدخل بريد إلكتروني أو اسم مستخدم",
"team_name_taken": "هذا الاسم مأخوذ بالفعل",
"must_enter_team_name": "يجب إدخال اسم فريق",
@ -1359,15 +1360,15 @@
"number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)",
"attendee_email_workflow": "اسم الحاضر",
"attendee_email_info": "البريد الإلكتروني للشخص الحجز",
"invalid_credential": "أوه لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. الرجاء إعادة التثبيت مرة أخرى.",
"invalid_credential": "لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. يرجى إعادة التثبيت مرة أخرى.",
"choose_common_schedule_team_event": "اختر جدولاً زمنياً مشتركاً",
"choose_common_schedule_team_event_description": "قم بتمكين هذا إذا كنت ترغب في استخدام جدول زمني مشترك بين المضيفين. عند تعطيل هذا الخيار، سيتم حجز كل مضيف على أساس جدوله الافتراضي.",
"choose_common_schedule_team_event_description": "قم بتمكين هذا الخيار إذا كنت ترغب في استخدام جدول زمني مشترك بين المضيفين. عند تعطيل هذا الخيار، سيتم حجز كل مضيف على أساس جدوله الافتراضي.",
"reason": "السبب",
"sender_id": "معرّف المرسل",
"sender_id_error_message": "فقط الحروف والأرقام والمسافات مسموح بها (بحد أقصى 11 مِحرَفاً)",
"sender_id_error_message": "يسمح بالحروف والأرقام والمسافات فقط (بحد أقصى 11 حرفًا)",
"test_routing_form": "اختبار استمارة إعادة التوجيه",
"test_preview": "اختبار المعاينة",
"route_to": "التوجيه إلى",
"test_preview_description": "اختبار نموذج التوجيه الخاص بك من دون تقديم أي بيانات",
"test_preview_description": "اختبار نموذج التوجيه الخاص بك دون تقديم أي بيانات",
"test_routing": "اختبار التوجيه"
}

View File

@ -17,7 +17,7 @@
"organizer": "Organizátor",
"need_to_reschedule_or_cancel": "Přeplánovat nebo zrušit?",
"cancellation_reason": "Důvod zrušení",
"cancellation_reason_placeholder": "Proč rušíte? (nepovinné)",
"cancellation_reason_placeholder": "Proč rušíte?\n",
"rejection_reason": "Důvod odmítnutí",
"rejection_reason_title": "Odmítnout žádost o rezervaci?",
"rejection_reason_description": "Opravdu chcete rezervaci odmítnout? Osobě, která se váš čas pokusila rezervovat, dáme vědět. Níž můžete uvést důvod.",
@ -653,7 +653,7 @@
"show_advanced_settings": "Zobrazit pokročilá nastavení",
"event_name": "Název události",
"event_name_tooltip": "Název, který se zobrazí v kalendářích",
"meeting_with_user": "Schůzka s {ATTENDEE}",
"meeting_with_user": "Schůzka s {{attendeeName}}",
"additional_inputs": "Další pole",
"additional_input_description": "Před potvrzením rezervace vyžadovat od plánující osoby zadání dalších vstupů",
"label": "Popisek",
@ -736,6 +736,7 @@
"toggle_calendars_conflict": "Zapněte kalendáře, u kterých chcete mít přehled o konfliktech a zabránit dvojí rezervaci.",
"select_destination_calendar": "Vytvořit události v",
"connect_additional_calendar": "Připojit další kalendář",
"calendar_updated_successfully": "Kalendář byl úspěšně aktualizován",
"conferencing": "Konference",
"calendar": "Kalendář",
"payments": "Platby",
@ -1108,6 +1109,7 @@
"incorrect_2fa": "Nesprávný kód dvoufázového ověření",
"which_event_type_apply": "Na jaký typ události se to bude vztahovat?",
"no_workflows_description": "Pracovní postupy umožňují jednoduchou automatizaci zasílání oznámení a připomínek, což vám umožní vytvářet procesy v souvislosti s vašimi událostmi.",
"timeformat_profile_hint": "Jedná se o interní nastavení, které nemá vliv na to, jak se vám nebo komukoli, kdo vás rezervuje, na veřejných rezervačních stránkách zobrazují časy.",
"create_workflow": "Vytvoření pracovního postupu",
"do_this": "Provést",
"turn_off": "Vypnout",
@ -1349,17 +1351,17 @@
"error_creating_team": "Chyba při vytváření týmu",
"you": "Vy",
"send_email": "Odeslat e-mail",
"member_already_invited": "Člen byl již pozván",
"member_already_invited": "Člen byl už pozván",
"enter_email_or_username": "Zadejte e-mail nebo uživatelské jméno",
"team_name_taken": "Toto jméno je již obsazeno",
"team_name_taken": "Toto jméno je už obsazeno",
"must_enter_team_name": "Musíte zadat název týmu",
"team_url_required": "Musíte zadat adresu URL týmu",
"team_url_taken": "Tato adresa URL je již obsazená",
"team_url_taken": "Tato adresa URL je už obsazená",
"team_publish": "Zveřejnit tým",
"number_sms_notifications": "Číslo telefonu (SMS oznámení)",
"attendee_email_workflow": "E-mail účastníka",
"attendee_email_info": "E-mail osoby provádějící rezervaci",
"invalid_credential": "Ale ne! Zdá se, že platnost oprávnění vypršela nebo byla zrušena. Proveďte prosím novou instalaci.",
"invalid_credential": "Ale ne! Zdá se, že platnost oprávnění vypršela nebo byla zrušena. Proveďte novou instalaci.",
"choose_common_schedule_team_event": "Vyberte společný plán",
"choose_common_schedule_team_event_description": "Tuto možnost povolte, pokud chcete používat společný plán mezi hostiteli. Při zakázání této možnosti bude každý hostitel rezervován na základě svého výchozího plánu.",
"reason": "Důvod",
@ -1367,7 +1369,7 @@
"sender_id_error_message": "Povolena jsou pouze písmena, číslice a mezery (max. 11 znaků)",
"test_routing_form": "Test směrovacího formuláře",
"test_preview": "Testovací náhled",
"route_to": "Směrovat kam:",
"route_to": "Směrovat na",
"test_preview_description": "Otestujte svůj směrovací formulář bez odeslání jakýchkoli dat",
"test_routing": "Test směrování"
}

View File

@ -53,20 +53,20 @@
"load_more_results": "Weitere Ergebnisse laden",
"integration_meeting_id": "{{integrationName}} Termin-ID: {{meetingId}}",
"confirmed_event_type_subject": "Bestätigt: {{eventType}} mit {{name}} am {{date}}",
"new_event_request": "Neue Event-Anfrage: {{attendeeName}} - {{date}} - {{eventType}}",
"new_event_request": "Neue Termin-Anfrage: {{attendeeName}} - {{date}} - {{eventType}}",
"confirm_or_reject_request": "Anfrage bestätigen oder ablehnen",
"check_bookings_page_to_confirm_or_reject": "Überprüfen Sie Ihre Buchungsseite, um die Buchung zu bestätigen oder abzulehnen.",
"event_awaiting_approval": "Ein neues Event wartet auf Ihre Bestätigung",
"event_awaiting_approval_recurring": "Ein wiederkehrender Termin wartet auf Ihre Genehmigung",
"event_awaiting_approval": "Ein neuer Termin wartet auf Ihre Bestätigung",
"event_awaiting_approval_recurring": "Ein wiederkehrender Termin wartet auf Ihre Bestätigung",
"someone_requested_an_event": "Jemand hat eine Buchungsanfrage gestellt.",
"someone_requested_password_reset": "Jemand hat einen Link angefordert, um Ihr Passwort zu ändern.",
"password_reset_instructions": "Wenn Sie dies nicht angefordert haben, können Sie diese E-Mail sicher ignorieren und Ihr Passwort wird nicht geändert.",
"event_awaiting_approval_subject": "Warte auf Bestätigung: {{eventType}} mit {{name}} um {{date}}",
"event_still_awaiting_approval": "Ein Event wartet noch auf Ihre Bestätigung",
"event_still_awaiting_approval": "Ein Termin wartet noch auf Ihre Bestätigung",
"booking_submitted_subject": "Buchung eingereicht: {{eventType}} mit {{name}} am {{date}}",
"your_meeting_has_been_booked": "Dein Termin wurde gebucht",
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde verschoben auf {{date}}.",
"event_has_been_rescheduled": "Ihr Event wurde verschoben.",
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde auf {{time}} ({{timeZone}}) am {{date}} verschoben.",
"event_has_been_rescheduled": "Ihr Termin wurde verschoben.",
"request_reschedule_title_attendee": "Anfrage zur Neuplanung Ihrer Buchung",
"request_reschedule_subtitle": "{{organizer}} hat die Buchung storniert und Sie gebeten, eine andere Zeit auszuwählen.",
"request_reschedule_title_organizer": "Sie haben {{attendee}} zur Neuplanung aufgefordert",
@ -74,7 +74,7 @@
"rescheduled_event_type_subject": "Anfrage für Neuplanung gesendet: {{eventType}} mit {{name}} am {{date}}",
"requested_to_reschedule_subject_attendee": "Aktion erforderlich - Neuplanung: Bitte buchen Sie eine neue Zeit für „{{eventType}}“ mit {{name}}",
"reschedule_reason": "Grund für die Neuplanung",
"hi_user_name": "Hi {{userName}}",
"hi_user_name": "Hallo {{userName}}",
"ics_event_title": "{{eventType}} mit {{name}}",
"new_event_subject": "Neuer Termin: {{attendeeName}} - {{date}} - {{eventType}}",
"join_by_entrypoint": "{{entryPoint}} beitreten",
@ -118,7 +118,7 @@
"webhook_response": "Webhook-Antwort",
"webhook_test": "Webhook-Test",
"manage_your_webhook": "Webhook verwalten",
"webhook_created_successfully": "Webhook erfolgreich aktualisiert!",
"webhook_created_successfully": "Webhook erfolgreich erstellt!",
"webhook_updated_successfully": "Webhook erfolgreich aktualisiert!",
"webhook_removed_successfully": "Webhook erfolgreich entfernt!",
"payload_template": "Payload Vorlage",
@ -142,7 +142,7 @@
"install_another": "Weitere installieren",
"until": "bis",
"powered_by": "betrieben von",
"unavailable": "Unverfügbar",
"unavailable": "Nicht verfügbar",
"set_work_schedule": "Arbeitszeiten festlegen",
"change_bookings_availability": "Verfügbarkeit für Buchungen ändern",
"select": "Auswählen...",
@ -178,14 +178,14 @@
"register": "Registrieren",
"page_doesnt_exist": "Diese Seite existiert nicht.",
"check_spelling_mistakes_or_go_back": "Prüfen Sie auf Rechtschreibfehler oder gehen Sie zur vorherigen Seite zurück.",
"404_page_not_found": "404: Diese Seite konnte nicht gefunden werden.",
"404_page_not_found": "Fehler 404: Diese Seite konnte nicht gefunden werden.",
"getting_started": "Los geht's",
"15min_meeting": "15 Minuten Termin",
"30min_meeting": "30 Minuten Termin",
"secret": "Passwort",
"leave_blank_to_remove_secret": "Leer lassen, um Passwort zu entfernen",
"secret_meeting": "Geheimer Termin",
"login_instead": "Stattdessen anmelden",
"login_instead": "Stattdessen einloggen",
"already_have_an_account": "Haben Sie bereits ein Konto?",
"create_account": "Konto erstellen",
"confirm_password": "Passwort bestätigen",
@ -240,7 +240,7 @@
"add_another_calendar": "Einen weiteren Kalender hinzufügen",
"other": "Sonstige",
"emailed_you_and_attendees": "Wir haben Ihnen und den anderen Teilnehmern eine Einladung zum Kalender mit allen Details zugeschickt.",
"emailed_you_and_attendees_recurring": "Wir haben Ihnn und den anderen Teilnemern eine Kalendereinladung für den ersten Termin dieses wiederkehrendem Ereignisses gesendet.",
"emailed_you_and_attendees_recurring": "Wir haben Ihnn und den anderen Teilnemern eine Kalendereinladung für den ersten Termin dieser wiederkehrenden Termins gesendet.",
"emailed_you_and_any_other_attendees": "Wir haben Ihnen und allen anderen Teilnehmer diesen Informationen per E-Mail geschickt.",
"needs_to_be_confirmed_or_rejected": "Ihre Buchung muss noch bestätigt oder abgelehnt werden.",
"needs_to_be_confirmed_or_rejected_recurring": "Ihr wiederkehrender Termin muss noch bestätigt oder abgelehnt werden.",
@ -318,8 +318,8 @@
"start_time": "Beginn",
"end_time": "Ende",
"buffer_time": "Pufferzeit",
"before_event": "Vor dem Ereignis",
"after_event": "Nach dem Event",
"before_event": "Vor dem Termin",
"after_event": "Nach dem Termin",
"event_buffer_default": "Keine Pufferzeit",
"buffer": "Puffer",
"your_day_starts_at": "Ihr Tag beginnt um",
@ -338,31 +338,31 @@
"automatically_adjust_theme": "Theme automatisch basierend auf den Einstellungen des Teilnehmers anpassen",
"user_dynamic_booking_disabled": "Einige der Benutzer in der Gruppe haben derzeit dynamische Gruppenbuchungen deaktiviert",
"allow_dynamic_booking": "Ermöglichen Sie es den Teilnehmenden, Sie über dynamische Gruppenbuchungen zu buchen",
"email": "Email",
"email_placeholder": "jdoe@example.com",
"email": "E-Mail",
"email_placeholder": "max@example.com",
"full_name": "Vollständiger Name",
"browse_api_documentation": "Durchsuchen Sie unsere API Dokumentation",
"leverage_our_api": "Nutzen Sie unsere API für die volle Kontrolle und Anpassbarkeit.",
"create_webhook": "Webhook erstellen",
"booking_cancelled": "Termin abgesagt",
"booking_rescheduled": "Termin verlegt",
"booking_created": "Buchung erstellt",
"booking_created": "Termin erstellt",
"meeting_ended": "Meeting beendet",
"form_submitted": "Formular gesendet",
"event_triggers": "Ereignis-Auslöser",
"event_triggers": "Event-Auslöser",
"subscriber_url": "Subscriber-URL",
"create_new_webhook": "Einen neuen Webhook erstellen",
"webhooks": "Webhooks",
"team_webhooks": "Team Webhooks",
"team_webhooks": "Team-Webhooks",
"create_new_webhook_to_account": "Erstellen Sie einen neuen Webhook in Ihrem Account",
"new_webhook": "Neuer Webhook",
"responsive_fullscreen_iframe": "Responsives Vollbild-iframe",
"responsive_fullscreen_iframe": "Responsives Vollbild-iFrame",
"loading": "Wird geladen...",
"deleting": "Lösche...",
"standard_iframe": "Standard iframe",
"standard_iframe": "Standard iFrame",
"developer": "Entwickler",
"manage_developer_settings": "Verwalten Sie Ihre Entwicklereinstellungen.",
"iframe_embed": "iframe Embed",
"iframe_embed": "iFrame-Einbettung",
"integrate_using_embed_or_webhooks": "Integrieren Sie Cal.com in Ihre Website mit unseren Einbettungs-Optionen oder erhalten Sie Buchungsdaten mit Webhooks.",
"schedule_a_meeting": "Termin planen",
"view_and_manage_billing_details": "Ihre Rechnungsdetails ansehen und verwalten",
@ -482,7 +482,7 @@
"members": "Mitglieder",
"member": "Mitglied",
"owner": "Inhaber",
"admin": "Admin",
"admin": "Administrator",
"administrator_user": "Administrator Benutzer",
"lets_create_first_administrator_user": "Lass uns als erstes den Administrator Benutzer anlegen.",
"new_member": "Neues Mitglied",
@ -567,7 +567,7 @@
"web3_metamask_added": "Metamask erfolgreich hinzugefügt",
"web3_metamask_disconnected": "Metamask erfolgreich getrennt",
"hours": "Stunden",
"your_email": "Ihre Emailadresse",
"your_email": "Ihre E-Mail-Adresse",
"change_avatar": "Profilbild ändern",
"language": "Sprache",
"timezone": "Zeitzone",
@ -590,8 +590,8 @@
"max": "Maximal",
"single_theme": "Einzelnes Theme",
"brand_color": "Markenfarbe",
"light_brand_color": "Brand Farbe (Helles Theme)",
"dark_brand_color": "Brand Farbe (Dunkles Theme)",
"light_brand_color": "Markenfarbe (Helles Theme)",
"dark_brand_color": "Markenfarbe (Dunkles Theme)",
"file_not_named": "Datei heißt nicht [idOrSlug]/[user]",
"create_team": "Team anlegen",
"name": "Name",
@ -604,6 +604,7 @@
"teams": "Teams",
"team": "Team",
"team_billing": "Abrechnung der Teams",
"team_billing_description": "Verwalte die Abrechungen deines Teams",
"upgrade_to_flexible_pro_title": "Wir haben die Abrechnung für Teams geändert",
"upgrade_to_flexible_pro_message": "Es gibt Mitglieder in Ihrem Team ohne Lizenz. Upgraden Sie Ihren Pro-Plan um fehlende Lizenzen abzudecken.",
"changed_team_billing_info": "Ab Januar 2022 berechnen wir pro Sitzplatz für Teammitglieder. Mitglieder deines Teams, die Pro kostenlos hatten, sind jetzt in einer 14 Tage Testphase. Sobald die Testversion abläuft, werden diese Mitglieder vor Ihrem Team verborgen, es sei denn, Sie upgraden jetzt.",
@ -614,7 +615,7 @@
"little_something_about": "Ein wenig über sich selbst.",
"profile_updated_successfully": "Profil erfolgreich aktualisiert",
"your_user_profile_updated_successfully": "Ihr Benutzerprofil wurde erfolgreich aktualisiert.",
"user_cannot_found_db": "Benutzer scheint eingeloggt zu sein, kann aber nicht in db gefunden werden",
"user_cannot_found_db": "Benutzer scheint eingeloggt zu sein, kann aber nicht in der Datenbank gefunden werden",
"embed_and_webhooks": "Einbetten & Webhooks",
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
@ -630,18 +631,18 @@
"error": "Fehler",
"at_least_characters_one": "Bitte geben Sie wenigstens ein Zeichen ein",
"at_least_characters_other": "Bitte geben Sie mindestens {{count}} Zeichen ein",
"team_logo": "Team Logo",
"team_logo": "Team-Logo",
"add_location": "Standort hinzufügen",
"attendees": "TeilnehmerInnen",
"add_attendees": "TeilnehmerInnen hinzufügen",
"attendees": "Teilnehmende",
"add_attendees": "Teilnehmende hinzufügen",
"show_advanced_settings": "Erweiterte Einstellungen anzeigen",
"event_name": "Termin Name",
"event_name": "Termin-Name",
"event_name_tooltip": "Der Name, der in Kalendern angezeigt wird",
"meeting_with_user": "Meeting mit {ATTENDEE}",
"meeting_with_user": "Meeting mit {{attendeeName}}",
"additional_inputs": "Zusätzliche Notizen",
"additional_input_description": "Erfordert die Eingabe zusätzlicher Eingaben durch den Planer vor der Buchungsbestätigung",
"label": "Bezeichnung",
"placeholder": "Placeholder",
"placeholder": "Platzhalter",
"type": "Typ",
"edit": "Bearbeiten",
"add_input": "Eingabe hinzufügen",
@ -657,7 +658,7 @@
"private_link_label": "Privater Link",
"private_link_hint": "Ihr privater Link wird nach jeder Nutzung neu generiert",
"copy_private_link": "Privaten Link kopieren",
"invitees_can_schedule": "Teilnehmer können folgendes buchen",
"invitees_can_schedule": "Teilnehmende können folgendes buchen",
"date_range": "Datumszeitraum",
"calendar_days": "Kalendertage",
"business_days": "Werktage",
@ -673,7 +674,7 @@
"within_date_range": "Innerhalb eines Zeitraums",
"indefinitely_into_future": "Auf unbestimmte Zeit in die Zukunft",
"add_new_custom_input_field": "Neues individuelles Eingabefeld hinzufügen",
"quick_chat": "Quick Chat",
"quick_chat": "Schneller Chat",
"add_new_team_event_type": "Neuen Termintyp hinzufügen",
"add_new_event_type": "Neuen Termintyp hinzufügen",
"new_event_type_to_book_description": "Erstellen Sie einen neuen Termintyp, mit dem Personen Zeiten buchen können.",
@ -688,6 +689,8 @@
"confirm_delete_account": "Ja, Account löschen",
"integrations": "Integrationen",
"apps": "Apps",
"apps_description": "Hier findest du eine Auflistung deiner Apps",
"apps_listing": "App-Liste",
"category_apps": "{{category}} Apps",
"app_store": "App Store",
"app_store_description": "Verbindet Menschen, Technologie und den Arbeitsplatz.",
@ -707,7 +710,7 @@
"embed_your_calendar": "Fügen Sie Ihren Kalender in Ihre Webseite ein",
"connect_your_favourite_apps": "Verbinden Sie Ihre Lieblings-Apps.",
"automation": "Automatisierung",
"configure_how_your_event_types_interact": "Konfigurieren Sie, wie Ihre Event-Typen mit Ihren Kalendern interagieren sollen.",
"configure_how_your_event_types_interact": "Konfigurieren Sie, wie Ihre Termin-Typen mit Ihren Kalendern interagieren sollen.",
"toggle_calendars_conflict": "Wechseln Sie die Kalender in denen nach Konflikten gesucht werden sollen, um Doppelbuchungen zu vermeiden.",
"select_destination_calendar": "Erstelle Termine im",
"connect_additional_calendar": "Zusätzlichen Kalender verbinden",
@ -723,7 +726,7 @@
"set_as_away": "Stelle deinen Status auf Abwesend",
"set_as_free": "Abwesenheitsstatus deaktivieren",
"user_away": "Dieser Benutzer ist derzeit abwesend.",
"user_away_description": "Die Person, mi der Sie einen Termin buchen wollen, ist zur Zeit abwesend und akzeptiert daher keine neuen Buchungen.",
"user_away_description": "Die Person, mit der Sie einen Termin buchen wollen, ist zur Zeit abwesend und akzeptiert daher keine neuen Buchungen.",
"meet_people_with_the_same_tokens": "Triff Leute mit den selben Token",
"only_book_people_and_allow": "Nur Buchungen von Personen erlauben, die die gleichen Token, DAOs oder NFTs teilen.",
"account_created_with_identity_provider": "Ihr Konto wurde mit einem Identitäts-Anbieter erstellt.",
@ -798,11 +801,11 @@
"set_calendar": "Legen Sie fest, wo neue Events hinzugefügt werden sollen, wenn Sie ausgebucht sind.",
"delete_schedule": "Verfügbarkeitsplan löschen",
"schedule_created_successfully": "{{scheduleName}} -Verfügbarkeitsplan erfolgreich erstellt",
"availability_updated_successfully": "Verfügbarkeit erfolgreich aktualisiert",
"availability_updated_successfully": "Verfügbarkeitsplan erfolgreich aktualisiert",
"schedule_deleted_successfully": "Verfügbarkeitsplan erfolgreich gelöscht",
"default_schedule_name": "Arbeitszeiten",
"new_schedule_heading": "Erstelle einen Verfügbarkeitsplan",
"new_schedule_description": "Das Erstellen von Verfügbarkeitsplan sorgt dafür, dass Ihre Verfügbarkeit bei mehreren Termintypen genutzt werden kann.",
"new_schedule_description": "Das Erstellen eines Verfügbarkeitsplan sorgt dafür, dass Ihre Verfügbarkeit bei mehreren Termintypen genutzt werden kann.",
"requires_ownership_of_a_token": "Erfordert Besitz eines Tokens, das zu der folgenden Adresse gehört:",
"example_name": "Max Mustermann",
"time_format": "Zeitformat",
@ -872,9 +875,9 @@
"user_impersonation_description": "Erlaubt es unserem Support-Team, sich vorübergehend mit Ihrem Konto anzumelden, um jegliche Probleme, die Sie gemeldet haben, schneller zu lösen.",
"team_impersonation_description": "Ermöglicht den Team-Administratoren, sich vorübergehend als Sie einzuloggen.",
"impersonate_user_tip": "Alle Verwendungen dieser Funktion werden geprüft.",
"impersonating_user_warning": "Imitiere den User {{user}}.",
"impersonating_user_warning": "Imitiere den Account {{user}}.",
"impersonating_stop_instructions": "<0>Klicke hier um zu stoppen </0>.",
"event_location_changed": "Aktualisiert - Ihr Ereignis hat den Standort geändert",
"event_location_changed": "Aktualisiert - Ihr Termin hat den Standort geändert",
"location_changed_event_type_subject": "Ort geändert: {{eventType}} mit {{name}} am {{date}}",
"current_location": "Aktueller Ort",
"user_phone": "Ihre Telefonnummer",
@ -890,7 +893,7 @@
"setting_up_zapier": "Einrichtung Ihrer Zapier-Integration",
"generate_api_key": "Api Key generieren",
"your_unique_api_key": "Ihr eindeutiger API-Key",
"copy_safe_api_key": "Kopieren Sie diesen API-Key und speichern Sie ihn an einem sicheren Ort. Wenn Sie diesen Key verlieren, müssen Sie einen neuen generieren.",
"copy_safe_api_key": "Kopieren Sie diesen API-Schlüssel und speichern Sie ihn an einem sicheren Ort. Wenn Sie diesen Schlüssel verlieren, müssen Sie einen neuen generieren.",
"install_zapier_app": "Bitte installieren Sie zuerst die Zapier-App im App Store.",
"connect_apple_server": "Mit Apple-Server verbinden",
"connect_caldav_server": "Mit CalDav-Server verbinden",
@ -911,7 +914,7 @@
"share_feedback": "Feedback teilen",
"resources": "Ressourcen",
"support_documentation": "Support Dokumentation",
"developer_documentation": "Entwickler Dokumentation",
"developer_documentation": "Entwickler-Dokumentation",
"get_in_touch": "Kontakt aufnehmen",
"contact_support": "Support kontaktieren",
"feedback": "Feedback",
@ -923,7 +926,7 @@
"nevermind": "Egal",
"go_to": "Gehe zu: ",
"zapier_invite_link": "Zapier Einladungs-Link",
"meeting_url_provided_after_confirmed": "Eine Temrin-URL wird angelegt, sobald die Buchung bestätigt wurde.",
"meeting_url_provided_after_confirmed": "Eine Termin-URL wird angelegt, sobald der Termin bestätigt wurde.",
"attendees_name": "Teilnehmername",
"dynamically_display_attendee_or_organizer": "Zeigt dynamisch entweder Ihnen den Namen Ihres Teilnehmers bzw. Ihrer Teilnehmerin an oder zeigt Ihrem/Ihrer Teilnehmerin Ihren Namen an",
"event_location": "Ort des Ereignisses",
@ -937,7 +940,7 @@
"number_provided": "Telefonnummer wird angegeben",
"before_event_trigger": "vor Beginn des Termins",
"event_cancelled_trigger": "wenn das Ereignis abgesagt wird",
"new_event_trigger": "wenn ein neues Ereignis gebucht wird",
"new_event_trigger": "wenn ein neer Termin gebucht wird",
"email_host_action": "E-Mail an den Veranstalter senden",
"email_attendee_action": "E-Mail an Teilnehmer senden",
"sms_attendee_action": "SMS an Teilnehmer senden",
@ -1012,7 +1015,7 @@
"invalid_input": "Ungültige Eingabe",
"broken_video_action": "Wir konnten den <1>{{location}}</1> Meeting-Link nicht zu Ihrem geplanten Ereignis hinzufügen. Kontaktieren Sie Ihre Eingeladenen oder aktualisieren Sie Ihr Kalenderereignis, um die Einzelheiten hinzuzufügen. Sie können entweder <3> Ihren Standort im Ereignistyp ändern</3> oder <5>die App entfernen und erneut hinzufügen.</5>",
"broken_calendar_action": "Wir konnten Ihren <1>{{calendar}}</1> nicht aktualisieren. <2> Bitte überprüfen Sie die Kalendereinstellungen oder entfernen Sie Ihren Kalender und fügen Sie Ihren Kalender erneut hinzu </2>",
"attendee_name": "Teilnehmername",
"attendee_name": "Name des Teilnehmenden",
"broken_integration": "Defekte Integration",
"problem_adding_video_link": "Beim Hinzufügen eines Video-Links ist ein Problem aufgetreten",
"problem_updating_calendar": "Es gab ein Problem beim Aktualisieren Ihres Kalenders",
@ -1064,30 +1067,31 @@
"connect_your_calendar_and_link": "Sie können Ihren Kalender von <1>hier</1> verbinden.",
"default_calendar_selected": "Standardkalender",
"hide_from_profile": "Im Profil ausblenden",
"event_setup_tab_title": "Event-Einrichtung",
"event_setup_tab_title": "Termin-Einrichtung",
"event_limit_tab_title": "Limits",
"event_limit_tab_description": "Wie oft können Sie gebucht werden",
"event_advanced_tab_description": "Kalendereinstellungen & mehr...",
"event_advanced_tab_title": "Erweitert",
"select_which_cal": "Wählen Sie den Kalender aus, zu dem Buchungen hinzugefügt werden sollen",
"custom_event_name": "Eigener Ereignisname",
"custom_event_name_description": "Erstellen Sie benutzerdefinierte Ereignisnamen, die im Kalenderereignis angezeigt werden sollen",
"select_which_cal": "Wählen Sie den Kalender aus, zu dem Termine hinzugefügt werden sollen",
"custom_event_name": "Eigener Terminname",
"custom_event_name_description": "Erstellen Sie benutzerdefinierte Terminnamen, die im Kalendereintrag angezeigt werden sollen",
"2fa_required": "Zwei-Faktor-Authentifizierung erforderlich",
"incorrect_2fa": "Falscher Zwei-Faktor-Authentifizierungscode",
"which_event_type_apply": "Auf welchen Ereignistyp wird dies angewandt?",
"which_event_type_apply": "Auf welchen Termintyp wird dies angewandt?",
"no_workflows_description": "Workflows ermöglichen die einfache Automatisierung des Versands von Benachrichtigungen und Erinnerungen, so dass Sie Prozesse rund um Ihre Ereignisse erstellen können.",
"timeformat_profile_hint": "Dies ist eine interne Einstellung und hat keinen Einfluss darauf, wie die Zeiten auf den öffentlichen Buchungsseiten für Sie oder jemanden, der Sie buchen möchte, angezeigt werden.",
"create_workflow": "Einen Workflow erstellen",
"do_this": "Mache dies",
"turn_off": "Abschalten",
"settings_updated_successfully": "Einstellungen erfolgreich aktualisiert",
"error_updating_settings": "Fehler beim Aktualisieren der Einstellungen",
"bio_hint": "Schreiben Sie eine kurze Beschreibung, welche auf Ihrer persönlichen Url-Seite erscheinen wird.",
"bio_hint": "Schreiben Sie eine kurze Beschreibung, welche auf Ihrer persönlichen Profil-Seite erscheinen wird.",
"delete_account_modal_title": "Account löschen",
"delete_my_account": "Meinen Account löschen",
"start_of_week": "Wochenstart",
"select_calendars": "Wählen Sie aus, in welchen Kalendern Sie nach Konflikten suchen wollen, um Doppelbuchungen zu vermeiden.",
"check_for_conflicts": "Auf Konflikte prüfen",
"adding_events_to": "Ereignisse hinzufügen zu",
"adding_events_to": "Termine hinzufügen zu",
"follow_system_preferences": "Systemeinstellungen folgen",
"custom_brand_colors": "Eigene Marken-Farben",
"customize_your_brand_colors": "Passen Sie Ihre eigene Markenfarbe auf Ihrer Buchungsseite an.",
@ -1118,7 +1122,11 @@
"invoices": "Rechnungen",
"embeds": "Embeds",
"impersonation": "Benutzeridentitätswechsel",
"impersonation_description": "Einstellungen um Benutzeridentitätswechsel zu verwalten",
"users": "Benutzer",
"profile_description": "Einstellungen für Ihr {{appName}} Profil verwalten",
"users_description": "Hier findest du eine Auflistung aller Benutzer",
"users_listing": "Benutzerliste",
"general_description": "Einstellungen für Ihre Sprache und Zeitzone verwalten",
"calendars_description": "Konfigurieren Sie, wie Ihre Event-Typen mit Ihren Kalendern interagieren sollen",
"appearance_description": "Einstellungen für Ihre Buchungsdarstellung verwalten",
@ -1188,7 +1196,7 @@
"exchange_authentication_ntlm": "NTLM-Authentifizierung",
"exchange_compression": "GZip-Kompression",
"routing_forms_description": "Hier sehen Sie alle Formulare und Weiterleitungen, die Sie erstellt haben.",
"routing_forms_send_email_owner": "E-Mail an Besitzer senden",
"routing_forms_send_email_owner": "E-Mail an Eigentümer senden",
"routing_forms_send_email_owner_description": "Sendet eine E-Mail an den Eigentümer, wenn das Formular abgeschickt wird",
"add_new_form": "Neues Formular hinzufügen",
"form_description": "Erstellen Sie Ihr Formular, um einen Bucher zu anzuleiten",
@ -1292,11 +1300,15 @@
"saml_sp_acs_url_copied": "ACS-URL kopiert!",
"saml_sp_entity_id_copied": "SP-Entitäts-ID kopiert!",
"saml_btn_configure": "Konfigurieren",
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie...",
"free_to_use_apps": "Kostenlos",
"enterprise_license": "Das ist eine Enterprise-Funktion",
"enterprise_license_description": "Um diese Funktion zu aktivieren, holen Sie sich einen Deployment-Schlüssel von der {{consoleUrl}}-Konsole und fügen Sie ihn als CALCOM_LICENSE_KEY zu Ihrer .env hinzu. Wenn Ihr Team bereits eine Lizenz hat, wenden Sie sich bitte an {{supportMail}} für Hilfe.",
"add_calendar": "Kalender hinzufügen",
"limit_future_bookings": "Zukünftige Buchungen begrenzen",
"limit_future_bookings_description": "Begrenzt, wie weit in die Zukunft dieses Ereignis gebucht werden kann",
"no_event_types": "Keine Ereignistypen eingerichtet",
"no_event_types_description": "{{name}} hat keine für Sie buchbaren Ereignistypen eingerichtet.",
"limit_future_bookings": "Zukünftige Termine begrenzen",
"limit_future_bookings_description": "Begrenzt, wie weit in die Zukunft dieser Termin gebucht werden kann",
"no_event_types": "Keine Termintypen eingerichtet",
"no_event_types_description": "{{name}} hat keine für Sie buchbaren Termintypen eingerichtet.",
"billing_frequency": "Rechnungshäufigkeit",
"monthly": "Monatlich",
"yearly": "Jährlich",
@ -1308,7 +1320,7 @@
"member_already_invited": "Mitglied wurde bereits eingeladen",
"enter_email_or_username": "E-Mail oder Benutzername eingeben",
"team_name_taken": "Dieser Name ist bereits vergeben",
"must_enter_team_name": "Teamname muss eingegeben werden",
"must_enter_team_name": "Team-Name muss eingegeben werden",
"team_url_required": "Team-URL muss eingegeben werden",
"team_url_taken": "Diese URL ist bereits vergeben",
"team_publish": "Team veröffentlichen",
@ -1321,8 +1333,8 @@
"reason": "Grund",
"sender_id": "Absender-ID",
"sender_id_error_message": "Nur Buchstaben, Zahlen und Leerzeichen erlaubt (max. 11 Zeichen)",
"test_routing_form": "Testleitungsformular",
"test_preview": "Testvorschau",
"test_routing_form": "Testweiterleitungsformular",
"test_preview": "Vorschau testen",
"route_to": "Route zu",
"test_preview_description": "Testen Sie Ihr Leitungsformular, ohne Daten zu senden",
"test_routing": "Testweiterleitung"

View File

@ -466,14 +466,14 @@
"booking_confirmation": "Confirm your {{eventTypeTitle}} with {{profileName}}",
"booking_reschedule_confirmation": "Reschedule your {{eventTypeTitle}} with {{profileName}}",
"in_person_meeting": "In-person meeting",
"attendeeInPerson":"In Person (Attendee Address)",
"inPerson":"In Person (Organizer Address)",
"attendeeInPerson": "In Person (Attendee Address)",
"inPerson": "In Person (Organizer Address)",
"link_meeting": "Link meeting",
"phone_call": "Attendee Phone Number",
"your_number": "Your phone number",
"phone_number": "Phone Number",
"attendee_phone_number": "Attendee Phone Number",
"organizer_phone_number" :"Organizer Phone Number",
"organizer_phone_number": "Organizer Phone Number",
"host_phone_number": "Your Phone Number",
"enter_phone_number": "Enter phone number",
"reschedule": "Reschedule",
@ -629,6 +629,7 @@
"teams": "Teams",
"team": "Team",
"team_billing": "Team Billing",
"team_billing_description": "Manage billing for your team",
"upgrade_to_flexible_pro_title": "We've changed billing for teams",
"upgrade_to_flexible_pro_message": "There are members in your team without a seat. Upgrade your pro plan to cover missing seats.",
"changed_team_billing_info": "As of January 2022 we charge on a per-seat basis for team members. Members of your team who had PRO for free are now on a 14 day trial. Once their trial expires these members will be hidden from your team unless you upgrade now.",
@ -663,7 +664,7 @@
"show_advanced_settings": "Show advanced settings",
"event_name": "Event Name",
"event_name_tooltip": "The name that will appear in calendars",
"meeting_with_user": "Meeting with {ATTENDEE}",
"meeting_with_user": "Meeting with {{attendeeName}}",
"additional_inputs": "Additional Inputs",
"additional_input_description": "Require scheduler to input additional inputs prior the booking is confirmed",
"label": "Label",
@ -704,6 +705,7 @@
"hide_event_type": "Hide event type",
"edit_location": "Edit location",
"into_the_future": "into the future",
"when_booked_with_less_than_notice": "When booked with less than <time></time> notice",
"within_date_range": "Within a date range",
"indefinitely_into_future": "Indefinitely into the future",
"add_new_custom_input_field": "Add new custom input field",
@ -723,6 +725,8 @@
"delete_account_confirmation_message": "Are you sure you want to delete your {{appName}} account? Anyone who you've shared your account link with will no longer be able to book using it and any preferences you have saved will be lost.",
"integrations": "Integrations",
"apps": "Apps",
"apps_description": "Here you can find a list of your apps",
"apps_listing": "App listing",
"category_apps": "{{category}} apps",
"app_store": "App Store",
"app_store_description": "Connecting people, technology and the workplace.",
@ -746,6 +750,7 @@
"toggle_calendars_conflict": "Toggle the calendars you want to check for conflicts to prevent double bookings.",
"select_destination_calendar": "Create events on",
"connect_additional_calendar": "Connect additional calendar",
"calendar_updated_successfully":"Calendar updated successfully",
"conferencing": "Conferencing",
"calendar": "Calendar",
"payments": "Payments",
@ -909,7 +914,7 @@
"impersonate": "Impersonate",
"user_impersonation_heading": "User Impersonation",
"user_impersonation_description": "Allows our support team to temporarily sign in as you to help us quickly resolve any issues you report to us.",
"team_impersonation_description": "Allows your team members to temporarily sign in as you.",
"team_impersonation_description": "Allows your team Owners/Admins to temporarily sign in as you.",
"allow_booker_to_select_duration": "Allow booker to select duration",
"impersonate_user_tip": "All uses of this feature is audited.",
"impersonating_user_warning": "Impersonating username \"{{user}}\".",
@ -1028,6 +1033,9 @@
"error_removing_app": "Error removing app",
"web_conference": "Web conference",
"requires_confirmation": "Requires confirmation",
"always_requires_confirmation": "Always",
"requires_confirmation_threshold": "Requires confirmation if booked with < {{time}} $t({{unit}}_timeUnit) notice",
"may_require_confirmation": "May require confirmation",
"nr_event_type_one": "{{count}} event type",
"nr_event_type_other": "{{count}} event types",
"add_action": "Add action",
@ -1125,6 +1133,7 @@
"incorrect_2fa": "Incorrect two factor authentication code",
"which_event_type_apply": "Which event type will this apply to?",
"no_workflows_description": "Workflows enable simple automation to send notifications & reminders enabling you to build processes around your events.",
"timeformat_profile_hint": "This is an internal setting and will not affect how times are displayed on public booking pages for you or anyone booking you.",
"create_workflow": "Create a workflow",
"do_this": "Do this",
"turn_off": "Turn off",
@ -1170,12 +1179,15 @@
"invoices": "Invoices",
"embeds": "Embeds",
"impersonation": "Impersonation",
"impersonation_description": "Settings to manage user impersonation",
"users": "Users",
"profile_description": "Manage settings for your {{appName}} profile",
"users_description": "Here you can find a list of all users",
"users_listing": "User listing",
"general_description": "Manage settings for your language and timezone",
"calendars_description": "Configure how your event types interact with your calendars",
"appearance_description": "Manage settings for your booking appearance",
"conferencing_description": "Manage your video conferencing apps for your meetings",
"conferencing_description": "Add your favourite video conferencing apps for your meetings",
"password_description": "Manage settings for your account passwords",
"2fa_description": "Manage settings for your account passwords",
"we_just_need_basic_info": "We just need some basic info to get your profile setup.",
@ -1381,17 +1393,50 @@
"choose_common_schedule_team_event_description": "Enable this if you want to use a common schedule between hosts. When disabled, each host will be booked based on their default schedule.",
"reason": "Reason",
"sender_id": "Sender ID",
"sender_id_error_message":"Only letters, numbers and spaces allowed (max. 11 characters)",
"sender_id_error_message": "Only letters, numbers and spaces allowed (max. 11 characters)",
"test_routing_form": "Test Routing Form",
"test_preview": "Test Preview",
"route_to": "Route to",
"test_preview_description": "Test your routing form without submitting any data",
"test_routing": "Test Routing",
"payment_app_disabled": "An admin has disabled a payment app",
"edit_event_type": "Edit event type",
"collective_scheduling": "Collective Scheduling",
"make_it_easy_to_book": "Make it easy to book your team when everyone is available.",
"find_the_best_person": "Find the best person available and cycle through your team.",
"fixed_round_robin": "Fixed round robin",
"add_one_fixed_attendee": "Add one fixed attendee and round robin through a number of attendees.",
"calcom_is_better_with_team": "Cal.com is better with teams",
"add_your_team_members": "Add your team members to your event types. Use collective scheduling to include everyone or find the most suitable person with round robin scheduling.",
"booking_limit_reached":"Booking Limit for this event type has been reached",
"admin_has_disabled": "An admin has disabled {{appName}}",
"disabled_app_affects_event_type": "An admin has disabled {{appName}} which affects your event type {{eventType}}",
"disable_payment_app": "The admin has disabled {{appName}} which affects your event type {{title}}. Attendees are still able to book this type of event but will not be prompted to pay. You may hide hide the event type to prevent this until your admin renables your payment method.",
"payment_disabled_still_able_to_book": "Attendees are still able to book this type of event but will not be prompted to pay. You may hide hide the event type to prevent this until your admin reenables your payment method.",
"app_disabled_with_event_type": "The admin has disabled {{appName}} which affects your event type {{title}}.",
"app_disabled_video": "The admin has disabled {{appName}} which may affect your event types. If you have event types with {{appName}} as the location then it will default to Cal Video.",
"app_disabled_subject": "{{appName}} has been disabled",
"navigate_installed_apps": "Go to installed apps",
"disabled_calendar": "If you have another calendar installed new bookings will be added to it. If not then connect a new calendar so you do not miss any new bookings.",
"enable_apps": "Enable Apps",
"enable_apps_description": "Enable apps that users can integrate with Cal.com",
"app_is_enabled": "{{appName}} is enabled",
"app_is_disabled": "{{appName}} is disabled",
"keys_have_been_saved": "Keys have been saved",
"disable_app": "Disable App",
"disable_app_description": "Disabling this app could cause problems with how your users interact with Cal",
"edit_keys": "Edit Keys",
"apps_description": "Enable apps for your instance of Cal",
"no_available_apps": "There are no available apps",
"no_available_apps_description": "Please ensure there are apps in your deployment under 'packages/app-store'",
"no_apps": "There are no apps enabled in this instance of Cal",
"apps_settings": "Apps settings",
"fill_this_field": "Please fill in this field",
"options": "Options",
"enter_option": "Enter Option {{index}}",
"add_an_option": "Add an option",
"radio": "Radio",
"individual":"Individual"
"individual":"Individual",
"kbar_search_placeholder" : "Start typing to search",
"set_as_default": "Set as default"
}

View File

@ -504,7 +504,7 @@
"add_team_members_description": "Invitar a otros a unirse a tu equipo",
"add_team_member": "Añadir miembro del equipo",
"invite_new_member": "Invitar a un nuevo miembro",
"invite_new_member_description": "Nota: Esto costará <1>un asiento adicional ($15/m)</1> en su suscripción.",
"invite_new_member_description": "Nota: Esto costará <1>un cupo adicional ($15/m)</1> en su suscripción.",
"invite_new_team_member": "Invita a alguien a tu equipo.",
"change_member_role": "Cambiar rol del miembro del equipo",
"disable_cal_branding": "Desactivar marca de {{appName}}",
@ -653,7 +653,7 @@
"show_advanced_settings": "Mostrar Opciones Avanzadas",
"event_name": "Nombre del Evento",
"event_name_tooltip": "Nombre que aparecerá en los calendarios",
"meeting_with_user": "Reunión con {ATTENDEE}",
"meeting_with_user": "Reunión con {{attendeeName}}",
"additional_inputs": "Campos Adicionales",
"additional_input_description": "Requerir que el planificador introduzca entradas adicionales antes de confirmar la reserva",
"label": "Etiqueta",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Código de autenticación de dos factores incorrecto",
"which_event_type_apply": "¿A qué tipo de evento se aplicará esto?",
"no_workflows_description": "Los flujos de trabajo permiten una automatización simple para enviar notificaciones y recordatorios que le permiten crear procesos para sus eventos.",
"timeformat_profile_hint": "Esta es una configuración interna y no afectará la manera en que se muestran los horarios en las páginas de reservas públicas para usted o para cualquier persona que lo solicite.",
"create_workflow": "Crear un flujo de trabajo",
"do_this": "Hacer esto",
"turn_off": "Apagar",
@ -1366,7 +1367,7 @@
"sender_id": "ID del remitente",
"sender_id_error_message": "Sólo se permiten letras, números y espacios (máx. 11 caracteres)",
"test_routing_form": "Prueba de formulario de ruta",
"test_preview": "Vista previa de la prueba",
"test_preview": "Vista previa de prueba",
"route_to": "Ruta hacia",
"test_preview_description": "Pruebe su formulario de ruta sin enviar ningún dato",
"test_routing": "Prueba de ruta"

View File

@ -67,7 +67,7 @@
"event_still_awaiting_approval": "Un événement attend toujours votre approbation",
"booking_submitted_subject": "Réservation confirmée : {{eventType}} avec {{name}} le {{date}}",
"your_meeting_has_been_booked": "Votre réunion a été réservée",
"event_type_has_been_rescheduled_on_time_date": "Votre {{eventType}} avec {{name}} a été reporté au {{date}}.",
"event_type_has_been_rescheduled_on_time_date": "Votre {{eventType}} avec {{name}} a été reporté à {{time}} ({{timeZone}}) le {{date}}.",
"event_has_been_rescheduled": "Votre évènement a été reprogrammé.",
"request_reschedule_title_attendee": "Demande de report de votre réservation",
"request_reschedule_subtitle": "{{organizer}} a annulé la réservation et vous a demandé de choisir un autre moment.",
@ -653,7 +653,7 @@
"show_advanced_settings": "Afficher les paramètres avancés",
"event_name": "Nom de l'événement",
"event_name_tooltip": "Le nom qui apparaîtra dans les calendriers",
"meeting_with_user": "Rendez-vous avec {ATTENDEE}",
"meeting_with_user": "Rendez-vous avec {{attendeeName}}",
"additional_inputs": "Entrées additionnelles",
"additional_input_description": "Exiger que le planificateur saisisse des entrées supplémentaires avant que la réservation soit confirmée",
"label": "Libellé",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Code d'authentification à deux facteurs incorrect",
"which_event_type_apply": "À quel type d'événement s'appliquera-t-il ?",
"no_workflows_description": "Les workflows permettent une automatisation simple pour envoyer des notifications et des rappels vous permettant de construire des processus autour de vos événements.",
"timeformat_profile_hint": "Ceci est un paramètre interne et n'affectera pas la façon dont les heures sont affichées sur les pages publiques de réservation pour vous ou toute personne qui réserve votre temps.",
"create_workflow": "Créer un workflow",
"do_this": "Effectuer ceci",
"turn_off": "Désactiver",

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "האירוע המתוכנן בוטל",
"organizer": "מארגן",
"need_to_reschedule_or_cancel": "רוצה לקבוע מועד חדש או לבטל?",
"cancellation_reason": "הסיבה לביטול",
"cancellation_reason_placeholder": "מדוע בחרת לבטל? (אופציונלי)",
"cancellation_reason": "הסיבה לביטול (לא חובה)",
"cancellation_reason_placeholder": "מדוע בחרת לבטל? (לא חובה)",
"rejection_reason": "הסיבה לדחייה",
"rejection_reason_title": "לדחות את בקשת ההזמנה?",
"rejection_reason_description": "בטוח שברצונך לדחות את ההזמנה? אנחנו ניידע את האדם שניסה להזמין. יש לך אפשרות להוסיף את הסיבה לדחייה למטה.",
@ -108,7 +108,7 @@
"link_expires": "נ.ב. תוקף הקישור יפוג תוך {{expiresIn}} שעות.",
"upgrade_to_per_seat": "שדרג למינוי לפי מקום",
"team_upgrade_seats_details": "לא התבצע תשלום עבור {{unpaidCount}} מקומות מתוך {{memberCount}} חברי הצוות שלך. תמורת ${{seatPrice}} לחודש למקום, עלות המינוי הכוללת המשוערת שלך היא ${{totalCost}} לחודש.",
"team_upgrade_banner_description": "אנחנו מודים לך על שניסית את החבילה החדשה שלנו לצוותים. שמנו לב שהצוות שלך, \"{{teamName}}\", זקוק לשדרוג.",
"team_upgrade_banner_description": "אנחנו מודים לך על כך שניסית את החבילה החדשה שלנו לצוותים. שמנו לב שהצוות שלך, \"{{teamName}}\", זקוק לשדרוג.",
"team_upgrade_banner_action": "כאן משדרגים",
"team_upgraded_successfully": "הצוות שלך שודרג בהצלחה!",
"use_link_to_reset_password": "נא להשתמש בקישור הבא כדי לאפס את הסיסמה",
@ -419,7 +419,7 @@
"forgotten_secret_description": "אם שכחת או איבדת את הסוד, יש לך אפשרות לשנות אותו, אבל חשוב לזכור שיהיה צורך לעדכן את כל השילובים שבהם נעשה שימוש בסוד זה",
"current_incorrect_password": "הסיסמה הנוכחית שגויה",
"password_hint_caplow": "שילוב של אותיות רישיות וקטנות",
"password_hint_min": "באורך של 7 תווים לפחות",
"password_hint_min": "באורך 8 תווים לפחות",
"password_hint_num": "לכלול לפחות ספרה אחת",
"invalid_password_hint": "הסיסמה חייבת להיות באורך של 7 תווים לפחות ולכלול לפחות ספרה אחת ושילוב של אותיות רישיות וקטנות",
"incorrect_password": "הסיסמה שגויה.",
@ -653,7 +653,7 @@
"show_advanced_settings": "הצגת הגדרות מתקדמות",
"event_name": "שם האירוע",
"event_name_tooltip": "השם שיופיע בלוחות השנה",
"meeting_with_user": "פגישה עם {ATTENDEE}",
"meeting_with_user": "פגישה עם {{attendeeName}}",
"additional_inputs": "אפשרויות קלט נוספות",
"additional_input_description": "לדרוש מכלי התזמון להוסיף פרטים לפני אישור ההזמנה",
"label": "תווית",
@ -784,7 +784,7 @@
"analytics": "ניתוח נתונים",
"empty_installed_apps_headline": "אין אפליקציות מותקנות",
"empty_installed_apps_description": "אפליקציות מאפשרות לך לשפר משמעותית את תהליכי העבודה שלך ואת תהליך קביעת המועדים.",
"empty_installed_apps_button": "נא לעיין ב-App Store או להתקין מהאפליקציות שלהלן",
"empty_installed_apps_button": "נא לעיין ב-App Store",
"manage_your_connected_apps": "ניהול האפליקציות המותקנות שלך או שינוי הגדרות",
"browse_apps": "עיון באפליקציות",
"features": "תכונות",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "קוד שגוי של אימות בשני גורמים",
"which_event_type_apply": "על איזה סוג אירוע זה יחול?",
"no_workflows_description": "תהליכי עבודה מאפשרים שימוש באוטומציה פשוטה לשליחת עדכונים ותזכורות, כך שאת/ה יכול/ה לתכנן תהליכים שקשורים לאירועים שלך.",
"timeformat_profile_hint": "זוהי הגדרה פנימית שלא תשפיע על אופן התצוגה של תאריכים ושעות בדפים של הזמנות ציבוריות עבורך או עבור מי שמבצע הזמנות בשמך.",
"create_workflow": "יצירת תהליך עבודה",
"do_this": "יש לבצע את הפעולה הזו",
"turn_off": "השבתה",
@ -1228,7 +1229,7 @@
"exchange_compression": "דחיסת GZip",
"routing_forms_description": "כאן ניתן לצפות בכל הטפסים והנתיבים שיצרת.",
"routing_forms_send_email_owner": "שליחת דוא\"ל לבעלים",
"routing_forms_send_email_owner_description": "שליחת דוא\"ל לבעלים כשהטופס נשלח",
"routing_forms_send_email_owner_description": "דוא\"ל נשלח לבעלים לאחר שליחת הטופס",
"add_new_form": "הוספת טופס חדש",
"form_description": "צור/י טופס משלך כדי לנתב אל מזמין/ה",
"copy_link_to_form": "להעתיק את הקישור לטופס",
@ -1340,11 +1341,11 @@
"limit_future_bookings": "הגבלת הזמנות עתידיות",
"limit_future_bookings_description": "הגבלת התאריך הרחוק ביותר בעתיד שעד אליו ניתן להזמין את האירוע הזה",
"no_event_types": "לא הוגדר אף סוג אירוע",
"no_event_types_description": "{{name}} לא הגדיר/ה אף סוג אירוע שבאפשרותך להזמין.",
"no_event_types_description": "לא הוגדר על ידי {{name}} אף סוג אירוע שבאפשרותך להזמין.",
"billing_frequency": "תדירות חיוב",
"monthly": "אחת לחודש",
"yearly": "אחת לשנה",
"checkout": "קופה",
"checkout": "תשלום",
"your_team_disbanded_successfully": "פיזור הצוות שלך בוצע בהצלחה",
"error_creating_team": "אירעה שגיאה במהלך יצירת הצוות",
"you": "את/ה",
@ -1363,11 +1364,11 @@
"choose_common_schedule_team_event": "בחירת לוח זמנים משותף",
"choose_common_schedule_team_event_description": "יש להפעיל אפשרות זו אם רוצים להשתמש בלוח זמנים משותף בין המארחים. אם האפשרות הזו מבוטלת, כל מארח יוזמן לפי לוח הזמנים המשמש כברירת המחדל שלו.",
"reason": "סיבה",
"sender_id": "מזהה שולח/ת",
"sender_id": "מזהה השולח/ת",
"sender_id_error_message": "מותר להשתמש רק באותיות, במספרים וברווחים (לכל היותר 11 תווים)",
"test_routing_form": "בדיקת טופס ניתוב",
"test_preview": "תצוגה מקדימה של הבדיקה",
"test_preview": "בדיקת התצוגה המקדימה",
"route_to": "ניתוב אל",
"test_preview_description": "בדיקת טופס הניתוב שלך בלי לשלוח נתונים",
"test_preview_description": "לבדוק את טופס הניתוב שלך בלי לשלוח נתונים",
"test_routing": "בדיקת ניתוב"
}

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Mostra impostazioni avanzate",
"event_name": "Nome Dell'Evento",
"event_name_tooltip": "Il nome che apparirà nei calendari",
"meeting_with_user": "Riunione con {ATTENDEE}",
"meeting_with_user": "Riunione con {{attendeeName}}",
"additional_inputs": "Input Aggiuntivi",
"additional_input_description": "Chiedi al creatore del programma di inserire dei campi di input aggiuntivi prima che la prenotazione sia confermata",
"label": "Etichetta",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Codice di autenticazione a due fattori errato",
"which_event_type_apply": "A quale tipo di evento si applicherà?",
"no_workflows_description": "I flussi di lavoro attivano una semplice automazione per inviare notifiche e promemoria che ti consentono di creare processi per i tuoi eventi.",
"timeformat_profile_hint": "Questa è un'impostazione interna e non influenzerà il modo in cui gli orari sono visualizzati nelle pagine di prenotazione pubbliche a te e a chiunque faccia una prenotazione.",
"create_workflow": "Crea un flusso di lavoro",
"do_this": "Esegui questa operazione",
"turn_off": "Disattiva",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "詳細設定を表示",
"event_name": "イベント名",
"event_name_tooltip": "カレンダーに表示される名前",
"meeting_with_user": "{ATTENDEE} とのミーティング",
"meeting_with_user": "{{attendeeName}} とのミーティング",
"additional_inputs": "追加入力",
"additional_input_description": "予約確認前にスケジュール設定者の追加入力が必要です",
"label": "ラベル",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "不正な二要素認証コード",
"which_event_type_apply": "これはどのイベントタイプに適用されますか?",
"no_workflows_description": "ワークフローを使用すると通知やリマインダーの送信を簡単に自動化することができ、イベントに関するプロセスを構築することができます。",
"timeformat_profile_hint": "これは内部設定であり、あなたやあなたを予約した人の公開予約ページの時間表示には影響しません。",
"create_workflow": "ワークフローを作成",
"do_this": "これを実行する",
"turn_off": "オフにする",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "고급 설정 표시",
"event_name": "이벤트 이름",
"event_name_tooltip": "캘린더에 표시될 이름",
"meeting_with_user": "{ATTENDEE} 님과의 회의",
"meeting_with_user": "{{attendeeName}} 님과의 회의",
"additional_inputs": "추가 입력",
"additional_input_description": "예약이 확정되기 전 스케줄러가 추가 입력을 해야 합니다",
"label": "레이블",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "잘못된 2단계 인증 코드",
"which_event_type_apply": "이것은 어떤 이벤트 유형에 적용될까요?",
"no_workflows_description": "워크플로를 사용하면 이벤트와 관련된 프로세스를 구축할 수 있도록 알림 및 미리 알림을 보내는 간단한 자동화가 가능합니다.",
"timeformat_profile_hint": "이것은 내부 설정이며 귀하 또는 귀하를 예약하는 모든 사람의 공개 예약 페이지에 시간이 표시되는 방식에는 영향을 미치지 않습니다.",
"create_workflow": "워크플로를 만듭니다",
"do_this": "이렇게 하세요",
"turn_off": "끄기",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Toon geavanceerde instellingen",
"event_name": "Naam van evenement",
"event_name_tooltip": "De naam die in kalenders wordt weergegeven",
"meeting_with_user": "Afspraak met {ATTENDEE}",
"meeting_with_user": "Afspraak met {{attendeeName}}",
"additional_inputs": "Extra invoer",
"additional_input_description": "Planner vereist om extra gegevens in te voeren voordat de boeking wordt bevestigd",
"label": "Omschrijving",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Onjuiste tweestapsverificatiecode",
"which_event_type_apply": "Op welk type gebeurtenis is dit van toepassing?",
"no_workflows_description": "Werkstromen maken eenvoudige automatisering van het versturen van meldingen en herinneringen mogelijk, zodat u processen rond uw gebeurtenissen kunt opbouwen.",
"timeformat_profile_hint": "Dit is een interne instelling en heeft geen invloed op hoe de tijden worden weergegeven op de openbare boekingspagina's voor u of voor iemand die u boekt.",
"create_workflow": "Maak een werkstroom",
"do_this": "Doe dit",
"turn_off": "Uitschakelen",

View File

@ -6,9 +6,9 @@
"second_other": "{{count}} sekunder",
"upgrade_now": "Oppgrader nå",
"accept_invitation": "Aksepter invitasjon",
"calcom_explained": "Cal.com er en åpen kildekodeversjon av Calendly som gir deg kontroll over dine egne data, arbeidsflyt og utseende.",
"calcom_explained": "{{appName}} er en åpen kildekodeversjon av Calendly som gir deg kontroll over dine egne data, arbeidsflyt og utseende.",
"have_any_questions": "Har du spørsmål? Vi er her for å hjelpe.",
"reset_password_subject": "Cal.com: Instruksjoner for tilbakestilling av passord",
"reset_password_subject": "{{appName}}: Instruksjoner for tilbakestilling av passord",
"event_declined_subject": "Avslått: {{eventType}} med {{name}} kl. {{date}}",
"event_cancelled_subject": "Avbrutt: {{eventType}} med {{name}} kl. {{date}}",
"event_request_declined": "Din hendelsesforespørsel har blitt avvist",
@ -52,6 +52,7 @@
"still_waiting_for_approval": "En hendelse venter fremdeles på godkjenning",
"event_is_still_waiting": "Hendelsesforespørsel venter fortsatt: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Ingen flere resultater",
"no_results": "Ingen resultater",
"load_more_results": "Last inn flere resultater",
"integration_meeting_id": "{{integrationName}} møte-ID: {{meetingId}}",
"confirmed_event_type_subject": "Bekreftet: {{eventType}} med {{name}} kl. {{date}}",
@ -99,9 +100,9 @@
"join_team": "Bli med i teamet",
"manage_this_team": "Administrer dette teamet",
"team_info": "Team Info",
"request_another_invitation_email": "Hvis du foretrekker å ikke bruke {{toEmail}} som Cal.com-e-post eller allerede har en Cal.com-konto, vennligst be om en annen invitasjon til denne e-posten.",
"request_another_invitation_email": "Hvis du foretrekker å ikke bruke {{toEmail}} som {{appName}}-e-post eller allerede har en {{appName}}-konto, vennligst be om en annen invitasjon til denne e-posten.",
"you_have_been_invited": "Du har blitt invitert til å bli med i teamet {{teamName}}",
"user_invited_you": "{{user}} inviterte deg til å bli med i teamet {{team}} på Cal.com",
"user_invited_you": "{{user}} inviterte deg til å bli med i teamet {{team}} på {{appName}}",
"hidden_team_member_title": "Du er skjult i dette teamet",
"hidden_team_member_message": "Setet ditt er ikke betalt for, enten oppgrader til PRO eller la teameieren få vite at de kan betale for setet ditt.",
"hidden_team_owner_message": "Du trenger en pro-konto for å bruke team, du er skjult inntil du oppgraderer.",
@ -113,8 +114,8 @@
"team_upgraded_successfully": "Teamet ditt ble oppgradert!",
"use_link_to_reset_password": "Bruk lenken nedenfor for å tilbakestille passordet ditt",
"hey_there": "Hei der,",
"forgot_your_password_calcom": "Glemt passordet? - Cal.com",
"delete_webhook_confirmation_message": "Er du sikker på at du vil slette denne webhooken? Du vil ikke lenger motta Cal.com møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller avlyst.",
"forgot_your_password_calcom": "Glemt passordet? - {{appName}}",
"delete_webhook_confirmation_message": "Er du sikker på at du vil slette denne webhooken? Du vil ikke lenger motta {{appName}} møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller avlyst.",
"confirm_delete_webhook": "Ja, slett webhook",
"edit_webhook": "Rediger Webhook",
"delete_webhook": "Slett Webhook",
@ -190,7 +191,7 @@
"30min_meeting": "30 Min Møte",
"secret": "Hemmelighet",
"leave_blank_to_remove_secret": "La stå tomt for å fjerne hemmeligheten",
"webhook_secret_key_description": "Sørg for at serveren din kun mottar de forventede Cal.com-forespørslene av sikkerhetsgrunner",
"webhook_secret_key_description": "Sørg for at serveren din kun mottar de forventede {{appName}}-forespørslene av sikkerhetsgrunner",
"secret_meeting": "Hemmelig Møte",
"login_instead": "Logg på i stedet",
"already_have_an_account": "Har du allerede en bruker?",
@ -234,13 +235,13 @@
"details": "Detaljer",
"welcome": "Velkommen",
"welcome_back": "Velkommen tilbake",
"welcome_to_calcom": "Velkommen til Cal.com",
"welcome_to_calcom": "Velkommen til {{appName}}",
"welcome_instructions": "Fortell oss hva vi skal kalle deg og gi oss beskjed om hvilken tidssone du er i. Du kan redigere dette senere.",
"connect_caldav": "Koble til CalDav (Beta)",
"credentials_stored_and_encrypted": "Din legitimasjon vil bli lagret og kryptert.",
"connect": "Tilkoble",
"try_for_free": "Prøv det gratis",
"create_booking_link_with_calcom": "Lag din egen booking lenke med Cal.com",
"create_booking_link_with_calcom": "Lag din egen booking lenke med {{appName}}",
"who": "Hvem",
"what": "Hva",
"when": "Når",
@ -248,6 +249,7 @@
"add_to_calendar": "Legg til i kalender",
"add_another_calendar": "Legg til en annen kalender",
"other": "Annet",
"email_sign_in_subject": "Påloggings-lenken din for {{appName}}",
"emailed_you_and_attendees": "Vi sendte deg og de andre deltakerne en kalenderinvitasjon med alle detaljene.",
"emailed_you_and_attendees_recurring": "Vi sendte deg og de andre deltakerne en kalenderinvitasjon på e-post for den første av disse gjentakende hendelsene.",
"emailed_you_and_any_other_attendees": "Du og eventuelle andre deltakere har fått e-post med denne informasjonen.",
@ -346,7 +348,7 @@
"dark": "Mørkt",
"automatically_adjust_theme": "Juster tema automatisk basert på den invitertes innstillinger",
"user_dynamic_booking_disabled": "Noen av brukerne i gruppen har for øyeblikket deaktivert dynamiske gruppebookinger",
"allow_dynamic_booking_tooltip": "Gruppebooking-lenker som kan opprettes dynamisk ved å legge til flere brukernavn med en '+'. eksempel: 'cal.com/bailey+peer'",
"allow_dynamic_booking_tooltip": "Gruppebooking-lenker som kan opprettes dynamisk ved å legge til flere brukernavn med en '+'. eksempel: '{{appName}}/bailey+peer'",
"allow_dynamic_booking": "Tillat deltakere å booke deg gjennom dynamiske gruppebookinger",
"email": "E-post",
"email_placeholder": "ola.nordman@eksempel.no",
@ -366,8 +368,8 @@
"team_webhooks": "Team Webhooker",
"create_new_webhook_to_account": "Opprett en ny webhook til kontoen din",
"new_webhook": "Ny Webhook",
"receive_cal_meeting_data": "Motta Cal-møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller kansellert.",
"receive_cal_event_meeting_data": "Motta Cal-møtedata på en spesifisert URL, i sanntid, når denne hendelsen er planlagt eller kansellert.",
"receive_cal_meeting_data": "Motta {{appName}}-møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller kansellert.",
"receive_cal_event_meeting_data": "Motta {{appName}}-møtedata på en spesifisert URL, i sanntid, når denne hendelsen er planlagt eller kansellert.",
"responsive_fullscreen_iframe": "Responsiv fullskjerms iFrame",
"loading": "Laster inn...",
"deleting": "Sletter...",
@ -375,7 +377,7 @@
"developer": "Utvikler",
"manage_developer_settings": "Administrer utviklerinnstillingene dine.",
"iframe_embed": "iframe Bygg inn",
"embed_calcom": "Den enkleste måten å bygge inn Cal.com på nettstedet ditt.",
"embed_calcom": "Den enkleste måten å bygge inn {{appName}} på nettstedet ditt.",
"integrate_using_embed_or_webhooks": "Integrer med nettstedet ditt ved å bruke våre innebyggings-alternativer, eller få bookinginformasjon i sanntid ved å bruke egendefinerte webhooks.",
"schedule_a_meeting": "Planlegg et møte",
"view_and_manage_billing_details": "Se og administrer betalingsdetaljene dine",
@ -511,8 +513,8 @@
"invite_new_member_description": "Merk: Dette vil <1>koste et ekstra sete ($15/m)</1> på abonnementet ditt.",
"invite_new_team_member": "Inviter noen til teamet ditt.",
"change_member_role": "Endre team-medlems-rolle",
"disable_cal_branding": "Deaktiver Cal merkeprofilering",
"disable_cal_branding_description": "Skjul all Cal.com-merkevareprofilering fra de offentlige sidene dine.",
"disable_cal_branding": "Deaktiver {{appName}} merkeprofilering",
"disable_cal_branding_description": "Skjul all {{appName}}-merkevareprofilering fra de offentlige sidene dine.",
"danger_zone": "Faresone",
"back": "Tilbake",
"cancel": "Avbryt",
@ -632,7 +634,7 @@
"changed_team_billing_info": "Fra januar 2022 tar vi betalt per sete for teammedlemmer. Medlemmer av teamet ditt som hadde PRO gratis, har nå en 14 dagers prøveversjon. Når prøveperioden deres utløper, vil disse medlemmene bli skjult for teamet ditt med mindre du oppgraderer nå.",
"create_manage_teams_collaborative": "Opprett og administrer team for å bruke samarbeidsfunksjoner.",
"only_available_on_pro_plan": "Denne funksjonen er bare tilgjengelig i Pro-planen",
"remove_cal_branding_description": "For å fjerne Cal-merket fra bookingsidene dine, må du oppgradere til en Pro-konto.",
"remove_cal_branding_description": "For å fjerne {{appName}}-merket fra bookingsidene dine, må du oppgradere til en Pro-konto.",
"edit_profile_info_description": "Rediger profilinformasjonen din, som viser på planleggingslenken.",
"change_email_tip": "Du må kanskje logge ut og inn igjen for å se endringen tre i kraft.",
"little_something_about": "Litt om deg selv.",
@ -681,21 +683,21 @@
"private_link_label": "Privat lenke",
"private_link_hint": "Din private lenke vil regenereres etter hver bruk",
"copy_private_link": "Kopier privat lenke",
"private_link_description": "Generer en privat URL for å dele uten å avsløre Cal-brukernavnet ditt",
"private_link_description": "Generer en privat URL for å dele uten å avsløre {{appName}}-brukernavnet ditt",
"invitees_can_schedule": "Inviterte kan velge tidspunkt",
"date_range": "Datointervall",
"calendar_days": "kalenderdager",
"business_days": "virkedager",
"set_address_place": "Angi en adresse eller sted",
"set_link_meeting": "Angi en lenke til møtet",
"cal_invitee_phone_number_scheduling": "Cal vil be den inviterte om å skrive inn et telefonnummer før valg av tidspunkt.",
"cal_provide_google_meet_location": "Cal vil oppgi en Google Meet-lokasjon.",
"cal_provide_zoom_meeting_url": "Cal vil angi en URL-adresse for Zoom-møte.",
"cal_provide_tandem_meeting_url": "Cal vil angi en URL for Tandem-møtet.",
"cal_provide_video_meeting_url": "Cal vil angi en videomøte-URL.",
"cal_invitee_phone_number_scheduling": "{{appName}} vil be den inviterte om å skrive inn et telefonnummer før valg av tidspunkt.",
"cal_provide_google_meet_location": "{{appName}} vil oppgi en Google Meet-lokasjon.",
"cal_provide_zoom_meeting_url": "{{appName}} vil angi en URL-adresse for Zoom-møte.",
"cal_provide_tandem_meeting_url": "{{appName}} vil angi en URL for Tandem-møtet.",
"cal_provide_video_meeting_url": "{{appName}} vil angi en videomøte-URL.",
"cal_provide_jitsi_meeting_url": "Vi vil generere en Jitsi Meet URL for deg.",
"cal_provide_huddle01_meeting_url": "Cal vil angi en Huddle01 web3 videomøte-URL.",
"cal_provide_teams_meeting_url": "Cal vil angi en URL for MS Teams møte. MERK: MÅ HA EN ARBEIDS- ELLER SKOLEKONTO",
"cal_provide_huddle01_meeting_url": "{{appName}} vil angi en Huddle01 web3 videomøte-URL.",
"cal_provide_teams_meeting_url": "{{appName}} vil angi en URL for MS Teams møte. MERK: MÅ HA EN ARBEIDS- ELLER SKOLEKONTO",
"require_payment": "Krev Betaling",
"commission_per_transaction": "provisjon per transaksjon",
"event_type_updated_successfully_description": "Din hendelsestype har blitt oppdatert.",
@ -718,7 +720,7 @@
"confirm_delete_event_type": "Ja, slett hendelsestype",
"delete_account": "Slett konto",
"confirm_delete_account": "Ja, slett konto",
"delete_account_confirmation_message": "Er du sikker på at du vil slette Cal.com-kontoen din? Alle du har delt konto-lenken med, vil ikke lenger kunne booke ved å bruke den, og eventuelle innstillinger du har lagret vil gå tapt.",
"delete_account_confirmation_message": "Er du sikker på at du vil slette {{appName}}-kontoen din? Alle du har delt konto-lenken med, vil ikke lenger kunne booke ved å bruke den, og eventuelle innstillinger du har lagret vil gå tapt.",
"integrations": "Integrasjoner",
"apps": "Apper",
"category_apps": "{{category}} apper",
@ -776,6 +778,7 @@
"trending_apps": "Populære Apper",
"explore_apps": "{{category}} apper",
"installed_apps": "Installerte Apper",
"free_to_use_apps": "Gratis",
"no_category_apps": "Ingen {{category}} apper",
"no_category_apps_description_calendar": "Legg til en kalenderapp for å se etter konflikter for å forhindre dobbelt-bookinger",
"no_category_apps_description_conferencing": "Prøv å legge til en konferanseapp for å integrere videosamtaler med klientene dine",
@ -814,9 +817,11 @@
"verify_wallet": "Verifiser Lommebok",
"connect_metamask": "Koble til Metamask",
"create_events_on": "Opprett hendelser på",
"enterprise_license": "Dette er en bedriftsfunksjon",
"enterprise_license_description": "For å aktivere denne funksjonen, skaff deg en distribusjonsnøkkel på {{consoleUrl}}-konsollen og legg den til i .env som CALCOM_LICENSE_KEY. Hvis teamet ditt allerede har en lisens, vennligst kontakt {{supportMail}} for å få hjelp.",
"missing_license": "Mangler Lisens",
"signup_requires": "Kommersiell lisens kreves",
"signup_requires_description": "Cal.com, Inc. tilbyr for øyeblikket ikke en gratis åpen kildekode-versjon av registreringssiden. For å få full tilgang til registreringskomponentene må du anskaffe en kommersiell lisens. For personlig bruk anbefaler vi Prisma Data Platform eller et annet Postgres-grensesnitt for å opprette kontoer.",
"signup_requires_description": "{{companyName}} tilbyr for øyeblikket ikke en gratis åpen kildekode-versjon av registreringssiden. For å få full tilgang til registreringskomponentene må du anskaffe en kommersiell lisens. For personlig bruk anbefaler vi Prisma Data Platform eller et annet Postgres-grensesnitt for å opprette kontoer.",
"next_steps": "Neste Skritt",
"acquire_commercial_license": "Skaff en kommersiell lisens",
"the_infrastructure_plan": "Infrastruktur-planen er bruksbasert og har oppstartsvennlige rabatter.",
@ -919,23 +924,23 @@
"update_location": "Oppdater Sted",
"location_updated": "Sted oppdatert",
"email_validation_error": "Det ser ikke ut som en e-postadresse",
"place_where_cal_widget_appear": "Plasser denne koden i din HTML der du vil at Cal-widgeten skal vises.",
"place_where_cal_widget_appear": "Plasser denne koden i din HTML der du vil at {{appName}}-widgeten skal vises.",
"create_update_react_component": "Opprett eller oppdater en eksisterende React-komponent som vist nedenfor.",
"copy_code": "Kopier Kode",
"code_copied": "Kode kopiert!",
"how_you_want_add_cal_site": "Hvordan vil du legge til Cal på nettstedet ditt?",
"choose_ways_put_cal_site": "Velg en av følgende måter å plassere Cal på nettstedet ditt.",
"how_you_want_add_cal_site": "Hvordan vil du legge til {{appName}} på nettstedet ditt?",
"choose_ways_put_cal_site": "Velg en av følgende måter å plassere {{appName}} på nettstedet ditt.",
"setting_up_zapier": "Sette opp din Zapier-integrasjon",
"generate_api_key": "Generer API nøkkel",
"generate_api_key_description": "Generer en API nøkkel til bruk med Cal.com på",
"generate_api_key_description": "Generer en API nøkkel til bruk med {{appName}} på",
"your_unique_api_key": "Din unike API nøkkel",
"copy_safe_api_key": "Kopier denne API nøkkelen og lagre den et trygt sted. Hvis du mister denne nøkkelen, må du generere en ny.",
"zapier_setup_instructions": "<0>Logg på Zapier-kontoen din og opprett en ny Zap.</0><1>Velg Cal.com som din Trigger-app. Velg også en utløserhendelse.</1><2>Velg kontoen din og skriv deretter inn din unike API nøkkel.</2><3>Test utløseren.</3><4>Du er klar!</4 >",
"zapier_setup_instructions": "<0>Logg på Zapier-kontoen din og opprett en ny Zap.</0><1>Velg {{appName}} som din Trigger-app. Velg også en utløserhendelse.</1><2>Velg kontoen din og skriv deretter inn din unike API nøkkel.</2><3>Test utløseren.</3><4>Du er klar!</4 >",
"install_zapier_app": "Installer først Zapier-appen i app butikken.",
"connect_apple_server": "Koble til Apple Server",
"connect_caldav_server": "Koble til CalDav (Beta)",
"calendar_url": "Kalender URL",
"apple_server_generate_password": "Generer et app-spesifikt passord for bruk med Cal.com på",
"apple_server_generate_password": "Generer et app-spesifikt passord for bruk med {{appName}} på",
"credentials_stored_encrypted": "Din legitimasjon vil bli lagret og kryptert.",
"it_stored_encrypted": "Det vil bli lagret og kryptert.",
"go_to_app_store": "Gå til App Butikken",
@ -1120,15 +1125,16 @@
"incorrect_2fa": "Feil to-faktor autentiseringskode",
"which_event_type_apply": "Hvilken hendelsestype vil dette gjelde for?",
"no_workflows_description": "Arbeidsflyter muliggjør enkel automatisering for å sende varsler og påminnelser slik at du kan bygge prosesser rundt hendelsene dine.",
"timeformat_profile_hint": "Dette er en intern innstilling og vil ikke påvirke hvordan tidspunkt vises på offentlige bookingsider for deg eller noen som booker deg.",
"create_workflow": "Lag en arbeidsflyt",
"do_this": "Gjør dette",
"turn_off": "Slå av",
"settings_updated_successfully": "Innstillingene ble oppdatert",
"error_updating_settings": "Feil under oppdatering av innstillinger",
"personal_cal_url": "Min personlige Cal URL",
"personal_cal_url": "Min personlige {{appName}} URL",
"bio_hint": "Noen setninger om deg selv. Dette vil vises på din personlige url-side.",
"delete_account_modal_title": "Slett Konto",
"confirm_delete_account_modal": "Er du sikker på at du vil slette Cal.com-kontoen din?",
"confirm_delete_account_modal": "Er du sikker på at du vil slette {{appName}}-kontoen din?",
"delete_my_account": "Slett kontoen min",
"start_of_week": "Begynnelsen av uken",
"select_calendars": "Velg hvilke kalendere du vil sjekke for konflikter for å forhindre dobbeltbookinger.",
@ -1138,7 +1144,7 @@
"custom_brand_colors": "Egendefinerte merkevare-farger",
"customize_your_brand_colors": "Tilpass din egen merkevare-farge på bookingsiden din.",
"pro": "Pro",
"removes_cal_branding": "Fjerner alt av Cal relatert merkeprofilering, f.eks. \"Powered by Cal.\"",
"removes_cal_branding": "Fjerner alt av {{appName}} relatert merkeprofilering, f.eks. \"Powered by {{appName}}.\"",
"profile_picture": "Profilbilde",
"upload": "Last opp",
"add_profile_photo": "Legg til profilbilde",
@ -1166,7 +1172,7 @@
"embeds": "Innbygginger",
"impersonation": "Etterligning",
"users": "Brukere",
"profile_description": "Administrer innstillinger for Cal profilen din",
"profile_description": "Administrer innstillinger for {{appName}} profilen din",
"general_description": "Administrer innstillinger for språk og tidssone",
"calendars_description": "Konfigurer hvordan hendelsestypene dine samhandler med kalenderne dine",
"appearance_description": "Administrer innstillinger for booking-utseendet",
@ -1204,7 +1210,7 @@
"create_your_first_form": "Lag ditt første skjema",
"create_your_first_form_description": "Med Viderekoblings-skjema kan du stille kvalifiserende spørsmål og videresende til riktig person eller hendelsestype.",
"create_your_first_webhook": "Lag din første Webhook",
"create_your_first_webhook_description": "Med Webhooks kan du motta møtedata i sanntid når noe skjer i Cal.com.",
"create_your_first_webhook_description": "Med Webhooks kan du motta møtedata i sanntid når noe skjer i {{appName}}.",
"for_a_maximum_of": "For maksimalt",
"event_one": "hendelse",
"event_other": "hendelser",
@ -1220,7 +1226,7 @@
"workflow_example_4": "Send deltakeren en e-post-påminnelse 1 time før hendelser starter",
"workflow_example_5": "Send verten en tilpasset e-post når en hendelse får nytt tidspunkt",
"workflow_example_6": "Send verten en tilpasset SMS når en ny hendelse er booket",
"welcome_to_cal_header": "Velkommen til Cal.com!",
"welcome_to_cal_header": "Velkommen til {{appName}}!",
"edit_form_later_subtitle": "Du kan redigere dette senere.",
"connect_calendar_later": "Jeg kobler til kalenderen min senere",
"set_my_availability_later": "Jeg setter opp min tilgjengelighet senere",
@ -1230,7 +1236,7 @@
"booking_appearance": "Booking Utseende",
"appearance_team_description": "Administrer innstillinger for teamets booking-utseende",
"only_owner_change": "Kun eieren av dette teamet kan gjøre endringer i teamets booking ",
"team_disable_cal_branding_description": "Fjerner alle Cal relaterte varemerker, f.eks. \"Powered by Cal\"",
"team_disable_cal_branding_description": "Fjerner alle {{appName}} relaterte varemerker, f.eks. \"Powered by {{appName}}\"",
"invited_by_team": "{{teamName}} har invitert deg til å bli med i teamet deres som en {{role}}",
"token_invalid_expired": "Token er enten ugyldig eller utløpt.",
"exchange_add": "Koble til Microsoft Exchange",
@ -1250,13 +1256,13 @@
"theme_dark": "Mørkt",
"theme_system": "System standard",
"add_a_team": "Legg til et team",
"add_webhook_description": "Motta møtedata i sanntid når noe skjer i Cal.com",
"add_webhook_description": "Motta møtedata i sanntid når noe skjer i {{appName}}",
"triggers_when": "Utløses når",
"test_webhook": "Vennligst ping-test før du oppretter.",
"enable_webhook": "Aktiver Webhook",
"add_webhook": "Legg til Webhook",
"webhook_edited_successfully": "Webhook lagret",
"webhooks_description": "Motta møtedata i sanntid når noe skjer i Cal.com",
"webhooks_description": "Motta møtedata i sanntid når noe skjer i {{appName}}",
"api_keys_description": "Generer API nøkler for å få tilgang til din egen konto",
"new_api_key": "Ny API nøkkel",
"active": "aktiv",
@ -1265,7 +1271,7 @@
"embeds_title": "HTML iframe-innbygging",
"embeds_description": "Bygg inn alle hendelsestypene dine på nettstedet ditt",
"create_first_api_key": "Lag din første API nøkkel",
"create_first_api_key_description": "API nøkler lar andre apper kommunisere med Cal.com",
"create_first_api_key_description": "API nøkler lar andre apper kommunisere med {{appName}}",
"back_to_signin": "Tilbake til innlogging",
"reset_link_sent": "Lenke for tilbakestilling er sendt",
"password_reset_email": "En e-post er på vei til {{email}} med instruksjoner for å tilbakestille passordet.",
@ -1273,14 +1279,14 @@
"password_updated": "Passord oppdatert!",
"pending_payment": "Venter på betaling",
"confirmation_page_rainbow": "Token vokter hendelsen din med tokens eller NFT-er på Ethereum, Polygon og mer.",
"not_on_cal": "Ikke på Cal.com",
"not_on_cal": "Ikke på {{appName}}",
"no_calendar_installed": "Ingen kalender installert",
"no_calendar_installed_description": "Du har ikke koblet til noen av kalenderne dine ennå",
"add_a_calendar": "Legg til en kalender",
"change_email_hint": "Det kan hende du må logge ut og inn igjen for å se at endringer trer i kraft",
"confirm_password_change_email": "Vennligst bekreft passordet ditt før du endrer e-postadressen din",
"seats": "seter",
"every_app_published": "Hver app publisert på Cal.com App Butikken er åpen kildekode og grundig testet via fagfellevurderinger. Cal.com, Inc. godkjenner eller sertifiserer likevel ikke disse appene med mindre de er publisert av Cal.com. Hvis du møter upassende innhold eller oppførsel, vennligst rapporter det.",
"every_app_published": "Hver app publisert på {{appName}} App Butikken er åpen kildekode og grundig testet via fagfellevurderinger. Uansett, {{companyName}} godkjenner eller sertifiserer likevel ikke disse appene med mindre de er publisert av {{appName}}. Hvis du møter upassende innhold eller oppførsel, vennligst rapporter det.",
"report_app": "Rapporter app",
"limit_booking_frequency": "Begrens bookingfrekvens",
"limit_booking_frequency_description": "Begrens hvor mange ganger denne hendelsen kan bookes",
@ -1383,5 +1389,9 @@
"test_preview_description": "Test viderekoblings-skjemaet ditt uten å sende noen data",
"test_routing": "Test Viderekobling",
"booking_limit_reached": "Booking-grensen for denne hendelsestypen er nådd",
"fill_this_field": "Vennligst fyll ut dette feltet"
"fill_this_field": "Vennligst fyll ut dette feltet",
"options": "Alternativer",
"enter_option": "Angi alternativ {{index}}",
"add_an_option": "Legg til et alternativ",
"radio": "Radio"
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Twoje zaplanowane wydarzenie zostało anulowane",
"organizer": "Organizator",
"need_to_reschedule_or_cancel": "Potrzebujesz zmienić harmonogram lub anulować?",
"cancellation_reason": "Powód anulowania",
"cancellation_reason_placeholder": "Dlaczego anulujesz? (opcjonalne)",
"cancellation_reason": "Powód anulowania (opcjonalnie)",
"cancellation_reason_placeholder": "Dlaczego anulujesz?",
"rejection_reason": "Powód odrzucenia",
"rejection_reason_title": "Odrzucić prośbę o rezerwację?",
"rejection_reason_description": "Czy jesteś pewien, że chcesz odrzucić prośbę o rezerwację? Damy znać osobie, która próbowała jej dokonać. Poniżej możesz podać powód.",
@ -419,7 +419,7 @@
"forgotten_secret_description": "Jeśli nie pamiętasz tego wpisu tajnego lub nie masz do niego dostępu, możesz go zmienić, ale pamiętaj, że wszystkie integracje korzystające z tego wpisu tajnego będą musiały zostać zaktualizowane.",
"current_incorrect_password": "Bieżące hasło jest nieprawidłowe",
"password_hint_caplow": "Kombinacja wielkich i małych liter",
"password_hint_min": "Długość co najmniej 7 znaków",
"password_hint_min": "Długość co najmniej 8 znaków",
"password_hint_num": "Zawrzyj co najmniej 1 cyfrę",
"invalid_password_hint": "Hasło musi składać się z co najmniej 7 znaków, zawierać co najmniej jedną cyfrę oraz kombinację wielkich i małych liter",
"incorrect_password": "Hasło jest nieprawidłowe.",
@ -653,7 +653,7 @@
"show_advanced_settings": "Pokaż ustawienia zaawansowane",
"event_name": "Nazwa wydarzenia",
"event_name_tooltip": "Nazwa, która pojawi się w kalendarzach",
"meeting_with_user": "Spotkanie z {ATTENDEE}",
"meeting_with_user": "Spotkanie z {{attendeeName}}",
"additional_inputs": "Dodatkowe Wejścia",
"additional_input_description": "Wymagaj od osoby układającej harmonogram wprowadzania dodatkowych danych przed potwierdzeniem rezerwacji",
"label": "Etykieta",
@ -784,7 +784,7 @@
"analytics": "Analityka",
"empty_installed_apps_headline": "Brak zainstalowanych aplikacji",
"empty_installed_apps_description": "Aplikacje umożliwiają znaczne usprawnienie przebiegu pracy i uproszczenie planowania.",
"empty_installed_apps_button": "Przeglądaj sklep App Store lub zainstaluj z poniższych aplikacji",
"empty_installed_apps_button": "Przeglądaj sklep App Store",
"manage_your_connected_apps": "Zarządzaj zainstalowanymi aplikacjami lub zmień ustawienia",
"browse_apps": "Przeglądaj aplikacje",
"features": "Funkcje",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Nieprawidłowy kod uwierzytelnienia dwuskładnikowego",
"which_event_type_apply": "Do jakiego rodzaju wydarzenia będzie się to odnosić?",
"no_workflows_description": "Przepływy pracy umożliwiają prostą automatyzację wysyłania powiadomień i przypomnień, dzięki czemu możesz tworzyć procesy dotyczące Twoich wydarzeń.",
"timeformat_profile_hint": "To jest ustawienie wewnętrzne, które nie będzie miało wpływu na to, jak godziny będą wyświetlane Tobie lub komuś, kto dokonuje u Ciebie rezerwacji, na publicznych stronach rezerwacji.",
"create_workflow": "Utwórz przepływ pracy",
"do_this": "Zrób to",
"turn_off": "Wyłącz",
@ -1344,17 +1345,17 @@
"billing_frequency": "Częstotliwość rozliczania",
"monthly": "Co miesiąc",
"yearly": "Co rok",
"checkout": "Kasa",
"checkout": "Do kasy",
"your_team_disbanded_successfully": "Twój zespół został rozwiązany.",
"error_creating_team": "Podczas tworzenia zespołu wystąpił błąd.",
"error_creating_team": "Podczas tworzenia zespołu wystąpił błąd",
"you": "Ty",
"send_email": "Wyślij wiadomość e-mail",
"member_already_invited": "Użytkownik został już zaproszony.",
"member_already_invited": "Członek został już zaproszony",
"enter_email_or_username": "Wprowadź adres e-mail lub nazwę użytkownika",
"team_name_taken": "Ta nazwa jest już zajęta.",
"must_enter_team_name": "Musisz podać nazwę zespołu.",
"team_url_required": "Musisz podać adres URL zespołu.",
"team_url_taken": "Ten adres URL jest już używany.",
"team_name_taken": "Ta nazwa jest już zajęta",
"must_enter_team_name": "Musisz wprowadzić nazwę zespołu",
"team_url_required": "Musisz wprowadzić adres URL zespołu",
"team_url_taken": "Ten adres URL jest już zajęty",
"team_publish": "Opublikuj zespół",
"number_sms_notifications": "Numer telefonu (powiadomienia SMS)",
"attendee_email_workflow": "Adres e-mail uczestnika",
@ -1365,9 +1366,9 @@
"reason": "Powód",
"sender_id": "Identyfikator nadawcy",
"sender_id_error_message": "Dozwolone są tylko litery, cyfry i spacje (maksymalnie 11 znaków)",
"test_routing_form": "Testuj formularz do przekierowania",
"test_preview": "Podgląd testu",
"test_routing_form": "Przetestuj formularz przekierowania",
"test_preview": "Przetestuj podgląd",
"route_to": "Przekieruj do",
"test_preview_description": "Przetestuj swój formularz przekierowania bez przesyłania jakicholwiek danych",
"test_routing": "Testuj przekierowanie"
"test_preview_description": "Przetestuj swój formularz przekierowania bez przesyłania jakichkolwiek danych",
"test_routing": "Przetestuj przekierowanie"
}

View File

@ -654,7 +654,7 @@
"show_advanced_settings": "Mostrar Configurações Avançadas",
"event_name": "Nome do Evento",
"event_name_tooltip": "O nome que aparecerá nos calendários",
"meeting_with_user": "Reunião com {ATTENDEE}",
"meeting_with_user": "Reunião com {{attendeeName}}",
"additional_inputs": "Campos adicionais",
"additional_input_description": "Solicitar que o agendador insira entradas adicionais antes que o pedido seja confirmado",
"label": "Etiqueta",
@ -1109,6 +1109,7 @@
"incorrect_2fa": "Código de autenticação de dois fatores incorreto",
"which_event_type_apply": "A que tipo de evento isto se aplica?",
"no_workflows_description": "Fluxo de trabalho permitem uma simples automação para enviar notificações e lembretes que permitem que você crie processos em torno de seus eventos.",
"timeformat_profile_hint": "Esta é uma configuração interna e não afetará a quantidade de exibições em páginas públicas de reservas para você ou outra pessoa que estiver fazendo reserva.",
"create_workflow": "Criar fluxo de trabalho",
"do_this": "Faça isso",
"turn_off": "Desligar",

View File

@ -504,7 +504,7 @@
"add_team_members_description": "Convidar outros a juntarem-se à sua equipa",
"add_team_member": "Adicionar membro da equipa",
"invite_new_member": "Convidar um Novo Membro",
"invite_new_member_description": "Nota: isto i ter o <1>custo de um lugar adicional ($15/m)</1> na sua subscrição.",
"invite_new_member_description": "Nota: isto vai ter o <1>custo de um lugar adicional ($15/m)</1> na sua subscrição.",
"invite_new_team_member": "Convide alguém para a sua equipa.",
"change_member_role": "Alterar função de membro da equipa",
"disable_cal_branding": "Desativar a marca {{appName}}",
@ -653,7 +653,7 @@
"show_advanced_settings": "Mostrar Configurações Avançadas",
"event_name": "Nome do Evento",
"event_name_tooltip": "O nome que será mostrado nos calendários",
"meeting_with_user": "Reunião com {ATTENDEE}",
"meeting_with_user": "Reunião com {{attendeeName}}",
"additional_inputs": "Campos adicionais",
"additional_input_description": "Requerer que o responsável pelo agendamento especifique entradas complementares antes do pedido ser confirmado",
"label": "Etiqueta",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Código de autenticação de dois fatores incorreto",
"which_event_type_apply": "A que tipo de evento isto se aplica?",
"no_workflows_description": "Os fluxos de trabalho permitem automações simples para enviar notificações e lembretes, permitindo criar processos à volta dos seus eventos.",
"timeformat_profile_hint": "Esta é uma configuração interna e que não irá afetar como os horários serão apresentados nas páginas públicas de agendamento, seja para si ou para qualquer pessoa que esteja a fazer uma reserva.",
"create_workflow": "Criar um fluxo de trabalho",
"do_this": "Fazer isto",
"turn_off": "Desligar",

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Evenimentul programat a fost anulat",
"organizer": "Organizator",
"need_to_reschedule_or_cancel": "Trebuie să reprogramați sau să anulați?",
"cancellation_reason": "Motivul anulării",
"cancellation_reason_placeholder": "De ce anulați? (opțional)",
"cancellation_reason": "Motivul anulării (opțional)",
"cancellation_reason_placeholder": "De ce anulați?",
"rejection_reason": "Motivul respingerii",
"rejection_reason_title": "Respingeți solicitarea de rezervare?",
"rejection_reason_description": "Sigur doriți să respingeți rezervarea? Vom anunța persoana care a încercat să rezerve. Puteți specifica un motiv mai jos.",
@ -108,8 +108,8 @@
"link_expires": "p.s. Expiră în {{expiresIn}} ore.",
"upgrade_to_per_seat": "Faceți upgrade la nivelul Per licență",
"team_upgrade_seats_details": "Printre cei {{memberCount}} (de) membri din echipa dvs., {{unpaidCount}} (de) locuri nu sunt plătite. La {{seatPrice}} USD/lună/licență, costul total estimat al abonamentului dvs. este de {{totalCost}} USD/lună.",
"team_upgrade_banner_description": "Vă mulțumim că ați încercat noul nostru plan pentru echipe. Am observat că echipa dvs. „{{teamName}}” trebuie upgradată.",
"team_upgrade_banner_action": "Upgradați aici",
"team_upgrade_banner_description": "Vă mulțumim că ați încercat noul nostru plan pentru echipe. Am observat că planul echipei dvs. „{{teamName}}” necesită un upgrade.",
"team_upgrade_banner_action": "Realizați upgrade aici",
"team_upgraded_successfully": "Echipa dvs. a fost actualizată cu succes!",
"use_link_to_reset_password": "Utilizați linkul de mai jos pentru a vă reseta parola",
"hey_there": "Bună,",
@ -419,7 +419,7 @@
"forgotten_secret_description": "Dacă ați pierdut sau ați uitat acest secret, îl puteți modifica, dar rețineți că toate integrările cu acest secret vor trebui actualizate",
"current_incorrect_password": "Parola curentă este incorectă",
"password_hint_caplow": "Combinație de litere mari și litere mici",
"password_hint_min": "Cel puțin 7 caractere",
"password_hint_min": "Cel puțin 8 caractere",
"password_hint_admin_min": "Cel puțin 15 caractere",
"password_hint_num": "Conține cel puțin 1 cifră",
"invalid_password_hint": "Parola trebuie să aibă cel puțin 7 caractere, să conțină cel puțin o cifră și o combinație de litere mari și litere mici",
@ -505,7 +505,7 @@
"add_team_members_description": "Invitați și alte persoane în echipa dvs.",
"add_team_member": "Adăugați un membru al echipei",
"invite_new_member": "Invitați un nou membru în echipă",
"invite_new_member_description": "Notă: Acest lucru va <1>costa un loc suplimentar (15 $/m)</1> din abonamentul dvs.",
"invite_new_member_description": "Notă: acest lucru va <1>adăuga un loc suplimentar (15 USD/lună)</1> la abonamentul dvs.",
"invite_new_team_member": "Invită pe cineva în echipa ta.",
"change_member_role": "Schimbați rolul de membru al echipei",
"disable_cal_branding": "Dezactivare branding {{appName}}",
@ -654,7 +654,7 @@
"show_advanced_settings": "Afișează setările avansate",
"event_name": "Denumirea evenimentului",
"event_name_tooltip": "Numele care va apărea în calendare",
"meeting_with_user": "Întâlnire cu {ATTENDEE}",
"meeting_with_user": "Întâlnire cu {{attendeeName}}",
"additional_inputs": "Date suplimentare",
"additional_input_description": "Solicitați planificatorului să introducă intrări suplimentare înainte ca rezervarea să fie confirmată",
"label": "Eticheta",
@ -785,7 +785,7 @@
"analytics": "Analiză",
"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.",
"empty_installed_apps_button": "Accesați App Store sau instalați o aplicație de mai jos",
"empty_installed_apps_button": "Navigați în App Store",
"manage_your_connected_apps": "Gestionați aplicațiile instalate sau modificați setările",
"browse_apps": "Răsfoiți aplicațiile",
"features": "Caracteristici",
@ -1109,6 +1109,7 @@
"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 vă permit să construiți procese în jurul evenimentelor dvs.",
"timeformat_profile_hint": "Aceasta este o setare internă și nu va afecta modul în care sunt afișate orele pe paginile publice de rezervare pentru dvs. sau pentru oricine vă face rezervarea.",
"create_workflow": "Creați un flux de lucru",
"do_this": "Faceți acest lucru",
"turn_off": "Dezactivați",
@ -1228,8 +1229,8 @@
"exchange_authentication_ntlm": "Autentificare NTLM",
"exchange_compression": "Compresie GZip",
"routing_forms_description": "Puteți vedea aici toate formularele și parcursurile pe care le-ați creat.",
"routing_forms_send_email_owner": "Trimiteți e-mail către proprietar",
"routing_forms_send_email_owner_description": "Trimite un e-mail către proprietar atunci când formularul este trimis",
"routing_forms_send_email_owner": "Trimiteți un e-mail către proprietar",
"routing_forms_send_email_owner_description": "Trimite un e-mail către proprietar atunci când este trimis formularul",
"add_new_form": "Adăugați un formular nou",
"form_description": "Creați formularul pentru direcționarea unei persoane care face rezervare",
"copy_link_to_form": "Copiați linkul în formular",
@ -1345,31 +1346,31 @@
"billing_frequency": "Frecvența de facturare",
"monthly": "Lunar",
"yearly": "Anual",
"checkout": "Pagina de plată",
"checkout": "Finalizare cumpărături",
"your_team_disbanded_successfully": "Echipa dvs. a fost desființată cu succes",
"error_creating_team": "Eroare la crearea echipei",
"you": "Dvs.",
"send_email": "Trimiteți e-mail",
"send_email": "Trimiteți un e-mail",
"member_already_invited": "Membrul a fost deja invitat",
"enter_email_or_username": "Introduceți un e-mail sau un nume de utilizator",
"team_name_taken": "Acest nume este deja luat",
"team_name_taken": "Acest nume este folosit deja",
"must_enter_team_name": "Trebuie să introduceți un nume de echipă",
"team_url_required": "Trebuie să introduceți un URL de echipă",
"team_url_taken": "Acest URL este deja luat",
"team_url_taken": "Acest URL este folosit deja",
"team_publish": "Publicați echipa",
"number_sms_notifications": "Număr de telefon (notificări SMS)",
"attendee_email_workflow": "E-mail participant",
"attendee_email_info": "E-mailul persoanei care efectuează rezervarea",
"invalid_credential": "Vai, nu! Se pare că permisiunea a expirat sau a fost revocată. Reinstalați din nou.",
"choose_common_schedule_team_event": "Alegeți un program comun",
"choose_common_schedule_team_event_description": "Activați această opțiune dacă doriți să folosiți un program comun între gazde. Atunci când este dezactivat, fiecare gazdă va fi rezervată în funcție de programul lor implicit.",
"choose_common_schedule_team_event_description": "Activați această opțiune dacă doriți să folosiți un program comun între gazde. Atunci când este dezactivată, fiecare gazdă va fi rezervată în funcție de programul său implicit.",
"reason": "Motiv",
"sender_id": "ID expeditor",
"sender_id_error_message": "Sunt permise doar litere, numere și spații (maxim 11 caractere)",
"test_routing_form": "Formular de redirecționare test",
"test_preview": "Previzualizare test",
"test_routing_form": "Testați formularul de redirecționare",
"test_preview": "Testați previzualizarea",
"route_to": "Direcționați către",
"test_preview_description": "Testați-vă formularul de redirecționare fără a trimite date",
"test_routing": "Redirecționare test",
"booking_limit_reached": "Limita de rezervare pentru acest tip de eveniment a fost atinsă"
"test_routing": "Testați redirecționarea",
"booking_limit_reached": "A fost atinsă limita de rezervare pentru acest tip de eveniment"
}

View File

@ -654,7 +654,7 @@
"show_advanced_settings": "Показать дополнительные параметры",
"event_name": "Название события",
"event_name_tooltip": "Название, которое будет отображаться в календарях",
"meeting_with_user": "Встреча с {ATTENDEE}",
"meeting_with_user": "Встреча с {{attendeeName}}",
"additional_inputs": "Дополнительные вопросы",
"additional_input_description": "Необходимо ввести в планировщик дополнительную информацию до подтверждения бронирования",
"label": "Название",
@ -1109,6 +1109,7 @@
"incorrect_2fa": "Неверный код двухфакторной авторизации",
"which_event_type_apply": "К какому типу событий применить?",
"no_workflows_description": "С помощью рабочих процессов можно с легкостью автоматизировать отправку уведомлений и напоминаний, формируя процессы вокруг событий.",
"timeformat_profile_hint": "Это внутренняя настройка. Для вас и других пользователей, оформляющих бронирование, отображение времени на публичных страницах бронирования не изменится.",
"create_workflow": "Создать рабочий процесс",
"do_this": "Сделать",
"turn_off": "Выключить",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Pokaži napredna podešavanja",
"event_name": "Ime Događaja",
"event_name_tooltip": "Ime koje će se pojaviti u kalendarima",
"meeting_with_user": "Sastanak sa {ATTENDEE}",
"meeting_with_user": "Sastanak sa {{attendeeName}}",
"additional_inputs": "Dodatni unosi",
"additional_input_description": "Zahtevaj od planera da unese dodatne unose pre potvrde rezervacije",
"label": "Oznaka",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Netačan kôd za dvostruku potvrdu identiteta",
"which_event_type_apply": "Na koji tip događaja će se ovo primeniti?",
"no_workflows_description": "Radni tokovi omogućavaju jednostavnu automatizaciju slanja obaveštenja i podsetnika što vam omogućava da izgradite procese oko vaših događaja.",
"timeformat_profile_hint": "Ovo je interno podešavanje i neće uticati koliko puta će biti prikazano na javnim stranicama za rezervisanje, za vas ili bilo koga ko vas rezerviše.",
"create_workflow": "Kreirajte radni tok",
"do_this": "Uradi ovo",
"turn_off": "Isključi",

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Din schemalagda bokning ställdes in",
"organizer": "Arrangör",
"need_to_reschedule_or_cancel": "Behöver du boka om eller avboka?",
"cancellation_reason": "Anledning till avbokning",
"cancellation_reason_placeholder": "Varför avbokar du? (frivilligt)",
"cancellation_reason": "Orsaker till annullering (valfritt)",
"cancellation_reason_placeholder": "Varför avbryter du?",
"rejection_reason": "Anledning till att avböja",
"rejection_reason_title": "Avböj bokningsförfrågan?",
"rejection_reason_description": "Är du säker på att du vill avböja bokningen? Vi kommer att meddela personen som försökte boka. Du kan ange en anledning nedan.",
@ -108,7 +108,7 @@
"link_expires": "p.s. Den går ut om {{expiresIn}} timmar.",
"upgrade_to_per_seat": "Uppgradera till Per-Seat",
"team_upgrade_seats_details": "Av {{memberCount}} medlemmar i ditt lag är {{unpaidCount}} plats(er) obetalda. Vid ${{seatPrice}}/m per plats är den uppskattade totala kostnaden för ditt medlemskap ${{totalCost}}/m.",
"team_upgrade_banner_description": "Tack för att du testade vår nya lagplan. Vi märkte att ditt team \"{{teamName}}\" måste uppgraderas.",
"team_upgrade_banner_description": "Tack för att du testade vår nya teamplan. Vi märkte att ditt team \"{{teamName}}\" måste uppgraderas.",
"team_upgrade_banner_action": "Uppgradera här",
"team_upgraded_successfully": "Ditt team har uppgraderats!",
"use_link_to_reset_password": "Använd länken nedan för att återställa ditt lösenord",
@ -419,7 +419,7 @@
"forgotten_secret_description": "Om du tappat bort eller glömt den här hemligheten kan du ändra den, men var medveten om att alla integrationer som använder hemligheten måste uppdateras",
"current_incorrect_password": "Nuvarande lösenord är felaktigt",
"password_hint_caplow": "Blandning av versaler och gemener",
"password_hint_min": "Minst 7 tecken långt",
"password_hint_min": "Minst 8 tecken",
"password_hint_num": "Innehåller minst 1 siffra",
"invalid_password_hint": "Lösenordet måste innehålla minst 7 tecken och har minst en siffra samt en blandning av versaler och gemener",
"incorrect_password": "Lösenordet är felaktigt.",
@ -504,7 +504,7 @@
"add_team_members_description": "Bjud in andra att gå med i ditt team",
"add_team_member": "Lägg till teammedlem",
"invite_new_member": "Bjud in en ny teammedlem",
"invite_new_member_description": "Notera: Detta kommer att <1>kosta en extra plats ($15/m)</1> på ditt abonnemang.",
"invite_new_member_description": "Obs! Detta kommer att <1>kosta en extra plats (150 kr/mån)</1> i din prenumeration.",
"invite_new_team_member": "Bjud in någon till ditt team.",
"change_member_role": "Ändra teammedlemmens roll",
"disable_cal_branding": "Inaktivera {{appName}}-varumärke",
@ -653,7 +653,7 @@
"show_advanced_settings": "Visa avancerade inställningar",
"event_name": "Händelsenamn",
"event_name_tooltip": "Namnet som kommer visas i kalendrar",
"meeting_with_user": "Möte med {ATTENDEE}",
"meeting_with_user": "Möte med {{attendeeName}}",
"additional_inputs": "Ytterligare inmatningar",
"additional_input_description": "Kräv schemaläggaren att mata in ytterligare ingångar innan bokningen bekräftas",
"label": "Etikett",
@ -784,7 +784,7 @@
"analytics": "Analys",
"empty_installed_apps_headline": "Inga appar installerade",
"empty_installed_apps_description": "Med appar kan du förbättra ditt arbetsflöde och din schemaläggning avsevärt.",
"empty_installed_apps_button": "Utforska App Store eller installera från apparna nedan",
"empty_installed_apps_button": "Utforska App Store",
"manage_your_connected_apps": "Hantera dina installerade appar eller ändra inställningar",
"browse_apps": "Bläddra bland appar",
"features": "Egenskaper",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Felaktig tvåfaktorsautentiseringskod",
"which_event_type_apply": "Vilken händelsetyp kommer detta att gälla?",
"no_workflows_description": "Arbetsflöden möjliggör enkel automatisering för att skicka meddelanden och påminnelser så att du kan bygga processer kring dina händelser.",
"timeformat_profile_hint": "Detta är en intern inställning och kommer inte att påverka hur tider visas på offentliga bokningssidor för dig eller någon som bokar dig.",
"create_workflow": "Skapa ett arbetsflöde",
"do_this": "Gör detta",
"turn_off": "Stäng av",
@ -1295,7 +1296,7 @@
"fetching_calendars_error": "Det gick inte att hämta dina kalendrar. <1>Försök igen</1> eller kontakta kundtjänst.",
"calendar_connection_fail": "Det gick inte att ansluta kalender",
"booking_confirmation_success": "Bokningsbekräftelsen lyckades",
"booking_rejection_success": "Avvisning av bokning lyckades",
"booking_rejection_success": "Bokningen avvisades",
"booking_confirmation_fail": "Bokningsbekräftelsen misslyckades",
"we_wont_show_again": "Vi kommer inte att visa detta igen",
"couldnt_update_timezone": "Vi kunde inte uppdatera tidszonen",
@ -1346,28 +1347,28 @@
"yearly": "Årligen",
"checkout": "Kassa",
"your_team_disbanded_successfully": "Ditt team har upplösts",
"error_creating_team": "Fel vid skapandet av team",
"error_creating_team": "Det gick inte att skapa team",
"you": "Du",
"send_email": "Skicka e-post",
"member_already_invited": "Medlemmen har redan blivit inbjuden",
"member_already_invited": "Medlemmen har redan bjudits in",
"enter_email_or_username": "Ange en e-postadress eller ett användarnamn",
"team_name_taken": "Detta namn är redan upptaget",
"team_name_taken": "Det här namnet används redan",
"must_enter_team_name": "Teamnamn måste anges",
"team_url_required": "Team-URL måste anges",
"team_url_taken": "Denna URL är redan upptagen",
"team_url_taken": "Denna URL används redan",
"team_publish": "Publicera team",
"number_sms_notifications": "Telefonnummer (SMS-notiser)",
"attendee_email_workflow": "Deltagarens e-postadress",
"attendee_email_info": "Personens e-postadress för bokning",
"invalid_credential": "Åh nej! Ser ut som om behörigheten löpte ut eller återkallades. Installera om igen.",
"invalid_credential": "Åh nej! Det verkar som om behörigheten löpt ut eller återkallats. Installera om igen.",
"choose_common_schedule_team_event": "Välj ett gemensamt schema",
"choose_common_schedule_team_event_description": "Aktivera den här funktionen om du vill använda ett gemensamt schema mellan värdarna. När den är inaktiverad bokas varje värd enligt sitt standardschema.",
"reason": "Anledning",
"sender_id": "Avsändarens ID",
"sender_id_error_message": "Endast bokstäver, siffror och mellanslag tillåtna (max. 11 tecken)",
"test_routing_form": "Formulär för routingtest",
"test_preview": "Testförhandsgranskning",
"route_to": "Rutt till",
"test_preview_description": "Testa ditt routing-formulär utan att skicka några data",
"reason": "Orsak",
"sender_id": "Avsändar-ID",
"sender_id_error_message": "Endast bokstäver, siffror och mellanslag tillåts (maximalt 11 tecken)",
"test_routing_form": "Testa routingformulär",
"test_preview": "Testa förhandsgranskning",
"route_to": "Dirigera till",
"test_preview_description": "Testa ditt routingformulär utan att skicka data",
"test_routing": "Testa routing"
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Planlanan etkinliğiniz iptal edildi",
"organizer": "Organizatör",
"need_to_reschedule_or_cancel": "Yeniden planlamanız veya iptal etmeniz mi gerekiyor?",
"cancellation_reason": "İptal nedeni",
"cancellation_reason_placeholder": "Neden iptal ediyorsunuz? (isteğe bağlı)",
"cancellation_reason": "İptal nedeni (isteğe bağlı)",
"cancellation_reason_placeholder": "Neden iptal ediyorsunuz?",
"rejection_reason": "Ret nedeni",
"rejection_reason_title": "Rezervasyon isteği reddedilsin mi?",
"rejection_reason_description": "Rezervasyonu reddetmek istediğinizden emin misiniz? Rezervasyon yaptırmaya çalışan kişiye haber vereceğiz. Aşağıda bir neden sunabilirsiniz.",
@ -108,7 +108,7 @@
"link_expires": "Not: Süre {{expiresIn}} saat içinde dolacak.",
"upgrade_to_per_seat": "Koltuk Başına Yükselt",
"team_upgrade_seats_details": "Ekibinizdeki {{memberCount}} üyeden {{unpaidCount}} yer ücretsiz. Alan başına aylık {{seatPrice}} $ ile üyeliğinizin tahmini toplam maliyeti aylık {{totalCost}} $'dır.",
"team_upgrade_banner_description": "Yeni ekip planımızı denediğiniz için teşekkür ederiz. \"{{teamName}}\" ekip planınızın yükseltilmesi gerektiğini fark ettik.",
"team_upgrade_banner_description": "Yeni ekip planımızı denediğiniz için teşekkür ederiz. \"{{teamName}}\" adlı ekibinizin yükseltilmesi gerektiğini fark ettik.",
"team_upgrade_banner_action": "Buradan yükseltin",
"team_upgraded_successfully": "Ekibiniz başarıyla yükseltildi!",
"use_link_to_reset_password": "Şifrenizi sıfırlamak için aşağıdaki bağlantıyı kullanın",
@ -419,7 +419,7 @@
"forgotten_secret_description": "Bu gizli anahtarı kaybettiyseniz veya unuttuysanız değiştirebilirsiniz ancak bu gizli anahtarı kullanan tüm entegrasyonların güncellenmesi gerekecektir",
"current_incorrect_password": "Mevcut şifre hatalı",
"password_hint_caplow": "Büyük ve küçük harf karışımı",
"password_hint_min": "En az 7 karakter uzunluğunda",
"password_hint_min": "En az 8 karakter uzunluğunda",
"password_hint_num": "En az 1 rakam içermelidir",
"invalid_password_hint": "Şifre en az 7 karakter uzunluğunda olmalı, en az bir rakam içermeli ve büyük ve küçük harflerin karışımından oluşmalıdır",
"incorrect_password": "Şifre hatalı.",
@ -504,7 +504,7 @@
"add_team_members_description": "Ekibinize diğer kişileri davet edin",
"add_team_member": "Ekip üyesi ekle",
"invite_new_member": "Yeni bir ekip üyesi davet et",
"invite_new_member_description": "Not: Bu, <1>aboneliğiniz için ekstra yer ücretine (15 $/ay)</1> mal olacaktır.",
"invite_new_member_description": "Not: Bu, aboneliğiniz için <1>ekstra yer ücretine (15 $/ay)</1> mal olacak.",
"invite_new_team_member": "Ekibinize birini davet edin.",
"change_member_role": "Ekip üyesi rolünü değiştir",
"disable_cal_branding": "{{appName}} markasını devre dışı bırak",
@ -653,7 +653,7 @@
"show_advanced_settings": "Gelişmiş ayarları göster",
"event_name": "Etkinlik Adı",
"event_name_tooltip": "Takvimlerde görünecek isim",
"meeting_with_user": "{ATTENDEE} ile toplantı",
"meeting_with_user": "{{attendeeName}} ile toplantı",
"additional_inputs": "Ek Girişler",
"additional_input_description": "Rezervasyon onaylanmadan önce planlayıcının ek veri girişi yapması gereklidir",
"label": "Etiket",
@ -784,7 +784,7 @@
"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 ya da aşağıdaki uygulamaları Yükleyin",
"empty_installed_apps_button": "Uygulama Mağazasına göz atın",
"manage_your_connected_apps": "Yüklü uygulamalarınızı yönetin veya ayarları değiştirin",
"browse_apps": "Uygulamalara göz at",
"features": "Özellikler",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "İki adımlı kimlik doğrulama kodu hatalı",
"which_event_type_apply": "Bu hangi etkinlik türü için geçerli olacak?",
"no_workflows_description": "İş akışları, etkinliklerinizle ilgili süreçler oluşturmanıza olanak tanıyan bildirimler ve hatırlatıcılar göndermek için basit otomasyona imkanı tanır.",
"timeformat_profile_hint": "Bu dahili bir ayardır ve sizin yaptığınız veya sizin için rezervasyon yapan herhangi birinin genel rezervasyon sayfalarında saatlerin nasıl görüntülendiğini etkilemez.",
"create_workflow": "Bir iş akışı oluşturun",
"do_this": "Bunu yap",
"turn_off": "Kapat",
@ -1227,7 +1228,7 @@
"exchange_authentication_ntlm": "NTLM kimlik doğrulama",
"exchange_compression": "GZip sıkıştırma",
"routing_forms_description": "Oluşturduğunuz tüm formları ve rotaları burada görebilirsiniz.",
"routing_forms_send_email_owner": "Sahibine E-posta Gönder",
"routing_forms_send_email_owner": "Sahibine E-posta Gönderin",
"routing_forms_send_email_owner_description": "Form gönderildiğinde sahibine bir e-posta gönderir",
"add_new_form": "Yeni form ekle",
"form_description": "Rezervasyon yapan kişiyi yönlendirmek için formunuzu oluşturun",
@ -1311,7 +1312,7 @@
"how_long_after": "Etkinlik sona erdikten ne kadar sonra?",
"no_available_slots": "Uygun yer yok",
"time_available": "Uygun zaman",
"cant_find_the_right_video_app_visit_our_app_store": "Doğru video uygulamasını bulamıyor musunuz? <1>App Store</1> sayfamızı ziyaret edin.",
"cant_find_the_right_video_app_visit_our_app_store": "Doğru video uygulamasını bulamıyor musunuz? <1>Uygulama Mağazamızı</1> ziyaret edin.",
"install_new_calendar_app": "Yeni takvim uygulamasını yükleyin",
"make_phone_number_required": "Rezervasyon etkinliği için telefon numarasını gerekli hale getirin",
"new_event_type_availability": "{{eventTypeTitle}} Müsaitlik Durumu",
@ -1345,28 +1346,28 @@
"monthly": "Aylık",
"yearly": "Yıllık",
"checkout": "Ödeme yap",
"your_team_disbanded_successfully": "Ekibiniz başarıyla tasfiye edildi",
"your_team_disbanded_successfully": "Ekibiniz başarıyla dağıtıldı",
"error_creating_team": "Ekip oluşturulurken bir hata oluştu",
"you": "Siz",
"send_email": "E-posta gönder",
"member_already_invited": "Üye zaten davet edildi",
"enter_email_or_username": "Bir e-posta veya kullanıcı adı girin",
"team_name_taken": "Bu ad zaten alınmış",
"must_enter_team_name": "Bir ekip adı girilmelidir",
"team_url_required": "Bir ekip URL'si girilmelidir",
"team_url_taken": "Bu URL zaten alınmış",
"team_name_taken": "Bu ad zaten kullanılıyor",
"must_enter_team_name": "Ekip adı girilmelidir",
"team_url_required": "Ekip URL'si girilmelidir",
"team_url_taken": "Bu URL zaten kullanılıyor",
"team_publish": "Ekibi yayınla",
"number_sms_notifications": "Telefon numarası (SMS bildirimleri)",
"attendee_email_workflow": "Katılımcı e-postası",
"attendee_email_info": "Rezervasyon yaptıran kişinin e-postası",
"invalid_credential": "Hata! İznin süresi dolmuş veya iptal edilmiş gibi görünüyor. Lütfen yeniden yükleyin.",
"choose_common_schedule_team_event": "Ortak bir program seçin",
"choose_common_schedule_team_event_description": "Toplantı sahipleri arasında ortak bir plan kullanmak istiyorsanız bunu etkinleştirin. Devre dışı bırakıldığında, her toplantı sahibi varsayılan programlarına göre rezervasyon yapabilir.",
"reason": "Neden",
"choose_common_schedule_team_event_description": "Toplantı sahipleri arasında ortak bir plan kullanmak istiyorsanız bunu etkinleştirin. Devre dışı bırakıldığında, her toplantı sahibi varsayılan programına göre rezerve edilir.",
"reason": "Sebep",
"sender_id": "Gönderici Kimliği",
"sender_id_error_message": "Sadece harflere, sayılara ve boşluklara izin verilir (en fazla 11 karakter)",
"test_routing_form": "Yönlendirme Formunu Test Et",
"test_preview": "Test Önizlemesi",
"test_routing_form": "Yönlendirme Formunu Test Etme",
"test_preview": "Ön İzlemeyi Test Et",
"route_to": "Şuraya yönlendir:",
"test_preview_description": "Herhangi bir veri göndermeden yönlendirme formunuzu test edin",
"test_routing": "Yönlendirmeyi Test Et"

View File

@ -504,7 +504,7 @@
"add_team_members_description": "Запросіть когось у свою команду",
"add_team_member": "Додати учасника команди",
"invite_new_member": "Запросити нового учасника команди",
"invite_new_member_description": "Зверніть увагу: у вашій підписці потрібно буде доплатити за <1>одне додаткове місце (15 дол./міс.)</1>.",
"invite_new_member_description": "Зверніть увагу: у вашій підписці потрібно буде оплатити <1>одне додаткове місце (15 дол. США)</1>.",
"invite_new_team_member": "Запросіть когось у свою команду.",
"change_member_role": "Змініть роль учасника команди",
"disable_cal_branding": "Вимкнути фірмове оформлення {{appName}}",
@ -653,7 +653,7 @@
"show_advanced_settings": "Показати додаткові параметри",
"event_name": "Тип заходу",
"event_name_tooltip": "Ім’я, яке показуватиметься в календарях",
"meeting_with_user": "Нарада з користувачем {ATTENDEE}",
"meeting_with_user": "Нарада з користувачем {{attendeeName}}",
"additional_inputs": "Додаткові поля введення",
"additional_input_description": "Вимагати від особи, що хоче створити бронювання, надавати додаткові відомості",
"label": "Мітка",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Неправильний код двоетапної автентифікації",
"which_event_type_apply": "До якого типу заходів це застосовуватиметься?",
"no_workflows_description": "Робочі процеси це проста автоматизація сповіщень і нагадувань, що дає вам змогу будувати процеси, пов’язані з вашими заходами.",
"timeformat_profile_hint": "Це внутрішній параметр, який не вплине на відображення часу на загальнодоступних сторінках бронювання.",
"create_workflow": "Створити робочий процес",
"do_this": "Зробити це",
"turn_off": "Вимкнути",
@ -1365,7 +1366,7 @@
"reason": "Причина",
"sender_id": "Ідентифікатор відправника",
"sender_id_error_message": "Дозволяються тільки літери, цифри та пробіли (макс. 11 символів)",
"test_routing_form": "Перевірити форму переспрямування",
"test_routing_form": "Перевірка форми переспрямування",
"test_preview": "Перевірити попередній перегляд",
"route_to": "Куди переспрямувати",
"test_preview_description": "Перевірка вашої форми переспрямування без надсилання даних",

View File

@ -504,7 +504,7 @@
"add_team_members_description": "Mời người khác tham gia nhóm của bạn",
"add_team_member": "Thêm thành viên nhóm",
"invite_new_member": "Mời một thành viên mới",
"invite_new_member_description": "Lưu ý: Cái này sẽ <1>tốn khoản phụ trội (15$/tháng)</1> cho gói đăng ký của bạn.",
"invite_new_member_description": "Lưu ý: Cái này sẽ <1>tốn phí thêm thành viên (15$/tháng)</1> cho gói đăng ký của bạn.",
"invite_new_team_member": "Mời ai đó vào nhóm của bạn.",
"change_member_role": "Thay đổi vai trò thành viên trong nhóm",
"disable_cal_branding": "Tắt thương hiệu {{appName}}",
@ -653,7 +653,7 @@
"show_advanced_settings": "Hiển thị cài đặt nâng cao",
"event_name": "Tên sự kiện",
"event_name_tooltip": "Tên sẽ xuất hiện trong lịch",
"meeting_with_user": "Gặp gỡ với {ATTENDEE}",
"meeting_with_user": "Gặp gỡ với {{attendeeName}}",
"additional_inputs": "Trường bổ sung",
"additional_input_description": "Yêu cầu người đặt lịch hãy nhập thông tin đầu vào bổ sung trước khi lịch hẹn được xác nhận",
"label": "Label",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "Sai mã xác thực hai yếu tố",
"which_event_type_apply": "Cái này sẽ áp dụng cho loại sự kiện nào?",
"no_workflows_description": "Tiến độ công việc giúp đơn giản hoá việc tạo tự động chức năng gửi thông báo & lời nhắc, nhằm giúp bạn có thể lập nên các quy trình xử lí xung quanh các sự kiện.",
"timeformat_profile_hint": "Đây là cài đặt nội bộ và sẽ không ảnh hưởng đến cách hiển thị thời gian trên trang lịch hẹn công cộng cho bạn hoặc cho bất kỳ ai đặt lịch hẹn với bạn.",
"create_workflow": "Tạo một tiến độ công việc",
"do_this": "Làm cái này",
"turn_off": "Tắt",
@ -1360,7 +1361,7 @@
"attendee_email_workflow": "Email người tham dự",
"attendee_email_info": "Email người tham gia lịch hẹn",
"invalid_credential": "Ôi không! Có vẻ như quyền đã hết hạn hoặc đã bị thu hồi. Vui lòng cài đặt lại.",
"choose_common_schedule_team_event": "Chọn một lịch thông thường",
"choose_common_schedule_team_event": "Chọn một lịch chung",
"choose_common_schedule_team_event_description": "Bật cái này nếu bạn muốn dùng lịch thông thường giữa các chủ sự kiện. Khi tắt đi, mỗi chủ sự kiện sẽ được đặt lịch theo lịch mặc định của họ.",
"reason": "Lý do",
"sender_id": "ID người gửi",

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "您预约的活动已被取消",
"organizer": "组织者",
"need_to_reschedule_or_cancel": "需要重新安排还是取消?",
"cancellation_reason": "取消原因",
"cancellation_reason_placeholder": "您为什么要取消?(选填)",
"cancellation_reason": "取消原因(选填)",
"cancellation_reason_placeholder": "您为什么要取消?",
"rejection_reason": "拒绝原因",
"rejection_reason_title": "拒绝预约请求?",
"rejection_reason_description": "您确定要拒绝预约吗?我们会通知试图预约的人,您可以在下面解释原因。",
@ -419,7 +419,7 @@
"forgotten_secret_description": "如果您丢失或忘记此密码,您可以对其进行更改,但请注意,使用此密码的所有集成都需要更新",
"current_incorrect_password": "当前密码不正确",
"password_hint_caplow": "大写和小写字母混合",
"password_hint_min": "至少 7 个字符长",
"password_hint_min": "至少 8 个字符长",
"password_hint_num": "包含至少 1 个数字",
"invalid_password_hint": "密码必须是至少 7 个字符长,包含至少一个数字,并且是大写和小写字母混合",
"incorrect_password": "密码不正确。",
@ -504,7 +504,7 @@
"add_team_members_description": "邀请其他人加入您的团队",
"add_team_member": "添加团队成员",
"invite_new_member": "邀请新的团队成员",
"invite_new_member_description": "注意:这将在您订阅的基础上<1>收取一个额外位置的费用(15 美元/月)</1>",
"invite_new_member_description": "注意:这将在您订阅的基础上<1>收取一个额外位置的费用(15 美元/月)</1>",
"invite_new_team_member": "邀请某人加入您的团队。",
"change_member_role": "更改团队成员角色",
"disable_cal_branding": "禁用 {{appName}} 品牌标志",
@ -601,7 +601,7 @@
"monthly_other": "月",
"yearly_one": "年",
"yearly_other": "年",
"plus_more": "另外 {{count}}",
"plus_more": "另外 {{count}}",
"max": "最大",
"single_theme": "主题",
"brand_color": "品牌颜色",
@ -653,7 +653,7 @@
"show_advanced_settings": "显示高级设置",
"event_name": "活动名称",
"event_name_tooltip": "在日历中显示的名称",
"meeting_with_user": "与 {ATTENDEE} 的会议",
"meeting_with_user": "与 {{attendeeName}} 的会议",
"additional_inputs": "更多输入框",
"additional_input_description": "在确认预约之前,需要预约者输入额外的信息",
"label": "标签",
@ -784,7 +784,7 @@
"analytics": "分析",
"empty_installed_apps_headline": "未安装任何应用",
"empty_installed_apps_description": "通过应用可以显著增强工作流程和改善日程安排生活。",
"empty_installed_apps_button": "浏览 App Store 或从以下应用中选择安装",
"empty_installed_apps_button": "浏览 App Store",
"manage_your_connected_apps": "管理已安装的应用或更改设置",
"browse_apps": "浏览应用",
"features": "功能",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "两步验证代码不正确",
"which_event_type_apply": "这将适用于哪种活动类型?",
"no_workflows_description": "工作流程可实现简单的自动化来发送通知和提醒,让您可以围绕活动构建流程。",
"timeformat_profile_hint": "这是一项内部设置,不会影响公开预约页面上向您或向预约您的任何人显示时间的方式。",
"create_workflow": "创建工作流程",
"do_this": "执行此操作",
"turn_off": "关闭",
@ -1367,7 +1368,7 @@
"sender_id_error_message": "只允许字母、数字和空格(最多 11 个字符)",
"test_routing_form": "测试途径表格",
"test_preview": "测试预览",
"route_to": "途径",
"route_to": "根据途径找到",
"test_preview_description": "测试途径表格而不提交任何数据",
"test_routing": "测试途径"
}

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "顯示進階設定",
"event_name": "活動名稱",
"event_name_tooltip": "會顯示在行事曆上的名字",
"meeting_with_user": "與 {ATTENDEE} 的會議",
"meeting_with_user": "與 {{attendeeName}} 的會議",
"additional_inputs": "額外輸入欄",
"additional_input_description": "確認預約前,預約者需先輸入額外的內容",
"label": "標籤",
@ -1108,6 +1108,7 @@
"incorrect_2fa": "兩階段認證代碼不正確",
"which_event_type_apply": "這將套用哪一種活動類型?",
"no_workflows_description": "工作流程可啟用簡易的自動化流程來傳送通知與提醒,讓您為活動打造流程。",
"timeformat_profile_hint": "此為內部設定,您或要預約您時間的使用者在公開預約頁面上看見的時間並不會受到影響。",
"create_workflow": "建立工作流程",
"do_this": "執行此操作",
"turn_off": "關閉",

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@ -1 +1 @@
Subproject commit 139809e7c3c7267689809a6664b0690dcff93811
Subproject commit c358039b1c1d0c5c0b7901c6489261c4e8e8c6e0

View File

@ -60,6 +60,7 @@
},
"devDependencies": {
"@snaplet/copycat": "^0.3.0",
"@types/dompurify": "^2.4.0",
"@types/jest": "^28.1.7",
"dotenv-checker": "^1.1.5",
"husky": "^8.0.1",

View File

@ -62,6 +62,7 @@ function generateFiles() {
const browserOutput = [`import dynamic from "next/dynamic"`];
const metadataOutput = [];
const schemasOutput = [];
const appKeysSchemasOutput = [];
const serverOutput = [];
const appDirs: { name: string; path: string }[] = [];
@ -190,6 +191,20 @@ function generateFiles() {
})
);
appKeysSchemasOutput.push(
...getObjectExporter("appKeysSchemas", {
fileToBeImported: "zod.ts",
// Import path must have / even for windows and not \
importBuilder: (app) =>
`import { appKeysSchema as ${getVariableName(app.name)}_keys_schema } from "./${app.path.replace(
/\\/g,
"/"
)}/zod";`,
// Key must be appId as this is used by eventType metadata and lookup is by appId
entryBuilder: (app) => ` "${getAppId(app)}":${getVariableName(app.name)}_keys_schema ,`,
})
);
browserOutput.push(
...getObjectExporter("InstallAppButtonMap", {
fileToBeImported: "components/InstallAppButton.tsx",
@ -225,6 +240,7 @@ function generateFiles() {
["apps.server.generated.ts", serverOutput],
["apps.browser.generated.tsx", browserOutput],
["apps.schemas.generated.ts", schemasOutput],
["apps.keys-schemas.generated.ts", appKeysSchemasOutput],
];
filesToGenerate.forEach(([fileName, output]) => {
fs.writeFileSync(`${APP_STORE_PATH}/${fileName}`, formatOutput(`${banner}${output.join("\n")}`));

View File

@ -26,7 +26,10 @@ export async function getAppWithMetadata(app: { dirName: string }) {
/** Mainly to use in listings for the frontend, use in getStaticProps or getServerSideProps */
export async function getAppRegistry() {
const dbApps = await prisma.app.findMany({ select: { dirName: true, slug: true, categories: true } });
const dbApps = await prisma.app.findMany({
where: { enabled: true },
select: { dirName: true, slug: true, categories: true, enabled: true },
});
const apps = [] as Omit<App, "key">[];
for await (const dbapp of dbApps) {
const app = await getAppWithMetadata(dbapp);
@ -51,7 +54,10 @@ export async function getAppRegistry() {
}
export async function getAppRegistryWithCredentials(userId: number) {
const dbApps = await prisma.app.findMany({ include: { credentials: { where: { userId } } } });
const dbApps = await prisma.app.findMany({
where: { enabled: true },
include: { credentials: { where: { userId } } },
});
const apps = [] as (Omit<App, "key"> & {
credentials: Credential[];
})[];

View File

@ -26,7 +26,7 @@ export default function AppCard({
const [animationRef] = useAutoAnimate<HTMLDivElement>();
return (
<div className="mb-4 mt-2 rounded-md border border-gray-200">
<div className={`mb-4 mt-2 rounded-md border border-gray-200 ${!app.enabled && "grayscale"}`}>
<div className="p-4 text-sm sm:p-8">
<div className="flex w-full flex-col gap-2 sm:flex-row sm:gap-0">
{/* Don't know why but w-[42px] isn't working, started happening when I started using next/dynamic */}
@ -44,6 +44,7 @@ export default function AppCard({
{app?.isInstalled ? (
<div className="ml-auto flex items-center">
<Switch
disabled={!app.enabled}
onCheckedChange={(enabled) => {
if (switchOnClick) {
switchOnClick(enabled);
@ -54,7 +55,7 @@ export default function AppCard({
/>
</div>
) : (
<OmniInstallAppButton className="ml-auto flex items-center" appId={app?.slug} />
<OmniInstallAppButton className="ml-auto flex items-center" appId={app.slug} />
)}
</div>
</div>

View File

@ -0,0 +1,34 @@
import { useMemo } from "react";
import { classNames } from "@calcom/lib";
import { HorizontalTabs, VerticalTabs } from "@calcom/ui";
import getAppCategories from "../_utils/getAppCategories";
const AppCategoryNavigation = ({
baseURL,
children,
containerClassname,
className,
}: {
baseURL: string;
children: React.ReactNode;
containerClassname: string;
className?: string;
}) => {
const appCategories = useMemo(() => getAppCategories(baseURL), [baseURL]);
return (
<div className={classNames("flex flex-col p-2 md:p-0 xl:flex-row", className)}>
<div className="hidden xl:block">
<VerticalTabs tabs={appCategories} sticky linkProps={{ shallow: true }} />
</div>
<div className="block overflow-x-scroll xl:hidden">
<HorizontalTabs tabs={appCategories} linkProps={{ shallow: true }} />
</div>
<main className={containerClassname}>{children}</main>
</div>
);
};
export default AppCategoryNavigation;

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