Compare commits

...
47 Commits
Author SHA1 Message Date
zomars 368d1afe27 v2.0.5 2022-10-10 17:01:53 -06:00
zomars 2069fd7248 Update settings.json 2022-10-10 16:27:31 -06:00
7ed21b692e #4306: Added darkmode support for select by styling via tailwind (#4824)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-10-10 18:50:43 +00:00
4fcdc7bf5c added badge for unconfirmed bookings (#4912)
* added badge for unconfirmed bookings

* added link to unconfirmed bookings

* added rounded prop to badge, added unconfirmed badge to botto nav

* Supporting recurring events + bug fix

* Tooltip and hover style

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Leo Giovanetti <hello@leog.me>
2022-10-10 17:00:09 +00:00
2a0a293f8c Add new workflow action: Send email to specific email address (#4929)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-10-10 13:40:20 +00:00
1b15b71a24 Feat/improve animation on public booking page (#4848)
* Improve responsive layout for timezone select on booking page.

* Close TZ dropdown on booking page when it's hidden for mobile/desktop, this prevents having a dropdown menu floating around.

* Improved animation for showing timeslots on booking page.

* Changed back button on booking page to sm+ to md+ like it was before.

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-10-10 13:24:06 +00:00
github-actions[bot]andCrowdin Bot 5d9889a90c New Crowdin translations by Github Action (#4927)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-10-10 13:42:19 +01:00
Peer Richelsen a7ee3366eb Update CONTRIBUTING.md 2022-10-10 09:31:33 +01:00
c68e7595fe New Crowdin translations by Github Action (#4923)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-10-10 09:28:18 +01:00
Herbert Mühlburger 3f6be3b2c0 correct german spelling (#4925) 2022-10-10 09:27:44 +01:00
Hariom Balhara f1647248a2 PaymentPage: Fix localStorage access issue in incognito (#4922)
* localStorage isnt available at server and in incognito mode

* Do translation server side for Payment Page
2022-10-10 08:11:47 +00:00
Leo Giovanettiandkodiakhq[bot] d7db1ad303 Hiding event type loses recurring config (#4911)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-10-09 12:36:39 +00:00
Udit Takkar bd48fd01b1 fix: remove hr add spacing, fix link in env file (#4916) 2022-10-09 11:08:47 +00:00
github-actions[bot]andCrowdin Bot fabf692a08 New Crowdin translations by Github Action (#4904)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-10-08 18:10:55 +01:00
Alex van Andel ab62fc5d47 Bugfix/dst available hour mismatch (#4909)
* Need to use local time, not inviteeDate

* Use selected timeZone to perform localWorkingHours logic

* Adding comment

* More comments
2022-10-07 16:10:48 -06:00
zomars 0c4cb2e2b0 Added denullish utils for zod
Needed for API refactoring
2022-10-07 14:48:41 -06:00
Carina WollendorferandCarinaWolli 7d77132102 Add 'after event ends' trigger to workflows (#4882)
* add after event ends workflow trigger to enum

* add after meetings ends trigger

* fix label for time span

* add deleted label for message template

* add lost changes from merge conflict

* set reminder for already existing bookings

* fix label 'How long before event starts/ends'

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-10-07 12:18:28 -06:00
Joe Au-Yeung 50ac5f2892 Fix MS Teams missing refresh token (#4910) 2022-10-07 13:59:58 -04:00
Peer Richelsen fd12fcc4b8 v2 for help dialog (#4901) 2022-10-07 13:21:49 +01:00
+2 58453d0f2f /event-types/detail wrong empty screen for workflows (#4757)
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Klinger Matheus <50892465+KlingerMatheus@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: Olusanya Timothy <48022904+seunexplicit@users.noreply.github.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Klinger Matheus <50892465+KlingerMatheus@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: Olusanya Timothy <48022904+seunexplicit@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2022-10-07 09:25:30 +00:00
Hariom Balhara 44fd1b9fef Improve default app README.mdex (#4893) 2022-10-07 07:29:09 +00:00
Leo Giovanetti 0098591a56 Applying feedback from a merged PR (#4887) 2022-10-06 17:38:50 -03:00
Leo Giovanetti 41c1e960f8 Adjusting jest and expanding test builders (#4837)
* Adjusting jest and expanding test builders

* Removing unneeded jest config
2022-10-06 13:29:20 -06:00
Leo Giovanetti 4e49d32c61 Fixing unconfirmed/recurring bookings (#4881) 2022-10-06 13:23:22 -06:00
+9 0b29c5063a Wrong css in event configuration page buttons (#4869)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: C000Ldude <coolmagnas@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Klinger Matheus <50892465+KlingerMatheus@users.noreply.github.com>
Co-authored-by: Eman <emmanuelgatwech@gmail.com>
Co-authored-by: Olusanya Timothy <48022904+seunexplicit@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
2022-10-06 12:16:32 -06:00
zomars 95a92567cb Update change-username.e2e.ts 2022-10-06 12:15:14 -06:00
+7 9e183e6ad1 Remove black ring on focus when copying private link (#4883)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: MuriloAmarals <muralha2000@gmail.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: C000Ldude <coolmagnas@gmail.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Klinger Matheus <50892465+KlingerMatheus@users.noreply.github.com>
Co-authored-by: Eman <emmanuelgatwech@gmail.com>
Co-authored-by: Olusanya Timothy <48022904+seunexplicit@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-10-06 15:18:16 +00:00
Peer Richelsen 7900f8bab5 fixes to booking page (#4876) 2022-10-06 14:33:57 +00:00
Hariom Balharaandkodiakhq[bot] 50904d2588 useForm defaultValues should be available initially (#4863)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-10-06 13:37:52 +00:00
github-actions[bot]andCrowdin Bot 6f811f4f0a New Crowdin translations by Github Action (#4877)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-10-06 14:14:10 +01:00
sean-brydonandBailey Pumfleet d9000d0cbe Fix height on trash (#4875)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-10-06 13:33:51 +01:00
sean-brydonandBailey Pumfleet ff78e66051 Add successful booking redirect URL to booking page (#4871)
* Add redirect URL field and switch

* Remove and add new translation for description

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-10-06 13:32:11 +01:00
Hariom Balharaandkodiakhq[bot] 7c30f8ca4b Routing Forms Type nigration (#4861)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-10-06 12:21:04 +00:00
Jeroen Reumkens 0ebce77a7b #4851: Always take timeformat on booking page from profile if user is logged in. Also hides timeformat toggle on booking page for logged in users. For non loggedin users we still infer by looking at browser timezone, after that we set a localstorage which we will look at, and which will get updated when the user updates the toggle in the timezone dropdown. (#4865) 2022-10-06 10:25:41 +00:00
sean-brydon 4282adac4b Fix text wrap (#4867) 2022-10-06 10:57:57 +01:00
Nafees Nazikandkodiakhq[bot] ef611e2360 fix: fixes multiple issues in routing form (#4676)
* feat: update forms procedure to match getServerSideProps

* feat: init react query and use prefetched data

* feat: invalidate onSettled while creating a form

* feat: use optimistic updates when deleting

* fix: add optional chaining

* feat: add server-side translation

* feat: add correct translation key

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-10-06 06:49:03 +00:00
github-actions[bot]andCrowdin Bot 10ab86b332 New Crowdin translations by Github Action (#4772)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-10-05 20:23:28 +01:00
Peer Richelsen f04cfef0af reduced padding top on mobile onboarding (#4856) 2022-10-05 18:46:20 +00:00
Alex van Andel 50151403c4 v2.0.4 2022-10-05 16:27:01 +01:00
Peer RichelsenandAlex van Andel 7aa131f349 added TimezoneChangeDialog (#4818)
* nit

* added timezoneChangeDialog

* prevent interactOutside

* nit

* fixed title prop clash

* nit

* remind in one day if clicked outside

* Use utcOffset instead of tz string for checking diff

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-10-05 14:33:29 +00:00
Jeroen Reumkens d88bdc368b Ensure we have to correct tabindexes for addons. Before addons AFTER the input still would get tab focus before the input. (#4849) 2022-10-05 14:22:06 +00:00
Alex van Andel 255938fda0 Remove 'avatar' from query (#4846) 2022-10-05 13:37:42 +00:00
Jeroen ReumkensandPeer Richelsen 5766d6f5f4 Improve responsive layout of timezone select on booking page (#4839)
* Improve responsive layout for timezone select on booking page.

* Close TZ dropdown on booking page when it's hidden for mobile/desktop, this prevents having a dropdown menu floating around.

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-10-05 13:11:26 +00:00
Peer Richelsen 9d299c6d5f fixed event type z-index (#4844) 2022-10-05 12:58:59 +00:00
f49783b453 Bugfix/reinstate tab name with fixed test (#4842)
* added ?tabName=setup to event-type detail link to give it active status (#4830)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Use pathname instead of href directly

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-10-05 11:55:11 +00:00
Jeroen Reumkens 246fce3ab8 #3785: Converted billing settings page to v2. (#4795) 2022-10-04 22:54:15 +00:00
zomars 82f262f60d Quickfix for settings layout on mobile 2022-10-04 15:24:59 -05:00
117 changed files with 2186 additions and 901 deletions
+1 -1
View File
@@ -97,7 +97,7 @@ LARK_OPEN_VERIFICATION_TOKEN=""
# - WEB3
# Used for the Web3 plugin
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/web3/README.md
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/rainbow/README.md
ALCHEMY_API_KEY=""
INFURA_API_KEY=""
# *********************************************************************************************************
+1 -1
View File
@@ -6,5 +6,5 @@
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"spellright.language": ["en"],
"spellright.documentTypes": ["markdown"]
"spellright.documentTypes": ["markdown", "typescript"]
}
-17
View File
@@ -4,23 +4,6 @@ Contributions are what make the open source community such an amazing place to l
- Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission.
## Areas of expertise
### Legend
✅ = has knowledge
🥇 = is their main priority
⚠️ = is the only one with knowledge
👀 = has no knowledge but wants to be onboarded
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://dynamic-svgs.vercel.app/image.svg?dark">
<img alt="Areas of expertise table" src="https://dynamic-svgs.vercel.app/image.svg">
</picture>
## Developing
The development branch is `main`. This is the branch that all pull
@@ -16,7 +16,7 @@ type AvailableTimesProps = {
eventTypeId: number;
recurringCount: number | undefined;
eventTypeSlug: string;
date: Dayjs;
date?: Dayjs;
seatsPerTimeSlot?: number | null;
slots?: Slot[];
isLoading: boolean;
@@ -44,8 +44,10 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
setBrand(getComputedStyle(document.documentElement).getPropertyValue("--brand-color").trim());
}, []);
if (!date) return null;
return (
<div className="dark:bg-darkgray-100 mt-8 flex flex-col px-4 text-center sm:mt-0 sm:w-1/3 sm:p-5 md:-mb-5">
<div className="dark:bg-darkgray-100 mt-8 flex h-full w-full flex-col px-4 text-center sm:mt-0 sm:p-5 md:-mb-5 md:min-w-[200px] lg:min-w-[300px]">
<div className="mb-4 text-left text-base">
<span className="text-bookingdarker dark:text-darkgray-800 mb-8 w-1/2 break-words font-semibold text-gray-900">
{nameOfDay(i18n.language, Number(date.format("d")))}
@@ -150,4 +152,6 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
);
};
AvailableTimes.displayName = "AvailableTimes";
export default AvailableTimes;
+30 -37
View File
@@ -29,7 +29,7 @@ type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
type BookingItemProps = BookingItem & {
listingStatus: BookingListingStatus;
recurringBookings?: BookingItem[];
recurringBookings: inferQueryOutput<"viewer.bookings">["recurringInfo"];
};
function BookingListItem(booking: BookingItemProps) {
@@ -53,6 +53,16 @@ function BookingListItem(booking: BookingItemProps) {
},
});
const isUpcoming = new Date(booking.endTime) >= new Date();
const isPast = new Date(booking.endTime) < new Date();
const isCancelled = booking.status === BookingStatus.CANCELLED;
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
const isRejected = booking.status === BookingStatus.REJECTED;
const isPending = booking.status === BookingStatus.PENDING;
const isRecurring = booking.recurringEventId !== null;
const isTabRecurring = booking.listingStatus === "recurring";
const isTabUnconfirmed = booking.listingStatus === "unconfirmed";
const bookingConfirm = async (confirm: boolean) => {
let body = {
bookingId: booking.id,
@@ -61,28 +71,18 @@ function BookingListItem(booking: BookingItemProps) {
};
/**
* Only pass down the recurring event id when we need to confirm the entire series, which happens in
* the "Recurring" tab, to support confirming discretionally in the "Recurring" tab.
* the "Recurring" tab and "Unconfirmed" tab, to support confirming discretionally in the "Recurring" tab.
*/
if (booking.listingStatus === "recurring" && booking.recurringEventId !== null) {
if ((isTabRecurring || isTabUnconfirmed) && isRecurring) {
body = Object.assign({}, body, { recurringEventId: booking.recurringEventId });
}
mutation.mutate(body);
};
const isUpcoming = new Date(booking.endTime) >= new Date();
const isPast = new Date(booking.endTime) < new Date();
const isCancelled = booking.status === BookingStatus.CANCELLED;
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
const isRejected = booking.status === BookingStatus.REJECTED;
const isPending = booking.status === BookingStatus.PENDING;
const pendingActions: ActionType[] = [
{
id: "reject",
label:
booking.listingStatus === "recurring" && booking.recurringEventId !== null
? t("reject_all")
: t("reject"),
label: (isTabRecurring || isTabUnconfirmed) && isRecurring ? t("reject_all") : t("reject"),
onClick: () => {
setRejectionDialogIsOpen(true);
},
@@ -91,10 +91,7 @@ function BookingListItem(booking: BookingItemProps) {
},
{
id: "confirm",
label:
booking.listingStatus === "recurring" && booking.recurringEventId !== null
? t("confirm_all")
: t("confirm"),
label: (isTabRecurring || isTabUnconfirmed) && isRecurring ? t("confirm_all") : t("confirm"),
onClick: () => {
bookingConfirm(true);
},
@@ -107,17 +104,10 @@ function BookingListItem(booking: BookingItemProps) {
let bookedActions: ActionType[] = [
{
id: "cancel",
label:
booking.listingStatus === "recurring" && booking.recurringEventId !== null
? t("cancel_all_remaining")
: t("cancel"),
label: isTabRecurring && isRecurring ? t("cancel_all_remaining") : t("cancel"),
/* When cancelling we need to let the UI and the API know if the intention is to
cancel all remaining bookings or just that booking instance. */
href: `/cancel/${booking.uid}${
booking.listingStatus === "recurring" && booking.recurringEventId !== null
? "?allRemainingBookings=true"
: ""
}`,
href: `/cancel/${booking.uid}${isTabRecurring && isRecurring ? "?allRemainingBookings=true" : ""}`,
icon: Icon.FiX,
},
{
@@ -151,7 +141,7 @@ function BookingListItem(booking: BookingItemProps) {
},
];
if (booking.listingStatus === "recurring" && booking.recurringEventId !== null) {
if (isTabRecurring && isRecurring) {
bookedActions = bookedActions.filter((action) => action.id !== "edit_booking");
}
@@ -188,12 +178,8 @@ function BookingListItem(booking: BookingItemProps) {
let recurringStrings: string[] = [];
let recurringDates: Date[] = [];
if (booking.recurringBookings && booking.eventType.recurringEvent?.freq !== undefined) {
[recurringStrings, recurringDates] = extractRecurringDates(
booking.recurringBookings,
user?.timeZone,
i18n
);
if (booking.recurringBookings !== undefined && booking.eventType.recurringEvent?.freq !== undefined) {
[recurringStrings, recurringDates] = extractRecurringDates(booking, user?.timeZone, i18n);
}
const location = booking.location || "";
@@ -403,16 +389,21 @@ const RecurringBookingsTooltip = ({
recurringDates,
}: RecurringBookingsTooltipProps) => {
const { t } = useLocale();
const now = new Date();
return (
(booking.recurringBookings &&
booking.eventType?.recurringEvent?.freq &&
(booking.listingStatus === "recurring" || booking.listingStatus === "cancelled") && (
(booking.listingStatus === "recurring" ||
booking.listingStatus === "unconfirmed" ||
booking.listingStatus === "cancelled") && (
<div className="underline decoration-gray-400 decoration-dashed underline-offset-2">
<div className="flex">
<Tooltip
content={recurringStrings.map((aDate, key) => (
<p key={key}>{aDate}</p>
<p key={key} className={classNames(recurringDates[key] < now && "line-through")}>
{aDate}
</p>
))}>
<div className="text-gray-600 dark:text-white">
<Icon.FiRefreshCcw
@@ -427,7 +418,9 @@ const RecurringBookingsTooltip = ({
: getEveryFreqFor({
t,
recurringEvent: booking.eventType.recurringEvent,
recurringCount: booking.recurringBookings.length,
recurringCount: recurringDates.filter((date) => {
return date >= now;
}).length,
})}
</p>
</div>
+17 -14
View File
@@ -1,8 +1,8 @@
import { FC, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import TimezoneSelect, { ITimezoneOption } from "@calcom/ui/form/TimezoneSelect";
import { Switch } from "@calcom/ui/v2";
import TimezoneSelect, { ITimezoneOption } from "@calcom/ui/v2/core/TimezoneSelect";
import { is24h, timeZone } from "../../lib/clock";
@@ -10,9 +10,10 @@ type Props = {
onSelectTimeZone: (selectedTimeZone: string) => void;
onToggle24hClock: (is24hClock: boolean) => void;
timeFormat: string;
hideTimeFormatToggle?: boolean;
};
const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone, timeFormat }) => {
const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone, timeFormat, hideTimeFormatToggle }) => {
const [selectedTimeZone, setSelectedTimeZone] = useState("");
const [is24hClock, setIs24hClock] = useState(timeFormat === "HH:mm" && true);
const { t } = useLocale();
@@ -33,20 +34,22 @@ const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone, timeFormat
};
return selectedTimeZone !== "" ? (
<div className="max-w-80 dark:border-darkgray-300 dark:bg-darkgray-200 absolute z-10 w-full rounded-sm border border-gray-200 bg-white px-4 pt-4 pb-3 shadow-sm">
<div className="dark:border-darkgray-300 dark:bg-darkgray-200 rounded-sm border border-gray-200 bg-white px-4 pt-4 pb-3 shadow-sm">
<div className="mb-4 flex">
<div className="text-sm font-medium text-gray-600 dark:text-white">{t("time_options")}</div>
<div className="ml-auto flex items-center">
<label className="ltl:mr-3 mr-2 align-text-top text-sm font-medium text-neutral-700 ltr:ml-3 rtl:mr-3 dark:text-white">
{t("am_pm")}
</label>
<Switch
name="24hClock"
label={t("24_h")}
defaultChecked={is24hClock}
onCheckedChange={handle24hClockToggle}
/>
</div>
{!hideTimeFormatToggle && (
<div className="ml-auto flex items-center">
<label className="ltl:mr-3 mr-2 align-text-top text-sm font-medium text-neutral-700 ltr:ml-3 rtl:mr-3 dark:text-white">
{t("am_pm")}
</label>
<Switch
name="24hClock"
label={t("24_h")}
defaultChecked={is24hClock}
onCheckedChange={handle24hClockToggle}
/>
</div>
)}
</div>
<TimezoneSelect
id="timeZone"
@@ -1,10 +1,11 @@
// Get router variables
import autoAnimate from "@formkit/auto-animate";
import { EventType } from "@prisma/client";
import { SchedulingType } from "@prisma/client";
import * as Collapsible from "@radix-ui/react-collapsible";
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 { useReducer, useEffect, useMemo, useState, useRef } from "react";
import { Toaster } from "react-hot-toast";
import { FormattedNumber, IntlProvider } from "react-intl";
import { z } from "zod";
@@ -128,6 +129,11 @@ const SlotPicker = ({
const { date, setQuery: setDate } = useRouterQuery("date");
const { month, setQuery: setMonth } = useRouterQuery("month");
const router = useRouter();
const slotPickerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
slotPickerRef.current && autoAnimate(slotPickerRef.current);
}, [slotPickerRef]);
useEffect(() => {
if (!router.isReady) return;
@@ -175,10 +181,8 @@ const SlotPicker = ({
<DatePicker
isLoading={isLoading}
className={classNames(
"mt-8 w-full px-4 pb-4 sm:mt-0 sm:min-w-[455px] md:px-5",
selectedDate
? "sm:dark:border-darkgray-200 border-gray-200 sm:w-1/2 sm:border-r sm:p-4 sm:pr-6 md:w-1/3"
: "sm:p-4"
"mt-8 px-4 pb-4 sm:mt-0 md:min-w-[300px] md:px-5 lg:min-w-[455px]",
selectedDate ? "sm:dark:border-darkgray-200 border-gray-200 sm:border-r sm:p-4 sm:pr-6" : "sm:p-4"
)}
includedDates={Object.keys(slots).filter((k) => slots[k].length > 0)}
locale={isLocaleReady ? i18n.language : "en"}
@@ -193,10 +197,10 @@ const SlotPicker = ({
weekStart={weekStart}
/>
{selectedDate && (
<div ref={slotPickerRef}>
<AvailableTimes
isLoading={isLoading}
slots={slots[selectedDate.format("YYYY-MM-DD")]}
slots={selectedDate && slots[selectedDate.format("YYYY-MM-DD")]}
date={selectedDate}
timeFormat={timeFormat}
eventTypeId={eventType.id}
@@ -205,7 +209,7 @@ const SlotPicker = ({
recurringCount={recurringEventCount}
ethSignature={ethSignature}
/>
)}
</div>
</>
);
};
@@ -215,11 +219,13 @@ function TimezoneDropdown({
onChangeTimeZone,
timeZone,
timeFormat,
hideTimeFormatToggle,
}: {
onChangeTimeFormat: (newTimeFormat: string) => void;
onChangeTimeZone: (newTimeZone: string) => void;
timeZone?: string;
timeFormat: string;
hideTimeFormatToggle?: boolean;
}) {
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
@@ -240,8 +246,8 @@ function TimezoneDropdown({
};
return (
<Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen} className="flex">
<Collapsible.Trigger className="min-w-32 dark:text-darkgray-600 mb-2 -ml-2 px-2 text-left text-gray-600">
<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">
<p className="text-sm font-medium">
<Icon.FiGlobe className="mr-[10px] ml-[2px] -mt-[2px] inline-block h-4 w-4" />
{timeZone}
@@ -251,15 +257,21 @@ function TimezoneDropdown({
<Icon.FiChevronDown className="ml-1 inline-block h-4 w-4" />
)}
</p>
</Collapsible.Trigger>
<Collapsible.Content>
<TimeOptions
onSelectTimeZone={handleSelectTimeZone}
onToggle24hClock={handleToggle24hClock}
timeFormat={timeFormat}
/>
</Collapsible.Content>
</Collapsible.Root>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
hideWhenDetached
align="start"
className="animate-fade-in-up absolute left-0 top-2 w-80 max-w-[calc(100vw_-_1.5rem)]">
<TimeOptions
onSelectTimeZone={handleSelectTimeZone}
onToggle24hClock={handleToggle24hClock}
timeFormat={timeFormat}
hideTimeFormatToggle={hideTimeFormatToggle}
/>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
}
@@ -288,7 +300,14 @@ const useRouterQuery = <T extends string>(name: T) => {
export type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps;
const timeFormatTotimeFormatString = (timeFormat?: number | null) => {
if (!timeFormat) return null;
return timeFormat === 24 ? "HH:mm" : "h:mma";
};
const AvailabilityPage = ({ profile, eventType }: Props) => {
const { data: user } = trpc.useQuery(["viewer.me"]);
const timeFormatFromProfile = timeFormatTotimeFormatString(user?.timeFormat);
const router = useRouter();
const isEmbed = useIsEmbed();
const query = dateQuerySchema.parse(router.query);
@@ -301,8 +320,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
const isBackgroundTransparent = useIsBackgroundTransparent();
const [timeZone, setTimeZone] = useState<string>();
const [timeFormat, setTimeFormat] = useState(detectBrowserTimeFormat);
const [isAvailableTimesVisible, setIsAvailableTimesVisible] = useState<boolean>();
const [timeFormat, setTimeFormat] = useState(timeFormatFromProfile || detectBrowserTimeFormat);
const [gateState, gateDispatcher] = useReducer(
(state: GateState, newState: Partial<GateState>) => ({
...state,
@@ -315,10 +333,6 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
setTimeZone(localStorageTimeZone() || dayjs.tz.guess());
}, []);
useEffect(() => {
setIsAvailableTimesVisible(!!query.date);
}, [query.date]);
// TODO: Improve this;
useExposePlanGlobally(eventType.users.length === 1 ? eventType.users[0].plan : "PRO");
@@ -337,14 +351,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
// get dynamic user list here
const userList = eventType.users ? eventType.users.map((user) => user.username).filter(notEmpty) : [];
// Recurring event sidebar requires more space
const maxWidth = isAvailableTimesVisible
? recurringEventCount
? "max-w-6xl"
: "max-w-5xl"
: recurringEventCount
? "max-w-4xl"
: "max-w-3xl";
const timezoneDropdown = useMemo(
() => (
<TimezoneDropdown
@@ -352,9 +359,12 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
onChangeTimeFormat={setTimeFormat}
timeZone={timeZone}
onChangeTimeZone={setTimeZone}
// Currently we don't allow the user to change the timeformat when they're logged in,
// the only way to change it is if they go to their profile.
hideTimeFormatToggle={!!timeFormatFromProfile}
/>
),
[timeZone, timeFormat]
[timeZone, timeFormat, timeFormatFromProfile]
);
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
@@ -384,203 +394,109 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
<div>
<main
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed
? classNames(maxWidth)
: classNames("transition-max-width mx-auto my-0 duration-500 ease-in-out md:my-24", maxWidth)
"flex flex-col",
shouldAlignCentrally ? "items-center" : "items-start",
!isEmbed && classNames("mx-auto my-0 ease-in-out md:my-24")
)}>
<div
style={availabilityDatePickerEmbedStyles}
className={classNames(
isBackgroundTransparent
? ""
: "dark:bg-darkgray-100 sm:dark:border-darkgray-300 bg-white pb-4 md:pb-0",
"border-bookinglightest overflow-hidden rounded-md md:border",
isEmbed ? "mx-auto" : maxWidth
)}>
{/* mobile: details */}
<div className="block px-4 pt-4 sm:p-8 md:hidden">
<div>
<UserAvatars
profile={profile}
users={eventType.users}
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
size={9}
truncateAfter={5}
/>
<div className="mt-4">
<div className="dark:text-darkgray-900 mt-2 gap-2">
<p className="break-words text-sm font-medium text-gray-600 dark:text-gray-300">
{profile.name}
</p>
<h1 className="text-bookingdark dark:text-darkgray-900 mb-4 break-words text-xl font-semibold">
{eventType.title}
</h1>
<div className="flex flex-col space-y-3">
{eventType?.description && (
<div className="dark:text-darkgray-600 flex py-1 text-sm font-medium text-gray-600">
<div>
<Icon.FiInfo className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
</div>
<EventTypeDescriptionSafeHTML eventType={eventType} />
<div>
<div
style={availabilityDatePickerEmbedStyles}
className={classNames(
isBackgroundTransparent
? ""
: "dark:bg-darkgray-100 sm:dark:border-darkgray-300 bg-white pb-4 md:pb-0",
"border-bookinglightest overflow-hidden rounded-md md:border",
isEmbed && "mx-auto"
)}>
<div className="overflow-hidden md:flex">
<div
className={classNames(
"sm:dark:border-darkgray-200 flex flex-col border-gray-200 p-5 sm:border-r",
"min-w-full md:w-[280px] md:min-w-[280px]",
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
)}>
<UserAvatars
profile={profile}
users={eventType.users}
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
size={10}
truncateAfter={3}
/>
<h2 className="mt-2 break-words text-sm font-medium text-gray-600 dark:text-gray-300">
{profile.name}
</h2>
<h1 className="font-cal dark:text-darkgray-900 mb-6 break-words text-2xl font-semibold text-gray-900">
{eventType.title}
</h1>
<div className="dark:text-darkgray-600 flex flex-col space-y-3 text-sm font-medium text-gray-600">
{eventType?.description && (
<div className="flex">
<div>
<Icon.FiInfo className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
</div>
)}
{eventType?.requiresConfirmation && (
<p className="dark:text-darkgray-600 text-gray-600">
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{t("requires_confirmation")}
</p>
)}
<AvailableEventLocations locations={eventType.locations} />
<p className="dark:text-darkgray-600 text-gray-600">
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
{eventType.length} {t("minutes")}
<EventTypeDescriptionSafeHTML eventType={eventType} />
</div>
)}
{eventType?.requiresConfirmation && (
<div className="flex items-center">
<div>
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 " />
</div>
{t("requires_confirmation")}
</div>
)}
<AvailableEventLocations locations={eventType.locations} />
<p className="text-sm font-medium">
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
{eventType.length} {t("minutes")}
</p>
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline">
{t("occurrence", {
count: recurringEventCount,
})}
</p>
</div>
</div>
)}
{eventType.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
</p>
{eventType.price > 0 && (
<div className="dark:text-darkgray-600 text-gray-600">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
</div>
)}
{!rescheduleUid && eventType.recurringEvent && (
<div className="dark:text-darkgray-600 flex items-center text-gray-600">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-1 ml-[2px] inline-block h-4 w-4 shrink-0 text-gray-500" />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 dark:text-darkgray-600 h-7 rounded-sm border-gray-300 bg-white text-sm text-gray-600 [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500 dark:bg-gray-600"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="dark:text-darkgray-600 inline text-gray-600">
{t("occurrence", {
count: recurringEventCount,
})}
</p>
</div>
</div>
)}
{timezoneDropdown}
<div className="md:hidden">
{/* Temp disabled booking?.startTime && rescheduleUid && (
<div>
<p
className="mt-8 text-gray-600 dark:text-darkgray-600"
data-testid="former_time_p_mobile">
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-darkgray-600">
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-500" />
{typeof booking.startTime === "string" &&
parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)*/}
</div>
</div>
)}
{timezoneDropdown}
</div>
</div>
</div>
</div>
<div className="overflow-hidden sm:flex">
<div
className={
"sm:dark:border-darkgray-200 hidden overflow-hidden border-gray-200 p-5 sm:border-r md:flex md:flex-col " +
(isAvailableTimesVisible ? "sm:w-1/3" : recurringEventCount ? "sm:w-2/3" : "sm:w-1/2")
}>
<UserAvatars
profile={profile}
users={eventType.users}
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
size={10}
truncateAfter={3}
/>
<h2 className="break-words text-sm font-medium text-gray-600 dark:text-gray-300 lg:mt-2">
{profile.name}
</h2>
<h1 className="font-cal dark:text-darkgray-900 mb-6 break-words text-2xl text-gray-900">
{eventType.title}
</h1>
<div className="dark:text-darkgray-600 flex flex-col space-y-3 text-sm font-medium text-gray-600">
{eventType?.description && (
<div className="flex">
<div>
<Icon.FiInfo className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
</div>
<EventTypeDescriptionSafeHTML eventType={eventType} />
{!isEmbed && (
<div className="mt-auto hidden md:block">
<GoBackToPreviousPage t={t} />
</div>
)}
{eventType?.requiresConfirmation && (
<div className="flex items-center">
<div>
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 " />
</div>
{t("requires_confirmation")}
</div>
)}
<AvailableEventLocations locations={eventType.locations} />
<p className="text-sm font-medium">
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
{eventType.length} {t("minutes")}
</p>
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline">
{t("occurrence", {
count: recurringEventCount,
})}
</p>
</div>
</div>
)}
{eventType.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
{timezoneDropdown}
</div>
{!isEmbed && <GoBackToPreviousPage t={t} />}
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
<div>
<p
className="mt-4 mb-3 text-gray-600 dark:text-darkgray-600"
@@ -593,26 +509,33 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
</p>
</div>
)*/}
</div>
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
? ([
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
].indexOf(profile.weekStart) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
: profile.weekStart /* Allows providing weekStart as number */
}
eventType={eventType}
timeFormat={timeFormat}
timeZone={timeZone}
users={userList}
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
recurringEventCount={recurringEventCount}
ethSignature={gateState.rainbowToken}
/>
</div>
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
? (["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].indexOf(
profile.weekStart
) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
: profile.weekStart /* Allows providing weekStart as number */
}
eventType={eventType}
timeFormat={timeFormat}
timeZone={timeZone}
users={userList}
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
recurringEventCount={recurringEventCount}
ethSignature={gateState.rainbowToken}
/>
</div>
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && <PoweredByCal />}
</div>
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && <PoweredByCal />}
</main>
</div>
<Toaster position="bottom-right" />
@@ -712,23 +712,25 @@ const BookingPage = ({
/>
)}
{input.type === EventTypeCustomInputType.BOOL && (
<div className="flex h-5 items-center">
<input
type="checkbox"
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
required={input.required}
id={"custom_" + input.id}
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black disabled:bg-gray-200 ltr:mr-2 rtl:ml-2 disabled:dark:text-gray-500"
placeholder=""
disabled={disabledExceptForOwner}
/>
<label
htmlFor={"custom_" + input.id}
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{input.label}
</label>
<div className="my-6">
<div className="flex">
<input
type="checkbox"
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
required={input.required}
id={"custom_" + input.id}
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black disabled:bg-gray-200 ltr:mr-2 rtl:ml-2 disabled:dark:text-gray-500"
placeholder=""
disabled={disabledExceptForOwner}
/>
<label
htmlFor={"custom_" + input.id}
className="-mt-px block text-sm font-medium text-gray-700 dark:text-white">
{input.label}
</label>
</div>
</div>
)}
</div>
+1
View File
@@ -77,6 +77,7 @@ const TableActions: FC<Props> = ({ actions }) => {
{actions.map((action) => {
const button = (
<Button
className="whitespace-nowrap"
key={action.id}
data-testid={action.id}
href={action.href}
@@ -1,5 +1,4 @@
import React from "react";
import Select from "react-select";
import { OptionProps } from "react-select";
import { InstallAppButton } from "@calcom/app-store/components";
@@ -8,6 +7,7 @@ import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App";
import { Button } from "@calcom/ui";
import { Icon } from "@calcom/ui/Icon";
import { Select } from "@calcom/ui/v2";
import { QueryCell } from "@lib/QueryCell";
@@ -65,23 +65,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
name="additionalCalendar"
placeholder={t("install_another")}
options={options}
styles={{
placeholder: (defaultStyles) => {
return {
...defaultStyles,
color: "#3E3E3E",
marginLeft: "3px",
};
},
control: (defaultStyles) => {
return {
...defaultStyles,
borderRadius: "6px",
};
},
}}
isSearchable={false}
className="min-w-52 block w-full flex-1 rounded-none rounded-r-sm border-gray-300 text-sm font-medium text-gray-700"
isLoading={isLoading}
components={{ Option: ImageOption }}
/>
@@ -8,7 +8,7 @@ import { weekdayNames } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import Button from "@calcom/ui/v2/core/Button";
import Select from "@calcom/ui/v2/core/form/Select";
import Select from "@calcom/ui/v2/core/form/select";
import { SkeletonText } from "@calcom/ui/v2/core/skeleton";
import { SelectSkeletonLoader } from "@components/v2/availability/SkeletonLoader";
@@ -40,6 +40,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
const { t } = useLocale();
const [showEventNameTip, setShowEventNameTip] = useState(false);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const [seatsInputVisible, setSeatsInputVisible] = useState(!!eventType.seatsPerTimeSlot);
const [customInputs, setCustomInputs] = useState<EventTypeCustomInput[]>(
@@ -248,6 +249,48 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
)}
/>
<hr />
<Controller
name="successRedirectUrl"
control={formMethods.control}
defaultValue={hashedUrl}
render={({ field: { value, onChange } }) => (
<>
<div className="flex space-x-3 ">
<Switch
name="successRedirectUrlCheck"
fitToHeight={true}
defaultChecked={redirectUrlVisible}
onCheckedChange={(e) => {
setRedirectUrlVisible(e);
onChange(e ? value : "");
}}
/>
<div className="flex flex-col">
<Skeleton as={Label} className="text-sm font-semibold leading-none text-black">
{t("redirect_success_booking")}
</Skeleton>
<Skeleton as="p" className="-mt-2 text-sm leading-normal text-gray-600">
{t("redirect_url_description")}
</Skeleton>
</div>
</div>
{redirectUrlVisible && (
<div className="">
<TextField
label={t("redirect_success_booking")}
placeholder={t("external_redirect_url")}
required={redirectUrlVisible}
type="text"
defaultValue={eventType.successRedirectUrl || ""}
{...formMethods.register("successRedirectUrl")}
/>
</div>
)}
</>
)}
/>
<hr />
<Controller
name="hashedLink"
control={formMethods.control}
@@ -228,27 +228,29 @@ function EventTypeSingleLayout({
<Icon.FiMoreHorizontal className="group-hover:text-gray-800" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<DropdownMenuItem className="focus:ring-gray-100">
<Button
color="minimal"
StartIcon={Icon.FiExternalLink}
target="_blank"
href={permalink}
rel="noreferrer">
rel="noreferrer"
className="min-w-full">
{t("preview")}
</Button>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem className="focus:ring-gray-100">
<Button color="minimal" StartIcon={Icon.FiLink}>
{t("copy_link")}
</Button>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuItem className="focus:ring-gray-100">
<Button
color="minimal"
StartIcon={Icon.FiTrash}
disabled={!hasPermsToDelete}
onClick={() => setDeleteDialogOpen(true)}>
onClick={() => setDeleteDialogOpen(true)}
className="min-w-full">
{t("delete")}
</Button>
</DropdownMenuItem>
+18 -5
View File
@@ -47,14 +47,27 @@ export const parseRecurringDates = (
return [dateStrings, rule.all()];
};
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
export const extractRecurringDates = (
bookings: BookingItem[],
booking: inferQueryOutput<"viewer.bookings">["bookings"][number] & {
eventType: { recurringEvent: RecurringEvent | null };
recurringEventId: string | null;
recurringBookings: inferQueryOutput<"viewer.bookings">["recurringInfo"];
},
timeZone: string | undefined,
i18n: I18n
): [string[], Date[]] => {
const dateStrings = bookings.map((booking) => processDate(dayjs(booking.startTime).tz(timeZone), i18n));
const allDates = dateStrings.map((dateString) => dayjs(dateString).tz(timeZone).toDate());
const { count = 0, ...rest } =
booking.eventType.recurringEvent !== null ? booking.eventType.recurringEvent : {};
const recurringInfo = booking.recurringBookings.find(
(val) => val.recurringEventId === booking.recurringEventId
);
const allDates = new RRule({
...rest,
count: recurringInfo?._count.recurringEventId,
dtstart: recurringInfo?._min.startTime,
}).all();
const dateStrings = allDates.map((r) => {
return processDate(dayjs(r).tz(timeZone), i18n);
});
return [dateStrings, allDates];
};
+1
View File
@@ -7,6 +7,7 @@ import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry
const V2_WHITELIST = [
"/settings/admin",
"/settings/billing",
"/settings/developer/webhooks",
"/settings/developer/api-keys",
"/settings/my-account",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.0.3",
"version": "2.0.5",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
-1
View File
@@ -182,7 +182,6 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
<h2 className="dark:text-darkgray-700 pr-2 text-sm font-semibold text-gray-700">
{type.title}
</h2>
<p className="dark:text-darkgray-600 hidden text-sm font-normal leading-none text-gray-600 md:block">{`/${user.username}/${type.slug}`}</p>
</div>
<EventTypeDescription eventType={type} />
</a>
+1 -12
View File
@@ -54,17 +54,6 @@ export default function Bookings() {
const isEmpty = !query.data?.pages[0]?.bookings.length;
// Get all recurring events of the series with the same recurringEventId
const defineRecurrentBookings = (
booking: BookingOutput,
groupedBookings: Record<string, BookingOutput[]>
) => {
let recurringBookings = undefined;
if (booking.recurringEventId !== null) {
recurringBookings = groupedBookings[booking.recurringEventId];
}
return { recurringBookings };
};
const shownBookings: Record<string, BookingOutput[]> = {};
const filterBookings = (booking: BookingOutput) => {
if (status === "recurring" || status === "cancelled") {
@@ -104,7 +93,7 @@ export default function Bookings() {
<BookingListItem
key={booking.id}
listingStatus={status}
{...defineRecurrentBookings(booking, shownBookings)}
recurringBookings={page.recurringInfo}
{...booking}
/>
))}
+1
View File
@@ -542,6 +542,7 @@ const EventTypesPage = () => {
<title>Home | Cal.com</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}
@@ -92,7 +92,7 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="mx-auto px-4 py-24">
<div className="mx-auto px-4 py-6 md:py-24">
<div className="relative">
<div className="sm:mx-auto sm:w-full sm:max-w-[600px]">
<div className="mx-auto sm:max-w-[520px]">
@@ -129,7 +129,7 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
event.preventDefault();
goToIndex(currentStepIndex + 1);
}}
className="mt-24 cursor-pointer px-4 py-2 font-sans text-sm font-medium">
className="mt-8 cursor-pointer px-4 py-2 font-sans text-sm font-medium">
{headers[currentStepIndex]?.skipText}
</Button>
</div>
+1 -2
View File
@@ -8,6 +8,7 @@ import React, { useEffect } from "react";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { CAL_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
@@ -17,7 +18,6 @@ import { Button } from "@calcom/ui/v2/core";
import EventTypeDescription from "@calcom/ui/v2/modules/event-types/EventTypeDescription";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { useLocale } from "@lib/hooks/useLocale";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -56,7 +56,6 @@ function TeamPage({ team }: TeamPageProps) {
<div className="flex-shrink">
<div className="flex flex-wrap items-center space-x-2">
<h2 className="dark:text-darkgray-700 text-sm font-semibold text-gray-700">{type.title}</h2>
<p className="dark:text-darkgray-600 hidden text-sm font-normal leading-none text-gray-600 md:block">{`/${team.slug}/${type.slug}`}</p>
</div>
<EventTypeDescription className="text-sm" eventType={type} />
</div>
@@ -12,10 +12,10 @@ import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import type { Schedule as ScheduleType } from "@calcom/types/schedule";
import { Icon } from "@calcom/ui";
import TimezoneSelect from "@calcom/ui/form/TimezoneSelect";
import Button from "@calcom/ui/v2/core/Button";
import Shell from "@calcom/ui/v2/core/Shell";
import Switch from "@calcom/ui/v2/core/Switch";
import TimezoneSelect from "@calcom/ui/v2/core/TimezoneSelect";
import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider";
import { Form, Label } from "@calcom/ui/v2/core/form/fields";
import showToast from "@calcom/ui/v2/core/notifications";
+2 -13
View File
@@ -55,20 +55,9 @@ export default function Bookings() {
const isEmpty = !query.data?.pages[0]?.bookings.length;
// Get all recurring events of the series with the same recurringEventId
const defineRecurrentBookings = (
booking: BookingOutput,
groupedBookings: Record<string, BookingOutput[]>
) => {
let recurringBookings = undefined;
if (booking.recurringEventId !== null) {
recurringBookings = groupedBookings[booking.recurringEventId];
}
return { recurringBookings };
};
const shownBookings: Record<string, BookingOutput[]> = {};
const filterBookings = (booking: BookingOutput) => {
if (status === "recurring" || status === "cancelled") {
if (status === "recurring" || status == "unconfirmed" || status === "cancelled") {
if (!booking.recurringEventId) {
return true;
}
@@ -111,7 +100,7 @@ export default function Bookings() {
<BookingListItem
key={booking.id}
listingStatus={status}
{...defineRecurrentBookings(booking, shownBookings)}
recurringBookings={page.recurringInfo}
{...booking}
/>
))}
+1 -1
View File
@@ -52,7 +52,7 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
const { t } = useLocale();
return (
<Link href={`/event-types/${type.id}`}>
<Link href={`/event-types/${type.id}?tabName=setup`}>
<a
className="flex-grow truncate text-sm"
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
@@ -0,0 +1,86 @@
import { useState } from "react";
import { HelpScout, useChat } from "react-live-chat-loader";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Button } from "@calcom/ui/v2";
import Meta from "@calcom/ui/v2/core/Meta";
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
interface CtaRowProps {
title: string;
description: string;
children: React.ReactNode;
className?: string;
}
const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
return (
<>
<section className={classNames("flex flex-col sm:flex-row", className)}>
<div>
<h2 className="font-medium">{title}</h2>
<p>{description}</p>
</div>
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pt-0 sm:pl-3">{children}</div>
</section>
<hr className="border-neutral-200" />
</>
);
};
const BillingView = () => {
const { t } = useLocale();
const { data: user } = trpc.useQuery(["viewer.me"]);
const isPro = user?.plan === "PRO";
const [, loadChat] = useChat();
const [showChat, setShowChat] = useState(false);
const onContactSupportClick = () => {
setShowChat(true);
loadChat({ open: true });
};
return (
<>
<Meta title={t("billing")} description={t("manage_billing_description")} />
<div className="space-y-6 text-sm sm:space-y-8">
{!isPro && (
<CtaRow title={t("billing_freeplan_title")} description={t("billing_freeplan_description")}>
<form target="_blank" method="POST" action="/api/upgrade">
<Button type="submit" EndIcon={Icon.FiExternalLink}>
{t("billing_freeplan_cta")}
</Button>
</form>
</CtaRow>
)}
<CtaRow
className={classNames(!isPro && "pointer-events-none opacity-30")}
title={t("billing_manage_details_title")}
description={t("billing_manage_details_description")}>
<Button
color={isPro ? "primary" : "secondary"}
href="/api/integrations/stripepayment/portal"
target="_blank"
EndIcon={Icon.FiExternalLink}>
{t("billing_portal")}
</Button>
</CtaRow>
<CtaRow title={t("billing_help_title")} description={t("billing_help_description")}>
<Button color="secondary" onClick={onContactSupportClick}>
{t("billing_help_cta")}
</Button>
</CtaRow>
{showChat && <HelpScout color="#292929" icon="message" horizontalPosition="right" zIndex="1" />}
</div>
</>
);
};
BillingView.getLayout = getLayout;
export default BillingView;
@@ -4,12 +4,12 @@ import { useForm, Controller } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { setIs24hClockInLocalStorage } from "@calcom/lib/timeFormat";
import { trpc } from "@calcom/trpc/react";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/v2/core/Button";
import Meta from "@calcom/ui/v2/core/Meta";
import TimezoneSelect from "@calcom/ui/v2/core/TimezoneSelect";
import Select from "@calcom/ui/v2/core/form/Select";
import { Form, Label } from "@calcom/ui/v2/core/form/fields";
import Select from "@calcom/ui/v2/core/form/select";
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
import showToast from "@calcom/ui/v2/core/notifications";
import { SkeletonContainer, SkeletonText, SkeletonButton } from "@calcom/ui/v2/core/skeleton";
@@ -34,25 +34,32 @@ const SkeletonLoader = () => {
interface GeneralViewProps {
localeProp: string;
user: inferQueryOutput<"viewer.me">;
}
const WithQuery = withQuery(["viewer.public.i18n"], { trpc: { context: { skipBatch: true } } });
const GeneralQueryView = () => {
const { t } = useLocale();
const { data: user, isLoading } = trpc.useQuery(["viewer.me"]);
if (isLoading) return <SkeletonLoader />;
if (!user) {
throw new Error(t("something_went_wrong"));
}
return (
<WithQuery
success={({ data }) => <GeneralView localeProp={data.locale} />}
success={({ data }) => <GeneralView user={user} localeProp={data.locale} />}
customLoader={<SkeletonLoader />}
/>
);
};
const GeneralView = ({ localeProp }: GeneralViewProps) => {
const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
const router = useRouter();
const utils = trpc.useContext();
const { t } = useLocale();
const { data: user, isLoading } = trpc.useQuery(["viewer.me"]);
const mutation = trpc.useMutation("viewer.updateProfile", {
onSuccess: () => {
showToast(t("settings_updated_successfully"), "success");
@@ -93,20 +100,18 @@ const GeneralView = ({ localeProp }: GeneralViewProps) => {
value: localeProp || "",
label: localeOptions.find((option) => option.value === localeProp)?.label || "",
},
timeZone: user?.timeZone || "",
timeZone: user.timeZone || "",
timeFormat: {
value: user?.timeFormat || 12,
label: timeFormatOptions.find((option) => option.value === user?.timeFormat)?.label || 12,
value: user.timeFormat || 12,
label: timeFormatOptions.find((option) => option.value === user.timeFormat)?.label || 12,
},
weekStart: {
value: user?.weekStart,
label: nameOfDay(localeProp, user?.weekStart === "Sunday" ? 0 : 1),
value: user.weekStart,
label: nameOfDay(localeProp, user.weekStart === "Sunday" ? 0 : 1),
},
},
});
if (isLoading) return <SkeletonLoader />;
return (
<Form
form={formMethods}
+2 -1
View File
@@ -25,7 +25,8 @@ test.describe("Change username on settings", () => {
await users.deleteAll();
});
test("User can change username", async ({ page, users, prisma }) => {
/** 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 });
await user.login();
+3 -1
View File
@@ -1,5 +1,7 @@
import { expect } from "@playwright/test";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { randomString } from "../lib/random";
import { test } from "./lib/fixtures";
@@ -87,7 +89,7 @@ test.describe("Event Types tests", () => {
);
const href = await firstElement.getAttribute("href");
if (!href) throw new Error("No href found for event type");
const [eventTypeId] = href.split("/").reverse();
const [eventTypeId] = new URL(WEBAPP_URL + href).pathname.split("/").reverse();
const firstTitle = await page.locator(`[data-testid=event-type-title-${eventTypeId}]`).innerText();
const firstFullSlug = await page.locator(`[data-testid=event-type-slug-${eventTypeId}]`).innerText();
const firstSlug = firstFullSlug.split("/")[2];
+2 -1
View File
@@ -3,6 +3,7 @@ import type Prisma from "@prisma/client";
import { Prisma as PrismaType, UserPlan } from "@prisma/client";
import { hash } from "bcryptjs";
import dayjs from "@calcom/dayjs";
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { prisma } from "@calcom/prisma";
@@ -257,7 +258,7 @@ const createUser = async (
password: await hashPassword(uname),
emailVerified: new Date(),
completedOnboarding: opts?.completedOnboarding ?? true,
timeZone: opts?.timeZone ?? TimeZoneEnum.UK,
timeZone: opts?.timeZone ?? dayjs.tz.guess(),
locale: opts?.locale ?? "en",
schedules:
opts?.completedOnboarding ?? true
+32 -2
View File
@@ -837,7 +837,7 @@
"redirect_success_booking": "إعادة التوجيه عند الحجز ",
"you_are_being_redirected": "ستتم إعادة توجيهك إلى {{ url }} خلال $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "لاستخدام هذه الميزة، تحتاج إلى الترقية إلى حساب Pro.",
"redirect_url_description": "إعادة التوجيه إلى عنوان URL مخصص بعد الحجز بنجاح",
"duplicate": "تكرار",
"offer_seats": "عرض مقاعد",
"offer_seats_description": "عرض مقاعد للحجز (هذا يعطل الضيوف والتسجيل في الحجز)",
@@ -1254,6 +1254,7 @@
"back_to_signin": "العودة لتسجيل الدخول",
"reset_link_sent": "تم إرسال رابط إعادة الضبط",
"password_reset_email": "ثمة بريد إلكتروني في طريقه إلى {{email}} مع تعليمات لإعادة تعيين كلمة المرور الخاصة بك.",
"password_reset_leading": "إذا لم تتلقَ رسالة إلكترونية قريباُ، تحقق من أن عنوان البريد الإلكتروني الذي أدخلته صحيح، وتحقق من مجلد البريد الطفيلي لديك، ثم تواصل مع قسم الدعم إذا استمرت المشكلة.",
"password_updated": "تم تحديث كلمة المرور!",
"pending_payment": "في انتظار الدفع",
"confirmation_page_rainbow": "رمز بوابة الحدث الخاص بك مع الرموز أو NFTs على Ethereum وPolygon وغيرها.",
@@ -1265,5 +1266,34 @@
"confirm_password_change_email": "الرجاء تأكيد كلمة المرور الخاصة بك قبل تغيير عنوان بريدك الإلكتروني",
"seats": "مقاعد",
"every_app_published": "كل تطبيق منشور على متجر تطبيقات Cal.com هو مصدر مفتوح ويتم اختباره بشكل شامل عن طريق مراجعات الأقران. لا يدعم Cal.com أو يصادق على هذه التطبيقات ما لم يتم نشرها بواسطة Cal.com. إذا واجهت محتوى أو سلوك غير مناسب يرجى الإبلاغ عن ذلك.",
"report_app": "الإبلاغ عن التطبيق"
"report_app": "الإبلاغ عن التطبيق",
"team_name_required": "اسم الفريق مطلوب",
"how_additional_inputs_as_variables": "كيفية استخدام مدخلات إضافية كمتغيرات",
"format": "التنسيق",
"uppercase_for_letters": "استخدام الحروف الكبيرة لجميع الأحرف",
"replace_whitespaces_underscores": "استبدال المساحات البيضاء بالشرطات السفلية",
"manage_billing": "إدارة الفواتير",
"manage_billing_description": "إدارة كل الأشياء المفوترة",
"billing_freeplan_title": "أنت حاليًا على الخطة المجانية",
"billing_freeplan_description": "نحن نعمل بشكل أفضل ضمن فرق. وسّع سير عملك مع أحداث الذهاب والإياب والأحداث الجماعية و صنع نماذج مسارات متقدمة",
"billing_freeplan_cta": "جرب الآن",
"billing_manage_details_title": "عرض تفاصيل الفوترة وإدارتها",
"billing_manage_details_description": "اعرض تفاصيل الفوترة وعدّلها، بالإضافة إلى إمكانية إلغاء الاشتراك.",
"billing_portal": "بوابة الفواتير",
"billing_help_title": "هل تحتاج إلى أي شيء آخر؟",
"billing_help_description": "إذا كنت تحتاج إلى أي مساعدة إضافية بخصوص الفوترة، فإن فريق الدعم لدينا في انتظارك لتقديم المساعدة.",
"billing_help_cta": "الاتصال بالدعم",
"ignore_special_characters": "تجاهل الأحرف الخاصة في تسمية الإدخال الإضافية لديك. استخدم فقط الأحرف والأرقام",
"retry": "إعادة المحاولة",
"fetching_calendars_error": "حدثت مشكلة أثناء حفظ بياناتك. الرجاء <1>المحاولة من جديد</1> أو التواصل مع دعم العملاء.",
"calendar_connection_fail": "فشل الاتصال بالتقويم",
"booking_confirmation_success": "تم تأكيد الحجز بنجاح",
"booking_confirmation_fail": "فشل تأكيد الحجز",
"we_wont_show_again": "لن نعرض هذا مرة أخرى",
"couldnt_update_timezone": "لم نتمكن من تحديث المنطقة الزمنية",
"updated_timezone_to": "تم تحديث المنطقة الزمنية إلى {{formattedCurrentTz}}",
"update_timezone": "تحديث المنطقة الزمنية",
"update_timezone_question": "تحديث المنطقة الزمنية؟",
"update_timezone_description": "يبدو أن منطقتك الزمنية المحلية قد تغيرت إلى {{formattedCurrentTz}}. من المهم جداً أن يكون لديك المنطقة الزمنية المناسبة لمنع الحجز في أوقات غير مرغوب فيها. هل تريد تحديثها؟",
"dont_update": "لا تقم بالتحديث"
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Přesměrovat při rezervaci ",
"you_are_being_redirected": "Budete přesměrováni na {{ url }} za $t(s, {\"count\": {{s}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Pokud chcete použít tuto funkci, musíte provést aktualizaci na účet Pro.",
"duplicate": "Duplikovat",
"offer_seats": "Nabídka míst",
"offer_seats_description": "Nabídnout místa rezervacím (tím se vypnou rezervace hostů a s potvrzením)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Před změnou e-mailové adresy potvrďte své heslo",
"seats": "míst",
"every_app_published": "Každá aplikace zveřejněná v obchodě s aplikacemi na Cal.com má otevřený zdrojový kód a je důkladně testována prostřednictvím vzájemného hodnocení. Společnost Cal.com, Inc. nicméně tyto aplikace neschvaluje ani necertifikuje, pokud nejsou zveřejněny přímo společností Cal.com. Pokud se setkáte s nevhodným obsahem nebo chováním, nahlaste to.",
"report_app": "Nahlásit aplikaci"
"report_app": "Nahlásit aplikaci",
"billing_manage_details_title": "Zobrazit a spravovat fakturační údaje",
"billing_manage_details_description": "Zobrazit a upravovat fakturační údaje, možnost zrušit předplatné.",
"billing_help_title": "Potřebujete ještě něco?",
"billing_help_description": "Pokud potřebujete další pomoc s fakturací, náš tým podpory je zde, aby vám pomohl."
}
+13 -10
View File
@@ -188,7 +188,7 @@
"secret": "Passwort",
"leave_blank_to_remove_secret": "Leer lassen, um Passwort zu entfernen",
"webhook_secret_key_description": "Stellen Sie sicher, dass Ihr Server aus Sicherheitsgründen nur die erwarteten Cal.com-Anfragen empfängt",
"secret_meeting": "Geheimes Termin",
"secret_meeting": "Geheimer Termin",
"login_instead": "Stattdessen anmelden",
"already_have_an_account": "Haben Sie bereits ein Konto?",
"create_account": "Konto erstellen",
@@ -244,7 +244,7 @@
"where": "Wo",
"add_to_calendar": "Zum Kalender hinzufügen",
"add_another_calendar": "Einen weiteren Kalender hinzufügen",
"other": "Anderes",
"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_any_other_attendees": "Wir haben Ihnen und allen anderen Teilnehmer diesen Informationen per E-Mail geschickt.",
@@ -649,7 +649,7 @@
"event_name_tooltip": "Der Name, der in Kalendern angezeigt wird",
"meeting_with_user": "Meeting mit {ATTENDEE}",
"additional_inputs": "Zusätzliche Notizen",
"additional_input_description": "Erfordert die Eingabe zusätzlicher Eingaben durch den Zeitplaner vor der Buchungsbestätigung",
"additional_input_description": "Erfordert die Eingabe zusätzlicher Eingaben durch den Planer vor der Buchungsbestätigung",
"label": "Bezeichnung",
"placeholder": "Placeholder",
"type": "Typ",
@@ -837,7 +837,6 @@
"redirect_success_booking": "Weiterleitung bei Buchung ",
"you_are_being_redirected": "Sie werden in $t(second, {\"count\": {{seconds}} }) auf {{ url }} weitergeleitet.",
"external_redirect_url": "Externe Umleitungs-URL - Beginnt mit https://",
"redirect_url_upgrade_description": "Um diese Funktion nutzen zu können, müssen Sie ein Upgrade auf einen Pro-Account durchführen.",
"duplicate": "Duplizieren",
"offer_seats": "Plätze anbieten",
"offer_seats_description": "Bieten Sie Plätze für Buchungen an (dies deaktiviert Gäste & Opt-in-Buchungen)",
@@ -1082,10 +1081,10 @@
"using_additional_inputs_as_variables": "Wie kann ich zusätzliche Eingaben als Variablen verwenden?",
"download_desktop_app": "Desktop-App herunterladen",
"set_ping_link": "Ping-Link festlegen",
"rate_limit_exceeded": "Bewertungslimit überschritten",
"rate_limit_exceeded": "Anmeldeversuchsgrenze überschritten",
"when_something_happens": "Wenn etwas passiert",
"action_is_performed": "Eine Aktion wird ausgeführt",
"test_action": "Test-Aktion",
"test_action": "Aktion testen",
"notification_sent": "Benachrichtigung gesendet",
"no_input": "Kein Input",
"test_workflow_action": "Workflow-Aktion testen",
@@ -1142,7 +1141,7 @@
"two_factor_auth": "Zwei-Faktor-Authentifizierung",
"recurring_event_tab_description": "Einen wiederholenden Zeitplan einrichten",
"today": "heute",
"appearance": "Aussehen",
"appearance": "Darstellung",
"appearance_subtitle": "Einstellungen für Ihre Buchungsdarstellung verwalten",
"my_account": "Mein Account",
"general": "Allgemein",
@@ -1181,7 +1180,7 @@
"location_info": "Der Ort des Events",
"organizer_name_info": "Ihr Name",
"additional_notes_info": "Die zusätzlichen Anmerkungen der Buchung",
"attendee_name_info": "Der Name der Buchenden",
"attendee_name_info": "Name der buchenden Person",
"to": "An",
"attendee_required_enter_number": "Hier muss der Teilnehmer bei der Buchung eine Telefonnummer eingeben",
"workflow_turned_on_successfully": "{{workflowName}} Workflow erfolgreich {{offOn}}geschaltet",
@@ -1213,7 +1212,7 @@
"problem_saving_user_profile": "Beim Speichern Ihrer Daten ist ein Problem aufgetreten. Bitte versuchen Sie es erneut oder wenden Sie sich an den Support.",
"purchase_missing_seats": "Fehlende Plätze kaufen",
"slot_length": "Zeitfensterlänge",
"booking_appearance": "Buchungsdarstellung",
"booking_appearance": "Darstellung der Buchungsseite",
"appearance_team_description": "Einstellungen für die Buchungsdarstellung Ihres Teams verwalten",
"only_owner_change": "Nur der Besitzer dieses Teams kann Änderungen an den Buchungen des Teams vornehmen ",
"team_disable_cal_branding_description": "Entfernt alle Cal-bezogenen Markenzeichen, z.B. 'Powered by Cal.'",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Bitte bestätigen Sie Ihr Passwort, bevor Sie Ihre E-Mail-Adresse ändern",
"seats": "Plätze",
"every_app_published": "Jede App, die im Cal.com App Store veröffentlicht wird, ist Open Source und gründlich durch Peer Reviews getestet. Cal.com, Inc. unterstützt oder zertifiziert diese Apps jedoch nicht, es sei denn, sie werden von Cal.com veröffentlicht. Wenn Sie auf unangemessene Inhalte oder Verhaltensweisen stoßen, melden Sie diese bitte.",
"report_app": "App melden"
"report_app": "App melden",
"billing_manage_details_title": "Ihre Rechnungsdetails ansehen und verwalten",
"billing_manage_details_description": "Rechnungsdaten ansehen, bearbeiten oder Abonnement kündigen.",
"billing_help_title": "Brauchen Sie etwas anderes?",
"billing_help_description": "Wenn Sie weitere Hilfe bei der Rechnungsstellung benötigen, hilft Ihnen unser Support-Team gerne weiter."
}
+24 -2
View File
@@ -313,6 +313,7 @@
"past_bookings": "Your past bookings will show up here.",
"cancelled_bookings": "Your cancelled bookings will show up here.",
"unconfirmed_bookings": "Your unconfirmed bookings will show up here.",
"unconfirmed_bookings_tooltip": "Unconfirmed bookings",
"on": "on",
"and": "and",
"calendar_shows_busy_between": "Your calendar shows you as busy between",
@@ -837,7 +838,7 @@
"redirect_success_booking": "Redirect on booking ",
"you_are_being_redirected": "You are being redirected to {{ url }} in $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.",
"redirect_url_description": "Redirect to a custom URL after a successful booking",
"duplicate": "Duplicate",
"offer_seats": "Offer seats",
"offer_seats_description": "Offer seats for booking. This automatically disables guest & opt-in bookings.",
@@ -1272,10 +1273,31 @@
"format": "Format",
"uppercase_for_letters": "Use uppercase for all letters",
"replace_whitespaces_underscores": "Replace whitespaces with underscores",
"manage_billing": "Manage billing",
"manage_billing_description": "Manage all things billing",
"billing_freeplan_title": "You're currently on the FREE plan",
"billing_freeplan_description": "We work better in teams. Extend your workflows with round-robin and collective events and make advanced routing forms",
"billing_freeplan_cta": "Try now",
"billing_manage_details_title": "View and manage your billing details",
"billing_manage_details_description": "View and edit your billing details, as well as cancel your subscription.",
"billing_portal": "Billing portal",
"billing_help_title": "Need anything else?",
"billing_help_description": "If you need any further help with billing, our support team are here to help.",
"billing_help_cta": "Contact support",
"ignore_special_characters": "Ignore special characters in your Additional Input label. Use only letters and numbers",
"retry": "Retry",
"fetching_calendars_error": "There was a problem fetching your calendars. Please <1>try again</1> or reach out to customer support.",
"calendar_connection_fail": "Calendar connection failed",
"booking_confirmation_success": "Booking confirmation succeeded",
"booking_confirmation_fail": "Booking confirmation failed"
"booking_confirmation_fail": "Booking confirmation failed",
"we_wont_show_again": "We won't show this again",
"couldnt_update_timezone": "We couldn't update the timezone",
"updated_timezone_to": "Updated timezone to {{formattedCurrentTz}}",
"update_timezone": "Update timezone",
"update_timezone_question": "Update Timezone?",
"update_timezone_description": "It seems like your local timezone has changed to {{formattedCurrentTz}}. It's very important to have the correct timezone to prevent bookings at undesired times. Do you want to update it?",
"dont_update": "Don't update",
"email_address_action": "send email to a specific email address",
"after_event_trigger": "after event ends",
"how_long_after": "How long after event ends?"
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Redirigir al reservar ",
"you_are_being_redirected": "Serás redirigido a {{ url }} en $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Para poder usar esta función, necesita actualizarse a una cuenta Pro.",
"duplicate": "Duplicar",
"offer_seats": "Ofrecer plazas",
"offer_seats_description": "Ofrecer asientos para las reservas (desactiva las reservas de invitados y lsa optativas)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Confirme su contraseña antes de cambiar su dirección de correo electrónico",
"seats": "asientos",
"every_app_published": "Las aplicaciones publicadas en la tienda de aplicaciones de Cal.com son de código abierto y se han sometido a pruebas exhaustivas con las reseñas de pares. Sin embargo, Cal.com, Inc. no avala ni certifica estas aplicaciones a menos que Cal.com las haya publicado expresamente. Si encuentra contenido o comportamiento inapropiados, informe de ello.",
"report_app": "Denunciar aplicación"
"report_app": "Denunciar aplicación",
"billing_manage_details_title": "Ver y administrar sus datos de facturación",
"billing_manage_details_description": "Vea y edite sus datos de facturación, así como cancele su suscripción.",
"billing_help_title": "¿Necesita algo más?",
"billing_help_description": "Si necesita más ayuda con la facturación, nuestro equipo de soporte está aquí para ayudarlo."
}
+12 -5
View File
@@ -611,7 +611,7 @@
"create_first_team_and_invite_others": "Créez votre première équipe et invitez d'autres utilisateurs à travailler avec vous.",
"create_team_to_get_started": "Créer une équipe pour commencer",
"teams": "Équipes",
"team": "Equipe",
"team": "Équipe",
"team_billing": "Facturation d'équipe",
"upgrade_to_flexible_pro_title": "Nous avons modifié la facturation pour les équipes",
"upgrade_to_flexible_pro_message": "Des membres dans votre équipe n'ont pas de place. Mettez à niveau votre offre pro pour couvrir les places manquantes.",
@@ -707,7 +707,7 @@
"delete_account_confirmation_message": "Êtes-vous sûr de vouloir supprimer votre compte Cal.com ? Toute personne avec qui vous avez partagé le lien de votre compte ne pourra plus réserver en utilisant ce lien et toutes les préférences que vous avez enregistrées seront perdues.",
"integrations": "Intégrations",
"apps": "Applications",
"category_apps": "{{category}} applications",
"category_apps": "Applications {{category}}",
"app_store": "App Store",
"app_store_description": "Connecter les personnes, la technologie et l'espace de travail.",
"settings": "Paramètres",
@@ -837,7 +837,6 @@
"redirect_success_booking": "Rediriger vers la réservation ",
"you_are_being_redirected": "Vous êtes redirigé vers {{ url }} dans $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://exemple.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Pour pouvoir utiliser cette fonctionnalité, vous devez passer à un compte Pro.",
"duplicate": "Dupliquer",
"offer_seats": "Offrir des places",
"offer_seats_description": "Offrez des places pour réservations (cela désactive les invités et active les réservations)",
@@ -1213,7 +1212,7 @@
"problem_saving_user_profile": "Un problème est survenu lors de l'enregistrement de vos données. Veuillez réessayer ou contacter le service client.",
"purchase_missing_seats": "Acheter des places manquantes",
"slot_length": "Durée du créneau",
"booking_appearance": "Apparence de réservation",
"booking_appearance": "Apparence de page de réservation",
"appearance_team_description": "Gérer les paramètres pour l'apparence de réservation de votre équipe",
"only_owner_change": "Seul le propriétaire de cette équipe peut apporter des modifications à la réservation de l'équipe ",
"team_disable_cal_branding_description": "Supprime les marques associées à Cal, c'est-à-dire \"Powered by Cal\"",
@@ -1237,6 +1236,7 @@
"saml_config": "Configuration SAML",
"add_webhook_description": "Recevoir les données de la réunion en temps réel lorsque quelque chose se passe sur Cal.com",
"triggers_when": "Se déclenche quand",
"test_webhook": "Veuillez faire un test de ping avant de créer.",
"enable_webhook": "Activer le Webhook",
"add_webhook": "Ajouter un Webhook",
"webhook_edited_successfully": "Webhook enregistré",
@@ -1252,14 +1252,21 @@
"create_first_api_key_description": "Les clés API permettent à d'autres applications de communiquer avec Cal.com",
"back_to_signin": "Retour à la connexion",
"reset_link_sent": "Lien de réinitialisation envoyé",
"password_reset_email": "Un e-mail est transmis vers {{email}} avec les instructions pour réinitialiser votre mot de passe.",
"password_updated": "Mot de passe mis à jour !",
"pending_payment": "Paiement en attente",
"confirmation_page_rainbow": "Le jeton porte votre événement avec des jetons ou des NFT sur Ethereum, Polygone et plus encore.",
"not_on_cal": "Pas sur Cal.com",
"no_calendar_installed": "Aucun calendrier installé",
"no_calendar_installed_description": "Vous n'avez pas encore connecté vos calendriers",
"add_a_calendar": "Ajouter au calendrier",
"change_email_hint": "Vous devrez peut-être vous déconnecter et vous reconnecter pour que le changement prenne effet",
"confirm_password_change_email": "Veuillez confirmer votre mot de passe avant de changer votre adresse e-mail",
"seats": "places",
"every_app_published": "Chaque application publiée sur l'App Store de Cal.com est open source et soigneusement testée via les évaluations par les pairs. Néanmoins, Cal.com, Inc. n'approuve pas ou ne certifie pas ces applications à moins qu'elles ne soient publiées par Cal.com. Si vous rencontrez un contenu ou un comportement inapproprié, veuillez le signaler.",
"report_app": "Signaler une application"
"report_app": "Signaler une application",
"billing_manage_details_title": "Affichez et gérez vos informations de facturation",
"billing_manage_details_description": "Affichez et modifiez vos informations de facturation, et annuler votre abonnement.",
"billing_help_title": "Besoin d'autre chose ?",
"billing_help_description": "Si vous avez besoin d'aide pour la facturation, notre équipe d'assistance est là pour vous aider."
}
+236 -2
View File
@@ -142,6 +142,7 @@
"view_public_page": "צפייה בדף הציבורי",
"sign_out": "יציאה",
"add_another": "הוסף לוח זמנים נוסף",
"install_another": "התקנת אפליקציה נוספת",
"until": "עד",
"powered_by": "מופעל על ידי",
"unavailable": "לא זמין",
@@ -156,6 +157,7 @@
"checkbox": "תיבת סימון",
"is_required": "נדרש",
"required": "נדרש",
"optional": "אופציונלי",
"input_type": "סוג הקלט",
"rejected": "נדחה",
"unconfirmed": "לא אושר",
@@ -226,7 +228,9 @@
"connect_your_calendar_instructions": "קשר את לוח השנה שלך לצפייה בזמנים עמוסים ובאירועים חדשים באופן אוטומטי ברגע שנקבע מועד עבורם.",
"set_up_later": "להגדיר מאוחר יותר",
"current_time": "השעה הנוכחית",
"details": "פרטים",
"welcome": "ברוך הבא",
"welcome_back": "ברוך שובך",
"welcome_to_calcom": "ברוך הבא אל Cal.com",
"welcome_instructions": "אמור לנו באיזה שם ברצונך שנקרא לך ובאיזה אזור זמן אתה נמצא. ניתן יהיה לערוך את הפרטים האלה מאוחר יותר.",
"connect_caldav": "חיבור לשרת CalDav",
@@ -239,6 +243,7 @@
"when": "מתי",
"where": "היכן",
"add_to_calendar": "הוספה ללוח השנה",
"add_another_calendar": "הוספת לוח שנה אחר",
"other": "אחר",
"emailed_you_and_attendees": "שלחנו לך ולמשתתפים האחרים הודעת דוא\"ל עם הזמנה בלוח השנה הכוללת את כל הפרטים.",
"emailed_you_and_attendees_recurring": "שלחנו לך ולמשתתפים האחרים הודעת דוא\"ל עם הזמנה בלוח השנה לאירוע הראשון מבין האירועים החוזרים האלה.",
@@ -260,6 +265,8 @@
"enter_new_password": "נא להזין את הסיסמה החדשה שבה ברצונך להשתמש עבור החשבון.",
"reset_password": "איפוס סיסמה",
"change_your_password": "שינוי סיסמה",
"show_password": "הצגת הסיסמה",
"hide_password": "הסתרת הסיסמה",
"try_again": "לנסות שוב",
"request_is_expired": "תוקף הבקשה פג.",
"reset_instructions": "הזן את כתובת הדוא\"ל המשויכת לחשבון שלך, ואנו נשלח לך קישור לאיפוס הסיסמה.",
@@ -305,6 +312,7 @@
"recurring_bookings": "ברגע שמישהו יקבע איתך פגישה חוזרת, המועד שנקבע יוצג כאן.",
"past_bookings": "ההזמנות הקודמות שלך יוצגו כאן.",
"cancelled_bookings": "ההזמנות המבוטלות שלך יוצגו כאן.",
"unconfirmed_bookings": "ההזמנות שלך שלא אושרו יוצגו כאן.",
"on": "בתאריך",
"and": "וגם",
"calendar_shows_busy_between": "בלוח השנה מוצג כי אתה עסוק בין",
@@ -345,6 +353,7 @@
"booking_cancelled": "ההזמנה בוטלה",
"booking_rescheduled": "מועד ההזמנה השתנה",
"booking_created": "ההזמנה נוצרה",
"meeting_ended": "הפגישה הסתיימה",
"form_submitted": "הטופס נשלח",
"event_triggers": "גורמים מפעילים של אירועים",
"subscriber_url": "כתובת URL של המנוי/ה",
@@ -487,6 +496,9 @@
"lets_create_first_administrator_user": "עכשיו ניצור את המשתמש הראשון המוגדר כמנהל מערכת.",
"new_member": "חבר/ה חדש/ה",
"invite": "להזמין",
"add_team_members": "הוספת חברי צוות",
"add_team_members_description": "להזמין עוד אנשים להצטרף לצוות",
"add_team_member": "הוספת חבר צוות",
"invite_new_member": "להזמין חבר/ה חדש/ה",
"invite_new_team_member": "להזמין מישהו לצוות שלך.",
"change_member_role": "לשנות את תפקיד החבר/ה בצוות",
@@ -599,6 +611,7 @@
"create_first_team_and_invite_others": "צור את הצוות הראשון שלך והזמן משתמשים אחרים לעבוד יחד איתך.",
"create_team_to_get_started": "צור צוות כדי להתחיל",
"teams": "צוותים",
"team": "צוות",
"team_billing": "חיוב צוותים",
"upgrade_to_flexible_pro_title": "שינינו את החיוב בעבור צוותים",
"upgrade_to_flexible_pro_message": "יש בצוות שלך חברים שאין להם מקום. שדרג לחבילת Pro לדאוג למקומות החסרים.",
@@ -619,7 +632,9 @@
"billing": "חיוב",
"manage_your_billing_info": "נהל את פרטי החיוב ובטל את המינוי.",
"availability": "זמינות",
"edit_availability": "עריכת נתוני זמינות",
"configure_availability": "הגדר מועדים שבהם תהיה זמין להזמנות.",
"copy_times_to": "העתקת שעות אל",
"change_weekly_schedule": "שינוי לוח הזמנים השבועי שלך",
"logo": "לוגו",
"error": "שגיאה",
@@ -634,6 +649,7 @@
"event_name_tooltip": "השם שיופיע בלוחות השנה",
"meeting_with_user": "פגישה עם {ATTENDEE}",
"additional_inputs": "אפשרויות קלט נוספות",
"additional_input_description": "לדרוש מכלי התזמון להוסיף פרטים לפני אישור ההזמנה",
"label": "תווית",
"placeholder": "מציין מיקום",
"type": "סוג",
@@ -648,6 +664,8 @@
"disable_guests": "השבתת אורחים",
"disable_guests_description": "השבת את האפשרות להוסיף אורחים נוספים בעת ביצוע הזמנה.",
"private_link": "יצירת קישור פרטי",
"private_link_label": "קישור פרטי",
"private_link_hint": "הקישור הפרטי שלך ייווצר מחדש לאחר כל שימוש",
"copy_private_link": "העתקת קישור פרטי",
"private_link_description": "ניתן ליצור כתובת URL פרטית על מנת לשתף את האירוע מבלי לחשוף את שם המשתמש/ת שלך ב-Cal",
"invitees_can_schedule": "המוזמנים יכולים לקבוע מועד",
@@ -689,16 +707,21 @@
"delete_account_confirmation_message": "בטוח שברצונך למחוק את חשבון Cal.com שלך? כל מי ששיתפת איתו את הקישור לחשבון לא יוכל יותר להזמין באמצעותו, וכל ההעדפות ששמרת יאבדו.",
"integrations": "שילובים",
"apps": "אפליקציות",
"category_apps": "אפליקציות בקטגוריה {{category}}",
"app_store": "App Store",
"app_store_description": "מקשרים בין אנשים, טכנולוגיה ומקום העבודה.",
"settings": "הגדרות",
"event_type_moved_successfully": "סוג האירוע הועבר בהצלחה",
"next_step_text": "השלב הבא",
"next_step": "לדלג על שלב זה",
"prev_step": "לשלב הקודם",
"install": "התקנה",
"installed": "מותקן",
"active_install_one": "התקנה פעילה {{count}}",
"active_install_other": "{{count}} התקנות פעילות",
"globally_install": "התקנה גלובלית",
"app_successfully_installed": "האפליקציה הותקנה בהצלחה",
"app_could_not_be_installed": "לא ניתן היה להתקין את האפליקציה",
"disconnect": "התנתקות",
"embed_your_calendar": "הטמע את לוח השנה שלך בתוך דף האינטרנט",
"connect_your_favourite_apps": "חבר את האפליקציות האהובות עליך.",
@@ -709,6 +732,7 @@
"connect_additional_calendar": "חיבור לוח שנה נוסף",
"conferencing": "שיחות ועידה",
"calendar": "לוח שנה",
"payments": "תשלומים",
"not_installed": "לא מותקן",
"error_password_mismatch": "הסיסמאות אינן זהות.",
"error_required_field": "שדה זה הוא שדה חובה.",
@@ -742,9 +766,22 @@
"import_from": "ייבוא מ",
"access_token": "טוקן גישה",
"visit_roadmap": "מפת דרכים",
"featured_categories": "קטגוריות מוצגות",
"popular_categories": "קטגוריות פופולריות",
"number_apps_one": "אפליקציה {{count}}",
"number_apps_other": "{{count}} אפליקציות",
"trending_apps": "אפליקציות פופולריות",
"explore_apps": "אפליקציות בקטגוריה {{category}}",
"installed_apps": "אפליקציות מותקנות",
"no_category_apps": "אין אפליקציות בקטגוריה {{category}}",
"no_category_apps_description_calendar": "הוסף/י אפליקציית לוח שנה כדי לבדוק אם יש התנגשויות על מנת למנוע כפל הזמנות",
"no_category_apps_description_conferencing": "נסה/י להוסיף אפליקציית שיחות ועידה כדי לשלב שיחות וידאו עם הלקוחות",
"no_category_apps_description_payment": "הוסף/י אפליקציית תשלומים על מנת להפוך את ביצוע העסקאות בינך לבין הלקוחות לפשוט יותר",
"no_category_apps_description_other": "הוסף/י כל סוג אחר של אפליקציה לביצוע פעולות שונות",
"installed_app_calendar_description": "הגדר/י את לוח(ות) השנה לבדוק אם יש התנגשויות על מנת למנוע כפל הזמנות.",
"installed_app_conferencing_description": "הוסף/י את האפליקציות האהובות עליך לשיחות ועידה לשימוש בפגישות שלך",
"installed_app_payment_description": "הגדר/י את השירותים לעיבוד תשלומים שבהם יש להשתמש לחיוב הלקוחות.",
"installed_app_other_description": "כל האפליקציות המותקנות שלך מקטגוריות אחרות.",
"empty_installed_apps_headline": "אין אפליקציות מותקנות",
"empty_installed_apps_description": "אפליקציות מאפשרות לך לשפר משמעותית את תהליכי העבודה שלך ואת תהליך קביעת המועדים.",
"empty_installed_apps_button": "נא לעיין ב-App Store",
@@ -765,6 +802,7 @@
"remove": "הסר",
"add": "הוסף",
"installed_one": "מותקן",
"installed_other": "{{count}} הותקנו",
"verify_wallet": "אימות הארנק",
"connect_metamask": "קישור Metamask",
"create_events_on": "יצירת אירועים ב:",
@@ -782,6 +820,8 @@
"set_to_default": "הגדר לברירת המחדל",
"new_schedule_btn": "תזמון חדש",
"add_new_schedule": "הוסף מועד חדש",
"add_new_calendar": "הוספת לוח שנה חדש",
"set_calendar": "הגדר/י למתי יש להוסיף אירועים חדשים כשאת/ה כבר עסוק/ה.",
"delete_schedule": "מחק תזמון",
"schedule_created_successfully": "יצירת התזמון {{scheduleName}} בוצעה בהצלחה",
"availability_updated_successfully": "עדכון התזמון {{scheduleName}} בוצע בהצלחה",
@@ -797,7 +837,6 @@
"redirect_success_booking": "הפניה מחדש בעת הזמנה ",
"you_are_being_redirected": "אנחנו ננתב אותך אל {{ url }} בעוד $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "כדי להשתמש בתכונה זו, עליך לשדרג לחשבון Pro.",
"duplicate": "כפילות",
"offer_seats": "הצעת מקומות",
"offer_seats_description": "הצעת מקומות להזמנות (אפשרות זו משביתה אישורי הזמנות ואורחים)",
@@ -935,6 +974,7 @@
"workflows": "תהליכי עבודה",
"new_workflow_btn": "תהליך עבודה חדש",
"add_new_workflow": "הוספת תהליך עבודה חדש",
"reschedule_event_trigger": "כאשר נקבע מועד חדש לאירוע",
"trigger": "גורם מפעיל",
"triggers": "גורמים מפעילים",
"action": "פעולה",
@@ -962,10 +1002,13 @@
"change_username_standard_to_premium": "מאחר שאתה משנה את שם המשתמש מסטנדרטי לפרימיום, תועבר אל הקופה לצורך שדרוג.",
"change_username_premium_to_standard": "מאחר שאתה משנה את שם המשתמש מפרימיום לסטנדרטי, תועבר אל הקופה לצורך שנמוך.",
"go_to_stripe_billing": "למעבר אל דף החיוב",
"stripe_description": "דרישת תשלום עבור הזמנות (0.5% + €0.10 עמלה לכל עסקה)",
"trial_expired": "התוקף של גרסת הניסיון שלך פג",
"remove_app": "הסרת האפליקציה",
"yes_remove_app": "כן, להסיר את האפליקציה",
"are_you_sure_you_want_to_remove_this_app": "בטוח שברצונך להסיר את האפליקציה הזו?",
"app_removed_successfully": "הסרת האפליקציה בוצעה בהצלחה",
"error_removing_app": "שגיאה בהסרת האפליקציה",
"web_conference": "שיחת ועידה מקוונת",
"number_for_sms_reminders": "מספר טלפון (לתזכורות ב-SMS)",
"requires_confirmation": "נדרש אישור",
@@ -1021,18 +1064,209 @@
"navigate": "ניווט",
"open": "פתיחה",
"close": "סגירה",
"team_feature_teams": "זוהי תכונה של גרסת Team. כדאי לשדרג ל-Team כדי לראות את זמינות חברי הצוות.",
"team_feature_workflows": "זוהי תכונה של גרסת Team. כדאי לשדרג ל-Team כדי להפוך את התזכורות והעדכונים לגבי אירועים לאוטומטיים באמצעות תהליכי עבודה.",
"show_eventtype_on_profile": "הצג בפרופיל",
"embed": "הטמעה",
"new_username": "שם משתמש חדש",
"current_username": "שם משתמש נוכחי",
"example_1": "דוגמה 1",
"example_2": "דוגמה 2",
"additional_input_label": "תווית קלט נוספת",
"company_size": "גודל החברה",
"what_help_needed": "במה דרושה לך עזרה?",
"variable_format": "הפורמט של המשתנה",
"webhook_subscriber_url_reserved": "כתובת ה-URL של מנוי/מנויית Webhook כבר מוגדרת",
"custom_input_as_variable_info": "התעלמות מכל התווים המיוחדים של תווית הקלט הנוספת (שימוש באותיות ובספרות בלבד), שימוש באותיות רישיות עבור כל האותיות והחלפת רווחים במקפים תחתונים.",
"using_additional_inputs_as_variables": "איך ניתן להשתמש בסוגי קלט נוספים כמשתנים?",
"download_desktop_app": "הורדת האפליקציה למחשבים שולחניים",
"set_ping_link": "הגדרת קישור Ping",
"rate_limit_exceeded": "חריגה מההגבלה",
"when_something_happens": "כאשר משהו קורה",
"action_is_performed": "פעולה מבוצעת",
"test_action": "בדיקת פעולה",
"notification_sent": "נשלח עדכון",
"no_input": "אין קלט",
"test_workflow_action": "בדיקת פעולת תהליך עבודה",
"send_sms": "שליחת SMS",
"send_sms_to_number": "בטוח שברצונך לשלוח SMS למספר {{number}}?",
"missing_connected_calendar": "אף לוח שנה המוגדר כברירת מחדל לא מחובר",
"connect_your_calendar_and_link": "ניתן לחבר את לוח השנה שלך מ<1>כאן</1>.",
"default_calendar_selected": "לוח השנה המוגדר כברירת מחדל",
"hide_from_profile": "להסתיר מהפרופיל",
"event_setup_tab_title": "הגדרת האירוע",
"event_limit_tab_title": "מגבלות",
"event_limit_tab_description": "באיזו תדירות ניתן להזמין אותך",
"event_advanced_tab_description": "הגדרות לוח שנה ועוד...",
"event_advanced_tab_title": "מתקדם",
"select_which_cal": "בחר/י את לוח השנה שאליו יש להוסיף הזמנות",
"custom_event_name": "שם אירוע מותאם אישית",
"custom_event_name_description": "ניתן ליצור שמות אירועים בהתאמה אישית שיוצגו באירוע בלוח השנה",
"2fa_required": "נדרש אימות בשני גורמים",
"incorrect_2fa": "קוד שגוי של אימות בשני גורמים",
"which_event_type_apply": "על איזה סוג אירוע זה יחול?",
"no_workflows_description": "תהליכי עבודה מאפשרים שימוש באוטומציה פשוטה לשליחת עדכונים ותזכורות, כך שאת/ה יכול/ה לתכנן תהליכים שקשורים לאירועים שלך.",
"create_workflow": "יצירת תהליך עבודה",
"do_this": "יש לבצע את הפעולה הזו",
"turn_off": "השבתה",
"settings_updated_successfully": "עדכון ההגדרות בוצע בהצלחה",
"error_updating_settings": "שגיאה במהלך עדכון ההגדרות",
"personal_cal_url": "כתובת ה-URL של ה-Cal האישי שלי",
"bio_hint": "מספר משפטים אודותיך. המידע הזה יופיע בדף ה-URL האישי שלך.",
"delete_account_modal_title": "מחיקת חשבון",
"confirm_delete_account_modal": "בטוח שברצונך למחוק את חשבון Cal.com שלך?",
"delete_my_account": "מחיקת החשבון שלי",
"start_of_week": "תחילת השבוע",
"select_calendars": "בחר/י את לוחות השנה שבהם ברצונך לבדוק אם יש התנגשויות כדי למנוע כפל הזמנות.",
"check_for_conflicts": "בדיקת התנגשויות",
"adding_events_to": "הוספת אירועים ל",
"follow_system_preferences": "פעל לפי העדפות המערכת",
"custom_brand_colors": "צבעי מותג בהתאמה אישית",
"customize_your_brand_colors": "בצע/י התאמה אישית של דף ההזמנות שלך עם צבעי מותג משלך.",
"pro": "Pro",
"removes_cal_branding": "הסרת מיתוגים הקשורים ל-Cal, כלומר 'מופעל על ידי Cal'.",
"profile_picture": "תמונת פרופיל",
"upload": "העלאה",
"web3": "Web3",
"rainbow_token_gated": "סוג אירוע זה מוגן באמצעות טוקן.",
"rainbow_connect_wallet_gate": "קשר/י את הארנק שלך אם בבעלותך <1>{{name}}</1> ‏(<3>{{symbol}}</3>).",
"rainbow_insufficient_balance": "אין בארנק שלך מספיק <1>{{symbol}}</1>.",
"rainbow_sign_message_request": "חתום/י על בקשת ההודעה בארנק.",
"rainbow_signature_error": "שגיאה בעת בקשת חתימה מהארנק.",
"token_address": "כתובת הטוקן",
"blockchain": "בלוקצ'יין",
"old_password": "הסיסמה הקודמת",
"secure_password": "הסיסמה החדשה והסופר מאובטחת שלך",
"error_updating_password": "שגיאה בעת עדכון הסיסמה",
"two_factor_auth": "אימות בשני גורמים",
"recurring_event_tab_description": "הגדרת לוח זמנים חוזר",
"today": "היום",
"appearance": "מראה",
"appearance_subtitle": "ניהול ההגדרות של מראה ההזמנות שלך",
"my_account": "החשבון שלי",
"general": "כללי",
"calendars": "לוחות שנה",
"2fa_auth": "אימות בשני גורמים",
"invoices": "חשבוניות",
"embeds": "הטמעות",
"impersonation": "התחזות",
"users": "משתמשים",
"profile_description": "ניהול הגדרות עבור פרופיל Cal שלך",
"general_description": "ניהול הגדרות שפה ואזור זמן",
"calendars_description": "הגדרת האינטראקציה בין סוגי האירועים שלך לבין לוחות השנה שלך",
"appearance_description": "ניהול ההגדרות של מראה ההזמנות שלך",
"conferencing_description": "ניהול האפליקציות שלך לשיחות ועידה לשימוש בפגישות שלך",
"password_description": "ניהול הגדרות עבור סיסמאות החשבון שלך",
"2fa_description": "ניהול הגדרות עבור סיסמאות החשבון שלך",
"we_just_need_basic_info": "אנחנו זקוקים רק לכמה פרטים בסיסיים כדי להגדיר את הפרופיל שלך.",
"skip": "דלג",
"do_this_later": "לעשות זאת מאוחר יותר",
"set_availability_getting_started_subtitle_1": "הגדרת טווחי זמן שבהם את/ה זמין/ה",
"set_availability_getting_started_subtitle_2": "תוכל/י לבצע התאמה אישית של כל ההגדרות האלה מאוחר יותר בדף פרטי הזמינות.",
"connect_calendars_from_app_store": "ניתן להוסיף עוד לוחות שנה מה-App Store",
"connect_conference_apps": "חיבור אפליקציות של שיחות ועידה",
"connect_calendar_apps": "חיבור אפליקציות של לוחות שנה",
"connect_payment_apps": "חיבור אפליקציות של תשלומים",
"connect_other_apps": "חיבור אפליקציות אחרות",
"current_step_of_total": "שלב {{currentStep}} מתוך {{maxSteps}}",
"add_variable": "הוספת משתנה",
"custom_phone_number": "מספר טלפון בהתאמה אישית",
"message_template": "תבנית הודעות",
"email_subject": "נושא הדוא״ל",
"add_dynamic_variables": "הוספת משתני טקסט דינמיים",
"event_name_info": "שם סוג האירוע",
"event_date_info": "תאריך האירוע",
"event_time_info": "שעת ההתחלה של האירוע",
"location_info": "מיקום האירוע",
"organizer_name_info": "שמך",
"additional_notes_info": "הערות נוספות בהזמנה",
"attendee_name_info": "שם האדם שביצע את ההזמנה",
"to": "אל",
"attendee_required_enter_number": "דבר זה יחייב את המשתתף/ת להזין מספר טלפון בעת ההזמנה",
"workflow_turned_on_successfully": "תהליך העבודה {{workflowName}} הועבר למצב {{offOn}} בהצלחה",
"download_responses": "הורדת התגובות",
"create_your_first_form": "יצירת הטופס הראשון שלך",
"create_your_first_form_description": "באמצעות טפסי ניתוב ניתן לשאול שאלות סיווג ולנתב אל האדם או אל סוג האירוע המתאימים.",
"create_your_first_webhook": "יצירת ה-Webhook הראשון שלך",
"create_your_first_webhook_description": "עם רכיבי Webhook ניתן לקבל נתוני פגישה בזמן אמת כשמשהו קורה ב-Cal.com.",
"for_a_maximum_of": "למקסימום של",
"event_one": "אירוע",
"event_other": "אירועים",
"profile_team_description": "ניהול הגדרות עבור פרופיל הצוות",
"members_team_description": "משתמשים שמשתייכים לקבוצה",
"team_url": "כתובת ה-URL של הצוות",
"delete_team": "מחיקת צוות",
"team_members": "חברי הצוות",
"more": "עוד",
"more_page_footer": "אנחנו רואים באפליקציה לנייד הרחבה של האפליקציה האינטרנטית. אם עליך לבצע פעולות מורכבות, כדאי לחזור אל האפליקציה האינטרנטית.",
"workflow_example_1": "שליחת תזכורת ב-SMS למשתתף/ת 24 שעות לפני תחילת האירוע",
"workflow_example_2": "שליחת SMS מותאם אישית למשתתף/ת כאשר המועד של אירוע משתנה",
"workflow_example_3": "שליחת דוא\"ל מותאם אישית למארח/ת כאשר אירוע חדש מוזמן",
"workflow_example_4": "שליחת תזכורת בדוא\"ל למשתתף/ת שעה לפני תחילת אירועים",
"workflow_example_5": "שליחת דוא\"ל מותאם אישית למארח/ת כאשר המועד של אירוע משתנה",
"workflow_example_6": "שליחת SMS מותאם אישית למארח/ת כאשר אירוע חדש מוזמן",
"welcome_to_cal_header": "ברוך בואך אל Cal.com!",
"edit_form_later_subtitle": "ניתן יהיה לערוך את זה מאוחר יותר.",
"connect_calendar_later": "אקשר את לוח השנה שלי מאוחר יותר",
"set_my_availability_later": "אגדיר את הזמינות שלי בהמשך",
"problem_saving_user_profile": "היתה בעיה בשמירת הנתונים שלך. נסה/י שוב או פנה/י לתמיכת לקוחות.",
"purchase_missing_seats": "חסרים מקומות ברכישה",
"slot_length": "אורך חלון הזמן",
"booking_appearance": "מראה ההזמנה",
"appearance_team_description": "ניהול ההגדרות של מראה הזמנות הצוות שלך",
"only_owner_change": "רק הבעלים של הצוות יכולים לבצע שינויים בהזמנה של הצוות ",
"team_disable_cal_branding_description": "הסרת מיתוגים הקשורים ל-Cal, כלומר 'מופעל על ידי Cal'",
"invited_by_team": "הוזמנת על ידי {{teamName}} להצטרף לצוות בתפקיד {{role}}",
"token_invalid_expired": "הטוקן לא חוקי או שתוקפו פג.",
"exchange_add": "התחבר/י אל Microsoft Exchange",
"exchange_authentication": "שיטת אימות",
"exchange_authentication_standard": "אימות בסיסי",
"exchange_authentication_ntlm": "אימות NTLM",
"exchange_compression": "דחיסת GZip",
"routing_forms_description": "כאן ניתן לראות את כל הטפסים והנתיבים שיצרת.",
"add_new_form": "הוספת טופס חדש",
"form_description": "צור/י טופס משלך כדי לנתב אל מזמין/ה",
"copy_link_to_form": "העתק את הקישור לטופס",
"theme": "ערכת נושא",
"theme_applies_note": "חל רק על הדפים של הזמנות ציבוריות",
"theme_light": "בהיר",
"theme_dark": "כהה"
"theme_dark": "כהה",
"theme_system": "ברירת המחדל של המערכת",
"add_a_team": "הוספת צוות",
"saml_config": "תצורת SAML",
"add_webhook_description": "קבלת נתוני פגישה בזמן אמת כשמשהו קורה ב-Cal.com",
"triggers_when": "מופעל כאשר",
"test_webhook": "יש לבצע בדיקת Ping לפני היצירה.",
"enable_webhook": "הפעלת Webhook",
"add_webhook": "הוספת Webhook",
"webhook_edited_successfully": "ה-Webhook נשמר",
"webhooks_description": "קבלת נתוני פגישה בזמן אמת כשמשהו קורה ב-Cal.com",
"api_keys_description": "צור/י מפתחות API לקבלת גישה לחשבון שלך",
"new_api_key": "מפתח API חדש",
"active": "פעיל",
"api_key_updated": "שם מפתח ה-API עודכן",
"api_key_update_failed": "שגיאה בעת עדכון השם של מפתח ה-API",
"embeds_title": "הטמעת iframe של HTML",
"embeds_description": "הטמעת כל סוגי האירועים באתר",
"create_first_api_key": "יצירת מפתח ה-API הראשון שלך",
"create_first_api_key_description": "מפתחות API מאפשרים לאפליקציות אחרות לתקשר עם Cal.com",
"back_to_signin": "בחזרה לדף הכניסה",
"reset_link_sent": "נשלח קישור לאיפוס",
"password_reset_email": "שלחנו לך הודעת דוא\"ל לכתובת {{email}} עם הוראות לאיפוס הסיסמה.",
"password_updated": "הסיסמה עודכנה!",
"pending_payment": "בהמתנה לתשלום",
"confirmation_page_rainbow": "הגן/י על האירוע שלך באמצעות טוקנים או אסימוני NFT ב-Ethereum, ב-Polygon ועוד.",
"not_on_cal": "לא ב-Cal.com",
"no_calendar_installed": "לא מותקן אף לוח שנה",
"no_calendar_installed_description": "לא קישרת אף אחד מלוחות השנה שלך",
"add_a_calendar": "הוספת לוח שנה",
"change_email_hint": "ייתכן שיהיה צורך לצאת ולהיכנס שוב כדי לראות שהשינויים נכנסו לתוקף",
"confirm_password_change_email": "יש לאשר את הסיסמה לפני שינוי כתובת הדוא\"ל",
"seats": "מקומות",
"every_app_published": "כל אפליקציה שמתפרסמת ב-App Store של Cal.com היא בעלת קוד מקור פתוח. כל אפליקציה כזו גם נבדקה בקפידה על ידי עמיתים. למרות זאת, Cal.com, Inc.‎ אינה מעודדת שימוש באף אפליקציה כזו ואינה מעניקה אישור לאף אפליקציה כזו, אלא אם מדובר באפליקציות שפורסמו על ידי Cal.com. אם נתקלת בהתנהגות או בתכנים בלתי הולמים, עליך לדווח על האפליקציה.",
"report_app": "דיווח על האפליקציה",
"billing_manage_details_title": "הצגה וניהול של פרטי החיוב",
"billing_manage_details_description": "כאן ניתן להציג ולנהל את פרטי החיוב, וכן לבטל את המינוי.",
"billing_help_title": "צריך משהו נוסף?",
"billing_help_description": "אם דרוש לך סיוע נוסף בענייני חיוב, צוות התמיכה שלנו כאן כדי לעזור."
}
@@ -769,7 +769,7 @@
"featured_categories": "Categorie in evidenza",
"popular_categories": "Categorie popolari",
"number_apps_one": "{{count}} App",
"number_apps_other": "{{count}} App",
"number_apps_other": "{{count}} app",
"trending_apps": "App di tendenza",
"explore_apps": "App {{category}}",
"installed_apps": "App installate",
@@ -837,7 +837,6 @@
"redirect_success_booking": "Reindirizza su prenotazione ",
"you_are_being_redirected": "Verrai reindirizzato a {{ url }} tra $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Per poter utilizzare questa funzionalità, è necessario passare a un account Pro.",
"duplicate": "Duplica",
"offer_seats": "Imposta posti",
"offer_seats_description": "Imposta i posti da prenotare (ciò disabiliterà le prenotazioni ospite e le prenotazioni opt-in)",
@@ -1082,10 +1081,10 @@
"using_additional_inputs_as_variables": "Come usare i campi di input aggiuntivi come variabili?",
"download_desktop_app": "Scarica app desktop",
"set_ping_link": "Imposta link di Ping",
"rate_limit_exceeded": "Limite di frequenza superato",
"rate_limit_exceeded": "Limite superato",
"when_something_happens": "Quando succede qualcosa",
"action_is_performed": "Viene eseguita un'azione",
"test_action": "Azione di test",
"test_action": "Verifica azione",
"notification_sent": "Notifica inviata",
"no_input": "Nessun input",
"test_workflow_action": "Verifica azione del flusso di lavoro",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Conferma la password prima di cambiare l'indirizzo e-mail",
"seats": "posti",
"every_app_published": "Tutte le applicazioni pubblicate nell'App store di Cal.com sono open source e sono state testate in modo approfondito attraverso una valutazione tra pari. Tuttavia, Cal.com, Inc. non promuove né certifica tali applicazioni, a meno che non siano pubblicate da Cal.com. Se notate contenuti o comportamenti inappropriati, vi preghiamo di segnalarceli.",
"report_app": "Segnala app"
"report_app": "Segnala app",
"billing_manage_details_title": "Visualizza e gestisci i tuoi dettagli di fatturazione",
"billing_manage_details_description": "Visualizza e modifica i tuoi dati di fatturazione, puoi anche modificare la tua subcription.",
"billing_help_title": "Hai bisogno di altro?",
"billing_help_description": "Se hai bisogno di ulteriore aiuto per la fatturazione, il nostro team di supporto è qui per aiutarti."
}
@@ -632,7 +632,7 @@
"billing": "請求",
"manage_your_billing_info": "請求情報の管理とサブスクリプションのキャンセル。",
"availability": "利用可能期間",
"edit_availability": "空き状況を編集",
"edit_availability": "予定を編集",
"configure_availability": "予約が可能な時間を設定する。",
"copy_times_to": "次に時間をコピー",
"change_weekly_schedule": "ウィークリースケジュールを変更する",
@@ -837,7 +837,6 @@
"redirect_success_booking": "予約時にリダイレクト",
"you_are_being_redirected": "$t(second, {\"count\": {{seconds}} }) で {{ url }} にリダイレクトします",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "この機能を利用するには、Pro アカウントへのアップグレードが必要です。",
"duplicate": "複製",
"offer_seats": "座席を提供",
"offer_seats_description": "予約に座席を提供 (これはゲストとオプトイン予約を無効化します)",
@@ -1129,7 +1128,7 @@
"profile_picture": "プロフィール写真",
"upload": "アップロード",
"web3": "Web3",
"rainbow_token_gated": "このイベントタイプはトークンゲートされています。",
"rainbow_token_gated": "このイベントタイプはトークンゲート(トークンを持っている人だけに制限)されています。",
"rainbow_connect_wallet_gate": "<1>{{name}}</1>(<3>{{symbol}}</3>)を所有している場合、ウォレットを接続します。",
"rainbow_insufficient_balance": "接続されているウォレットには十分な<1>{{symbol}}</1>が含まれていません。",
"rainbow_sign_message_request": "ウォレットのメッセージリクエストに署名してください。",
@@ -1182,7 +1181,7 @@
"organizer_name_info": "あなたの名前",
"additional_notes_info": "予約の追加メモ",
"attendee_name_info": "予約者名",
"to": "宛先",
"to": "送信先",
"attendee_required_enter_number": "この場合、予約時に参加者の電話番号の入力が必要になります",
"workflow_turned_on_successfully": "{{workflowName}} ワークフローが正常に {{offOn}} になりました",
"download_responses": "回答をダウンロード",
@@ -1237,7 +1236,7 @@
"saml_config": "SAML 設定",
"add_webhook_description": "Cal.com で何かが起こったときにリアルタイムでミーティングデータを受信します",
"triggers_when": "次の場合にトリガー",
"test_webhook": "作成前に Ping テストを行ってください。",
"test_webhook": "作成前に Ping テストを実行してください。",
"enable_webhook": "ウェブフックを有効にする",
"add_webhook": "ウェブフックを追加",
"webhook_edited_successfully": "ウェブフックを保存しました",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "メールアドレスを変更する前にパスワードを確認してください",
"seats": "座席",
"every_app_published": "Cal.com アプリストアで公開されているアプリはいずれもオープンソースであり、ピアレビューを通じて徹底的にテストされています。しかしながら Cal.com, Inc は、Cal.com によって公開されたものでない限り、これらのアプリを推奨または認証していません。不適切なコンテンツや動作に遭遇した場合は、ご報告ください。",
"report_app": "アプリを報告"
"report_app": "アプリを報告",
"billing_manage_details_title": "請求情報の詳細を表示・管理する",
"billing_manage_details_description": "請求情報の詳細を表示・編集、およびサブスクリプションをキャンセルする。",
"billing_help_title": "他に必要なものはありませんか?",
"billing_help_description": "請求に関するサポートが必要な場合は、サポートチームがお手伝いします。"
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "예약 시 리디렉션",
"you_are_being_redirected": "$t(second, {\"count\": {{seconds}} }) 후에 {{ url }}(으)로 리디렉션됩니다.",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "이 기능을 사용하려면 Pro 계정으로 업그레이드해야 합니다.",
"duplicate": "복제",
"offer_seats": "좌석 제공",
"offer_seats_description": "예약에 좌석 제공(게스트를 비활성화하고 예약을 선택함)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "이메일 주소 변경 전 비밀번호를 확인하세요",
"seats": "좌석",
"every_app_published": "Cal.com App Store에 게시된 모든 앱은 오픈 소스이며 피어 리뷰를 통해 철저한 테스트를 거쳤습니다. 그럼에도 불구하고 Cal.com, Inc.는 Cal.com에서 게시하지 않는 한 이 앱을 보증 또는 인증하지 않습니다. 부적절한 콘텐츠나 활동을 발견하면 신고해 주시기 바랍니다.",
"report_app": "신고 앱"
"report_app": "신고 앱",
"billing_manage_details_title": "결제 세부정보 보기 및 관리",
"billing_manage_details_description": "결제 세부 정보 확인 및 수정, 혹은 구독을 취소합니다.",
"billing_help_title": "다른 것이 필요하십니까?",
"billing_help_description": "청구와 관련하여 추가 도움이 필요한 경우 지원 팀이 도와드리겠습니다."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Omleiding bij boeking ",
"you_are_being_redirected": "U wordt omgeleid naar {{ url }} over $t(seconde, {\"count\": {{seconden}} }).",
"external_redirect_url": "https://voorbeeld.com/omleiding-naar-mijn-succespagina",
"redirect_url_upgrade_description": "Om deze functie te gebruiken, moet u upgraden naar een Pro-account.",
"duplicate": "Dupliceren",
"offer_seats": "Zitplaatsen aanbieden",
"offer_seats_description": "Zitplaatsen aanbieden aan boekingen (dit schakelt gastboekingen en aanmeldingsboekingen uit)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Bevestig uw wachtwoord voordat u uw e-mailadres wijzigt",
"seats": "plaatsen",
"every_app_published": "Elke app die in de app store van Cal.com wordt gepubliceerd is open source en grondig getest via collegiale beoordelingen. Desondanks onderschrijft of certificeert Cal.com, Inc. deze apps niet, tenzij ze zijn gepubliceerd door Cal.com. Meld het als u ongepaste inhoud of gedrag tegenkomt.",
"report_app": "App melden"
"report_app": "App melden",
"billing_manage_details_title": "Bekijk en beheer uw betalingsgegevens",
"billing_manage_details_description": "Uw betalingsgegevens bekijken of uw account opzeggen.",
"billing_help_title": "Nog iets nodig?",
"billing_help_description": "Als u verdere hulp nodig heeft bij facturering, is ons ondersteuningsteam er om u te helpen."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Przekieruj przy rezerwacji ",
"you_are_being_redirected": "Zostałeś przekierowany do {{ url }} w $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/przekieruj-do-moja-strona",
"redirect_url_upgrade_description": "Aby korzystać z tej funkcji, musisz uaktualnić do konta Pro.",
"duplicate": "Duplikat",
"offer_seats": "Zaproponuj miejsca",
"offer_seats_description": "Zaoferuj miejsca do zarezerwowania (ta funkcja wyłącza rezerwacje gości i rezerwacje wstępne)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Potwierdź hasło przed zmianą adresu e-mail",
"seats": "miejsca",
"every_app_published": "Każda aplikacja opublikowana w sklepie App Store serwisu Cal.com jest otwartym oprogramowaniem i jest dokładnie testowana przez innych użytkowników. Niemniej jednak Cal.com, Inc. nie odpowiada za aplikacje ani nie certyfikuje ich, chyba że są one publikowane przez Cal.com. Jeśli natrafisz na nieodpowiednie treści lub zachowanie, zgłoś je.",
"report_app": "Zgłoś aplikację"
"report_app": "Zgłoś aplikację",
"billing_manage_details_title": "Zobacz i zarządzaj swoimi danymi rozliczeniowymi",
"billing_manage_details_description": "Zobacz i edytuj swoje dane rozliczeniowe, jak również anuluj subskrypcję.",
"billing_help_title": "Potrzebujesz czegoś innego?",
"billing_help_description": "Jeśli potrzebujesz dalszej pomocy przy rozliczaniu, nasz zespół wsparcia jest tutaj, aby pomóc."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Redirecionar após a reserva ",
"you_are_being_redirected": "Você está sendo redirecionado para {{ url }} em $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Para usar este recurso, atualize para uma conta Pro.",
"duplicate": "Duplicar",
"offer_seats": "Oferecer assentos",
"offer_seats_description": "Oferecer lugares para reserva (isso desativa convidados e confirmações de reserva)",
@@ -1266,5 +1265,9 @@
"confirm_password_change_email": "Confirme sua senha antes de alterar o endereço de e-mail",
"seats": "assentos",
"every_app_published": "Todos os aplicativos publicados na App Store da Cal.com são de código aberto e testados minuciosamente por meio de avaliações por pares. No entanto, a Cal.com, Inc. não endossa ou certifica esses aplicativos a não ser que sejam publicados pela Cal.com. Se você encontrar conteúdo ou comportamento inadequado, denuncie.",
"report_app": "Denunciar aplicativo"
"report_app": "Denunciar aplicativo",
"billing_manage_details_title": "Visualizar e gerenciar os seus detalhes de faturamento",
"billing_manage_details_description": "Ver e editar os seus dados de faturamento, bem como cancelar a sua assinatura.",
"billing_help_title": "Precisa de algo mais?",
"billing_help_description": "Se precisar de mais ajuda com o faturamento, o nosso time de suporte está aqui para ajudar."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Redireccionar ao reservar ",
"you_are_being_redirected": "Está a ser redireccionado para {{ url }} em $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://exemplo.com/redireccionar-para-pagina-de-sucesso",
"redirect_url_upgrade_description": "Para usar esta funcionalidade, tem de actualizar para uma conta Pro.",
"duplicate": "Duplicar",
"offer_seats": "Oferecer lugares",
"offer_seats_description": "Oferecer lugares para reservas (isto desactiva os convidados e confirmações nas reservas)",
@@ -1257,7 +1256,7 @@
"password_updated": "Palavra-passe atualizada!",
"pending_payment": "Pagamento pendente",
"confirmation_page_rainbow": "Controle o acesso ao seu evento com tokens ou NFT na Ethereum, Polygon e muito mais.",
"not_on_cal": "Não em Cal.com",
"not_on_cal": "Não existe em Cal.com",
"no_calendar_installed": "Nenhum calendário instalado",
"no_calendar_installed_description": "Ainda não associou nenhum dos seus calendários",
"add_a_calendar": "Adicionar um calendário",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Por favor, confirme a sua palavra-passe antes de alterar o seu endereço de e-mail",
"seats": "lugares",
"every_app_published": "Todas as aplicações publicadas na loja de aplicações Cal.com são de código aberto e minuciosamente testadas através da avaliação por pares. No entanto, Cal.com, Inc. não apoia ou certifica estas aplicações a não ser que sejam publicadas pela Cal.com. Se encontrar conteúdos ou comportamentos inadequados, por favor, reporte a situação.",
"report_app": "Reportar aplicação"
"report_app": "Reportar aplicação",
"billing_manage_details_title": "Visualizar e gerir os seus detalhes de faturação",
"billing_manage_details_description": "Ver e editar os seus dados de faturação, bem como cancelar a sua assinatura.",
"billing_help_title": "Precisa de algo mais?",
"billing_help_description": "Se precisar de mais ajuda com a faturação, a nossa equipa de suporte está aqui para ajudar."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Redirecționare la rezervare ",
"you_are_being_redirected": "Sunteți redirecționat către {{ url }} în $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Pentru a utiliza această caracteristică, trebuie să faceți upgrade la un cont Pro.",
"duplicate": "Duplicare",
"offer_seats": "Oferiți locuri",
"offer_seats_description": "Oferiți locuri pentru rezervări (se dezactivează astfel oaspeții și rezervările la alegere)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Te rugăm să confirmi parola înainte de a-ți schimba adresa de e-mail",
"seats": "locuri",
"every_app_published": "Fiecare aplicație publicată pe Cal.com App Store este open source și testată temeinic prin evaluări inter pares. Cu toate acestea, Cal.com, Inc. nu susține sau certifică aceste aplicații decât dacă sunt publicate de Cal.com. Dacă întâmpini un conținut sau un comportament necorespunzător, te rugăm să îl raportezi.",
"report_app": "Raportează aplicația"
"report_app": "Raportează aplicația",
"billing_manage_details_title": "Vizualizați și gestionați detaliile de facturare",
"billing_manage_details_description": "Vizualizați și editați detaliile de facturare, precum și anulați abonarea.",
"billing_help_title": "Aveți nevoie de altceva?",
"billing_help_description": "Dacă ai nevoie de ajutor suplimentar pentru facturare, echipa noastră de asistență este aici pentru a te ajuta."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Перенаправление при бронировании ",
"you_are_being_redirected": "Перенаправление на {{ url }} произойдет через $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Чтобы использовать эту функцию, необходимо перейти на аккаунт Pro.",
"duplicate": "Создать копию",
"offer_seats": "Предложить места",
"offer_seats_description": "Предлагать места при бронировании (при этом отключается гостевой режим и бронирование с подтверждением)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Подтвердите свой пароль, чтобы изменить адрес электронной почты",
"seats": "места",
"every_app_published": "Все приложения на Cal.com App Store – это решения с открытым исходным кодом, которые тщательно тестируются коллегами. Тем не менее Cal.com, Inc. не дает гарантию в отношении только тех приложений, которые опубликованы Cal.com. Если вы обнаружите неприемлемое содержание или поведение, просим сообщать об этом.",
"report_app": "Пожаловаться на приложение"
"report_app": "Пожаловаться на приложение",
"billing_manage_details_title": "Просмотр и управление платежными данными",
"billing_manage_details_description": "Просматривайте и редактируйте платежные реквизиты, а также отменяйте подписку.",
"billing_help_title": "Нужно что-нибудь еще?",
"billing_help_description": "Если вам нужна дальнейшая помощь с оплатой счета, наша служба поддержки готова помочь."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Preusmeri na rezervaciju ",
"you_are_being_redirected": "Preusmeravamo vas na {{ url }} za $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Da biste koristili ovu funkciju, morate izvršiti nadogradnju na Pro nalog.",
"duplicate": "Dupliraj",
"offer_seats": "Ponudi mesta",
"offer_seats_description": "Ponudi mesta za zakazivanje (ovo onemogućava gostujuća i opciona zakazivanja)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Potvrdite svoju lozinku pre promene imejl adrese",
"seats": "sedišta",
"every_app_published": "Svaka aplikacija objavljena na Cal.com prodavnici je otvorenog tipa i detaljno je testirana kroz recenzije saradnika. Bez obzira na to, Cal.com, Inc. ne podržava ni sertifikuje ove aplikacije osim ako ih nije objavio Cal.com. Ako naiđete na neprikladan sardžaj ili ponašanje, molimo vas da to prijavite.",
"report_app": "Prijavite aplikaciju"
"report_app": "Prijavite aplikaciju",
"billing_manage_details_title": "Pogledajte i upravljate vaše detalje naplate",
"billing_manage_details_description": "Pogledajte i uredite vaše detalje naplate, a i otkažite vašu pretplatu.",
"billing_help_title": "Treba li vam još nešto?",
"billing_help_description": "Ako vam treba pomoć sa naplatom, naš tim podrške je tu da pomogne."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Omdirigera vid bokning ",
"you_are_being_redirected": "Du omdirigeras till {{ url }} om $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "För att kunna använda den här funktionen måste du uppgradera till ett Pro-konto.",
"duplicate": "Duplicera",
"offer_seats": "Erbjud platser",
"offer_seats_description": "Erbjud platser till bokningar (Detta inaktiverar gäster och bokningar)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Vänligen bekräfta ditt lösenord innan du ändrar din e-postadress",
"seats": "platser",
"every_app_published": "Alla appar som publiceras i Cal.com App Store har öppen källkod och är noggrant testade genom peer reviews. Cal.com, Inc. stöder eller certifierar dock inte dessa appar om de inte publiceras av Cal.com. Om du stöter på olämpligt innehåll eller beteende ber vi dig att rapportera det.",
"report_app": "Rapportera app"
"report_app": "Rapportera app",
"billing_manage_details_title": "Visa och hantera dina faktureringsuppgifter",
"billing_manage_details_description": "Visa och redigera dina faktureringsuppgifter samt avsluta din prenumeration.",
"billing_help_title": "Behöver du något annat?",
"billing_help_description": "Om du behöver ytterligare hjälp med fakturering finns vårt supportteam här för att hjälpa till."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Rezervasyonu yönlendir ",
"you_are_being_redirected": "$t(second, {\"count\": {{seconds}} }) içinde {{ url }} adresine yönlendirileceksiniz.",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Bu özelliği kullanmak için Pro hesabına geçmeniz gerekiyor.",
"duplicate": "Çoğalt",
"offer_seats": "Yer teklif et",
"offer_seats_description": "Rezervasyonlar için koltuk teklif edin (bu işlem, misafirleri ve rezervasyon onaylarını devre dışı bırakır)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Lütfen e-posta adresinizi değiştirmeden önce şifrenizi onaylayın",
"seats": "yer",
"every_app_published": "Cal.com Uygulama Mağazası'nda yayınlanan her uygulama açık kaynak kodludur ve uzman incelemeleriyle kapsamlı bir şekilde test edilmiştir. Cal.com, Cal.com tarafından yayınlanmadıkça bu uygulamaları desteklemez veya onaylamaz. Herhangi bir uygunsuz içerik veya davranışla karşılaşırsanız lütfen bize bildirin.",
"report_app": "Uygulamayı bildir"
"report_app": "Uygulamayı bildir",
"billing_manage_details_title": "Fatura bilgilerinizi görüntüleyin ve yönetin",
"billing_manage_details_description": "Fatura bilgilerinizi görüntüleyin ve düzenleyin, ayrıca aboneliğinizi iptal edin.",
"billing_help_title": "Başka bir şeye ihtiyacınız var mı?",
"billing_help_description": "Faturalandırma konusunda daha fazla yardıma ihtiyacınız olursa destek ekibimiz size yardımcı olmaya hazır."
}
+203 -2
View File
@@ -776,6 +776,12 @@
"no_category_apps": "Немає додатків із категорії «{{category}}»",
"no_category_apps_description_calendar": "Додайте додаток-календар, щоб перевіряти розклад на наявність конфліктів і уникати подвійних бронювань",
"no_category_apps_description_conferencing": "Спробуйте додати програму для конференцій, щоб інтегрувати можливість відеовикликів зі своїми клієнтами",
"no_category_apps_description_payment": "Додайте платіжний додаток, щоб спростити транзакції між вами та вашими клієнтами",
"no_category_apps_description_other": "Додавайте інші додатки з різноманітними функціями",
"installed_app_calendar_description": "Налаштуйте календар, щоб перевіряти розклад на наявність конфліктів і уникати подвійних бронювань.",
"installed_app_conferencing_description": "Додавайте улюблені додатки для відеоконференцій, щоб проводити наради",
"installed_app_payment_description": "Укажіть, які служби обробки платежів використовувати, коли клієнти вам платять.",
"installed_app_other_description": "Усі ваші встановлені додатки з інших категорій.",
"empty_installed_apps_headline": "Не встановлено жодних додатків",
"empty_installed_apps_description": "Додатки дають змогу оптимізувати робочий процес і спростити роботу з графіком.",
"empty_installed_apps_button": "Переглянути магазин додатків",
@@ -796,6 +802,7 @@
"remove": "Вилучити",
"add": "Додати",
"installed_one": "Установлено",
"installed_other": "Встановлено {{count}}",
"verify_wallet": "Пройдіть перевірку гаманця",
"connect_metamask": "Підключіть Metamask",
"create_events_on": "Створюйте заходи в:",
@@ -814,6 +821,7 @@
"new_schedule_btn": "Створити розклад",
"add_new_schedule": "Додати новий розклад",
"add_new_calendar": "Додати новий календар",
"set_calendar": "Укажіть, куди додавати нові заходи, коли ваш час бронюють.",
"delete_schedule": "Видалити розклад",
"schedule_created_successfully": "Розклад «{{scheduleName}}» створено",
"availability_updated_successfully": "Розклад «{{scheduleName}}» оновлено",
@@ -829,7 +837,6 @@
"redirect_success_booking": "Перенаправляти під час бронювання ",
"you_are_being_redirected": "Ви перейдете за адресою {{ url }} через $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Щоб користуватися цією функцією, потрібен обліковий запис Pro.",
"duplicate": "Дублювати",
"offer_seats": "Запропонувати місця",
"offer_seats_description": "Пропонувати місця під час бронювання (при цьому вимикається гостьовий режим і бронювання з підтвердженням)",
@@ -967,6 +974,7 @@
"workflows": "Робочі процеси",
"new_workflow_btn": "Новий робочий процес",
"add_new_workflow": "Додати новий робочий процес",
"reschedule_event_trigger": "у момент перенесення заходу",
"trigger": "Тригер",
"triggers": "Тригери",
"action": "Дія",
@@ -994,10 +1002,13 @@
"change_username_standard_to_premium": "Оскільки ви змінюєте стандартне ім’я користувача на преміальне, відкриється оформлення переходу на дорожчу підписку.",
"change_username_premium_to_standard": "Оскільки ви змінюєте преміальне ім’я користувача на стандартне, відкриється оформлення переходу на дешевшу підписку.",
"go_to_stripe_billing": "Перейти до виставлення рахунків",
"stripe_description": "Вимагати оплату за бронювання (комісія за транзакцію: 0,5% + 0,10 €)",
"trial_expired": "Пробний період закінчився",
"remove_app": "Вилучити додаток",
"yes_remove_app": "Так, вилучити додаток",
"are_you_sure_you_want_to_remove_this_app": "Справді вилучити цей додаток?",
"app_removed_successfully": "Додаток вилучено",
"error_removing_app": "Помилка під час вилучення додатку",
"web_conference": "Вебконференція",
"number_for_sms_reminders": "Номер телефону (для SMS-нагадувань)",
"requires_confirmation": "Потрібне підтвердження",
@@ -1053,19 +1064,209 @@
"navigate": "Перейти",
"open": "Відкрити",
"close": "Закрити",
"team_feature_teams": "Ця функція доступна в плані Team. Перейдіть на нього, щоб бачити відомості про доступність учасників своєї команди.",
"team_feature_workflows": "Ця функція доступна в плані Team. Перейдіть на нього, щоб автоматизувати сповіщення та нагадування про заходи за допомогою робочих процесів.",
"show_eventtype_on_profile": "Показувати в профілі",
"embed": "Вбудувати",
"new_username": "Нове ім’я користувача",
"current_username": "Поточне ім’я користувача",
"example_1": "Приклад 1",
"example_2": "Приклад 2",
"additional_input_label": "Додатковий введений підпис",
"company_size": "Розмір компанії",
"what_help_needed": "З чим саме потрібна допомога?",
"variable_format": "Формат змінної",
"webhook_subscriber_url_reserved": "URL-адресу для абонента у вебгуку вже визначено",
"custom_input_as_variable_info": "Усі спеціальні символи в додатковому введеному підписі пропускаються (використовуються тільки літери та цифри), використовуються лише великі літери, пробіли замінюються на підкреслення.",
"using_additional_inputs_as_variables": "Як використовувати додаткові поля введення як змінні?",
"download_desktop_app": "Завантажити додаток для ПК",
"set_ping_link": "Установити Ping-посилання",
"rate_limit_exceeded": "Перевищено ліміт кількості запитів",
"when_something_happens": "Коли щось стається",
"action_is_performed": "Виконується дія",
"test_action": "Перевірити дію",
"notification_sent": "Надсилається сповіщення",
"no_input": "Нічого не введено",
"test_workflow_action": "Перевірити дію робочого циклу",
"send_sms": "Надіслати SMS",
"send_sms_to_number": "Справді надіслати SMS-повідомлення на номер {{number}}?",
"missing_connected_calendar": "Немає календаря за замовчуванням",
"connect_your_calendar_and_link": "Підключити календар можна <1>звідси</1>.",
"default_calendar_selected": "Календар за замовчуванням",
"hide_from_profile": "Не показувати в профілі",
"event_setup_tab_title": "Налаштування заходу",
"event_limit_tab_title": "Ліміти",
"event_limit_tab_description": "Як часто ваш час можуть бронювати",
"event_advanced_tab_description": "Налаштування календаря та інше…",
"event_advanced_tab_title": "Додатково",
"select_which_cal": "Виберіть календар, у який додаватимуться бронювання",
"custom_event_name": "Користувацька назва події",
"custom_event_name_description": "Вибирайте для заходів власні назви, що показуватимуться в календарі",
"2fa_required": "Необхідна двоетапна автентифікація",
"incorrect_2fa": "Неправильний код двоетапної автентифікації",
"which_event_type_apply": "До якого типу заходів це застосовуватиметься?",
"no_workflows_description": "Робочі процеси – це проста автоматизація сповіщень і нагадувань, що дає вам змогу будувати процеси, пов’язані з вашими заходами.",
"create_workflow": "Створити робочий процес",
"do_this": "Зробити це",
"turn_off": "Вимкнути",
"settings_updated_successfully": "Параметри оновлено",
"error_updating_settings": "Помилка оновлення параметрів",
"personal_cal_url": "Моя персональна URL-адреса Cal",
"bio_hint": "Кілька речень про вас. Ця інформація з’явиться на сторінці за вашою URL-адресою.",
"delete_account_modal_title": "Видалення облікового запису",
"confirm_delete_account_modal": "Справді видалити свій обліковий запис Cal.com?",
"delete_my_account": "Видалити мій обліковий запис",
"start_of_week": "Початок тижня",
"select_calendars": "Виберіть, які календарі потрібно перевірити на наявність конфліктів, щоб уникнути подвійних бронювань.",
"check_for_conflicts": "Перевірка конфліктів",
"adding_events_to": "Додавання подій у",
"follow_system_preferences": "Відповідно до системних параметрів",
"custom_brand_colors": "Користувацькі брендові кольори",
"customize_your_brand_colors": "Налаштуйте власний брендовий колір на сторінці бронювання.",
"pro": "Pro",
"removes_cal_branding": "Вилучення будь-яких пов’язаних із Cal брендингових надписів, як-от «На платформі Cal».",
"profile_picture": "Зображення профілю",
"upload": "Завантажити",
"web3": "Web3",
"rainbow_token_gated": "Цей тип заходу підтримує доступ тільки для власників токенів.",
"rainbow_connect_wallet_gate": "Якщо у вас є <1>{{name}}</1> (<3>{{symbol}}</3>), підключіть свій гаманець.",
"rainbow_insufficient_balance": "У вашому підключеному гаманці бракує <1>{{symbol}}</1>.",
"rainbow_sign_message_request": "Підпишіть запит на надсилання повідомлень у своєму гаманці.",
"rainbow_signature_error": "Сталася помилка запиту на підпис від вашого гаманця.",
"token_address": "Адреса токена",
"blockchain": "Блокчейн",
"old_password": "Старий пароль",
"secure_password": "Ваш новий суперзахищений пароль",
"error_updating_password": "Помилка оновлення пароля",
"two_factor_auth": "Двоетапна автентифікація",
"recurring_event_tab_description": "Налаштування розкладу з повторюваними заходами",
"today": "сьогодні",
"appearance": "Зовнішній вигляд",
"appearance_subtitle": "Налаштовуйте вигляд бронювання",
"my_account": "Мій обліковий запис",
"general": "Загальні",
"calendars": "Календарі",
"2fa_auth": "Двоетапна перевірка",
"invoices": "Рахунки-фактури",
"embeds": "Вбудовування",
"impersonation": "Вхід від імені користувача",
"users": "Користувачі",
"profile_description": "Керуйте налаштуваннями свого профілю cal",
"general_description": "Керуйте налаштуваннями мови та часового поясу",
"calendars_description": "Налаштуйте, як типи заходів мають взаємодіяти з вашими календарями",
"appearance_description": "Налаштовуйте вигляд бронювання",
"conferencing_description": "Керуйте додатками для відеоконференцій і нарад",
"password_description": "Керуйте налаштуваннями паролів облікових записів",
"2fa_description": "Керуйте налаштуваннями паролів облікових записів",
"we_just_need_basic_info": "Щоб налаштувати ваш профіль, нам потрібні базові відомості.",
"skip": "Пропустити",
"do_this_later": "Зробити це пізніше",
"set_availability_getting_started_subtitle_1": "Укажіть, у які проміжки часу ви доступні",
"set_availability_getting_started_subtitle_2": "Усе це можна налаштувати пізніше на сторінці «Доступність».",
"connect_calendars_from_app_store": "Ви можете додати більше календарів з магазину додатків",
"connect_conference_apps": "Підключити додатки для конференцій",
"connect_calendar_apps": "Підключити додатки календарів",
"connect_payment_apps": "Підключити платіжні додатки",
"connect_other_apps": "Підключити інші додатки",
"current_step_of_total": "Крок {{currentStep}} із {{maxSteps}}",
"add_variable": "Додати змінну",
"custom_phone_number": "Користувацький номер телефону",
"message_template": "Шаблон повідомлення",
"email_subject": "Тема листа",
"add_dynamic_variables": "Додати динамічні текстові змінні",
"event_name_info": "Назва типу заходу",
"event_date_info": "Дата заходу",
"event_time_info": "Час початку заходу",
"location_info": "Місце проведення заходу",
"organizer_name_info": "Ваше ім’я",
"additional_notes_info": "Додаткові примітки щодо бронювання",
"attendee_name_info": "Ім’я того, хто бронює",
"to": "Кому",
"attendee_required_enter_number": "Під час бронювання відвідувачу потрібно буде ввести номер телефону",
"workflow_turned_on_successfully": "Робочий процес «{{workflowName}}» {{offOn}}",
"download_responses": "Завантажити відповіді",
"create_your_first_form": "Створіть свою першу форму",
"create_your_first_form_description": "Форми маршрутизації дають змогу запитувати уточнювальні запитання та переходити до відповідної особи або типу заходу.",
"create_your_first_webhook": "Створіть свій перший вебгук",
"create_your_first_webhook_description": "Завдяки вебгукам ви можете отримувати дані про наради в реальному часі, коли на Cal.com щось стається.",
"for_a_maximum_of": "На максимум",
"event_one": "захід",
"event_other": "заходи",
"profile_team_description": "Керуйте налаштуваннями свого профілю команди",
"members_team_description": "Користувачі в цій групі",
"team_url": "URL-адреса команди",
"delete_team": "Видалити команду",
"team_members": "Учасники команди",
"more": "Більше",
"more_page_footer": "Додаток для мобільних пристроїв ми вважаємо розширенням вебдодатку. Якщо вам потрібно виконувати складні дії, скористайтеся саме останнім.",
"workflow_example_1": "Надсилати відвідувачу нагадування про захід в SMS-повідомленні за 24 години до початку",
"workflow_example_2": "Надсилати відвідувачу користувацьке SMS-повідомлення, якщо захід перенесено",
"workflow_example_3": "Надсилати ведучому користувацький електронний лист, якщо бронюється новий захід",
"workflow_example_4": "Надсилати відвідувачу нагадування про захід в електронному листі за 1 годину до початку",
"workflow_example_5": "Надсилати ведучому користувацький електронний лист, якщо захід перенесено",
"workflow_example_6": "Надсилати ведучому користувацьке SMS-повідомлення, якщо бронюється новий захід",
"welcome_to_cal_header": "Вітаємо в Cal.com!",
"edit_form_later_subtitle": "Це можна буде змінити пізніше.",
"connect_calendar_later": "Я підключу календар пізніше",
"set_my_availability_later": "Я вкажу дані про доступність пізніше",
"problem_saving_user_profile": "Під час збереження даних виникла проблема. Повторіть спробу або зверніться в службу підтримки.",
"purchase_missing_seats": "Придбати відсутні місця",
"slot_length": "Тривалість вікна",
"booking_appearance": "Вигляд бронювання",
"appearance_team_description": "Налаштовуйте вигляд бронювання своєї команди",
"only_owner_change": "Змінювати бронювання команди може лише її власник ",
"team_disable_cal_branding_description": "Вилучення будь-яких пов’язаних із Cal брендингових надписів, як-от «На платформі Cal»",
"invited_by_team": "{{teamName}} запросив(-ла) вас приєднатися до його або її команди в такій ролі: {{role}}",
"token_invalid_expired": "Токен недійсний або застарілий.",
"exchange_add": "Підключитися до Microsoft Exchange",
"exchange_authentication": "Метод автентифікації",
"exchange_authentication_standard": "Проста автентифікація",
"exchange_authentication_ntlm": "Автентифікація NTLM",
"exchange_compression": "Стиснення GZip",
"routing_forms_description": "Тут наведено всі створені форми та маршрути.",
"add_new_form": "Додати форму",
"form_description": "Створіть власну форму для маршрутизації автора бронювання",
"copy_link_to_form": "Копіювати посилання на форму",
"theme": "Тема",
"theme_applies_note": "Застосовується тільки до ваших загальнодоступних сторінок бронювання",
"theme_light": "Світла",
"theme_dark": "Темна",
"report_app": "Поскаржитися на додаток"
"theme_system": "Стандартне системне значення",
"add_a_team": "Додати команду",
"saml_config": "Конфігурація SAML",
"add_webhook_description": "Отримуйте дані про наради в реальному часі, коли на Cal.com щось стається",
"triggers_when": "Спрацьовує в таких випадках",
"test_webhook": "Перш ніж створювати, виконайте ping-тест.",
"enable_webhook": "Увімкнути вебгук",
"add_webhook": "Додати вебгук",
"webhook_edited_successfully": "Вебгук збережено",
"webhooks_description": "Отримуйте дані про наради в реальному часі, коли на Cal.com щось стається",
"api_keys_description": "Створюйте ключі API, щоб отримувати доступ до свого облікового запису",
"new_api_key": "Новий ключ API",
"active": "Активний",
"api_key_updated": "Назву ключа API оновлено",
"api_key_update_failed": "Помилка оновлення назви ключа API",
"embeds_title": "Вбудовування iframe HTML",
"embeds_description": "Вбудовайте події всіх типів у свій вебсайт",
"create_first_api_key": "Створіть свій перший ключ API",
"create_first_api_key_description": "Ключі API дають іншим додаткам змогу обмінюватися даними з Cal.com",
"back_to_signin": "Повернутися до входу",
"reset_link_sent": "Посилання для скидання надіслано",
"password_reset_email": "Електронний лист з інструкціями зі скидання пароля надіслано на {{email}}.",
"password_updated": "Пароль оновлено!",
"pending_payment": "Очікується оплата",
"confirmation_page_rainbow": "Увімкніть доступ до свого заходу тільки для власників звичайних або невзаємозамінних токенів Ethereum, Polygon тощо.",
"not_on_cal": "Не на Cal.com",
"no_calendar_installed": "Немає встановлених календарів",
"no_calendar_installed_description": "Ви ще не підключили свої календарі",
"add_a_calendar": "Додати календар",
"change_email_hint": "Щоб зміни почали діяти, можливо, знадобиться вийти із системи та ввійти знову",
"confirm_password_change_email": "Перш ніж змінювати адресу електронної пошти, підтвердьте свій пароль",
"seats": "місця",
"every_app_published": "Усі додатки, опубліковані в магазині Cal.com, мають відкритий код і ретельно перевіряються учасниками спільноти. Утім, Cal.com, Inc. рекомендує та сертифікує лише додатки, опубліковані компанією Cal.com. Якщо ви натрапили на неприйнятний вміст або поведінку, надішліть нам скаргу.",
"report_app": "Поскаржитися на додаток",
"billing_manage_details_title": "Переглядайте свої дані для виставлення рахунків і керуйте ними",
"billing_manage_details_description": "Тут ви можете переглянути й відредагувати свої дані для виставлення рахунків, а також скасувати підписку.",
"billing_help_title": "Потрібно щось інше?",
"billing_help_description": "Якщо вам потрібна додаткова допомога з виставленням рахунків, наша служба підтримки готова її надати."
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "Chuyển hướng khi đặt lịch hẹn ",
"you_are_being_redirected": "Bạn sẽ được chuyển hướng đến {{ url }} sau $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "Để sử dụng tính năng này, bạn cần nâng cấp lên tài khoản Pro.",
"duplicate": "Sao chép",
"offer_seats": "Cung cấp ghế ngồi",
"offer_seats_description": "Cung ứng chỗ cho lịch hẹn (Cái này sẽ vô hiệu hoá khách & lịch hẹn tham gia)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "Vui lòng xác nhận mật khẩu của bạn trước khi đổi địa chỉ email",
"seats": "ghế",
"every_app_published": "Mọi ứng dụng được công bố trên Cal.com App Store là dạng mã nguồn mở và được kiểm nghiệm kĩ càng thông qua khâu bình duyệt ngang hàng. Dẫu vậy, Cal.com, Inc. không công nhận hay chứng nhận những ứng dụng nếu chúng không do Cal.com công bố. Nếu bạn gặp nội dụng hay hành vi không đúng mực nào, vui lòng báo cáo vụ việc.",
"report_app": "Báo cáo ứng dụng"
"report_app": "Báo cáo ứng dụng",
"billing_manage_details_title": "Xem và quản lý chi tiết thanh toán của bạn",
"billing_manage_details_description": "Xem và chỉnh sửa chi tiết thanh toán của bạn, cũng như hủy đăng ký gói của bạn.",
"billing_help_title": "Cần gì nữa không?",
"billing_help_description": "Nếu bạn cần thêm bất kỳ trợ giúp nào về thanh toán, nhóm hỗ trợ của chúng tôi luôn sẵn sàng trợ giúp."
}
@@ -649,7 +649,7 @@
"event_name_tooltip": "在日历中显示的名称",
"meeting_with_user": "与 {ATTENDEE} 的会议",
"additional_inputs": "更多输入框",
"additional_input_description": "在确认预约之前,需要调度员输入额外的信息",
"additional_input_description": "在确认预约之前,需要预约者输入额外的信息",
"label": "标签",
"placeholder": "占位符",
"type": "类型",
@@ -837,7 +837,6 @@
"redirect_success_booking": "预约时重定向",
"you_are_being_redirected": "您将在 $t(second, {\"count\": {{seconds}} }) 秒后重定向到 {{ url }}。",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "要使用此功能,您需要升级到专业版帐户。",
"duplicate": "复制",
"offer_seats": "提供位置",
"offer_seats_description": "提供位置以供预约(这将禁止访客并选择预约)",
@@ -1190,7 +1189,7 @@
"create_your_first_form_description": "利用途径表格,您可以提出符合条件的问题并为正确的人或活动类型选择途径。",
"create_your_first_webhook": "创建您的第一个 Webhook",
"create_your_first_webhook_description": "通过 Webhook,当 Cal.com 上发生事件时,您可以实时接收会议数据。",
"for_a_maximum_of": "最大",
"for_a_maximum_of": "最多",
"event_one": "活动",
"event_other": "活动",
"profile_team_description": "管理团队资料的设置",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "请在更改电子邮件地址之前确认密码",
"seats": "位置",
"every_app_published": "Cal.com 应用商店上发布的每个应用都是开源的,并通过同行评审进行了全面测试。尽管如此,除非应用是由 Cal.com 发布的,否则 Cal.com, Inc. 不认可或认证这些应用。如果您遇到不当内容或行为,请举报。",
"report_app": "报告应用"
"report_app": "报告应用",
"billing_manage_details_title": "查看和管理您的账单详情",
"billing_manage_details_description": "查看和编辑您的账单详情,以及取消订阅。",
"billing_help_title": "还有其他需要?",
"billing_help_description": "如果您在支付帐单时需要任何进一步的帮助,我们的支持团队会给您提供帮助。"
}
@@ -837,7 +837,6 @@
"redirect_success_booking": "在預約時重新導向 ",
"you_are_being_redirected": "將於 $t(second, {\"count\": {{seconds}} }) 秒後將您重新導向至 {{ url }}。",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_upgrade_description": "您必須升級至專業版帳號才能使用此功能。",
"duplicate": "複製",
"offer_seats": "提供座位",
"offer_seats_description": "為預約提供席次 (這將停用賓客和主動加入的預約)",
@@ -1265,5 +1264,9 @@
"confirm_password_change_email": "請先確認密碼,再變更電子郵件地址",
"seats": "座位",
"every_app_published": "Cal.com App Store 上發佈的每個應用程式皆為開放原始碼,並經過同業審查仔細測試過。不過除了 Cal.com 發佈的應用程式外,Cal.com, Inc. 無法為其他應用程式背書或提出擔保。若您發現這些應用程式有不當內容或行為,請立即回報。",
"report_app": "回報應用程式"
"report_app": "回報應用程式",
"billing_manage_details_title": "檢視與管理付費細節",
"billing_manage_details_description": "檢視與編輯付費細節,以及取消訂閱。",
"billing_help_title": "還要什麼嗎?",
"billing_help_description": "如果有關於付費的進一步需求,我們的支援團隊隨時上前幫忙。"
}
+6 -6
View File
@@ -197,11 +197,6 @@ button[role="switch"][data-state="checked"] {
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.2);
}
/* React Select V2 */
.cal-react-select-container .cal-react-select__control {
@apply bg-white min-h-[36px] h-[36px] text-sm leading-4 border-gray-300 hover:border-neutral-400 focus-within:ring-2 focus-within:ring-neutral-800 focus-within:border-0 placeholder:text-gray-400 placeholder:text-sm placeholder:font-normal;
}
}
/* !important to style multi-email input */
@@ -344,6 +339,10 @@ button[role="switch"][data-state="checked"] {
animation: loader-inner 2s infinite ease-in;
}
.no-ring-inset {
--tw-ring-inset: unset;
}
@keyframes loader {
0% {
transform: rotate(0deg);
@@ -495,4 +494,5 @@ hr {
.h-screen {
height: -webkit-fill-available;
}
}
}
+6
View File
@@ -54,9 +54,15 @@ const config: Config = {
displayName: "@calcom/api",
roots: ["<rootDir>/apps/api"],
testMatch: ["**/test/lib/**/*.(spec|test).(ts|tsx|js)"],
setupFilesAfterEnv: ["<rootDir>/tests/config/singleton.ts"],
transform: {
"^.+\\.ts?$": "ts-jest",
},
globals: {
"ts-jest": {
tsconfig: "<rootDir>/apps/api/tsconfig.json",
},
},
transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"],
testEnvironment: "node",
clearMocks: true,
+5 -5
View File
@@ -119,11 +119,11 @@ const BaseAppFork = {
fs.writeFileSync(`${appDirPath}/config.json`, JSON.stringify(config, null, 2));
fs.writeFileSync(
`${appDirPath}/README.mdx`,
fs.readFileSync(`${appDirPath}/README.mdx`).toString().replace("_DESCRIPTION_", appDescription)
);
fs.writeFileSync(
`${appDirPath}/README.mdx`,
fs.readFileSync(`${appDirPath}/README.mdx`).toString().replace("_DESCRIPTION_", appDescription)
fs
.readFileSync(`${appDirPath}/README.mdx`)
.toString()
.replace(/_DESCRIPTION_/g, appDescription)
.replace(/_APP_DIR_/g, slug)
);
message = !editMode ? "Forked base app" : "Updated app";
yield message;
+7 -8
View File
@@ -1,13 +1,12 @@
---
description: _DESCRIPTION_
items:
- /api/app-store/_APP_DIR_/1.jpg
- /api/app-store/_APP_DIR_/2.jpg
- /api/app-store/_APP_DIR_/3.jpg
---
{/* Feel free to edit description or add other frontmatter. Frontmatter would be available in the components here as variables by same name */}
<div>
<Slider items={items} />
{description}
</div>
<hr />
<div style={{color:"gray", fontStyle:"italic"}} >
<div>Edit README.mdx file to update the content.</div>
<div>Edit config.json to update logo </div>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

@@ -62,12 +62,14 @@ function NewFormDialog({ appUrl }: { appUrl: string }) {
const mutation = trpc.useMutation("viewer.app_routing_forms.formMutation", {
onSuccess: (_data, variables) => {
utils.invalidateQueries("viewer.app_routing_forms.forms");
router.push(`${appUrl}/form-edit/${variables.id}`);
},
onError: () => {
showToast(`Something went wrong`, "error");
},
onSettled: () => {
utils.invalidateQueries("viewer.app_routing_forms.forms");
},
});
const hookForm = useForm<{
@@ -163,6 +165,15 @@ function Dialogs({
const utils = trpc.useContext();
const router = useRouter();
const deleteMutation = trpc.useMutation("viewer.app_routing_forms.deleteForm", {
onMutate: async ({ id: formId }) => {
await utils.cancelQuery(["viewer.app_routing_forms.forms"]);
const previousValue = utils.getQueryData(["viewer.app_routing_forms.forms"]);
if (previousValue) {
const filtered = previousValue.filter(({ id }) => id !== formId);
utils.setQueryData(["viewer.app_routing_forms.forms"], filtered);
}
return { previousValue };
},
onSuccess: () => {
showToast("Form deleted", "success");
setDeleteDialogOpen(false);
@@ -172,7 +183,10 @@ function Dialogs({
utils.invalidateQueries(["viewer.app_routing_forms.forms"]);
setDeleteDialogOpen(false);
},
onError: () => {
onError: (err, newTodo, context) => {
if (context?.previousValue) {
utils.setQueryData(["viewer.app_routing_forms.forms"], context.previousValue);
}
showToast("Something went wrong", "error");
},
});
@@ -1,6 +1,9 @@
// TODO: i18n
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import useApp from "@calcom/lib/hooks/useApp";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { AppGetServerSidePropsContext, AppPrisma, AppUser } from "@calcom/types/AppGetServerSideProps";
import { Icon } from "@calcom/ui/Icon";
import { ButtonGroup, DropdownMenuSeparator, Tooltip } from "@calcom/ui/v2";
@@ -15,10 +18,12 @@ import { FormAction, FormActionsDropdown, FormActionsProvider } from "../../comp
import { getSerializableForm } from "../../lib/getSerializableForm";
export default function RoutingForms({
forms,
forms: forms_,
appUrl,
}: inferSSRProps<typeof getServerSideProps> & { appUrl: string }) {
const { t } = useLocale();
const { data: forms } = trpc.useQuery(["viewer.app_routing_forms.forms"], { initialData: forms_ });
const { data: typeformApp } = useApp("typeform");
function NewFormButton() {
@@ -33,7 +38,7 @@ export default function RoutingForms({
<FormActionsProvider appUrl={appUrl}>
<div className="-mx-4 md:-mx-8">
<div className="mb-10 w-full px-4 pb-2 sm:px-6 md:px-8">
{!forms.length ? (
{!forms?.length ? (
<EmptyScreen
Icon={Icon.FiGitMerge}
headline={t("create_your_first_form")}
@@ -41,7 +46,7 @@ export default function RoutingForms({
buttonRaw={<NewFormButton />}
/>
) : null}
{forms.length ? (
{forms?.length ? (
<div className="mb-16 overflow-hidden bg-white">
<List data-testid="routing-forms-list">
{forms.map((form, index) => {
@@ -201,6 +206,7 @@ export const getServerSideProps = async function getServerSideProps(
return {
props: {
...(await serverSideTranslations(context.locale ?? "", ["common"])),
forms: serializableForms,
},
};
@@ -170,7 +170,11 @@ function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>
);
})}
<div className="mt-4 flex justify-end space-x-2 rtl:space-x-reverse">
<Button loading={responseMutation.isLoading} type="submit" color="primary">
<Button
className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast dark:border-transparent"
loading={responseMutation.isLoading}
type="submit"
color="primary">
{t("submit")}
</Button>
</div>
@@ -7,6 +7,7 @@ import { sendGenericWebhookPayload } from "@calcom/lib/webhooks/sendPayload";
import { TRPCError } from "@calcom/trpc/server";
import { createProtectedRouter, createRouter } from "@calcom/trpc/server/createRouter";
import { getSerializableForm } from "./lib/getSerializableForm";
import { isAllowed } from "./lib/isAllowed";
import { zodFields, zodRoutes } from "./zod";
@@ -144,14 +145,24 @@ const app_RoutingForms = createRouter()
createProtectedRouter()
.query("forms", {
async resolve({ ctx: { user, prisma } }) {
return await prisma.app_RoutingForms_Form.findMany({
const forms = await prisma.app_RoutingForms_Form.findMany({
where: {
userId: user.id,
},
orderBy: {
createdAt: "asc",
createdAt: "desc",
},
include: {
_count: {
select: {
responses: true,
},
},
},
});
const serializableForms = forms.map((form) => getSerializableForm(form));
return serializableForms;
},
})
.query("formQuery", {
+1 -1
View File
@@ -6,7 +6,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
const scopes = ["OnlineMeetings.ReadWrite"];
const scopes = ["OnlineMeetings.ReadWrite", "offline_access"];
let client_id = "";
@@ -8,7 +8,7 @@ import { decodeOAuthState } from "../../_utils/decodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
const scopes = ["OnlineMeetings.ReadWrite"];
const scopes = ["OnlineMeetings.ReadWrite", "offline_access"];
let client_id = "";
let client_secret = "";
@@ -49,7 +49,6 @@ const o365Auth = async (credential: Credential) => {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
scope: "User.Read OnlineMeetings.ReadWrite",
client_id,
refresh_token: refreshToken,
grant_type: "refresh_token",
@@ -57,6 +56,10 @@ const o365Auth = async (credential: Credential) => {
}),
});
const responseBody = await handleErrorsJson(response);
if (responseBody.error) {
console.error(responseBody);
throw new HttpError({ statusCode: 500, message: "Error contacting MS Teams" });
}
// set expiry date as offset from current time.
responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000);
delete responseBody.expires_in;
@@ -21,9 +21,7 @@ const RainbowInstallForm: React.FC<RainbowInstallFormProps> = ({
return (
<>
<hr className="my-2 border-neutral-200" />
<div className="block items-center sm:flex">
<div className="mt-4 block items-center sm:flex">
<div className="min-w-48 mb-4 sm:mb-0">
<label htmlFor="blockchainId" className="flex text-sm font-medium text-neutral-700">
{t("Blockchain")}
+1
View File
@@ -26,6 +26,7 @@
"eslint": "^8.22.0",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"tailwind-scrollbar": "^2.0.1",
"tailwindcss": "^3.1.8",
"typescript": "^4.7.4"
}
+4
View File
@@ -209,6 +209,7 @@ module.exports = {
require("@tailwindcss/forms"),
require("@tailwindcss/typography"),
require("@tailwindcss/line-clamp"),
require("tailwind-scrollbar"),
require("tailwindcss-radix")(),
plugin(({ addVariant }) => {
addVariant("mac", ".mac &");
@@ -216,4 +217,7 @@ module.exports = {
addVariant("ios", ".ios &");
}),
],
variants: {
scrollbar: ["rounded", "dark"],
},
};
@@ -0,0 +1,21 @@
import Link from "next/link";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import Badge from "@calcom/ui/v2/core/Badge";
export default function UnconfirmedBookingBadge() {
const { t } = useLocale();
const { data: unconfirmedBookingCount } = trpc.useQuery(["viewer.bookingUnconfirmedCount"]);
if (!unconfirmedBookingCount) return null;
else
return (
<Link href="/bookings/unconfirmed">
<a title={t("unconfirmed_bookings_tooltip")}>
<Badge rounded variant="orange" className="hover:bg-orange-800 hover:text-orange-100">
{unconfirmedBookingCount}
</Badge>
</a>
</Link>
);
}
@@ -12,6 +12,7 @@ import { WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
import { localStorage } from "@calcom/lib/webstorage";
import { Icon } from "@calcom/ui/Icon";
import type { PaymentPageProps } from "../pages/payment";
@@ -21,11 +22,14 @@ const PaymentPage: FC<PaymentPageProps> = (props) => {
const { t } = useLocale();
const [is24h, setIs24h] = useState(isBrowserLocale24h());
const [date, setDate] = useState(dayjs.utc(props.booking.startTime));
const [timezone, setTimezone] = useState<string | null>(null);
useTheme(props.profile.theme);
const isEmbed = useIsEmbed();
useEffect(() => {
let embedIframeWidth = 0;
setDate(date.tz(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()));
const _timezone = localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess();
setTimezone(_timezone);
setDate(date.tz(_timezone));
setIs24h(!!getIs24hClockFromLocalStorage());
if (isEmbed) {
requestAnimationFrame(function fixStripeIframe() {
@@ -95,9 +99,7 @@ const PaymentPage: FC<PaymentPageProps> = (props) => {
{date.format("dddd, DD MMMM YYYY")}
<br />
{date.format(is24h ? "H:mm" : "h:mma")} - {props.eventType.length} mins{" "}
<span className="text-gray-500">
({localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()})
</span>
<span className="text-gray-500">({timezone})</span>
</div>
{props.booking.location && (
<>
@@ -5,6 +5,8 @@ import { PaymentData } from "@calcom/app-store/stripepayment/lib/server";
import prisma from "@calcom/prisma";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { ssrInit } from "@server/lib/ssr";
export type PaymentPageProps = inferSSRProps<typeof getServerSideProps>;
const querySchema = z.object({
@@ -12,6 +14,8 @@ const querySchema = z.object({
});
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
const { uid } = querySchema.parse(context.query);
const rawPayment = await prisma.payment.findFirst({
where: {
@@ -102,6 +106,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
user,
eventType,
booking,
trpcState: ssr.dehydrate(),
payment,
profile,
},
@@ -5,8 +5,8 @@ import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { Icon } from "@calcom/ui/Icon";
import Button from "@calcom/ui/v2/core/Button";
import ContactMenuItem from "./ContactMenuItem";
@@ -40,7 +40,7 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
};
return (
<div className="w-full border-gray-300 bg-white">
<div className="w-full rounded-md border-gray-300 bg-white">
<div className="w-full py-5">
<p className="mb-1 px-5 text-neutral-500">{t("resources").toUpperCase()}</p>
<a
@@ -53,7 +53,7 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
<Icon.FiExternalLink
className={classNames(
"text-neutral-400 group-hover:text-neutral-500",
"ml-1 h-5 w-5 flex-shrink-0 ltr:mr-3"
"ml-1 mt-px h-4 w-4 flex-shrink-0 ltr:mr-3"
)}
/>
</a>
@@ -67,7 +67,7 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
<Icon.FiExternalLink
className={classNames(
"text-neutral-400 group-hover:text-neutral-500",
"ml-1 h-5 w-5 flex-shrink-0 ltr:mr-3"
"ml-1 mt-px h-4 w-4 flex-shrink-0 ltr:mr-3"
)}
/>
</a>
@@ -6,8 +6,10 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Button, Avatar, Badge } from "@calcom/ui/v2";
import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2";
import Avatar from "@calcom/ui/v2/core/Avatar";
import Badge from "@calcom/ui/v2/core/Badge";
import Button from "@calcom/ui/v2/core/Button";
import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2/core/skeleton";
const AddNewTeamMemberSkeleton = () => {
return (
@@ -58,7 +60,7 @@ const AddNewTeamMembers = (props: { teamId: number }) => {
<Avatar
gravatarFallbackMd5="teamMember"
size="mdLg"
imageSrc={member?.avatar}
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
alt="owner-avatar"
/>
<div>
@@ -66,10 +66,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
for (const reminder of unscheduledReminders) {
if (dayjs(reminder.scheduledDate).isBefore(dateInSeventyTwoHours)) {
try {
const sendTo =
reminder.workflowStep.action === WorkflowActions.EMAIL_HOST
? reminder.booking?.user?.email
: reminder.booking?.attendees[0].email;
let sendTo;
switch (reminder.workflowStep.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = reminder.booking?.user?.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = reminder.booking?.attendees[0].email;
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo = reminder.workflowStep.sendTo;
}
const name =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
@@ -7,7 +7,17 @@ import { z } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Form, Label, Select } from "@calcom/ui/v2";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
EmailField,
Form,
Label,
Select,
} from "@calcom/ui/v2";
import { WORKFLOW_ACTIONS } from "../../lib/constants";
import { getWorkflowActionOptions } from "../../lib/getOptions";
@@ -27,13 +37,14 @@ export const AddActionDialog = (props: IAddActionDialog) => {
const { t } = useLocale();
const { isOpenDialog, setIsOpenDialog, addAction } = props;
const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState(false);
const [isEmailAddressNeeded, setIsEmailAddressNeeded] = useState(false);
const actionOptions = getWorkflowActionOptions(t);
const formSchema = z.object({
action: z.enum(WORKFLOW_ACTIONS),
sendTo: z
.string()
.refine((val) => isValidPhoneNumber(val))
.refine((val) => isValidPhoneNumber(val) || val.includes("@"))
.optional(),
});
@@ -58,6 +69,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
form.unregister("action");
setIsOpenDialog(false);
setIsPhoneNumberNeeded(false);
setIsEmailAddressNeeded(false);
}}>
<div className="mt-5 space-y-1">
<Label htmlFor="label">{t("action")}:</Label>
@@ -75,11 +87,17 @@ export const AddActionDialog = (props: IAddActionDialog) => {
form.setValue("action", val.value);
if (val.value === WorkflowActions.SMS_NUMBER) {
setIsPhoneNumberNeeded(true);
} else {
setIsEmailAddressNeeded(false);
} else if (val.value === WorkflowActions.EMAIL_ADDRESS) {
setIsEmailAddressNeeded(true);
setIsPhoneNumberNeeded(false);
} else {
setIsEmailAddressNeeded(false);
setIsPhoneNumberNeeded(false);
form.unregister("sendTo");
}
form.unregister("sendTo");
form.clearErrors("action");
form.clearErrors("sendTo");
}
}}
options={actionOptions}
@@ -109,6 +127,11 @@ export const AddActionDialog = (props: IAddActionDialog) => {
</div>
</div>
)}
{isEmailAddressNeeded && (
<div className="mt-5">
<EmailField required label={t("email_address")} {...form.register("sendTo")} />
</div>
)}
<DialogFooter>
<DialogClose asChild>
<Button
@@ -118,6 +141,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
form.unregister("sendTo");
form.unregister("action");
setIsPhoneNumberNeeded(false);
setIsEmailAddressNeeded(false);
}}>
{t("cancel")}
</Button>
@@ -8,11 +8,10 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Button, showToast, Switch, Tooltip } from "@calcom/ui/v2";
import { Button, showToast, Switch, Tooltip, EmptyScreen } from "@calcom/ui/v2";
import LicenseRequired from "../../../common/components/v2/LicenseRequired";
import { getActionIcon } from "../../lib/getActionIcon";
import EmptyScreen from "./EmptyScreen";
import SkeletonLoader from "./SkeletonLoaderEventWorkflowsTab";
import { WorkflowType } from "./WorkflowListPage";
@@ -85,6 +84,9 @@ const WorkflowListItem = (props: ItemProps) => {
case WorkflowActions.SMS_NUMBER:
sendTo.add(step.sendTo || "");
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo.add(step.sendTo || "");
break;
}
});
@@ -203,15 +205,22 @@ function EventWorkflowsTab(props: Props) {
})}
</div>
) : (
<EmptyScreen
buttonText={t("create_workflow")}
buttonOnClick={() => createMutation.mutate()}
IconHeading={Icon.FiZap}
headline={t("workflows")}
description={t("no_workflows_description")}
isLoading={createMutation.isLoading}
showExampleWorkflows={false}
/>
<div className="pt-4 before:border-0">
<EmptyScreen
Icon={Icon.FiZap}
headline={t("workflows")}
description={t("no_workflows_description")}
buttonRaw={
<Button
target="_blank"
color="secondary"
onClick={() => createMutation.mutate()}
loading={createMutation.isLoading}>
{t("create_workflow")}
</Button>
}
/>
</div>
)
) : (
<SkeletonLoader />
@@ -19,8 +19,8 @@ import { Icon } from "@calcom/ui/Icon";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { Button, DialogClose, DialogContent } from "@calcom/ui/v2";
import ConfirmationDialogContent from "@calcom/ui/v2/core/ConfirmationDialogContent";
import Select from "@calcom/ui/v2/core/form/Select";
import { Label, TextArea } from "@calcom/ui/v2/core/form/fields";
import { EmailField, Label, TextArea } from "@calcom/ui/v2/core/form/fields";
import Select from "@calcom/ui/v2/core/form/select";
import { AddVariablesDropdown } from "../../components/v2/AddVariablesDropdown";
import {
@@ -49,18 +49,29 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
step?.action === WorkflowActions.SMS_NUMBER ? true : false
);
const [isEmailAddressNeeded, setIsEmailAddressNeeded] = useState(
step?.action === WorkflowActions.EMAIL_ADDRESS ? true : false
);
const [isCustomReminderBodyNeeded, setIsCustomReminderBodyNeeded] = useState(
step?.template === WorkflowTemplates.CUSTOM ? true : false
);
const [isEmailSubjectNeeded, setIsEmailSubjectNeeded] = useState(
step?.action === WorkflowActions.EMAIL_ATTENDEE || step?.action === WorkflowActions.EMAIL_HOST
step?.action === WorkflowActions.EMAIL_ATTENDEE ||
step?.action === WorkflowActions.EMAIL_HOST ||
step?.action === WorkflowActions.EMAIL_ADDRESS
? true
: false
);
const [showTimeSection, setShowTimeSection] = useState(
form.getValues("trigger") === WorkflowTriggerEvents.BEFORE_EVENT ? true : false
form.getValues("trigger") === WorkflowTriggerEvents.BEFORE_EVENT ||
form.getValues("trigger") === WorkflowTriggerEvents.AFTER_EVENT
);
const [showTimeSectionAfter, setShowTimeSectionAfter] = useState(
form.getValues("trigger") === WorkflowTriggerEvents.AFTER_EVENT
);
const actionOptions = getWorkflowActionOptions(t);
@@ -147,12 +158,21 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
onChange={(val) => {
if (val) {
form.setValue("trigger", val.value);
if (val.value === WorkflowTriggerEvents.BEFORE_EVENT) {
if (
val.value === WorkflowTriggerEvents.BEFORE_EVENT ||
val.value === WorkflowTriggerEvents.AFTER_EVENT
) {
setShowTimeSection(true);
if (val.value === WorkflowTriggerEvents.AFTER_EVENT) {
setShowTimeSectionAfter(true);
} else {
setShowTimeSectionAfter(false);
}
form.setValue("time", 24);
form.setValue("timeUnit", TimeUnit.HOUR);
} else {
setShowTimeSection(false);
setShowTimeSectionAfter(false);
form.unregister("time");
form.unregister("timeUnit");
}
@@ -166,7 +186,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
/>
{showTimeSection && (
<div className="mt-5">
<Label>{t("how_long_before")}</Label>
<Label>{showTimeSectionAfter ? t("how_long_after") : t("how_long_before")}</Label>
<TimeTimeUnitInput form={form} />
</div>
)}
@@ -256,12 +276,20 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
if (val) {
if (val.value === WorkflowActions.SMS_NUMBER) {
setIsPhoneNumberNeeded(true);
setIsEmailAddressNeeded(false);
} else if (val.value === WorkflowActions.EMAIL_ADDRESS) {
setIsEmailAddressNeeded(true);
setIsPhoneNumberNeeded(false);
} else {
setIsEmailAddressNeeded(false);
setIsPhoneNumberNeeded(false);
}
form.unregister(`steps.${step.stepNumber - 1}.sendTo`);
form.clearErrors(`steps.${step.stepNumber - 1}.sendTo`);
if (
val.value === WorkflowActions.EMAIL_ATTENDEE ||
val.value === WorkflowActions.EMAIL_HOST
val.value === WorkflowActions.EMAIL_HOST ||
val.value === WorkflowActions.EMAIL_ADDRESS
) {
setIsEmailSubjectNeeded(true);
} else {
@@ -302,6 +330,15 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)}
</div>
)}
{isEmailAddressNeeded && (
<div className="mt-5 rounded-md bg-gray-50 p-4">
<EmailField
required
label={t("email_address")}
{...form.register(`steps.${step.stepNumber - 1}.sendTo`)}
/>
</div>
)}
<div className="mt-5">
<Label>{t("message_template")}</Label>
<Controller
@@ -437,16 +474,16 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
reminderBody,
template: step.template,
});
}
} else {
const isNumberValid =
form.formState.errors.steps &&
form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo
? false
: true;
const isNumberValid =
form.formState.errors.steps &&
form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo
? false
: true;
if (isPhoneNumberNeeded && isNumberValid && !isEmpty) {
setConfirmationDialogOpen(true);
if (isPhoneNumberNeeded && isNumberValid && !isEmpty) {
setConfirmationDialogOpen(true);
}
}
}}
color="secondary">
@@ -4,12 +4,14 @@ export const WORKFLOW_TRIGGER_EVENTS = [
WorkflowTriggerEvents.BEFORE_EVENT,
WorkflowTriggerEvents.EVENT_CANCELLED,
WorkflowTriggerEvents.NEW_EVENT,
WorkflowTriggerEvents.AFTER_EVENT,
WorkflowTriggerEvents.RESCHEDULE_EVENT,
] as const;
export const WORKFLOW_ACTIONS = [
WorkflowActions.EMAIL_HOST,
WorkflowActions.EMAIL_ATTENDEE,
WorkflowActions.EMAIL_ADDRESS,
WorkflowActions.SMS_ATTENDEE,
WorkflowActions.SMS_NUMBER,
] as const;
@@ -29,7 +29,7 @@ export const scheduleEmailReminder = async (
evt: BookingInfo,
triggerEvent: WorkflowTriggerEvents,
action: WorkflowActions,
timeBefore: {
timeSpan: {
time: number | null;
timeUnit: TimeUnit | null;
},
@@ -42,11 +42,15 @@ export const scheduleEmailReminder = async (
const { startTime, endTime } = evt;
const uid = evt.uid as string;
const currentDate = dayjs();
const timeUnit: timeUnitLowerCase | undefined =
timeBefore.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase;
const scheduledDate =
timeBefore.time && timeUnit ? dayjs(startTime).subtract(timeBefore.time, timeUnit) : null;
const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase;
let scheduledDate = null;
if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) {
scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null;
} else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) {
scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null;
}
if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) {
console.error("Sendgrid credentials are missing from the .env file");
return;
@@ -57,9 +61,26 @@ export const scheduleEmailReminder = async (
method: "POST",
});
const name = action === WorkflowActions.EMAIL_HOST ? evt.organizer.name : evt.attendees[0].name;
const attendeeName = action === WorkflowActions.EMAIL_HOST ? evt.attendees[0].name : evt.organizer.name;
const timeZone = action === WorkflowActions.EMAIL_HOST ? evt.organizer.timeZone : evt.attendees[0].timeZone;
let name = "";
let attendeeName = "";
let timeZone = "";
switch (action) {
case WorkflowActions.EMAIL_HOST:
name = evt.organizer.name;
attendeeName = evt.attendees[0].name;
timeZone = evt.organizer.timeZone;
break;
case WorkflowActions.EMAIL_ATTENDEE:
name = evt.attendees[0].name;
attendeeName = evt.organizer.name;
timeZone = evt.attendees[0].timeZone;
break;
case WorkflowActions.EMAIL_ADDRESS:
name = "";
attendeeName = evt.attendees[0].name;
timeZone = evt.organizer.timeZone;
}
let emailContent = {
emailSubject,
@@ -113,7 +134,11 @@ export const scheduleEmailReminder = async (
} catch (error) {
console.log("Error sending Email");
}
} else if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT && scheduledDate) {
} else if (
(triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT ||
triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) &&
scheduledDate
) {
// Sendgrid to schedule emails
// Can only schedule at least 60 minutes and at most 72 hours in advance
if (
@@ -30,7 +30,8 @@ export const scheduleWorkflowReminders = async (
if (
workflow.trigger === WorkflowTriggerEvents.BEFORE_EVENT ||
(workflow.trigger === WorkflowTriggerEvents.NEW_EVENT && !isRescheduleEvent) ||
(workflow.trigger === WorkflowTriggerEvents.RESCHEDULE_EVENT && isRescheduleEvent)
(workflow.trigger === WorkflowTriggerEvents.RESCHEDULE_EVENT && isRescheduleEvent) ||
workflow.trigger === WorkflowTriggerEvents.AFTER_EVENT
) {
workflow.steps.forEach(async (step) => {
if (step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.SMS_NUMBER) {
@@ -50,10 +51,21 @@ export const scheduleWorkflowReminders = async (
);
} else if (
step.action === WorkflowActions.EMAIL_ATTENDEE ||
step.action === WorkflowActions.EMAIL_HOST
step.action === WorkflowActions.EMAIL_HOST ||
step.action === WorkflowActions.EMAIL_ADDRESS
) {
const sendTo =
step.action === WorkflowActions.EMAIL_HOST ? evt.organizer.email : evt.attendees[0].email;
let sendTo = "";
switch (step.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = evt.organizer.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = evt.attendees[0].email;
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo = step.sendTo || "";
}
scheduleEmailReminder(
evt,
workflow.trigger,
@@ -107,10 +119,21 @@ export const sendCancelledReminders = async (
);
} else if (
step.action === WorkflowActions.EMAIL_ATTENDEE ||
step.action === WorkflowActions.EMAIL_HOST
step.action === WorkflowActions.EMAIL_HOST ||
step.action === WorkflowActions.EMAIL_ADDRESS
) {
const sendTo =
step.action === WorkflowActions.EMAIL_HOST ? evt.organizer.email : evt.attendees[0].email;
let sendTo = "";
switch (step.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = evt.organizer.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = evt.attendees[0].email;
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo = step.sendTo || "";
}
scheduleEmailReminder(
evt,
workflow.trigger,
@@ -42,7 +42,7 @@ export const scheduleSMSReminder = async (
reminderPhone: string | null,
triggerEvent: WorkflowTriggerEvents,
action: WorkflowActions,
timeBefore: {
timeSpan: {
time: number | null;
timeUnit: TimeUnit | null;
},
@@ -50,13 +50,17 @@ export const scheduleSMSReminder = async (
workflowStepId: number,
template: WorkflowTemplates
) => {
const { startTime } = evt;
const { startTime, endTime } = evt;
const uid = evt.uid as string;
const currentDate = dayjs();
const timeUnit: timeUnitLowerCase | undefined =
timeBefore.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase;
const scheduledDate =
timeBefore.time && timeUnit ? dayjs(startTime).subtract(timeBefore.time, timeUnit) : null;
const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase;
let scheduledDate = null;
if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) {
scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null;
} else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) {
scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null;
}
const name = action === WorkflowActions.SMS_ATTENDEE ? evt.attendees[0].name : "";
const attendeeName = action === WorkflowActions.SMS_ATTENDEE ? evt.organizer.name : evt.attendees[0].name;
@@ -96,7 +100,11 @@ export const scheduleSMSReminder = async (
} catch (error) {
console.log(`Error sending SMS with error ${error}`);
}
} else if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT && scheduledDate) {
} else if (
(triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT ||
triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) &&
scheduledDate
) {
// Can only schedule at least 60 minutes in advance and at most 7 days in advance
if (
currentDate.isBefore(scheduledDate.subtract(1, "hour")) &&
@@ -18,7 +18,9 @@ const emailReminderTemplate = (
.tz(timeZone)
.format("YYYY MMM D")} at ${dayjs(startTime).tz(timeZone).format("h:mmA")} ${timeZone}.`;
const introHtml = `<body>Hi ${name},<br><br>This is a reminder about your upcoming event.<br><br>`;
const introHtml = `<body>Hi${
name ? " " + name : ""
},<br><br>This is a reminder about your upcoming event.<br><br>`;
const eventHtml = `<div style="font-weight: bold;">Event:</div>${eventName}<br><br>`;
@@ -55,7 +55,7 @@ const formSchema = z.object({
template: z.nativeEnum(WorkflowTemplates),
sendTo: z
.string()
.refine((val) => isValidPhoneNumber(val))
.refine((val) => isValidPhoneNumber(val) || val.includes("@"))
.nullable(),
})
.array(),
@@ -77,6 +77,7 @@ function WorkflowPage() {
mode: "onBlur",
resolver: zodResolver(formSchema),
});
const { workflow: workflowId } = router.isReady ? querySchema.parse(router.query) : { workflow: -1 };
const utils = trpc.useContext();
@@ -8,8 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput } from "@calcom/trpc/react";
import Button from "@calcom/ui/v2/core/Button";
import Switch from "@calcom/ui/v2/core/Switch";
import Select from "@calcom/ui/v2/core/form/Select";
import { Form, Label, TextArea, TextField } from "@calcom/ui/v2/core/form/fields";
import Select from "@calcom/ui/v2/core/form/select";
import customTemplate, { hasTemplateIntegration } from "../utils/integrationTemplate";
import WebhookTestDisclosure from "./WebhookTestDisclosure";
@@ -12,7 +12,6 @@ export async function getTeamWithMembers(id?: number, slug?: string) {
id: true,
plan: true,
bio: true,
avatar: true,
});
const teamSelect = Prisma.validator<Prisma.TeamSelect>()({
+17 -4
View File
@@ -42,7 +42,9 @@ const splitAvailableTime = (
const getSlots = ({ inviteeDate, frequency, minimumBookingNotice, workingHours, eventLength }: GetSlots) => {
// current date in invitee tz
const startDate = dayjs().add(minimumBookingNotice, "minute");
const startOfDayUTC = dayjs.utc().startOf("day");
// This code is ran client side, startOf() does some conversions based on the
// local tz of the client. Sometimes this shifts the day incorrectly.
const startOfDayUTC = dayjs.utc().set("hour", 0).set("minute", 0).set("second", 0);
const startOfInviteeDay = inviteeDate.startOf("day");
// checks if the start date is in the past
@@ -61,9 +63,20 @@ const getSlots = ({ inviteeDate, frequency, minimumBookingNotice, workingHours,
startTime: /* Why? */ startOfDayUTC.add(schedule.startTime, "minute"),
endTime: /* Why? */ startOfDayUTC.add(schedule.endTime, "minute"),
}));
const localWorkingHours = getWorkingHours({ utcOffset: -inviteeDate.utcOffset() }, workingHoursUTC).filter(
(hours) => hours.days.includes(inviteeDate.day())
);
// Dayjs does not expose the timeZone value publicly through .get("timeZone")
// instead, we as devs are required to somewhat hack our way to get the ...
// tz value as string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const timeZone: string = (inviteeDate as any)["$x"]["$timezone"];
const localWorkingHours = getWorkingHours(
{
// initialize current day with timeZone without conversion, just parse.
utcOffset: -dayjs.tz(dayjs(), timeZone).utcOffset(),
},
workingHoursUTC
).filter((hours) => hours.days.includes(inviteeDate.day()));
const slots: Dayjs[] = [];
+111 -1
View File
@@ -1,5 +1,5 @@
import { faker } from "@faker-js/faker";
import { Prisma, User, UserPlan } from "@prisma/client";
import { Booking, BookingStatus, EventType, Prisma, UserPlan, Webhook } from "@prisma/client";
import { CalendarEvent, Person, VideoCallData } from "@calcom/types/Calendar";
@@ -28,6 +28,116 @@ export const buildPerson = (person?: Partial<Person>): Person => {
};
};
export const buildBooking = (booking?: Partial<Booking>): Booking => {
return {
id: faker.datatype.number(),
uid: faker.datatype.uuid(),
userId: null,
eventTypeId: null,
title: faker.lorem.sentence(),
description: faker.lorem.paragraph(),
customInputs: null,
startTime: faker.date.future(),
endTime: faker.date.future(),
location: null,
createdAt: new Date(),
updatedAt: null,
status: BookingStatus.ACCEPTED,
paid: false,
destinationCalendarId: null,
cancellationReason: null,
rejectionReason: null,
dynamicEventSlugRef: null,
dynamicGroupSlugRef: null,
rescheduled: null,
fromReschedule: null,
recurringEventId: null,
smsReminderNumber: null,
scheduledJobs: [],
...booking,
};
};
export const buildEventType = (eventType?: Partial<EventType>): EventType => {
return {
id: faker.datatype.number(),
title: faker.lorem.sentence(),
slug: faker.lorem.slug(),
description: faker.lorem.paragraph(),
position: 1,
locations: null,
length: 15,
hidden: false,
userId: null,
teamId: null,
eventName: faker.lorem.words(),
timeZone: null,
periodType: "UNLIMITED",
periodStartDate: null,
periodEndDate: null,
periodDays: null,
periodCountCalendarDays: null,
recurringEvent: null,
requiresConfirmation: false,
disableGuests: false,
hideCalendarNotes: false,
minimumBookingNotice: 120,
beforeEventBuffer: 0,
afterEventBuffer: 0,
seatsPerTimeSlot: null,
schedulingType: null,
scheduleId: null,
price: 0,
currency: "usd",
slotInterval: null,
metadata: null,
successRedirectUrl: null,
...eventType,
};
};
export const buildWebhook = (webhook?: Partial<Webhook>): Webhook => {
return {
id: faker.datatype.uuid(),
eventTypeId: faker.datatype.number(),
subscriberUrl: "http://mockedURL.com",
payloadTemplate: null,
createdAt: faker.datatype.datetime(),
appId: null,
userId: null,
secret: faker.lorem.slug(),
active: true,
eventTriggers: [],
...webhook,
};
};
export const buildSubscriberEvent = (booking?: Partial<Booking>) => {
return {
type: booking?.title || "",
title: booking?.title,
description: "",
additionalNotes: "",
customInputs: {},
startTime: booking?.startTime,
endTime: booking?.endTime,
organizer: {
name: "",
email: "",
timeZone: "",
language: {
locale: "en",
},
},
attendees: [],
location: "",
destinationCalendar: null,
hideCalendar: false,
uid: booking?.uid,
metadata: {},
};
};
export const buildCalendarEvent = (event?: Partial<CalendarEvent>): CalendarEvent => {
return {
uid: faker.datatype.uuid(),
@@ -0,0 +1 @@
UPDATE "Credential" SET "type" = 'routing-forms_other' WHERE "type" = 'routing_forms_other';
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "WorkflowTriggerEvents" ADD VALUE 'AFTER_EVENT';
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "WorkflowActions" ADD VALUE 'EMAIL_ADDRESS';
+2
View File
@@ -531,6 +531,7 @@ enum WorkflowTriggerEvents {
BEFORE_EVENT
EVENT_CANCELLED
NEW_EVENT
AFTER_EVENT
RESCHEDULE_EVENT
}
@@ -539,6 +540,7 @@ enum WorkflowActions {
EMAIL_ATTENDEE
SMS_ATTENDEE
SMS_NUMBER
EMAIL_ADDRESS
}
model WorkflowStep {
+1 -1
View File
@@ -55,7 +55,7 @@
"type": "raycast_other"
},
{
"dirName": "n8n",
"dirName": "n8n",
"categories": ["automation"],
"slug": "n8n",
"type": "n8n_automation"
+6 -6
View File
@@ -291,7 +291,7 @@ async function main() {
recurringEventId: Buffer.from("yoga-class").toString("base64"),
startTime: dayjs().add(1, "day").toDate(),
endTime: dayjs().add(1, "day").add(30, "minutes").toDate(),
status: BookingStatus.PENDING,
status: BookingStatus.ACCEPTED,
},
{
uid: uuid(),
@@ -299,7 +299,7 @@ async function main() {
recurringEventId: Buffer.from("yoga-class").toString("base64"),
startTime: dayjs().add(1, "day").add(1, "week").toDate(),
endTime: dayjs().add(1, "day").add(1, "week").add(30, "minutes").toDate(),
status: BookingStatus.PENDING,
status: BookingStatus.ACCEPTED,
},
{
uid: uuid(),
@@ -307,7 +307,7 @@ async function main() {
recurringEventId: Buffer.from("yoga-class").toString("base64"),
startTime: dayjs().add(1, "day").add(2, "week").toDate(),
endTime: dayjs().add(1, "day").add(2, "week").add(30, "minutes").toDate(),
status: BookingStatus.PENDING,
status: BookingStatus.ACCEPTED,
},
{
uid: uuid(),
@@ -315,7 +315,7 @@ async function main() {
recurringEventId: Buffer.from("yoga-class").toString("base64"),
startTime: dayjs().add(1, "day").add(3, "week").toDate(),
endTime: dayjs().add(1, "day").add(3, "week").add(30, "minutes").toDate(),
status: BookingStatus.PENDING,
status: BookingStatus.ACCEPTED,
},
{
uid: uuid(),
@@ -323,7 +323,7 @@ async function main() {
recurringEventId: Buffer.from("yoga-class").toString("base64"),
startTime: dayjs().add(1, "day").add(4, "week").toDate(),
endTime: dayjs().add(1, "day").add(4, "week").add(30, "minutes").toDate(),
status: BookingStatus.PENDING,
status: BookingStatus.ACCEPTED,
},
{
uid: uuid(),
@@ -331,7 +331,7 @@ async function main() {
recurringEventId: Buffer.from("yoga-class").toString("base64"),
startTime: dayjs().add(1, "day").add(5, "week").toDate(),
endTime: dayjs().add(1, "day").add(5, "week").add(30, "minutes").toDate(),
status: BookingStatus.PENDING,
status: BookingStatus.ACCEPTED,
},
],
},
+92 -2
View File
@@ -1,6 +1,15 @@
import { z } from "zod";
import z, { ZodNullable, ZodObject, ZodOptional } from "zod";
/* eslint-disable no-underscore-dangle */
import type {
objectInputType,
objectOutputType,
ZodNullableDef,
ZodOptionalDef,
ZodRawShape,
ZodTypeAny,
} from "zod";
import { LocationType } from "@calcom/app-store/locations";
import dayjs from "@calcom/dayjs";
import { slugify } from "@calcom/lib/slugify";
@@ -147,3 +156,84 @@ export const successRedirectUrl = z
.regex(/^http(s)?:\/\/.*/),
])
.optional();
export type ZodDenullish<T extends ZodTypeAny> = T extends ZodNullable<infer U> | ZodOptional<infer U>
? ZodDenullish<U>
: T;
export type ZodDenullishShape<T extends ZodRawShape> = {
[k in keyof T]: ZodDenullish<T[k]>;
};
export const denullish = <T extends ZodTypeAny>(schema: T): ZodDenullish<T> =>
(schema instanceof ZodNullable || schema instanceof ZodOptional
? denullish((schema._def as ZodNullableDef | ZodOptionalDef).innerType)
: schema) as ZodDenullish<T>;
type UnknownKeysParam = "passthrough" | "strict" | "strip";
/**
* @see https://github.com/3x071c/lsg-remix/blob/e2a9592ba3ec5103556f2cf307c32f08aeaee32d/app/lib/util/zod.ts
*/
export function denullishShape<
T extends ZodRawShape,
UnknownKeys extends UnknownKeysParam = "strip",
Catchall extends ZodTypeAny = ZodTypeAny,
Output = objectOutputType<T, Catchall>,
Input = objectInputType<T, Catchall>
>(
obj: ZodObject<T, UnknownKeys, Catchall, Output, Input>
): ZodObject<ZodDenullishShape<T>, UnknownKeys, Catchall> {
const a = entries(obj.shape).map(([field, schema]) => [field, denullish(schema)] as const) as {
[K in keyof T]: [K, ZodDenullish<T[K]>];
}[keyof T][];
return new ZodObject({
...obj._def,
shape: () => fromEntries(a) as unknown as ZodDenullishShape<T>, // TODO: Safely assert type
});
}
/**
* Like Object.entries, but with actually useful typings
* @param obj The object to turn into a tuple array (`[key, value][]`)
* @returns The constructed tuple array from the given object
* @see https://github.com/3x071c/lsg-remix/blob/e2a9592ba3ec5103556f2cf307c32f08aeaee32d/app/lib/util/entries.ts
*/
export const entries = <O>(
obj: O
): {
readonly [K in keyof O]: [K, O[K]];
}[keyof O][] => {
return Object.entries(obj) as {
[K in keyof O]: [K, O[K]];
}[keyof O][];
};
/**
* Returns a type with all readonly notations removed (traverses recursively on an object)
*/
type DeepWriteable<T> = T extends Readonly<{
-readonly [K in keyof T]: T[K];
}>
? {
-readonly [K in keyof T]: DeepWriteable<T[K]>;
}
: T; /* Make it work with readonly types (this is not strictly necessary) */
type FromEntries<T> = T extends [infer Keys, unknown][]
? { [K in Keys & PropertyKey]: Extract<T[number], [K, unknown]>[1] }
: never;
/**
* Like Object.fromEntries, but with actually useful typings
* @param arr The tuple array (`[key, value][]`) to turn into an object
* @returns Object constructed from the given entries
* @see https://github.com/3x071c/lsg-remix/blob/e2a9592ba3ec5103556f2cf307c32f08aeaee32d/app/lib/util/fromEntries.ts
*/
export const fromEntries = <
E extends [PropertyKey, unknown][] | ReadonlyArray<readonly [PropertyKey, unknown]>
>(
entries: E
): FromEntries<DeepWriteable<E>> => {
return Object.fromEntries(entries) as FromEntries<DeepWriteable<E>>;
};
+64 -34
View File
@@ -1,7 +1,7 @@
import { AppCategories, BookingStatus, IdentityProvider, MembershipRole, Prisma } from "@prisma/client";
import _ from "lodash";
import { authenticator } from "otplib";
import { z } from "zod";
import z from "zod";
import app_RoutingForms from "@calcom/app-store/ee/routing-forms/trpc-router";
import ethRouter from "@calcom/app-store/rainbow/trpc/router";
@@ -454,20 +454,12 @@ const loggedInViewerRouter = createProtectedRouter()
// handled separately for each occurrence
OR: [
{
AND: [
{ NOT: { recurringEventId: { equals: null } } },
{
status: {
notIn: [BookingStatus.PENDING, BookingStatus.CANCELLED, BookingStatus.REJECTED],
},
},
],
recurringEventId: { not: null },
status: { notIn: [BookingStatus.PENDING, BookingStatus.CANCELLED, BookingStatus.REJECTED] },
},
{
AND: [
{ recurringEventId: { equals: null } },
{ status: { notIn: [BookingStatus.CANCELLED, BookingStatus.REJECTED] } },
],
recurringEventId: { equals: null },
status: { notIn: [BookingStatus.CANCELLED, BookingStatus.REJECTED] },
},
],
},
@@ -493,7 +485,15 @@ const loggedInViewerRouter = createProtectedRouter()
},
unconfirmed: {
endTime: { gte: new Date() },
status: { equals: BookingStatus.PENDING },
OR: [
{
recurringEventId: { not: null },
status: { equals: BookingStatus.PENDING },
},
{
status: { equals: BookingStatus.PENDING },
},
],
},
};
const bookingListingOrderby: Record<
@@ -572,7 +572,26 @@ const loggedInViewerRouter = createProtectedRouter()
skip,
});
let bookings = bookingsQuery.map((booking) => {
const recurringInfo = await prisma.booking.groupBy({
by: ["recurringEventId"],
_min: {
startTime: true,
},
_count: {
recurringEventId: true,
},
where: {
recurringEventId: {
not: { equals: null },
},
status: {
notIn: [BookingStatus.CANCELLED],
},
userId: user.id,
},
});
const bookings = bookingsQuery.map((booking) => {
return {
...booking,
eventType: {
@@ -583,26 +602,8 @@ const loggedInViewerRouter = createProtectedRouter()
endTime: booking.endTime.toISOString(),
};
});
const bookingsFetched = bookings.length;
const seenBookings: Record<string, boolean> = {};
// Remove duplicate recurring bookings for upcoming status.
// Couldn't use distinct in query because the distinct column would be different for recurring and non recurring event.
// We might be actually sending less then the limit, due to this filter
// TODO: Figure out a way to fix it.
if (bookingListingByStatus === "upcoming") {
bookings = bookings.filter((booking) => {
if (!booking.recurringEventId) {
return true;
}
if (seenBookings[booking.recurringEventId]) {
return false;
}
seenBookings[booking.recurringEventId] = true;
return true;
});
}
let nextCursor: typeof skip | null = skip;
if (bookingsFetched > take) {
nextCursor += bookingsFetched;
@@ -612,6 +613,7 @@ const loggedInViewerRouter = createProtectedRouter()
return {
bookings,
recurringInfo,
nextCursor,
};
},
@@ -1436,6 +1438,34 @@ const loggedInViewerRouter = createProtectedRouter()
},
});
},
})
.query("bookingUnconfirmedCount", {
async resolve({ ctx }) {
const { prisma, user } = ctx;
const count = await prisma.booking.count({
where: {
status: BookingStatus.PENDING,
userId: user.id,
endTime: { gt: new Date() },
},
});
const recurringGrouping = await prisma.booking.groupBy({
by: ["recurringEventId"],
_count: {
recurringEventId: true,
},
where: {
recurringEventId: { not: { equals: null } },
status: { equals: "PENDING" },
userId: user.id,
},
});
return recurringGrouping.reduce((prev, current) => {
// recurringEventId is the total number of recurring instances for a booking
// we need to substract all but one, to represent a single recurring booking
return prev - (current._count?.recurringEventId - 1);
}, count);
},
});
export const viewerRouter = createRouter()
@@ -20,6 +20,7 @@ import getWebhooks from "@calcom/features/webhooks/utils/getWebhooks";
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { bookingConfirmPatchBodySchema } from "@calcom/prisma/zod-utils";
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
@@ -280,7 +280,7 @@ export const eventTypesRouter = createProtectedRouter()
until: recurringEvent.until as unknown as Prisma.InputJsonObject,
tzid: recurringEvent.tzid,
};
} else {
} else if (recurringEvent === null) {
data.recurringEvent = Prisma.DbNull;
}
@@ -372,7 +372,7 @@ export const workflowsRouter = createProtectedRouter()
let newEventTypes: number[] = [];
if (activeOn.length) {
if (trigger === WorkflowTriggerEvents.BEFORE_EVENT) {
if (trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT) {
newEventTypes = newActiveEventTypes;
}
if (newEventTypes.length > 0) {
@@ -416,15 +416,25 @@ export const workflowsRouter = createProtectedRouter()
};
if (
step.action === WorkflowActions.EMAIL_HOST ||
step.action === WorkflowActions.EMAIL_ATTENDEE
step.action === WorkflowActions.EMAIL_ATTENDEE ||
step.action === WorkflowActions.EMAIL_ADDRESS
) {
const sendTo =
step.action === WorkflowActions.EMAIL_HOST
? bookingInfo.organizer?.email
: bookingInfo.attendees[0].email;
let sendTo = "";
switch (step.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = bookingInfo.organizer?.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = bookingInfo.attendees[0].email;
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo = step.sendTo || "";
}
await scheduleEmailReminder(
bookingInfo,
WorkflowTriggerEvents.BEFORE_EVENT,
trigger,
step.action,
{
time,
@@ -440,7 +450,7 @@ export const workflowsRouter = createProtectedRouter()
await scheduleSMSReminder(
bookingInfo,
step.sendTo || "",
WorkflowTriggerEvents.BEFORE_EVENT,
trigger,
step.action,
{
time,
@@ -503,7 +513,11 @@ export const workflowsRouter = createProtectedRouter()
},
data: {
action: newStep.action,
sendTo: newStep.action === WorkflowActions.SMS_NUMBER ? newStep.sendTo : null,
sendTo:
newStep.action === WorkflowActions.SMS_NUMBER ||
newStep.action === WorkflowActions.EMAIL_ADDRESS
? newStep.sendTo
: null,
stepNumber: newStep.stepNumber,
workflowId: newStep.workflowId,
reminderBody: newStep.template === WorkflowTemplates.CUSTOM ? newStep.reminderBody : null,
@@ -536,7 +550,10 @@ export const workflowsRouter = createProtectedRouter()
return eventTypeId;
}
});
if (eventTypesToUpdateReminders && trigger === WorkflowTriggerEvents.BEFORE_EVENT) {
if (
eventTypesToUpdateReminders &&
(trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT)
) {
const bookingsOfEventTypes = await ctx.prisma.booking.findMany({
where: {
eventTypeId: {
@@ -574,15 +591,25 @@ export const workflowsRouter = createProtectedRouter()
};
if (
newStep.action === WorkflowActions.EMAIL_HOST ||
newStep.action === WorkflowActions.EMAIL_ATTENDEE
newStep.action === WorkflowActions.EMAIL_ATTENDEE ||
newStep.action === WorkflowActions.EMAIL_ADDRESS
) {
const sendTo =
newStep.action === WorkflowActions.EMAIL_HOST
? bookingInfo.organizer?.email
: bookingInfo.attendees[0].email;
let sendTo = "";
switch (newStep.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = bookingInfo.organizer?.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = bookingInfo.attendees[0].email;
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo = newStep.sendTo || "";
}
await scheduleEmailReminder(
bookingInfo,
WorkflowTriggerEvents.BEFORE_EVENT,
trigger,
newStep.action,
{
time,
@@ -598,7 +625,7 @@ export const workflowsRouter = createProtectedRouter()
await scheduleSMSReminder(
bookingInfo,
newStep.sendTo || "",
WorkflowTriggerEvents.BEFORE_EVENT,
trigger,
newStep.action,
{
time,
@@ -637,7 +664,8 @@ export const workflowsRouter = createProtectedRouter()
data: step,
});
if (
trigger === WorkflowTriggerEvents.BEFORE_EVENT &&
(trigger === WorkflowTriggerEvents.BEFORE_EVENT ||
trigger === WorkflowTriggerEvents.AFTER_EVENT) &&
eventTypesToCreateReminders &&
step.action !== WorkflowActions.SMS_ATTENDEE
) {
@@ -677,12 +705,22 @@ export const workflowsRouter = createProtectedRouter()
if (
step.action === WorkflowActions.EMAIL_ATTENDEE ||
step.action === WorkflowActions.EMAIL_HOST
step.action === WorkflowActions.EMAIL_HOST ||
step.action === WorkflowActions.EMAIL_ADDRESS
) {
const sendTo =
step.action === WorkflowActions.EMAIL_HOST
? bookingInfo.organizer?.email
: bookingInfo.attendees[0].email;
let sendTo = "";
switch (step.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = bookingInfo.organizer?.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = bookingInfo.attendees[0].email;
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo = step.sendTo || "";
}
await scheduleEmailReminder(
bookingInfo,
trigger,
@@ -701,7 +739,7 @@ export const workflowsRouter = createProtectedRouter()
await scheduleSMSReminder(
bookingInfo,
step.sendTo,
WorkflowTriggerEvents.BEFORE_EVENT,
trigger,
step.action,
{
time,
@@ -817,7 +855,11 @@ export const workflowsRouter = createProtectedRouter()
};
}
if (action === WorkflowActions.EMAIL_ATTENDEE || action === WorkflowActions.EMAIL_HOST) {
if (
action === WorkflowActions.EMAIL_ATTENDEE ||
action === WorkflowActions.EMAIL_HOST ||
action === WorkflowActions.EMAIL_ADDRESS
) {
scheduleEmailReminder(
evt,
WorkflowTriggerEvents.NEW_EVENT,

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