Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8143816c18 | ||
|
|
8243ac3c00 | ||
|
|
207e120d66 | ||
|
|
b25d04da1c | ||
|
|
7064acf756 | ||
|
|
716407ad90 | ||
|
|
af0b08bb2b | ||
|
|
3e27735ccb | ||
|
|
76c087f29c | ||
|
|
e05bcf98c2 | ||
|
|
636e88d467 | ||
|
|
34a61214d3 | ||
|
|
b1af30e026 | ||
|
|
870b59d617 | ||
|
|
e25094ccab | ||
|
|
1376f0991f | ||
|
|
443329b99f | ||
|
|
a1fc4ec26d | ||
|
|
d462298e79 | ||
|
|
965cff38c2 | ||
|
|
f96e909dce | ||
|
|
a4d6f7da62 | ||
|
|
ce9413e0dd | ||
|
|
8fc0dfb763 | ||
|
|
690edcf0a7 | ||
|
|
b7dbf10068 | ||
|
|
83b4ba94fc | ||
|
|
62a09e7098 | ||
|
|
d2c4e2894d | ||
|
|
070d314897 | ||
|
|
0a76368e98 | ||
|
|
99287d9eb4 | ||
|
|
100035cb89 | ||
|
|
1750dbead6 | ||
|
|
4c8ed3d11f | ||
|
|
f21c4eb11e | ||
|
|
3f71a41d7e | ||
|
|
1ba191c4ed | ||
|
|
6414903567 | ||
|
|
e62d8b4596 | ||
|
|
e27b1b1bc2 | ||
|
|
9507138f29 | ||
|
|
e5fa202a09 | ||
|
|
447098bc97 | ||
|
|
dc5e042cdd | ||
|
|
92fb365b54 | ||
|
|
161f5c7000 | ||
|
|
c8744768c4 | ||
|
|
b34a568a93 | ||
|
|
a669403604 | ||
|
|
7b2bfc2a64 | ||
|
|
7655c0c867 | ||
|
|
6fbabb4b26 | ||
|
|
edfc9ff082 | ||
|
|
59a9a77e3f | ||
|
|
8e756d7648 | ||
|
|
5763c1c2f1 | ||
|
|
6c5ae0b4d9 | ||
|
|
9cb9ce2e42 | ||
|
|
018be43017 | ||
|
|
4f720c8c9b | ||
|
|
06e1e113f0 | ||
|
|
3fc6f38da9 |
+7
-1
@@ -31,11 +31,14 @@ NEXT_PUBLIC_CONSOLE_URL='http://localhost:3004'
|
||||
NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js'
|
||||
|
||||
# To enable SAML login, set both these variables
|
||||
# @see https://github.com/calcom/cal.com/tree/main/packages/ee#setting-up-saml-login
|
||||
# @see https://github.com/calcom/cal.com/tree/main/packages/features/ee#setting-up-saml-login
|
||||
# SAML_DATABASE_URL="postgresql://postgres:@localhost:5450/cal-saml"
|
||||
SAML_DATABASE_URL=
|
||||
# SAML_ADMINS='pro@example.com'
|
||||
SAML_ADMINS=
|
||||
# NEXT_PUBLIC_HOSTED_CAL_FEATURES=1
|
||||
NEXT_PUBLIC_HOSTED_CAL_FEATURES=
|
||||
|
||||
# If you use Heroku to deploy Postgres (or use self-signed certs for Postgres) then uncomment the follow line.
|
||||
# @see https://devcenter.heroku.com/articles/connecting-heroku-postgres#connecting-in-node-js
|
||||
# PGSSLMODE='no-verify'
|
||||
@@ -149,3 +152,6 @@ SENTRY_IGNORE_API_RESOLUTION_ERROR=
|
||||
NEXT_PUBLIC_APP_NAME="Cal.com"
|
||||
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com"
|
||||
NEXT_PUBLIC_COMPANY_NAME="Cal.com, Inc."
|
||||
# Set this to true in to disable new signups
|
||||
# NEXT_PUBLIC_DISABLE_SIGNUP=true
|
||||
NEXT_PUBLIC_DISABLE_SIGNUP=
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
"♻️ autoupdate":
|
||||
- apps/web/public/static/locales/**/*.json
|
||||
|
||||
automerge:
|
||||
- apps/web/public/static/locales/**/*.json
|
||||
|
||||
"❗️ migrations":
|
||||
- packages/prisma/migrations/**/migration.sql
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
required-checks:
|
||||
uses: calcom/cal.com/.github/workflows/required-checks.yml@main
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -21,8 +23,8 @@ jobs:
|
||||
with:
|
||||
upload_translations: true
|
||||
download_translations: true
|
||||
pull_request_labels: "♻️ autoupdate, automerge"
|
||||
env:
|
||||
# So required checks run on Crowdin PRs
|
||||
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
paths:
|
||||
- apps/web/**
|
||||
- packages/embeds/**
|
||||
workflow_call:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
name: E2E test
|
||||
on:
|
||||
push:
|
||||
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
|
||||
pull_request_target: # So we can test on forks
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- apps/web/**
|
||||
- packages/**
|
||||
workflow_call:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
name: "Meta Workflow: Require Conditional Status Checks"
|
||||
on:
|
||||
push:
|
||||
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_call:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
name: Unit tests
|
||||
on:
|
||||
push:
|
||||
branches: [ feat/routing-forms/reporting ] # TODO: Remove this after merged in main
|
||||
pull_request_target: # So we can test on forks
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- apps/web/**
|
||||
- packages/**
|
||||
workflow_run:
|
||||
workflows: [Crowdin Action]
|
||||
types: [completed]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
+1
-1
Submodule apps/console updated: 8dd0779c43...9aac72159e
@@ -504,7 +504,7 @@ const tabs = [
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<small className="flex py-4 text-neutral-500">
|
||||
<small className="flex py-4 text-gray-500">
|
||||
{t("place_where_cal_widget_appear", { appName: APP_NAME })}
|
||||
</small>
|
||||
</div>
|
||||
@@ -556,7 +556,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<small className="flex py-4 text-neutral-500">{t("create_update_react_component")}</small>
|
||||
<small className="flex py-4 text-gray-500">{t("create_update_react_component")}</small>
|
||||
<TextArea
|
||||
data-testid="embed-react"
|
||||
ref={ref as typeof ref & MutableRefObject<HTMLTextAreaElement>}
|
||||
@@ -650,7 +650,7 @@ const ChooseEmbedTypesDialogContent = () => {
|
||||
<div className="order-none box-border flex-none rounded-sm border border-solid bg-white">
|
||||
{embed.illustration}
|
||||
</div>
|
||||
<div className="mt-2 font-medium text-neutral-900">{embed.title}</div>
|
||||
<div className="mt-2 font-medium text-gray-900">{embed.title}</div>
|
||||
<p className="text-sm text-gray-500">{embed.subtitle}</p>
|
||||
</button>
|
||||
))}
|
||||
@@ -827,7 +827,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
onOpenChange={() => setIsEmbedCustomizationOpen((val) => !val)}>
|
||||
<CollapsibleTrigger
|
||||
type="button"
|
||||
className="flex w-full items-center text-base font-medium text-neutral-900">
|
||||
className="flex w-full items-center text-base font-medium text-gray-900">
|
||||
<div>
|
||||
{embedType === "inline"
|
||||
? "Inline Embed Customization"
|
||||
@@ -838,7 +838,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
<Icon.FiChevronRight
|
||||
className={`${
|
||||
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
|
||||
} ml-auto h-5 w-5 text-neutral-500`}
|
||||
} ml-auto h-5 w-5 text-gray-500`}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="text-sm">
|
||||
@@ -1001,11 +1001,11 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
open={isBookingCustomizationOpen}
|
||||
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
|
||||
<CollapsibleTrigger className="flex w-full" type="button">
|
||||
<div className="text-base font-medium text-neutral-900">Cal Booking Customization</div>
|
||||
<div className="text-base font-medium text-gray-900">Cal Booking Customization</div>
|
||||
<Icon.FiChevronRight
|
||||
className={`${
|
||||
isBookingCustomizationOpen ? "rotate-90 transform" : ""
|
||||
} ml-auto h-5 w-5 text-neutral-500`}
|
||||
} ml-auto h-5 w-5 text-gray-500`}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
|
||||
@@ -67,7 +67,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
||||
onClick={onClick}
|
||||
className={classNames(
|
||||
isCurrent
|
||||
? "border-neutral-900 text-neutral-900"
|
||||
? "border-neutral-900 text-gray-900"
|
||||
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
|
||||
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
|
||||
className
|
||||
@@ -76,7 +76,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
||||
{tab.icon && (
|
||||
<tab.icon
|
||||
className={classNames(
|
||||
isCurrent ? "text-neutral-900" : "text-gray-400 group-hover:text-gray-500",
|
||||
isCurrent ? "text-gray-900" : "text-gray-400 group-hover:text-gray-500",
|
||||
"-ml-0.5 hidden h-4 w-4 ltr:mr-2 rtl:ml-2 sm:inline-block"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { ComponentProps } from "react";
|
||||
|
||||
import { ErrorBoundary, Icon, Shell } from "@calcom/ui";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { ErrorBoundary, Icon } from "@calcom/ui";
|
||||
|
||||
import NavTabs from "./NavTabs";
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
||||
import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { App as AppType } from "@calcom/types/App";
|
||||
import { Button, Icon, Shell, showToast, SkeletonButton, SkeletonText, HeadSeo } from "@calcom/ui";
|
||||
import { Button, Icon, showToast, SkeletonButton, SkeletonText, HeadSeo } from "@calcom/ui";
|
||||
|
||||
const Component = ({
|
||||
name,
|
||||
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
EmptyScreen,
|
||||
Icon,
|
||||
List,
|
||||
ShellSubHeading,
|
||||
showToast,
|
||||
AppSkeletonLoader as SkeletonLoader,
|
||||
Switch,
|
||||
ShellSubHeading,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
@@ -184,9 +184,7 @@ function ConnectedCalendarsList(props: Props) {
|
||||
<div className="border-t border-gray-200">
|
||||
{!fromOnboarding && (
|
||||
<>
|
||||
<p className="px-4 pt-4 text-sm text-neutral-500">
|
||||
{t("toggle_calendars_conflict")}
|
||||
</p>
|
||||
<p className="px-4 pt-4 text-sm text-gray-500">{t("toggle_calendars_conflict")}</p>
|
||||
<ul className="space-y-2 p-4">
|
||||
{item.calendars.map((cal) => (
|
||||
<CalendarSwitch
|
||||
|
||||
@@ -2,19 +2,9 @@ import { useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { ComponentProps } from "react";
|
||||
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { EmptyScreen, Icon, Shell, HorizontalTabs, HorizontalTabItemProps } from "@calcom/ui";
|
||||
|
||||
const tabs: HorizontalTabItemProps[] = [
|
||||
{
|
||||
name: "app_store",
|
||||
href: "/apps",
|
||||
},
|
||||
{
|
||||
name: "installed_apps",
|
||||
href: "/apps/installed",
|
||||
},
|
||||
];
|
||||
import { EmptyScreen, Icon } from "@calcom/ui";
|
||||
|
||||
type AppsLayoutProps = {
|
||||
children: React.ReactNode;
|
||||
@@ -30,11 +20,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
||||
if (session.status === "loading") return <></>;
|
||||
|
||||
return (
|
||||
<Shell {...rest} actions={actions?.("hidden sm:block")}>
|
||||
<Shell {...rest} actions={actions?.("block")}>
|
||||
<div className="flex flex-col xl:flex-row">
|
||||
<div className="block lg:hidden">
|
||||
<HorizontalTabs tabs={tabs} actions={actions?.("ml-6 mr-4 mt-3 block sm:hidden")} />
|
||||
</div>
|
||||
<main className="w-full">
|
||||
{emptyStore ? (
|
||||
<EmptyScreen
|
||||
|
||||
@@ -2,10 +2,10 @@ import React, { ComponentProps } from "react";
|
||||
|
||||
import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation";
|
||||
import { InstalledAppVariants } from "@calcom/app-store/utils";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Icon, Shell } from "@calcom/ui";
|
||||
import type { HorizontalTabItemProps } from "@calcom/ui/v2/core/navigation/tabs/HorizontalTabItem";
|
||||
import type { VerticalTabItemProps } from "@calcom/ui/v2/core/navigation/tabs/VerticalTabItem";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
|
||||
|
||||
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
|
||||
{
|
||||
@@ -44,13 +44,15 @@ export default function InstalledAppsLayout({
|
||||
children,
|
||||
...rest
|
||||
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
||||
const variant: typeof InstalledAppVariants[number] = "payment";
|
||||
|
||||
const query = trpc.viewer.integrations.useQuery({
|
||||
variant: InstalledAppVariants.payment,
|
||||
variant,
|
||||
onlyInstalled: true,
|
||||
});
|
||||
let actualTabs = tabs;
|
||||
if (query.data?.items.length === 0) {
|
||||
actualTabs = tabs.filter((tab) => tab.name !== InstalledAppVariants.payment);
|
||||
actualTabs = tabs.filter((tab) => tab.name !== variant);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useRouter } from "next/router";
|
||||
import React, { ComponentProps, useEffect } from "react";
|
||||
|
||||
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { ErrorBoundary, Shell } from "@calcom/ui";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { ErrorBoundary } from "@calcom/ui";
|
||||
|
||||
import { UserPermissionRole } from ".prisma/client";
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
slug: eventTypeSlug,
|
||||
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
|
||||
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
|
||||
ethSignature,
|
||||
...(ethSignature ? { ethSignature } : {}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -119,7 +119,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
prefetch={false}
|
||||
className={classNames(
|
||||
"text-primary-500 hover:border-gray-900 hover:bg-gray-50",
|
||||
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent dark:text-neutral-200",
|
||||
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand dark:text-darkgray-800 mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent",
|
||||
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
||||
)}
|
||||
data-testid="time">
|
||||
|
||||
@@ -76,7 +76,7 @@ const BookingDescription: FC<Props> = (props) => {
|
||||
profile={profile}
|
||||
users={eventType.users}
|
||||
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
|
||||
size={10}
|
||||
size="sm"
|
||||
truncateAfter={3}
|
||||
/>
|
||||
<h2 className="mt-2 break-words text-sm font-medium text-gray-600 dark:text-gray-300">
|
||||
|
||||
@@ -356,7 +356,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
<div
|
||||
title={booking.title}
|
||||
className={classNames(
|
||||
"max-w-10/12 sm:max-w-56 text-sm font-medium leading-6 text-neutral-900 md:max-w-full",
|
||||
"max-w-10/12 sm:max-w-56 text-sm font-medium leading-6 text-gray-900 md:max-w-full",
|
||||
isCancelled ? "line-through" : ""
|
||||
)}>
|
||||
{booking.title}
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function CancelBooking(props: Props) {
|
||||
placeholder={t("cancellation_reason_placeholder")}
|
||||
value={cancellationReason}
|
||||
onChange={(e) => setCancellationReason(e.target.value)}
|
||||
className="mt-2 mb-4 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white "
|
||||
className="dark:bg-darkgray-100 dark:border-darkgray-400 mt-2 mb-4 w-full dark:text-white "
|
||||
rows={3}
|
||||
/>
|
||||
<div className="flex flex-col-reverse rtl:space-x-reverse ">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
|
||||
import AvatarGroup, { AvatarGroupProps } from "@components/ui/AvatarGroup";
|
||||
import { AvatarGroup, AvatarGroupProps } from "@calcom/ui";
|
||||
|
||||
export const UserAvatars = ({
|
||||
profile,
|
||||
@@ -14,7 +13,6 @@ export const UserAvatars = ({
|
||||
const showMembers = !users.find((user) => user.name === profile.name) && props.showMembers;
|
||||
return (
|
||||
<AvatarGroup
|
||||
border="border-2 dark:border-darkgray-100 border-white"
|
||||
items={
|
||||
[
|
||||
{
|
||||
@@ -38,7 +36,7 @@ export const UserAvatars = ({
|
||||
href?: string;
|
||||
}[]
|
||||
}
|
||||
size={props.size}
|
||||
size="sm"
|
||||
truncateAfter={props.truncateAfter}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -109,7 +109,7 @@ export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="min-w-4 mb-2">
|
||||
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-neutral-700">
|
||||
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-gray-700">
|
||||
{t("availability")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import * as RadioGroup from "@radix-ui/react-radio-group";
|
||||
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Controller, useFormContext, UseFormRegisterReturn, useWatch } from "react-hook-form";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType";
|
||||
@@ -12,13 +12,95 @@ import { PeriodType } from "@calcom/prisma/client";
|
||||
import type { BookingLimit } from "@calcom/types/Calendar";
|
||||
import { Button, DateRangePicker, Icon, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
|
||||
|
||||
const MinimumBookingNoticeInput = React.forwardRef<
|
||||
HTMLInputElement,
|
||||
Omit<UseFormRegisterReturn<"minimumBookingNotice">, "ref">
|
||||
>(function MinimumBookingNoticeInput({ ...passThroughProps }, ref) {
|
||||
const { t } = useLocale();
|
||||
const { setValue, getValues } = useFormContext<FormValues>();
|
||||
const durationTypeOptions: {
|
||||
value: DurationType;
|
||||
label: string;
|
||||
}[] = [
|
||||
{
|
||||
label: t("minutes"),
|
||||
value: "minutes",
|
||||
},
|
||||
{
|
||||
label: t("hours"),
|
||||
value: "hours",
|
||||
},
|
||||
{
|
||||
label: t("days"),
|
||||
value: "days",
|
||||
},
|
||||
];
|
||||
|
||||
const [minimumBookingNoticeDisplayValues, setMinimumBookingNoticeDisplayValues] = useState<{
|
||||
type: DurationType;
|
||||
value: number;
|
||||
}>({
|
||||
type: findDurationType(getValues(passThroughProps.name)),
|
||||
value: convertToNewDurationType(
|
||||
"minutes",
|
||||
findDurationType(getValues(passThroughProps.name)),
|
||||
getValues(passThroughProps.name)
|
||||
),
|
||||
});
|
||||
// keep hidden field in sync with minimumBookingNoticeDisplayValues
|
||||
useEffect(() => {
|
||||
setValue(
|
||||
passThroughProps.name,
|
||||
convertToNewDurationType(
|
||||
minimumBookingNoticeDisplayValues.type,
|
||||
"minutes",
|
||||
minimumBookingNoticeDisplayValues.value
|
||||
)
|
||||
);
|
||||
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
|
||||
|
||||
return (
|
||||
<div className="flex items-end justify-end">
|
||||
<div className="w-1/2 md:w-full">
|
||||
<InputField
|
||||
required
|
||||
defaultValue={minimumBookingNoticeDisplayValues.value}
|
||||
onChange={(e) =>
|
||||
setMinimumBookingNoticeDisplayValues({
|
||||
...minimumBookingNoticeDisplayValues,
|
||||
value: parseInt(e.target.value || "0", 10),
|
||||
})
|
||||
}
|
||||
label={t("minimum_booking_notice")}
|
||||
type="number"
|
||||
placeholder="0"
|
||||
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
|
||||
/>
|
||||
<input type="hidden" ref={ref} {...passThroughProps} />
|
||||
</div>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
|
||||
defaultValue={durationTypeOptions.find(
|
||||
(option) => option.value === minimumBookingNoticeDisplayValues.type
|
||||
)}
|
||||
onChange={(input) => {
|
||||
if (input) {
|
||||
setMinimumBookingNoticeDisplayValues({
|
||||
...minimumBookingNoticeDisplayValues,
|
||||
type: input.value,
|
||||
});
|
||||
}
|
||||
}}
|
||||
options={durationTypeOptions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
|
||||
const { t } = useLocale();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const minimumBookingNoticeType = useRef<DurationType>(findDurationType(eventType.minimumBookingNotice));
|
||||
const prevBookingNoticeType = useRef<DurationType>(minimumBookingNoticeType.current);
|
||||
|
||||
const minimumBookingNoticeInDurationTypeFormValue = formMethods.watch("minimumBookingNoticeInDurationType");
|
||||
|
||||
const PERIOD_TYPES = [
|
||||
{
|
||||
@@ -49,59 +131,8 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
defaultValue: periodType?.type,
|
||||
});
|
||||
|
||||
const onMinimumNoticeDurationTypeChange = useMemo(
|
||||
() => (durationType?: DurationType) => {
|
||||
if (typeof durationType === "undefined") return;
|
||||
|
||||
// Store current selected type in ref to use in previous run for comparrison.
|
||||
minimumBookingNoticeType.current = durationType;
|
||||
|
||||
// Uses the previous selected type and converts the value to the new type.
|
||||
// Eg day was selected before, user selects hours now, so we multiple * 24 and round up.
|
||||
const minimumBookingNoticeInDurationType = convertToNewDurationType(
|
||||
prevBookingNoticeType.current,
|
||||
durationType,
|
||||
Number(minimumBookingNoticeInDurationTypeFormValue)
|
||||
);
|
||||
|
||||
// Uses the round up value in the new type to calculate the value in minutes.
|
||||
// We should NOT use minimumBookingNoticeInDurationTypeFormValue here, since that
|
||||
// would break in the case that the user enters 90 minutes, changes the type to
|
||||
// hours (that will show 2 hours), but the value in minutes would then remain 90,
|
||||
// which is something else than the user things they save.
|
||||
const minimumBookingNoticeInMinutes = convertToNewDurationType(
|
||||
durationType,
|
||||
"minutes",
|
||||
Number(minimumBookingNoticeInDurationType)
|
||||
);
|
||||
|
||||
// Updates both form values as well as stores the current type as previous type in ref.
|
||||
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
|
||||
formMethods.setValue("minimumBookingNoticeInDurationType", minimumBookingNoticeInDurationType);
|
||||
prevBookingNoticeType.current = durationType;
|
||||
},
|
||||
[formMethods, minimumBookingNoticeInDurationTypeFormValue]
|
||||
);
|
||||
|
||||
/**
|
||||
* When the user inputs a new value for minimumBookingNoticeInDurationType,
|
||||
* we calculate the value in minutes, and update this hidden field as well.
|
||||
*/
|
||||
const onMinimumNoticeChange = useMemo(
|
||||
() => (duration: string) => {
|
||||
const minimumBookingNoticeInMinutes = convertToNewDurationType(
|
||||
minimumBookingNoticeType.current,
|
||||
"minutes",
|
||||
Number(duration)
|
||||
);
|
||||
|
||||
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
|
||||
},
|
||||
[formMethods]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||
<div className="w-full">
|
||||
<Label htmlFor="beforeBufferTime">{t("before_event")} </Label>
|
||||
@@ -168,56 +199,10 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||
<div className="flex w-full items-end justify-end">
|
||||
<Controller
|
||||
name="minimumBookingNotice"
|
||||
control={formMethods.control}
|
||||
render={() => {
|
||||
const durationTypeOptions = [
|
||||
{
|
||||
label: t("minutes"),
|
||||
value: "minutes",
|
||||
},
|
||||
{
|
||||
label: t("hours"),
|
||||
value: "hours",
|
||||
},
|
||||
{
|
||||
label: t("days"),
|
||||
value: "days",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-1/2 md:w-3/4">
|
||||
<InputField
|
||||
required
|
||||
label={t("minimum_booking_notice")}
|
||||
type="number"
|
||||
placeholder="120"
|
||||
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
|
||||
{...formMethods.register("minimumBookingNoticeInDurationType", {
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onMinimumNoticeChange(event.target.value),
|
||||
})}
|
||||
/>
|
||||
<input type="hidden" {...formMethods.register("minimumBookingNotice")} />
|
||||
</div>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
|
||||
defaultValue={durationTypeOptions.find(
|
||||
(option) => option.value === minimumBookingNoticeType.current
|
||||
)}
|
||||
onChange={(input) => onMinimumNoticeDurationTypeChange(input?.value as DurationType)}
|
||||
options={durationTypeOptions}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||
<div className="w-full">
|
||||
<Label htmlFor="minimumBookingNotice">{t("minimum_booking_notice")} </Label>
|
||||
<MinimumBookingNoticeInput {...formMethods.register("minimumBookingNotice")} />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Label htmlFor="slotInterval">{t("slot_interval")} </Label>
|
||||
@@ -252,9 +237,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-8" />
|
||||
|
||||
<hr />
|
||||
<Controller
|
||||
name="bookingLimits"
|
||||
control={formMethods.control}
|
||||
@@ -276,8 +259,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
</SettingsToggle>
|
||||
)}
|
||||
/>
|
||||
|
||||
<hr className="my-8" />
|
||||
<hr />
|
||||
<Controller
|
||||
name="periodType"
|
||||
control={formMethods.control}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { SchedulingType } from "@prisma/client/";
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||
import { useMemo } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { ComponentProps } from "react";
|
||||
import { Controller, useFormContext, useWatch, Control } from "react-hook-form";
|
||||
import { Options } from "react-select";
|
||||
|
||||
import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTeamSelect";
|
||||
import CheckedTeamSelect, {
|
||||
CheckedSelectOption,
|
||||
} from "@calcom/features/eventtypes/components/CheckedTeamSelect";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Label, Select } from "@calcom/ui";
|
||||
@@ -22,14 +25,66 @@ const mapUserToValue = ({ id, name, username, email }: IMemberToValue) => ({
|
||||
email,
|
||||
});
|
||||
|
||||
export const EventTeamTab = ({
|
||||
eventType,
|
||||
team,
|
||||
teamMembers,
|
||||
}: Pick<EventTypeSetupProps, "eventType" | "teamMembers" | "team">) => {
|
||||
const FixedHosts = ({
|
||||
control,
|
||||
labelText,
|
||||
placeholder,
|
||||
options = [],
|
||||
...rest
|
||||
}: {
|
||||
control: Control<FormValues>;
|
||||
labelText: string;
|
||||
placeholder: string;
|
||||
options?: Options<CheckedSelectOption>;
|
||||
} & Partial<ComponentProps<typeof CheckedTeamSelect>>) => {
|
||||
return (
|
||||
<div className="flex flex-col space-y-5 bg-gray-50 p-4">
|
||||
<div>
|
||||
<Label>{labelText}</Label>
|
||||
<Controller
|
||||
name="hostsFixed"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<CheckedTeamSelect
|
||||
isDisabled={false}
|
||||
onChange={(options) => {
|
||||
onChange(
|
||||
options.map((option) => ({
|
||||
isFixed: true,
|
||||
userId: parseInt(option.value, 10),
|
||||
}))
|
||||
);
|
||||
}}
|
||||
value={value
|
||||
.map(
|
||||
(host) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
options.find((member) => member.value === host.userId.toString())!
|
||||
)
|
||||
.filter(Boolean)}
|
||||
controlShouldRenderValue={false}
|
||||
options={options}
|
||||
placeholder={placeholder}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const EventTeamTab = ({ team, teamMembers }: Pick<EventTypeSetupProps, "teamMembers" | "team">) => {
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const { t } = useLocale();
|
||||
|
||||
const schedulingType = useWatch({
|
||||
control: formMethods.control,
|
||||
name: "schedulingType",
|
||||
});
|
||||
|
||||
const schedulingTypeOptions: {
|
||||
value: SchedulingType;
|
||||
label: string;
|
||||
@@ -47,14 +102,13 @@ export const EventTeamTab = ({
|
||||
},
|
||||
];
|
||||
|
||||
const teamMembersToValues = useMemo(() => {
|
||||
return teamMembers.map(mapUserToValue);
|
||||
}, [teamMembers]);
|
||||
const teamMembersOptions = teamMembers.map(mapUserToValue);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{team && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-col pb-8">
|
||||
<div className="space-y-5">
|
||||
<div className="flex flex-col">
|
||||
<Label>{t("scheduling_type")}</Label>
|
||||
<Controller
|
||||
name="schedulingType"
|
||||
@@ -72,30 +126,75 @@ export const EventTeamTab = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<Label>{t("team")}</Label>
|
||||
<Controller
|
||||
name="users"
|
||||
{schedulingType === SchedulingType.COLLECTIVE && (
|
||||
<FixedHosts
|
||||
options={teamMembersOptions}
|
||||
placeholder={t("add_attendees")}
|
||||
labelText={t("team")}
|
||||
control={formMethods.control}
|
||||
defaultValue={eventType.users.map((user) => user.id.toString())}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CheckedTeamSelect
|
||||
isDisabled={false}
|
||||
onChange={(options) => onChange(options.map((user) => user.value))}
|
||||
value={value
|
||||
.map(
|
||||
(userId) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
teamMembers.map(mapUserToValue).find((member) => member.value === userId)!
|
||||
)
|
||||
.filter(Boolean)}
|
||||
controlShouldRenderValue={false}
|
||||
options={teamMembersToValues}
|
||||
placeholder={t("add_attendees")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{schedulingType === SchedulingType.ROUND_ROBIN && (
|
||||
<>
|
||||
<FixedHosts
|
||||
options={teamMembersOptions}
|
||||
isOptionDisabled={(option) =>
|
||||
!!formMethods.getValues("hosts").find((host) => host.userId.toString() === option.value)
|
||||
}
|
||||
placeholder={t("add_fixed_hosts")}
|
||||
labelText={t("fixed_hosts")}
|
||||
control={formMethods.control}
|
||||
/>
|
||||
<div className="flex flex-col space-y-5 bg-gray-50 p-4">
|
||||
<div>
|
||||
<Label>{t("round_robin_hosts")}</Label>
|
||||
<Controller
|
||||
name="hosts"
|
||||
control={formMethods.control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CheckedTeamSelect
|
||||
isDisabled={false}
|
||||
onChange={(options) =>
|
||||
onChange(
|
||||
options.map((option) => ({
|
||||
isFixed: false,
|
||||
userId: parseInt(option.value, 10),
|
||||
}))
|
||||
)
|
||||
}
|
||||
value={value
|
||||
.map(
|
||||
(host) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
teamMembers
|
||||
.map(mapUserToValue)
|
||||
.find((member) => member.value === host.userId.toString())!
|
||||
)
|
||||
.filter(Boolean)}
|
||||
controlShouldRenderValue={false}
|
||||
options={teamMembers.map(mapUserToValue)}
|
||||
isOptionDisabled={(option) =>
|
||||
!!formMethods
|
||||
.getValues("hostsFixed")
|
||||
.find((host) => host.userId.toString() === option.value)
|
||||
}
|
||||
placeholder={t("add_attendees")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{/*<TextField
|
||||
required
|
||||
type="number"
|
||||
label={t("minimum_round_robin_hosts_count")}
|
||||
defaultValue={1}
|
||||
{...formMethods.register("minimumHostCount")}
|
||||
addOnSuffix={<>{t("hosts")}</>}
|
||||
/>*/}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMemo, useState, Suspense } from "react";
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import { TbWebhook } from "react-icons/tb";
|
||||
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -15,15 +16,15 @@ import {
|
||||
ButtonGroup,
|
||||
ConfirmationDialogContent,
|
||||
Dialog,
|
||||
Divider,
|
||||
DropdownMenuSeparator,
|
||||
Dropdown,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownItem,
|
||||
DropdownMenuTrigger,
|
||||
HorizontalTabs,
|
||||
Icon,
|
||||
Label,
|
||||
Shell,
|
||||
showToast,
|
||||
Skeleton,
|
||||
Switch,
|
||||
@@ -55,12 +56,15 @@ function getNavigation(props: {
|
||||
installedAppsNumber: number;
|
||||
}) {
|
||||
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
|
||||
const duration =
|
||||
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
|
||||
|
||||
return [
|
||||
{
|
||||
name: "event_setup_tab_title",
|
||||
href: `/event-types/${eventType.id}?tabName=setup`,
|
||||
icon: Icon.FiLink,
|
||||
info: `${eventType.length} Mins`, // TODO: Get this from props
|
||||
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
|
||||
},
|
||||
{
|
||||
name: "availability",
|
||||
@@ -151,7 +155,7 @@ function EventTypeSingleLayout({
|
||||
// If there is a team put this navigation item within the tabs
|
||||
if (team) {
|
||||
navigation.splice(2, 0, {
|
||||
name: "scheduling_type",
|
||||
name: "assignment",
|
||||
href: `/event-types/${eventType.id}?tabName=team`,
|
||||
icon: Icon.FiUsers,
|
||||
info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin",
|
||||
@@ -246,32 +250,36 @@ function EventTypeSingleLayout({
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem className="focus:ring-gray-100">
|
||||
<Button
|
||||
color="minimal"
|
||||
StartIcon={Icon.FiExternalLink}
|
||||
<DropdownItem
|
||||
target="_blank"
|
||||
type="button"
|
||||
StartIcon={Icon.FiExternalLink}
|
||||
href={permalink}
|
||||
rel="noreferrer"
|
||||
className="min-w-full">
|
||||
rel="noreferrer">
|
||||
{t("preview")}
|
||||
</Button>
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="focus:ring-gray-100">
|
||||
<Button color="minimal" StartIcon={Icon.FiLink}>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
StartIcon={Icon.FiLink}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(permalink);
|
||||
showToast("Link copied!", "success");
|
||||
}}>
|
||||
{t("copy_link")}
|
||||
</Button>
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="focus:ring-gray-100">
|
||||
<Button
|
||||
color="minimal"
|
||||
<DropdownItem
|
||||
type="button"
|
||||
StartIcon={Icon.FiTrash}
|
||||
disabled={!hasPermsToDelete}
|
||||
onClick={() => setDeleteDialogOpen(true)}
|
||||
className="min-w-full">
|
||||
onClick={() => setDeleteDialogOpen(true)}>
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<Divider />
|
||||
<DropdownMenuSeparator className="block sm:hidden" />
|
||||
<div className="flex items-center rounded-md py-1.5 px-4 sm:hidden sm:hover:bg-gray-100">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function RecurringEventController({
|
||||
{recurringEventState && (
|
||||
<div data-testid="recurring-event-collapsible" className="text-sm">
|
||||
<div className="flex items-center">
|
||||
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("repeats_every")}</p>
|
||||
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">{t("repeats_every")}</p>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -89,7 +89,7 @@ export default function RecurringEventController({
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center">
|
||||
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("for_a_maximum_of")}</p>
|
||||
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">{t("for_a_maximum_of")}</p>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -105,7 +105,7 @@ export default function RecurringEventController({
|
||||
setRecurringEventState(newVal);
|
||||
}}
|
||||
/>
|
||||
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">
|
||||
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">
|
||||
{t("events", {
|
||||
count: recurringEventState.count,
|
||||
})}
|
||||
|
||||
@@ -16,7 +16,7 @@ const CreateEventsOnCalendarSelect = (props: ICreateEventsOnCalendarSelectProps)
|
||||
<>
|
||||
<div className="mt-6 flex flex-row">
|
||||
<div className="w-full">
|
||||
<label htmlFor="createEventsOn" className="flex text-sm font-medium text-neutral-700">
|
||||
<label htmlFor="createEventsOn" className="flex text-sm font-medium text-gray-700">
|
||||
{t("create_events_on")}
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
|
||||
@@ -19,7 +19,8 @@ interface IUserSettingsProps {
|
||||
const UserSettings = (props: IUserSettingsProps) => {
|
||||
const { user, nextStep } = props;
|
||||
const { t } = useLocale();
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState(user.timeZone ?? dayjs.tz.guess());
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState(dayjs.tz.guess());
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
|
||||
@@ -22,8 +22,8 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
||||
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
|
||||
/>
|
||||
<section className="line-clamp-4 mt-2 w-full space-y-1">
|
||||
<p className="font-medium text-neutral-900 dark:text-white">{member.name}</p>
|
||||
<p className="line-clamp-3 overflow-ellipsis text-sm font-normal text-neutral-500 dark:text-white">
|
||||
<p className="font-medium text-gray-900 dark:text-white">{member.name}</p>
|
||||
<p className="line-clamp-3 overflow-ellipsis text-sm font-normal text-gray-500 dark:text-white">
|
||||
{member.bio || t("user_from_team", { user: member.name, team: teamName })}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
||||
)}
|
||||
|
||||
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
|
||||
{props.heading && <h2 className="font-cal text-center text-3xl text-neutral-900">{props.heading}</h2>}
|
||||
{props.heading && <h2 className="font-cal text-center text-3xl text-gray-900">{props.heading}</h2>}
|
||||
</div>
|
||||
{props.loading && (
|
||||
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-50">
|
||||
@@ -35,7 +35,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
||||
<div className="border-1 mx-2 rounded-md border-gray-200 bg-white px-4 py-10 sm:px-10">
|
||||
{props.children}
|
||||
</div>
|
||||
<div className="mt-8 text-center text-sm text-neutral-600">
|
||||
<div className="mt-8 text-center text-sm text-gray-600">
|
||||
{props.footerText}
|
||||
<Credits />
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function LinkIconButton(props: LinkIconButtonProps) {
|
||||
type="button"
|
||||
{...props}
|
||||
className="text-md flex items-center rounded-sm px-2 py-1 text-sm font-medium text-gray-700 hover:bg-gray-200 hover:text-gray-900">
|
||||
<props.Icon className="h-4 w-4 text-neutral-500 ltr:mr-2 rtl:ml-2" />
|
||||
<props.Icon className="h-4 w-4 text-gray-500 ltr:mr-2 rtl:ml-2" />
|
||||
{props.children}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -13,8 +13,8 @@ export default function SettingInputContainer({
|
||||
<div className="space-y-3">
|
||||
<div className="block sm:flex">
|
||||
<div className="min-w-48 mb-4 sm:mb-0">
|
||||
<label htmlFor={htmlFor} className="mt-1 flex text-sm font-medium text-neutral-700">
|
||||
<Icon className="mt-0.5 h-4 w-4 text-neutral-500 ltr:mr-2 rtl:ml-2" />
|
||||
<label htmlFor={htmlFor} className="mt-1 flex text-sm font-medium text-gray-700">
|
||||
<Icon className="mt-0.5 h-4 w-4 text-gray-500 ltr:mr-2 rtl:ml-2" />
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
|
||||
{React.createElement(
|
||||
descriptionAsLabel ? "div" : "label",
|
||||
{
|
||||
className: "flex text-sm font-medium text-neutral-700",
|
||||
className: "flex text-sm font-medium text-gray-700",
|
||||
...(!descriptionAsLabel
|
||||
? {
|
||||
htmlFor: rest.id,
|
||||
@@ -39,7 +39,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
|
||||
{
|
||||
className: classNames(
|
||||
"relative flex items-start",
|
||||
descriptionAsLabel ? "text-neutral-700" : "text-neutral-900"
|
||||
descriptionAsLabel ? "text-gray-700" : "text-gray-900"
|
||||
),
|
||||
},
|
||||
<>
|
||||
|
||||
@@ -50,7 +50,7 @@ export const CheckedSelect = ({
|
||||
{option.label}
|
||||
<Icon.FiX
|
||||
onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
|
||||
className="float-right mt-0.5 h-5 w-5 cursor-pointer text-neutral-500"
|
||||
className="float-right mt-0.5 h-5 w-5 cursor-pointer text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -10,7 +10,7 @@ const MinutesField = forwardRef<HTMLInputElement, Props>(({ label, ...rest }, re
|
||||
<div className="block sm:flex">
|
||||
{!!label && (
|
||||
<div className="min-w-48 mb-4 sm:mb-0">
|
||||
<label htmlFor={rest.id} className="flex h-full items-center text-sm font-medium text-neutral-700">
|
||||
<label htmlFor={rest.id} className="flex h-full items-center text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// lets refactor and move this into packages/lib/hooks/
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const useMediaQuery = (query: string) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.4.6",
|
||||
"version": "2.5.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
@@ -89,7 +89,6 @@
|
||||
"next-seo": "^4.26.0",
|
||||
"next-themes": "^0.2.0",
|
||||
"next-transpile-modules": "^10.0.0",
|
||||
"nock": "^13.2.8",
|
||||
"nodemailer": "^6.7.8",
|
||||
"otplib": "^12.0.1",
|
||||
"qrcode": "^1.5.1",
|
||||
|
||||
@@ -26,12 +26,11 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
|
||||
import prisma from "@calcom/prisma";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { Icon, HeadSeo } from "@calcom/ui";
|
||||
import { Icon, HeadSeo, AvatarGroup } from "@calcom/ui";
|
||||
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
||||
import AvatarGroup from "@components/ui/AvatarGroup";
|
||||
import { AvatarSSR } from "@components/ui/AvatarSSR";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
@@ -64,15 +63,14 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
className="flex justify-between px-6 py-4"
|
||||
data-testid="event-type-link">
|
||||
<div className="flex-shrink">
|
||||
<p className="dark:text-darkgray-700 text-sm font-semibold text-neutral-900">{type.title}</p>
|
||||
<p className="dark:text-darkgray-700 text-sm font-semibold text-gray-900">{type.title}</p>
|
||||
<EventTypeDescription className="text-sm" eventType={type} />
|
||||
</div>
|
||||
<div className="mt-1 self-center">
|
||||
<AvatarGroup
|
||||
border="border-2 border-white dark:border-darkgray-100"
|
||||
truncateAfter={4}
|
||||
className="flex flex-shrink-0"
|
||||
size={10}
|
||||
size="sm"
|
||||
items={props.users.map((user) => ({
|
||||
alt: user.name || "",
|
||||
image: user.avatar || "",
|
||||
@@ -135,13 +133,13 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
||||
<div className="mb-8 text-center">
|
||||
<AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername} />
|
||||
<h1 className="font-cal mb-1 text-3xl text-neutral-900 dark:text-white">
|
||||
<h1 className="font-cal mb-1 text-3xl text-gray-900 dark:text-white">
|
||||
{nameOrUsername}
|
||||
{user.verified && (
|
||||
<Icon.BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
|
||||
)}
|
||||
</h1>
|
||||
<p className="dark:text-darkgray-600 text-s text-neutral-500">{user.bio}</p>
|
||||
<p className="dark:text-darkgray-600 text-s text-gray-500">{user.bio}</p>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { IdentityProvider } from "@prisma/client";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { hashPassword } from "@calcom/lib/auth";
|
||||
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { hashPassword } from "@lib/auth";
|
||||
import slugify from "@lib/slugify";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -12,6 +12,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
|
||||
res.status(403).json({ message: "Signup is disabled" });
|
||||
return;
|
||||
}
|
||||
|
||||
const data = req.body;
|
||||
const { email, password } = data;
|
||||
const username = slugify(data.username);
|
||||
|
||||
@@ -37,6 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
location: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
username: true,
|
||||
@@ -100,6 +101,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
organizer: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name,
|
||||
timeZone: user.timeZone,
|
||||
|
||||
@@ -4,9 +4,10 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { AppCard, Shell, SkeletonText } from "@calcom/ui";
|
||||
import { AppCard, SkeletonText } from "@calcom/ui";
|
||||
|
||||
export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
|
||||
@@ -2,8 +2,9 @@ import { InferGetStaticPropsType } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon, Shell, SkeletonText } from "@calcom/ui";
|
||||
import { Icon, SkeletonText } from "@calcom/ui";
|
||||
|
||||
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
|
||||
@@ -7,12 +7,31 @@ import { getSession } from "@calcom/lib/auth";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { AppCategories } from "@calcom/prisma/client";
|
||||
import { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import { AllApps, AppStoreCategories, Icon, TextField, TrendingAppsSlider } from "@calcom/ui";
|
||||
import {
|
||||
AllApps,
|
||||
AppStoreCategories,
|
||||
HorizontalTabItemProps,
|
||||
HorizontalTabs,
|
||||
Icon,
|
||||
TextField,
|
||||
TrendingAppsSlider,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import AppsLayout from "@components/apps/layouts/AppsLayout";
|
||||
|
||||
import { ssgInit } from "@server/lib/ssg";
|
||||
|
||||
const tabs: HorizontalTabItemProps[] = [
|
||||
{
|
||||
name: "app_store",
|
||||
href: "/apps",
|
||||
},
|
||||
{
|
||||
name: "installed_apps",
|
||||
href: "/apps/installed",
|
||||
},
|
||||
];
|
||||
|
||||
function AppsSearch({
|
||||
onChange,
|
||||
className,
|
||||
@@ -43,20 +62,30 @@ export default function Apps({ categories, appStore }: inferSSRProps<typeof getS
|
||||
heading={t("app_store")}
|
||||
subtitle={t("app_store_description")}
|
||||
actions={(className) => (
|
||||
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
|
||||
<div className="flex w-full flex-col md:flex-row md:justify-between lg:w-auto">
|
||||
<div className="lg:hidden">
|
||||
<HorizontalTabs tabs={tabs} />
|
||||
</div>
|
||||
<div>
|
||||
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
headerClassName="sm:hidden lg:block hidden"
|
||||
emptyStore={!appStore.length}>
|
||||
{!searchText && (
|
||||
<>
|
||||
<AppStoreCategories categories={categories} />
|
||||
<TrendingAppsSlider items={appStore} />
|
||||
</>
|
||||
)}
|
||||
<AllApps
|
||||
apps={appStore}
|
||||
searchText={searchText}
|
||||
categories={categories.map((category) => category.name)}
|
||||
/>
|
||||
<div className="flex flex-col gap-y-8">
|
||||
{!searchText && (
|
||||
<>
|
||||
<AppStoreCategories categories={categories} />
|
||||
<TrendingAppsSlider items={appStore} />
|
||||
</>
|
||||
)}
|
||||
<AllApps
|
||||
apps={appStore}
|
||||
searchText={searchText}
|
||||
categories={categories.map((category) => category.name)}
|
||||
/>
|
||||
</div>
|
||||
</AppsLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
EmptyScreen,
|
||||
Icon,
|
||||
List,
|
||||
ShellSubHeading,
|
||||
AppSkeletonLoader as SkeletonLoader,
|
||||
ShellSubHeading,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
@@ -92,8 +92,8 @@ function ConnectOrDisconnectIntegrationButton(props: {
|
||||
}
|
||||
|
||||
interface IntegrationsContainerProps {
|
||||
variant?: keyof typeof InstalledAppVariants;
|
||||
exclude?: (keyof typeof InstalledAppVariants)[];
|
||||
variant?: typeof InstalledAppVariants[number];
|
||||
exclude?: typeof InstalledAppVariants[number][];
|
||||
}
|
||||
|
||||
interface IntegrationsListProps {
|
||||
@@ -143,8 +143,10 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
|
||||
automation: Icon.FiShare2,
|
||||
analytics: Icon.FiBarChart,
|
||||
payment: Icon.FiCreditCard,
|
||||
web3: Icon.FiBarChart,
|
||||
other: Icon.FiGrid,
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryCell
|
||||
query={query}
|
||||
@@ -198,31 +200,29 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
|
||||
};
|
||||
|
||||
const querySchema = z.object({
|
||||
category: z.nativeEnum(InstalledAppVariants),
|
||||
category: z.enum(InstalledAppVariants),
|
||||
});
|
||||
|
||||
type querySchemaType = z.infer<typeof querySchema>;
|
||||
|
||||
export default function InstalledApps() {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const category = router.query.category;
|
||||
const category = router.query.category as querySchemaType["category"];
|
||||
const categoryList: querySchemaType["category"][] = [
|
||||
"payment",
|
||||
"conferencing",
|
||||
"automation",
|
||||
"analytics",
|
||||
"web3",
|
||||
];
|
||||
|
||||
return (
|
||||
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
|
||||
{(category === InstalledAppVariants.payment || category === InstalledAppVariants.conferencing) && (
|
||||
<IntegrationsContainer variant={category} />
|
||||
)}
|
||||
{(category === InstalledAppVariants.automation || category === InstalledAppVariants.analytics) && (
|
||||
<IntegrationsContainer variant={category} />
|
||||
)}
|
||||
{category === InstalledAppVariants.calendar && <CalendarListContainer />}
|
||||
{category === InstalledAppVariants.other && (
|
||||
<IntegrationsContainer
|
||||
exclude={[
|
||||
InstalledAppVariants.conferencing,
|
||||
InstalledAppVariants.calendar,
|
||||
InstalledAppVariants.analytics,
|
||||
InstalledAppVariants.automation,
|
||||
]}
|
||||
/>
|
||||
{categoryList.includes(category) && <IntegrationsContainer variant={category} />}
|
||||
{category === "calendar" && <CalendarListContainer />}
|
||||
{category === "other" && (
|
||||
<IntegrationsContainer variant={category} exclude={[...categoryList, "calendar"]} />
|
||||
)}
|
||||
</InstalledAppsLayout>
|
||||
);
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
||||
footerText={
|
||||
!success && (
|
||||
<>
|
||||
<Link href="/auth/login" className="font-medium text-neutral-900">
|
||||
<Link href="/auth/login" className="font-medium text-gray-900">
|
||||
{t("back_to_signin")}
|
||||
</Link>
|
||||
</>
|
||||
|
||||
@@ -115,7 +115,13 @@ export default function Login({
|
||||
description={t("login")}
|
||||
showLogo
|
||||
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
|
||||
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
|
||||
footerText={
|
||||
twoFactorRequired
|
||||
? TwoFactorFooter
|
||||
: process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== "true"
|
||||
? LoginFooter
|
||||
: null
|
||||
}>
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={methods.handleSubmit(onSubmit)} data-testid="login-form">
|
||||
<div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
|
||||
import { DateOverrideInputDialog, DateOverrideList } from "@calcom/features/schedules";
|
||||
import Schedule from "@calcom/features/schedules/components/Schedule";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { availabilityAsString } from "@calcom/lib/availability";
|
||||
import { yyyymmdd } from "@calcom/lib/date-fns";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
Form,
|
||||
Icon,
|
||||
Label,
|
||||
Shell,
|
||||
showToast,
|
||||
Skeleton,
|
||||
SkeletonText,
|
||||
@@ -60,7 +60,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
</h3>
|
||||
<p className="mb-4 text-sm text-neutral-500 ltr:mr-4 rtl:ml-4">{t("date_overrides_subtitle")}</p>
|
||||
<p className="mb-4 text-sm text-gray-500 ltr:mr-4 rtl:ml-4">{t("date_overrides_subtitle")}</p>
|
||||
<div className="mt-1 space-y-2">
|
||||
<DateOverrideList
|
||||
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
|
||||
|
||||
@@ -2,9 +2,10 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { EmptyScreen, Icon, Shell, showToast } from "@calcom/ui";
|
||||
import { EmptyScreen, Icon, showToast } from "@calcom/ui";
|
||||
|
||||
import { withQuery } from "@lib/QueryCell";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Shell, SkeletonText } from "@calcom/ui";
|
||||
import { SkeletonText } from "@calcom/ui";
|
||||
|
||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||
|
||||
@@ -55,7 +56,7 @@ const AvailabilityView = ({ user }: { user: User }) => {
|
||||
if (e.target.value) setSelectedDate(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<small className="block text-neutral-400">{t("hover_over_bold_times_tip")}</small>
|
||||
<small className="block text-gray-400">{t("hover_over_bold_times_tip")}</small>
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="bg-brand dark:bg-darkmodebrand overflow-hidden rounded-md">
|
||||
<div className="text-brandcontrast dark:text-darkmodebrandcontrast px-4 py-2 sm:px-6">
|
||||
@@ -81,13 +82,11 @@ const AvailabilityView = ({ user }: { user: User }) => {
|
||||
data-testid="troubleshooter-busy-time">
|
||||
<div className="px-4 py-5 text-black sm:p-6">
|
||||
{t("calendar_shows_busy_between")}{" "}
|
||||
<span
|
||||
className="font-medium text-neutral-800"
|
||||
title={dayjs(slot.start).format("HH:mm")}>
|
||||
<span className="font-medium text-gray-800" title={dayjs(slot.start).format("HH:mm")}>
|
||||
{dayjs(slot.start).format("HH:mm")}
|
||||
</span>{" "}
|
||||
{t("and")}{" "}
|
||||
<span className="font-medium text-neutral-800" title={dayjs(slot.end).format("HH:mm")}>
|
||||
<span className="font-medium text-gray-800" title={dayjs(slot.end).format("HH:mm")}>
|
||||
{dayjs(slot.end).format("HH:mm")}
|
||||
</span>{" "}
|
||||
{t("on")} {dayjs(slot.start).format("D")}{" "}
|
||||
|
||||
@@ -32,7 +32,7 @@ import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import prisma, { baseUserSelect } from "@calcom/prisma";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { Button, EmailInput, Icon, HeadSeo } from "@calcom/ui";
|
||||
@@ -322,7 +322,7 @@ export default function Success(props: SuccessProps) {
|
||||
}
|
||||
return t("emailed_you_and_attendees" + titleSuffix);
|
||||
}
|
||||
const userIsOwner = !!(session?.user?.id && eventType.users.find((user) => (user.id = session.user.id)));
|
||||
const userIsOwner = !!(session?.user?.id && eventType.owner?.id === session.user.id);
|
||||
useTheme(isSuccessBookingPage ? props.profile.theme : "light");
|
||||
const title = t(
|
||||
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
|
||||
@@ -379,7 +379,7 @@ export default function Success(props: SuccessProps) {
|
||||
<div
|
||||
className={classNames(
|
||||
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
||||
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 bg-white dark:border-neutral-700",
|
||||
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 dark:border-darkgray-200 bg-white",
|
||||
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
||||
)}
|
||||
role="dialog"
|
||||
@@ -408,7 +408,7 @@ export default function Success(props: SuccessProps) {
|
||||
</div>
|
||||
<div className="mt-6 mb-8 text-center last:mb-0">
|
||||
<h3
|
||||
className="text-2xl font-semibold leading-6 text-neutral-900 dark:text-white"
|
||||
className="text-2xl font-semibold leading-6 text-gray-900 dark:text-white"
|
||||
data-testid={isCancelled ? "cancelled-headline" : ""}
|
||||
id="modal-headline">
|
||||
{needsConfirmation && !isCancelled
|
||||
@@ -422,9 +422,10 @@ export default function Success(props: SuccessProps) {
|
||||
: t("meeting_is_scheduled")}
|
||||
</h3>
|
||||
<div className="mt-3">
|
||||
<p className="text-neutral-600 dark:text-gray-300">{getTitle()}</p>
|
||||
<p className="text-gray-600 dark:text-gray-300">{getTitle()}</p>
|
||||
</div>
|
||||
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-300 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
|
||||
|
||||
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-200 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
|
||||
{(isCancelled || reschedule) && cancellationReason && (
|
||||
<>
|
||||
<div className="font-medium">
|
||||
@@ -499,7 +500,7 @@ export default function Success(props: SuccessProps) {
|
||||
<>
|
||||
<div className="mt-9 font-medium">{t("additional_notes")}</div>
|
||||
<div className="col-span-2 mb-2 mt-9">
|
||||
<p>{bookingInfo.description}</p>
|
||||
<p className="break-words">{bookingInfo.description}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -594,7 +595,7 @@ export default function Success(props: SuccessProps) {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<hr className="border-bookinglightest dark:border-darkgray-300" />
|
||||
<hr className="border-bookinglightest dark:border-darkgray-200" />
|
||||
<CancelBooking
|
||||
booking={{ uid: bookingInfo?.uid, title: bookingInfo?.title, id: bookingInfo?.id }}
|
||||
profile={{ name: props.profile.name, slug: props.profile.slug }}
|
||||
@@ -845,6 +846,17 @@ export function RecurringBookings({
|
||||
}
|
||||
|
||||
const getEventTypesFromDB = async (id: number) => {
|
||||
const userSelect = {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
hideBranding: true,
|
||||
theme: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
email: true,
|
||||
timeZone: true,
|
||||
};
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id,
|
||||
@@ -863,17 +875,17 @@ const getEventTypesFromDB = async (id: number) => {
|
||||
locations: true,
|
||||
price: true,
|
||||
currency: true,
|
||||
owner: {
|
||||
select: userSelect,
|
||||
},
|
||||
users: {
|
||||
select: userSelect,
|
||||
},
|
||||
hosts: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
hideBranding: true,
|
||||
theme: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
email: true,
|
||||
timeZone: true,
|
||||
user: {
|
||||
select: userSelect,
|
||||
},
|
||||
},
|
||||
},
|
||||
team: {
|
||||
@@ -917,6 +929,7 @@ const schema = z.object({
|
||||
uid: z.string(),
|
||||
email: z.string().optional(),
|
||||
eventTypeSlug: z.string().optional(),
|
||||
cancel: stringToBoolean,
|
||||
});
|
||||
|
||||
const handleSeatsEventTypeOnBooking = (
|
||||
@@ -950,7 +963,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const ssr = await ssrInit(context);
|
||||
const parsedQuery = schema.safeParse(context.query);
|
||||
if (!parsedQuery.success) return { notFound: true };
|
||||
const { uid, email, eventTypeSlug } = parsedQuery.data;
|
||||
const { uid, email, eventTypeSlug, cancel } = parsedQuery.data;
|
||||
|
||||
const bookingInfo = await prisma.booking.findFirst({
|
||||
where: {
|
||||
@@ -1013,27 +1026,18 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
};
|
||||
}
|
||||
|
||||
if (!eventTypeRaw.users.length && eventTypeRaw.userId) {
|
||||
// TODO we should add `user User` relation on `EventType` so this extra query isn't needed
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: eventTypeRaw.userId,
|
||||
},
|
||||
select: baseUserSelect,
|
||||
});
|
||||
if (user) {
|
||||
eventTypeRaw.users.push({
|
||||
...user,
|
||||
avatar: "",
|
||||
allowDynamicBooking: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
eventTypeRaw.users = !!eventTypeRaw.hosts?.length
|
||||
? eventTypeRaw.hosts.map((host) => host.user)
|
||||
: eventTypeRaw.users;
|
||||
|
||||
if (!eventTypeRaw.users.length) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
if (!eventTypeRaw.owner)
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
eventTypeRaw.users.push({
|
||||
...eventTypeRaw.owner,
|
||||
});
|
||||
}
|
||||
|
||||
const eventType = {
|
||||
@@ -1045,7 +1049,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
|
||||
};
|
||||
|
||||
if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl) {
|
||||
if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl && !cancel) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: eventType.successRedirectUrl,
|
||||
|
||||
@@ -64,7 +64,6 @@ export type FormValues = {
|
||||
phone?: string;
|
||||
}[];
|
||||
customInputs: CustomInputParsed[];
|
||||
users: string[];
|
||||
schedule: number;
|
||||
periodType: PeriodType;
|
||||
periodDays: number;
|
||||
@@ -85,6 +84,8 @@ export type FormValues = {
|
||||
};
|
||||
successRedirectUrl: string;
|
||||
bookingLimits?: BookingLimit;
|
||||
hosts: { userId: number }[];
|
||||
hostsFixed: { userId: number }[];
|
||||
};
|
||||
|
||||
export type CustomInputParsed = typeof customInputSchema._output;
|
||||
@@ -120,7 +121,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
});
|
||||
|
||||
const { eventType, locationOptions, team, teamMembers, currentUserMembership } = props;
|
||||
|
||||
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||
@@ -186,6 +186,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
description: eventType.description ?? undefined,
|
||||
schedule: eventType.schedule || undefined,
|
||||
bookingLimits: eventType.bookingLimits || undefined,
|
||||
length: eventType.length,
|
||||
hidden: eventType.hidden,
|
||||
periodDates: {
|
||||
startDate: periodDates.startDate,
|
||||
@@ -195,12 +196,17 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
|
||||
schedulingType: eventType.schedulingType,
|
||||
minimumBookingNotice: eventType.minimumBookingNotice,
|
||||
minimumBookingNoticeInDurationType: convertToNewDurationType(
|
||||
"minutes",
|
||||
findDurationType(eventType.minimumBookingNotice),
|
||||
eventType.minimumBookingNotice
|
||||
),
|
||||
metadata,
|
||||
hosts: !!eventType.hosts?.length
|
||||
? eventType.hosts.filter((host) => !host.isFixed)
|
||||
: eventType.users
|
||||
.filter(() => eventType.schedulingType === SchedulingType.ROUND_ROBIN)
|
||||
.map((user) => ({ userId: user.id })),
|
||||
hostsFixed: !!eventType.hosts?.length
|
||||
? eventType.hosts.filter((host) => host.isFixed)
|
||||
: eventType.users
|
||||
.filter(() => eventType.schedulingType === SchedulingType.COLLECTIVE)
|
||||
.map((user) => ({ userId: user.id })),
|
||||
},
|
||||
resolver: zodResolver(
|
||||
z
|
||||
@@ -238,7 +244,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
/>
|
||||
),
|
||||
availability: <AvailabilityTab isTeamEvent={!!team} />,
|
||||
team: <EventTeamTab eventType={eventType} teamMembers={teamMembers} team={team} />,
|
||||
team: <EventTeamTab teamMembers={teamMembers} team={team} />,
|
||||
limits: <EventLimitsTab eventType={eventType} />,
|
||||
advanced: <EventAdvancedTab eventType={eventType} team={team} />,
|
||||
recurring: <EventRecurringTab eventType={eventType} />,
|
||||
@@ -279,6 +285,8 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
locations,
|
||||
metadata,
|
||||
customInputs,
|
||||
hosts: hostsInput,
|
||||
hostsFixed,
|
||||
// We don't need to send send these values to the backend
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
seatsPerTimeSlotEnabled,
|
||||
@@ -287,6 +295,11 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
...input
|
||||
} = values;
|
||||
|
||||
const hosts: (typeof hostsInput[number] & { isFixed?: boolean })[] = [];
|
||||
if (hostsInput || hostsFixed) {
|
||||
hosts.push(...hostsInput.concat(hostsFixed.map((host) => ({ isFixed: true, ...host }))));
|
||||
}
|
||||
|
||||
if (bookingLimits) {
|
||||
const isValid = validateBookingLimitOrder(bookingLimits);
|
||||
if (!isValid) throw new Error(t("event_setup_booking_limits_error"));
|
||||
@@ -304,6 +317,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
|
||||
updateMutation.mutate({
|
||||
...input,
|
||||
hosts,
|
||||
locations,
|
||||
recurringEvent,
|
||||
periodStartDate: periodDates.startDate,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
CreateEventTypeButton,
|
||||
EventTypeDescriptionLazy as EventTypeDescription,
|
||||
} from "@calcom/features/eventtypes/components";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||
@@ -26,9 +27,10 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
EmptyScreen,
|
||||
Icon,
|
||||
Shell,
|
||||
showToast,
|
||||
Switch,
|
||||
Avatar,
|
||||
AvatarGroup,
|
||||
Tooltip,
|
||||
} from "@calcom/ui";
|
||||
|
||||
@@ -37,8 +39,6 @@ import { HttpError } from "@lib/core/http/error";
|
||||
|
||||
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
||||
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
|
||||
import Avatar from "@components/ui/Avatar";
|
||||
import AvatarGroup from "@components/ui/AvatarGroup";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
@@ -279,9 +279,8 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||
{type.users?.length > 1 && (
|
||||
<AvatarGroup
|
||||
border="border-2 border-white"
|
||||
className="relative top-1 right-3"
|
||||
size={8}
|
||||
size="sm"
|
||||
truncateAfter={4}
|
||||
items={type.users.map((organizer) => ({
|
||||
alt: organizer.name || "",
|
||||
@@ -371,11 +370,12 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
{t("embed")}
|
||||
</EmbedButton>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||
<DropdownMenuSeparator />
|
||||
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
|
||||
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) && (
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
color="destructive"
|
||||
onClick={() => {
|
||||
setDeleteDialogOpen(true);
|
||||
setDeleteDialogTypeId(type.id);
|
||||
@@ -467,7 +467,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
{t("duplicate")}
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -512,12 +512,12 @@ const EventTypeListHeading = ({
|
||||
teamId,
|
||||
}: EventTypeListHeadingProps): JSX.Element => {
|
||||
return (
|
||||
<div className="mb-4 flex">
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
|
||||
<Avatar
|
||||
alt={profile?.name || ""}
|
||||
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
|
||||
size={8}
|
||||
size="sm"
|
||||
className="mt-1 inline ltr:mr-2 rtl:ml-2"
|
||||
/>
|
||||
</Link>
|
||||
@@ -528,7 +528,7 @@ const EventTypeListHeading = ({
|
||||
{profile?.name || ""}
|
||||
</Link>
|
||||
{membershipCount && teamId && (
|
||||
<span className="relative -top-px text-xs text-neutral-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
|
||||
<span className="relative -top-px text-xs text-gray-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
|
||||
<Link href={`/settings/teams/${teamId}/members`}>
|
||||
<Badge variant="gray">
|
||||
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
|
||||
@@ -538,7 +538,7 @@ const EventTypeListHeading = ({
|
||||
</span>
|
||||
)}
|
||||
{profile?.slug && (
|
||||
<Link href={`${CAL_URL}/${profile.slug}`} className="block text-xs text-neutral-500">
|
||||
<Link href={`${CAL_URL}/${profile.slug}`} className="block text-xs text-gray-500">
|
||||
{`${CAL_URL?.replace("https://", "")}/${profile.slug}`}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -178,6 +178,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
throw new Error("User from session not found");
|
||||
}
|
||||
|
||||
if (user.completedOnboarding) {
|
||||
return { redirect: { permanent: false, destination: "/event-types" } };
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(context.locale ?? "", ["common"])),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Shell, { MobileNavigationMoreItems } from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MobileNavigationMoreItems, Shell } from "@calcom/ui";
|
||||
|
||||
export default function MorePage() {
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -7,7 +7,6 @@ import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
ColorPicker,
|
||||
Form,
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
Switch,
|
||||
UpgradeTeamsBadge,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
@@ -49,6 +49,7 @@ const AppearanceView = () => {
|
||||
const session = useSession();
|
||||
const utils = trpc.useContext();
|
||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||
const { data: dataHasTeamPlan, isLoading: isLoadingHasTeamPlan } = trpc.viewer.teams.hasTeamPlan.useQuery();
|
||||
|
||||
const formMethods = useForm({
|
||||
defaultValues: {
|
||||
@@ -73,7 +74,8 @@ const AppearanceView = () => {
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading) return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
|
||||
if (isLoading || isLoadingHasTeamPlan)
|
||||
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
@@ -180,18 +182,18 @@ const AppearanceView = () => {
|
||||
<p className="font-semibold ltr:mr-2 rtl:ml-2">
|
||||
{t("disable_cal_branding", { appName: APP_NAME })}
|
||||
</p>
|
||||
<Badge variant="gray">{t("pro")}</Badge>
|
||||
{!dataHasTeamPlan?.hasTeamPlan && <UpgradeTeamsBadge />}
|
||||
</div>
|
||||
<p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p>
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Switch
|
||||
id="hideBranding"
|
||||
disabled={!session.data?.user.belongsToActiveTeam}
|
||||
disabled={!dataHasTeamPlan?.hasTeamPlan}
|
||||
onCheckedChange={(checked) =>
|
||||
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
|
||||
}
|
||||
checked={!session.data?.user.belongsToActiveTeam ? false : value}
|
||||
checked={!dataHasTeamPlan?.hasTeamPlan ? false : value}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -175,9 +175,7 @@ const CalendarsView = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full border-t border-gray-200">
|
||||
<p className="px-2 pt-4 text-sm text-neutral-500">
|
||||
{t("toggle_calendars_conflict")}
|
||||
</p>
|
||||
<p className="px-2 pt-4 text-sm text-gray-500">{t("toggle_calendars_conflict")}</p>
|
||||
<ul className="space-y-2 p-4">
|
||||
{item.calendars.map((cal) => (
|
||||
<CalendarSwitch
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Dropdown,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownItem,
|
||||
DropdownMenuTrigger,
|
||||
Icon,
|
||||
List,
|
||||
@@ -83,7 +84,7 @@ const ConferencingLayout = () => {
|
||||
}
|
||||
<div className="flex-grow truncate pl-2">
|
||||
<ListItemTitle component="h3" className="mb-1 space-x-2 rtl:space-x-reverse">
|
||||
<h3 className="truncate text-sm font-medium text-neutral-900">{app.title}</h3>
|
||||
<h3 className="truncate text-sm font-medium text-gray-900">{app.title}</h3>
|
||||
</ListItemTitle>
|
||||
<ListItemText component="p">{app.description}</ListItemText>
|
||||
</div>
|
||||
@@ -94,16 +95,17 @@ const ConferencingLayout = () => {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>
|
||||
<Button
|
||||
<DropdownItem
|
||||
type="button"
|
||||
color="destructive"
|
||||
StartIcon={Icon.FiTrash}
|
||||
disabled={app.isGlobal}
|
||||
StartIcon={Icon.FiTrash}
|
||||
onClick={() => {
|
||||
setDeleteCredentialId(app.credentialIds[0]);
|
||||
setDeleteAppModal(true);
|
||||
}}>
|
||||
{t("remove_app")}
|
||||
</Button>
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
|
||||
@@ -229,11 +229,7 @@ const ProfileView = () => {
|
||||
{/* Delete account Dialog */}
|
||||
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
data-testid="delete-account"
|
||||
color="destructive"
|
||||
className="mt-1 border-2"
|
||||
StartIcon={Icon.FiTrash2}>
|
||||
<Button data-testid="delete-account" color="destructive" className="mt-1" StartIcon={Icon.FiTrash2}>
|
||||
{t("delete_account")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
@@ -92,7 +92,11 @@ export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof g
|
||||
{...register("username")}
|
||||
required
|
||||
/>
|
||||
<EmailField {...register("email")} />
|
||||
<EmailField
|
||||
{...register("email")}
|
||||
disabled={prepopulateFormValues?.email}
|
||||
className="disabled:bg-gray-200 disabled:hover:cursor-not-allowed"
|
||||
/>
|
||||
<PasswordField
|
||||
labelProps={{
|
||||
className: "block text-sm font-medium text-gray-700",
|
||||
@@ -145,6 +149,12 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
prepopulateFormValues: undefined,
|
||||
};
|
||||
|
||||
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
// no token given, treat as a normal signup without verification token
|
||||
if (!token) {
|
||||
return {
|
||||
|
||||
@@ -12,13 +12,12 @@ 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";
|
||||
import { Avatar, Button, Icon, HeadSeo } from "@calcom/ui";
|
||||
import { Avatar, Button, Icon, HeadSeo, AvatarGroup } from "@calcom/ui";
|
||||
|
||||
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import Team from "@components/team/screens/Team";
|
||||
import AvatarGroup from "@components/ui/AvatarGroup";
|
||||
|
||||
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
function TeamPage({ team }: TeamPageProps) {
|
||||
@@ -57,10 +56,9 @@ function TeamPage({ team }: TeamPageProps) {
|
||||
</div>
|
||||
<div className="mt-1 self-center">
|
||||
<AvatarGroup
|
||||
border="border-2 border-white dark:border-darkgray-100"
|
||||
truncateAfter={4}
|
||||
className="flex flex-shrink-0"
|
||||
size={10}
|
||||
size="sm"
|
||||
items={type.users.map((user) => ({
|
||||
alt: user.name || "",
|
||||
title: user.name || "",
|
||||
|
||||
@@ -51,16 +51,21 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
id: true,
|
||||
slug: true,
|
||||
hidden: true,
|
||||
users: {
|
||||
hosts: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
username: true,
|
||||
timeZone: true,
|
||||
hideBranding: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
isFixed: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
username: true,
|
||||
timeZone: true,
|
||||
hideBranding: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
title: true,
|
||||
@@ -124,11 +129,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
periodEndDate: eventType.periodEndDate?.toString() ?? null,
|
||||
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
|
||||
locations: privacyFilteredLocations(locations),
|
||||
users: eventType.users.map((user) => ({
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
hideBranding: user.hideBranding,
|
||||
timeZone: user.timeZone,
|
||||
users: eventType.hosts.map(({ user: { name, username, hideBranding, timeZone } }) => ({
|
||||
name,
|
||||
username,
|
||||
hideBranding,
|
||||
timeZone,
|
||||
})),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { TeamsListing } from "@calcom/features/ee/teams/components";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, Icon, Shell } from "@calcom/ui";
|
||||
import { Button, Icon } from "@calcom/ui";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Událost stále čeká na schválení",
|
||||
"booking_submitted_subject": "Rezervace odeslána: {{eventType}} s {{name}} během {{date}}",
|
||||
"your_meeting_has_been_booked": "Vaše schůzka byla rezervována",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Váš {{eventType}} s {{name}} byl přesunut na {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Váš {{title}} byl přeplánován na {{date}}.",
|
||||
"event_has_been_rescheduled": "Změna - Vaše událost byla přesunuta na jindy",
|
||||
"request_reschedule_title_attendee": "Žádost o přeplánování rezervace",
|
||||
"request_reschedule_subtitle": "Organizátor {{organizer}} zrušil rezervaci a požádal vás, abyste si vybrali jiný čas.",
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"event_still_awaiting_approval": "Ein Termin wartet noch auf Ihre Bestätigung",
|
||||
"booking_submitted_subject": "Buchung eingereicht: {{eventType}} mit {{name}} am {{date}}",
|
||||
"your_meeting_has_been_booked": "Dein Termin wurde gebucht",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde auf {{time}} ({{timeZone}}) am {{date}} verschoben.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Ihr {{title}} wurde auf den {{date}} verschoben.",
|
||||
"event_has_been_rescheduled": "Ihr Termin wurde verschoben.",
|
||||
"request_reschedule_title_attendee": "Anfrage zur Neuplanung Ihrer Buchung",
|
||||
"request_reschedule_subtitle": "{{organizer}} hat die Buchung storniert und Sie gebeten, eine andere Zeit auszuwählen.",
|
||||
|
||||
@@ -38,20 +38,37 @@
|
||||
"confirm_or_reject_request": "Επιβεβαιώστε ή απορρίψτε το αίτημα",
|
||||
"check_bookings_page_to_confirm_or_reject": "Ελέγξτε τη σελίδα κρατήσεων για να επιβεβαιώσετε ή να απορρίψετε την κράτηση.",
|
||||
"event_awaiting_approval": "Ένα γεγονός περιμένει την έγκρισή σας",
|
||||
"ics_event_title": "{{eventType}} με {{name}}",
|
||||
"join_by_entrypoint": "Συμμετάσχετε από {{entryPoint}}",
|
||||
"notes": "Σημειώσεις",
|
||||
"manage_my_bookings": "Διαχείριση κρατήσεων",
|
||||
"event_type": "Τύπος Συμβάντος",
|
||||
"meeting_password": "Συνθηματικό Συνάντησης",
|
||||
"meeting_url": "URL Συνάντησης",
|
||||
"meeting_request_rejected": "Το αίτημα συνάντησής σας απορρίφθηκε",
|
||||
"hi": "Γεια",
|
||||
"join_team": "Γίνε μέλος της ομάδας",
|
||||
"manage_this_team": "Διαχείριση αυτής της ομάδας",
|
||||
"team_info": "Πληροφορίες Ομάδας",
|
||||
"request_another_invitation_email": "Αν προτιμάτε να μην χρησιμοποιείτε το {{toEmail}} ως {{appName}} email σας ή να έχετε ήδη λογαριασμό {{appName}} , παρακαλώ ζητήστε μια άλλη πρόσκληση σε αυτό το email.",
|
||||
"you_have_been_invited": "Έχετε προσκληθεί να γίνετε μέλος της ομάδας {{teamName}}",
|
||||
"user_invited_you": "Ο χρήστης {{user}} σας προσκάλεσε να συμμετάσχετε στην ομάδα {{team}} στο {{appName}}",
|
||||
"hidden_team_member_title": "Είστε κρυμμένοι σε αυτήν την ομάδα",
|
||||
"link_expires": "Υ.Γ. Λήγει σε {{expiresIn}} ώρες.",
|
||||
"team_upgrade_banner_action": "Αναβάθμισε εδώ",
|
||||
"team_upgraded_successfully": "Η ομάδα σας αναβαθμίστηκε επιτυχώς!",
|
||||
"use_link_to_reset_password": "Χρησιμοποιήστε τον παρακάτω σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασης",
|
||||
"hey_there": "Γεια σας,",
|
||||
"forgot_your_password_calcom": "Ξεχάσατε τον κωδικό πρόσβασης? - {{appName}}",
|
||||
"confirm_delete_webhook": "Ναι, διαγραφή webhook",
|
||||
"edit_webhook": "Επεξεργασία Webhook",
|
||||
"delete_webhook": "Διαγραφή Webhook",
|
||||
"webhook_status": "Κατάσταση Webhook",
|
||||
"webhook_enabled": "Ενεργοποιημένο Webhook",
|
||||
"webhook_disabled": "Απενεργοποιημένο Webhook",
|
||||
"webhook_response": "Απάντηση Webhook",
|
||||
"webhook_test": "Έλεγχος Webhook",
|
||||
"manage_your_webhook": "Διαχείριση του webhook σας",
|
||||
"webhook_created_successfully": "Το Webhook δημιουργήθηκε επιτυχώς!",
|
||||
"webhook_updated_successfully": "Το Webhook ενημερώθηκε επιτυχώς!",
|
||||
"webhook_removed_successfully": "Το Webhook αφαιρέθηκε επιτυχώς!",
|
||||
@@ -62,6 +79,14 @@
|
||||
"past": "Παρελθοντικά",
|
||||
"choose_a_file": "Επιλογή αρχείου...",
|
||||
"upload_image": "Μεταφόρτωση εικόνας",
|
||||
"view_notifications": "Εμφάνιση ειδοποιήσεων",
|
||||
"view_public_page": "Προβολή δημόσιας σελίδας",
|
||||
"copy_public_page_link": "Αντιγραφή συνδέσμου δημόσιας σελίδας",
|
||||
"sign_out": "Αποσύνδεση",
|
||||
"2fa_confirm_current_password": "Επιβεβαιώστε τον κωδικό πρόσβασης σας για να ξεκινήσετε.",
|
||||
"required": "Υποχρεωτικό",
|
||||
"optional": "Προαιρετικό",
|
||||
"guests": "Επισκέπτες",
|
||||
"username": "Όνομα χρήστη",
|
||||
"is_still_available": "είναι ακόμα διαθέσιμο.",
|
||||
"documentation": "Τεκμηρίωση",
|
||||
@@ -88,17 +113,37 @@
|
||||
"dont_have_an_account": "Δεν έχετε λογαριασμό;",
|
||||
"sign_in_account": "Συνδεθείτε στο λογαριασμό σας",
|
||||
"sign_in": "Είσοδος",
|
||||
"error_during_login": "Παρουσιάστηκε σφάλμα κατά τη σύνδεσή σας. Επιστρέψτε στην οθόνη σύνδεσης και προσπαθήστε ξανά.",
|
||||
"request_password_reset": "Αποστολή email επαναφοράς",
|
||||
"forgot_password": "Ξεχάσατε τον κωδικό πρόσβασης?",
|
||||
"all_apps": "Όλα",
|
||||
"check_email_reset_password": "Ελέγξτε το email σας. Σας στείλαμε ένα σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασής σας.",
|
||||
"few_sentences_about_yourself": "Μερικά λόγια για τον εσάς. Αυτό θα εμφανιστεί στην προσωπική σελίδα σας.",
|
||||
"set_availability": "Όρισε τη διαθεσιμότητά σου",
|
||||
"details": "Λεπτομέρειες",
|
||||
"welcome": "Καλωσορίσατε",
|
||||
"welcome_back": "Καλωσορίσατε πίσω",
|
||||
"welcome_to_calcom": "Καλωσορίσατε στο {{appName}}",
|
||||
"connect_caldav": "Σύνδεση στο CalDav (Beta)",
|
||||
"credentials_stored_and_encrypted": "Τα διαπιστευτήριά σας θα αποθηκευτούν και θα κρυπτογραφηθούν.",
|
||||
"connect": "Σύνδεση",
|
||||
"try_for_free": "Δοκιμάστε δωρεάν",
|
||||
"add_to_calendar": "Προσθήκη στο ημερολόγιο",
|
||||
"add_another_calendar": "Προσθήκη άλλου ημερολογίου",
|
||||
"email_sign_in_subject": "Ο σύνδεσμος εισόδου για {{appName}}",
|
||||
"meeting_is_scheduled": "Η συνάντηση έχει προγραμματιστεί",
|
||||
"submitted": "Η κράτησή σας έχει υποβληθεί",
|
||||
"booking_submitted": "Η κράτησή σας έχει υποβληθεί",
|
||||
"booking_confirmed": "Η κράτησή σας έχει επιβεβαιωθεί",
|
||||
"booking_confirmed_recurring": "Η επαναλαμβανόμενη συνάντησή σας έχει επιβεβαιωθεί",
|
||||
"warning_recurring_event_payment": "Οι πληρωμές δεν υποστηρίζονται ακόμη στις επαναλαμβανόμενες εκδηλώσεις",
|
||||
"enter_new_password": "Εισάγετε τον νέο κωδικό πρόσβασης που θέλετε για το λογαριασμό σας.",
|
||||
"reset_password": "Επαναφορά Κωδικού Πρόσβασης",
|
||||
"change_your_password": "Αλλαγή κωδικού πρόσβασης",
|
||||
"show_password": "Εμφάνιση κωδικού πρόσβασης",
|
||||
"hide_password": "Απόκρυψη κωδικού πρόσβασης",
|
||||
"try_again": "Δοκιμάστε ξανά",
|
||||
"request_is_expired": "Το αίτημα έχει λήξει.",
|
||||
"sunday_time_error": "Μη έγκυρη ώρα την Κυριακή",
|
||||
"monday_time_error": "Μη έγκυρη ώρα τη Δευτέρα",
|
||||
"tuesday_time_error": "Μη έγκυρη ώρα την Τρίτη",
|
||||
@@ -106,6 +151,127 @@
|
||||
"thursday_time_error": "Μη έγκυρη ώρα την Πέμπτη",
|
||||
"friday_time_error": "Μη έγκυρη ώρα την Παρασκευή",
|
||||
"saturday_time_error": "Μη έγκυρη ώρα το Σάββατο",
|
||||
"error_with_status_code_occured": "Παρουσιάστηκε ένα σφάλμα με κωδικό κατάστασης {{status}}.",
|
||||
"booking_already_cancelled": "Αυτή η κράτηση είχε ήδη ακυρωθεί",
|
||||
"booking_already_accepted_rejected": "Αυτή η κράτηση έγινε ήδη αποδεκτή ή απορρίφθηκε",
|
||||
"go_back_home": "Επιστροφή στην αρχική σελίδα",
|
||||
"or_go_back_home": "Ή επιστροφή στην αρχική σελίδα",
|
||||
"no_meeting_found": "Δε βρέθηκε συνάντηση",
|
||||
"bookings": "Κρατήσεις"
|
||||
"no_status_bookings_yet": "Δεν υπάρχουν {{status}} κρατήσεις",
|
||||
"no_status_bookings_yet_description": "Δεν έχετε {{status}} κρατήσεις. {{description}}",
|
||||
"bookings": "Κρατήσεις",
|
||||
"unconfirmed_bookings_tooltip": "Μη επιβεβαιωμένες κρατήσεις",
|
||||
"start_time": "Ώρα έναρξης",
|
||||
"end_time": "Ώρα λήξης",
|
||||
"your_day_starts_at": "Η ημέρα σας ξεκινάει στις",
|
||||
"your_day_ends_at": "Η ημέρα σας τελειώνει στις",
|
||||
"change_available_times": "Αλλαγή διαθέσιμων ωρών",
|
||||
"change_your_available_times": "Αλλάξτε τις διαθέσιμες ώρες σας",
|
||||
"change_start_end": "Αλλάξτε την ώρα έναρξης και λήξης της ημέρας σας",
|
||||
"current_start_date": "Αυτή τη στιγμή, η ημέρα σας έχει οριστεί να ξεκινήσει στις",
|
||||
"email": "Email",
|
||||
"email_placeholder": "jdoe@example.com",
|
||||
"loading": "Φόρτωση...",
|
||||
"deleting": "Διαγραφή...",
|
||||
"developer": "Προγραμματιστές",
|
||||
"schedule_a_meeting": "Προγραμματισμός συνάντησης",
|
||||
"view_and_manage_billing_details": "Προβολή και διαχείριση των στοιχείων χρέωσης",
|
||||
"view_and_edit_billing_details": "Προβολή και επεξεργασία των στοιχείων χρέωσης σας, καθώς και ακύρωση της συνδρομής σας.",
|
||||
"need_anything_else": "Χρειάζεστε οτιδήποτε άλλο;",
|
||||
"further_billing_help": "Αν χρειάζεστε περαιτέρω βοήθεια με την τιμολόγηση, η ομάδα υποστήριξής μας είναι εδώ για να βοηθήσει.",
|
||||
"contact": "Επικοινωνία",
|
||||
"contact_our_support_team": "Επικοινωνήστε με την ομάδα υποστήριξης",
|
||||
"no_event_types_have_been_setup": "Αυτός ο χρήστης δεν έχει ορίσει ακόμα τύπους γεγονότων.",
|
||||
"edit_logo": "Επεξεργασία λογοτύπου",
|
||||
"upload_a_logo": "Ανεβάστε ένα λογότυπο",
|
||||
"remove_logo": "Αφαίρεση λογότυπου",
|
||||
"enable": "Ενεργοποίηση",
|
||||
"2fa": "Ταυτοποίηση Δύο Παραμέτρων",
|
||||
"security": "Ασφάλεια",
|
||||
"manage_account_security": "Διαχειριστείτε την ασφάλεια του λογαριασμού σας.",
|
||||
"password": "Κωδικός",
|
||||
"password_updated_successfully": "Ο κωδικός πρόσβασης ενημερώθηκε επιτυχώς",
|
||||
"password_has_been_changed": "Ο κωδικός πρόσβασής σας έχει αλλάξει με επιτυχία.",
|
||||
"error_changing_password": "Σφάλμα στην αλλαγή κωδικού πρόσβασης",
|
||||
"something_went_wrong": "Κάτι πήγε στραβά.",
|
||||
"something_doesnt_look_right": "Κάτι δεν φαίνεται σωστά;",
|
||||
"please_try_again": "Παρακαλώ δοκιμάστε ξανά.",
|
||||
"super_secure_new_password": "Ο σούπερ ασφαλής νέος κωδικός πρόσβασης σας",
|
||||
"new_password": "Νέος Κωδικός",
|
||||
"your_old_password": "Ο παλιός σας κωδικός",
|
||||
"current_password": "Τρέχων Κωδικός",
|
||||
"change_password": "Αλλαγή Κωδικού",
|
||||
"change_secret": "Αλλαγή Μυστικού",
|
||||
"new_password_matches_old_password": "Ο νέος κωδικός πρόσβασης ταιριάζει με τον παλιό σας κωδικό. Παρακαλώ επιλέξτε διαφορετικό κωδικό πρόσβασης.",
|
||||
"your_name": "Το όνομά σας",
|
||||
"email_address": "Διεύθυνση ηλεκτρονικού ταχυδρομείου",
|
||||
"enter_valid_email": "Παρακαλώ εισάγετε ένα έγκυρο email",
|
||||
"location": "Τοποθεσία",
|
||||
"address": "Διεύθυνση",
|
||||
"enter_address": "Εισάγετε διεύθυνση",
|
||||
"yes": "ναι",
|
||||
"no": "όχι",
|
||||
"additional_notes": "Πρόσθετες σημειώσεις",
|
||||
"your_number": "Ο αριθμός τηλεφώνου σας",
|
||||
"phone_number": "Αριθμός Τηλεφώνου",
|
||||
"attendee_phone_number": "Αριθμός Τηλεφώνου Συμμετέχοντα",
|
||||
"host_phone_number": "Ο αριθμός τηλεφώνου σας",
|
||||
"enter_phone_number": "Εισάγετε αριθμό τηλεφώνου",
|
||||
"or": "Ή",
|
||||
"go_back": "Επιστροφή",
|
||||
"email_or_username": "Email ή Όνομα χρήστη",
|
||||
"role": "Ρόλος",
|
||||
"edit_role": "Επεξεργασία Ρόλου",
|
||||
"edit_team": "Επεξεργασία ομάδας",
|
||||
"reject": "Απορρίψτε",
|
||||
"reject_all": "Απόρριψη όλων",
|
||||
"accept": "Αποδοχή",
|
||||
"leave": "Αποχώρηση",
|
||||
"team_name": "Όνομα ομάδας",
|
||||
"your_team_name": "Το όνομα της ομάδας σας",
|
||||
"team_updated_successfully": "Επιτυχής ενημέρωση ομάδας",
|
||||
"your_team_updated_successfully": "Η ομάδα σας ενημερώθηκε με επιτυχία.",
|
||||
"about": "Σχετικά",
|
||||
"confirm_remove_member": "Ναι, αφαίρεση μέλους",
|
||||
"remove_member": "Αφαίρεση μέλους",
|
||||
"manage_your_team": "Διαχείριση ομάδας",
|
||||
"no_teams": "Δεν έχετε καμία ομάδα ακόμη.",
|
||||
"submit": "Υποβολή",
|
||||
"delete": "Διαγραφή",
|
||||
"update": "Ενημέρωση",
|
||||
"save": "Αποθήκευση",
|
||||
"pending": "Εκκρεμεί",
|
||||
"share": "Κοινοποίηση",
|
||||
"leave_team": "Αποχώρηση από ομάδα",
|
||||
"confirm_leave_team": "Ναι, αποχώρηση από την ομάδα",
|
||||
"preview": "Προεπισκόπηση",
|
||||
"link_copied": "Ο σύνδεσμος αντιγράφηκε!",
|
||||
"private_link_copied": "Ο ιδιωτικός σύνδεσμος αντιγράφηκε!",
|
||||
"title": "Τίτλος",
|
||||
"description": "Περιγραφή",
|
||||
"preview_team": "Προεπισκόπηση ομάδας",
|
||||
"duration": "Διάρκεια",
|
||||
"minutes": "Λεπτά",
|
||||
"one_time_link": "Σύνδεσμος μιας χρήσης",
|
||||
"first_day_of_week": "Πρώτη ημέρα της εβδομάδας",
|
||||
"weekly_one": "εβδομάδα",
|
||||
"weekly_other": "εβδομάδες",
|
||||
"monthly_one": "μήνας",
|
||||
"monthly_other": "μήνες",
|
||||
"yearly_one": "έτος",
|
||||
"yearly_other": "έτη",
|
||||
"max": "Μέγιστο",
|
||||
"create_team": "Δημιουργία ομάδας",
|
||||
"name": "Όνομα",
|
||||
"create_new_team": "Δημιουργία νέας ομάδας",
|
||||
"new_team": "Νέα Ομάδα",
|
||||
"teams": "Ομάδες",
|
||||
"team": "Ομάδα",
|
||||
"enabled": "Ενεργοποιημένο",
|
||||
"disabled": "Απενεργοποιημένο",
|
||||
"show_advanced_settings": "Εμφάνιση προχωρημένων ρυθμίσεων",
|
||||
"event_name_tooltip": "Το όνομα που θα εμφανίζεται στα ημερολόγια",
|
||||
"label": "Ετικέτα",
|
||||
"edit": "Επεξεργασία",
|
||||
"disable_guests": "Απενεργοποίηση επισκεπτών"
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"calcom_explained": "{{appName}} provides scheduling infrastructure for absolutely everyone.",
|
||||
"have_any_questions": "Have questions? We're here to help.",
|
||||
"reset_password_subject": "{{appName}}: Reset password instructions",
|
||||
"event_declined_subject": "Declined: {{eventType}} with {{name}} at {{date}}",
|
||||
"event_cancelled_subject": "Cancelled: {{eventType}} with {{name}} at {{date}}",
|
||||
"event_declined_subject": "Declined: {{title}} at {{date}}",
|
||||
"event_cancelled_subject": "Cancelled: {{title}} at {{date}}",
|
||||
"event_request_declined": "Your event request has been declined",
|
||||
"event_request_declined_recurring": "Your recurring event request has been declined",
|
||||
"event_request_cancelled": "Your scheduled event was cancelled",
|
||||
@@ -32,7 +32,7 @@
|
||||
"refund_failed": "The refund for the event {{eventType}} with {{userName}} on {{date}} failed.",
|
||||
"check_with_provider_and_user": "Please check with your payment provider and {{user}} how to handle this.",
|
||||
"a_refund_failed": "A refund failed",
|
||||
"awaiting_payment_subject": "Awaiting Payment: {{eventType}} with {{name}} on {{date}}",
|
||||
"awaiting_payment_subject": "Awaiting Payment: {{title}} on {{date}}",
|
||||
"meeting_awaiting_payment": "Your meeting is awaiting payment",
|
||||
"help": "Help",
|
||||
"price": "Price",
|
||||
@@ -64,11 +64,11 @@
|
||||
"someone_requested_an_event": "Someone has requested to schedule an event on your calendar.",
|
||||
"someone_requested_password_reset": "Someone has requested a link to change your password.",
|
||||
"password_reset_instructions": "If you didn't request this, you can safely ignore this email and your password will not be changed.",
|
||||
"event_awaiting_approval_subject": "Awaiting Approval: {{eventType}} with {{name}} at {{date}}",
|
||||
"event_awaiting_approval_subject": "Awaiting Approval: {{eventType}} at {{date}}",
|
||||
"event_still_awaiting_approval": "An event is still waiting for your approval",
|
||||
"booking_submitted_subject": "Booking Submitted: {{eventType}} with {{name}} at {{date}}",
|
||||
"booking_submitted_subject": "Booking Submitted: {{title}} at {{date}}",
|
||||
"your_meeting_has_been_booked": "Your meeting has been booked",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Your {{eventType}} with {{name}} has been rescheduled to {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Your {{title}} has been rescheduled to {{date}}.",
|
||||
"event_has_been_rescheduled": "Updated - Your event has been rescheduled",
|
||||
"request_reschedule_title_attendee": "Request to reschedule your booking",
|
||||
"request_reschedule_subtitle": "{{organizer}} has cancelled the booking and requested you to pick another time.",
|
||||
@@ -799,12 +799,14 @@
|
||||
"no_category_apps_description_analytics": "Add an analytics app for your booking pages",
|
||||
"no_category_apps_description_automation": "Add an automation app to use",
|
||||
"no_category_apps_description_other": "Add any other type of app to do all sorts of things",
|
||||
"no_category_apps_description_web3": "Add a web3 app for your booking pages",
|
||||
"installed_app_calendar_description": "Set the calendars to check for conflicts to prevent double bookings.",
|
||||
"installed_app_conferencing_description": "Add your favourite video conferencing apps for your meetings",
|
||||
"installed_app_payment_description": "Configure which payment processing services to use when charging your clients.",
|
||||
"installed_app_analytics_description": "Configure which analytics apps to use for your booking pages",
|
||||
"installed_app_other_description": "All your installed apps from other categories.",
|
||||
"installed_app_automation_description": "Configure which automation apps to use",
|
||||
"installed_app_web3_description": "Configure which web3 apps to use for your booking pages",
|
||||
"analytics": "Analytics",
|
||||
"empty_installed_apps_headline": "No apps installed",
|
||||
"empty_installed_apps_description": "Apps enable you to enhance your workflow and improve your scheduling life significantly.",
|
||||
@@ -1217,6 +1219,7 @@
|
||||
"connect_automation_apps": "Connect automation apps",
|
||||
"connect_analytics_apps": "Connect analytics apps",
|
||||
"connect_other_apps": "Connect other apps",
|
||||
"connect_web3_apps": "Connect web3 apps",
|
||||
"current_step_of_total": "Step {{currentStep}} of {{maxSteps}}",
|
||||
"add_variable": "Add variable",
|
||||
"custom_phone_number": "Custom phone number",
|
||||
@@ -1492,10 +1495,20 @@
|
||||
"invalid_admin_password": "You are set as an admin but you do not have a password length of at least 15 characters",
|
||||
"change_password_admin": "Change Password to gain admin access",
|
||||
"username_already_taken": "Username is already taken",
|
||||
"assignment": "Assignment",
|
||||
"fixed_hosts": "Fixed Hosts",
|
||||
"add_fixed_hosts": "Add fixed hosts",
|
||||
"round_robin_hosts": "Round-Robin Hosts",
|
||||
"minimum_round_robin_hosts_count": "Number of hosts required to attend",
|
||||
"hosts": "Hosts",
|
||||
"upgrade_to_enable_feature": "You need to create a team to enable this feature. Click to create a team.",
|
||||
"new_attendee": "New Attendee",
|
||||
"awaiting_approval": "Awaiting Approval",
|
||||
"requires_google_calendar": "This app requires a Google Calendar connection",
|
||||
"connected_google_calendar": "You have connected a Google Calendar account.",
|
||||
"using_meet_requires_calendar": "Using Google Meet requires a connected Google Calendar",
|
||||
"continue_to_install_google_calendar": "Continue to install Google Calendar",
|
||||
"install_google_meet": "Install Google Meet",
|
||||
"install_google_calendar": "Install Google Calendar"
|
||||
"install_google_calendar": "Install Google Calendar",
|
||||
"no_recordings_found": "No recordings found"
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Un evento aún está esperando su aprobación",
|
||||
"booking_submitted_subject": "Reserva enviada: {{eventType}} con {{name}} el {{date}}",
|
||||
"your_meeting_has_been_booked": "Su reunión ha sido reservada",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Tu {{eventType}} con {{name}} se reprogramó para el {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Tu {{title}} ha sido reprogramado para {{date}}.",
|
||||
"event_has_been_rescheduled": "Tu evento ha sido reprogramado.",
|
||||
"request_reschedule_title_attendee": "Solicitud para reprogramar su reserva",
|
||||
"request_reschedule_subtitle": "{{organizer}} ha cancelado la reserva y le ha solicitado que elija otra hora.",
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"day_one": "egun {{count}}",
|
||||
"day_other": "{{count}} egun",
|
||||
"second_one": "segundo {{count}}",
|
||||
"second_other": "{{count}} segundo",
|
||||
"accept_invitation": "Onartu gonbidapena",
|
||||
"have_any_questions": "Galderarik? Laguntzeko gaude.",
|
||||
"reset_password_subject": "{{appName}}: Pasahitza berrezartzeko argibideak",
|
||||
"event_declined_subject": "Baztertua: {{title}} {{date}}(e)an",
|
||||
"event_cancelled_subject": "Bertan behera: {{title}} {{date}}(e)an",
|
||||
"event_request_declined": "Zure gertaera-eskaera baztertua izan da",
|
||||
"event_request_declined_recurring": "Zure gertaera errepikari-eskaera baztertua izan da",
|
||||
"event_request_cancelled": "Zure programatutako gertaera baztertua izan da",
|
||||
"organizer": "Antolatzailea",
|
||||
"need_to_reschedule_or_cancel": "Programazioa aldatu edo bertan behera utzi behar duzu?",
|
||||
"cancellation_reason": "Bertan behera uztearen arrazoia (aukerakoa)",
|
||||
"cancellation_reason_placeholder": "Zergatik utzi duzu bertan behera?",
|
||||
"rejection_reason": "Errefusatzeko arrazoia",
|
||||
"rejection_reason_title": "Errefusatu erreserba-eskaera?",
|
||||
"rejection_reason_description": "Ziur zaude erreserba errefusatu nahi duzula? Erreserba-eskaera egin duen pertsonari jakinaraziko zaio. Arrazoi bat adieraz dezakezu behean.",
|
||||
"rejection_confirmation": "Errefusatu erreserba",
|
||||
"manage_this_event": "Kudeatu gertaera hau",
|
||||
"your_event_has_been_scheduled": "Zure gertaera programatu da",
|
||||
"error_message": "Errore-mezua honakoa ian da: '{{errorMessage}}'",
|
||||
"refund_failed_subject": "Itzulketak huts egin du: {{name}} - {{date}} - {{eventType}}",
|
||||
"refund_failed": "Huts egin du itzulketak {{eventType}} gertaerarako, {{userName}}(r)ekin {{date}}(e)an.",
|
||||
"a_refund_failed": "Itzulketa batek huts egin du",
|
||||
"awaiting_payment_subject": "Ordainketaren zain: {{title}} {{date}}(e)an",
|
||||
"help": "Laguntza",
|
||||
"price": "Prezioa",
|
||||
"paid": "Ordainduta",
|
||||
"refunded": "Itzulita",
|
||||
"payment": "Ordainketa",
|
||||
"pay_now": "Ordaindu orain",
|
||||
"no_more_results": "Emaitza gehiagorik ez",
|
||||
"no_results": "Emaitzarik ez",
|
||||
"load_more_results": "Kargatu emaitza gehiago",
|
||||
"confirmed_event_type_subject": "Baieztatua: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
|
||||
"confirm_or_reject_request": "Baieztatu edo errefusatu eskaera",
|
||||
"check_bookings_page_to_confirm_or_reject": "Begiratu zure erreserba-orrialdea erreserba baieztatu edo errefusatzeko.",
|
||||
"event_awaiting_approval": "Gertaera bat zure onarpenaren zain dago",
|
||||
"booking_submitted_subject": "Erreserba bidalita: {{title}} {{date}}(e)an",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Zure {{title}} getaeraren programazioa aldatu egin da {{date}}(e)ra.",
|
||||
"hi_user_name": "Kaixo {{name}}",
|
||||
"ics_event_title": "{{eventType}} {{name}}(r)ekin",
|
||||
"notes": "Oharrak",
|
||||
"manage_my_bookings": "Kudeatu nire erreserbak",
|
||||
"rejected_event_type_with_organizer": "Errefusatua: {{eventType}} {{organizer}}(r)ekin {{date}}(e)an",
|
||||
"hi": "Kaixo",
|
||||
"change_bookings_availability": "Aldatu noiz zauden prest erreserbak jasotzeko",
|
||||
"create_booking_link_with_calcom": "Sor ezazu zeure erreserba-esteka {{appName}}(e)kin",
|
||||
"user_needs_to_confirm_or_reject_booking": "{{user}}(e)k erreserba baieztatu edo errefusatu behar du oraindik.",
|
||||
"booking_submitted": "Zure erreserba bidali da",
|
||||
"booking_confirmed": "Zure erreserba baieztatu da",
|
||||
"back_to_bookings": "Itzuli erreserbatara",
|
||||
"really_cancel_booking": "Benetan bertan behera utzi nahi duzu zure erreserba?",
|
||||
"cannot_cancel_booking": "Ezin duzu erreserba hau bertan behera utzi",
|
||||
"booking_already_cancelled": "Erreserba hau bertan behera utzi da dagoeneko",
|
||||
"booking_already_accepted_rejected": "Erreserba hau onartu edo errefusatu da dagoeneko",
|
||||
"bookings": "Erreserbak",
|
||||
"past_bookings": "Zure iraganeko erreserbak agertuko dira hemen.",
|
||||
"cancelled_bookings": "Zure bertan behera utzitako erreserbak agertuko dira hemen.",
|
||||
"unconfirmed_bookings": "Zure baieztatu gabeko erreserbak agertuko dira hemen.",
|
||||
"unconfirmed_bookings_tooltip": "Baieztatu gabeko erreserbak",
|
||||
"booking_cancelled": "Erreserba bertan behera",
|
||||
"booking_rescheduled": "Erreserbaren programazioa aldatuta",
|
||||
"booking_created": "Erreserba sortuta",
|
||||
"edit_booking": "Aldatu erreserba",
|
||||
"reschedule_booking": "Aldatu erreserbaren programazioa",
|
||||
"location_changed_event_type_subject": "Kokapena aldatu da: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
|
||||
"booking_details": "Erreserbaren xehetasunak",
|
||||
"reminder_email": "Gogorarazpena: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
|
||||
"new_seat_subject": "{{name}} parte-hartzaile berria {{eventType}}(e)n {{date}}(e)an",
|
||||
"booking_appearance": "Erreserba-orriaren itxura",
|
||||
"limit_booking_frequency": "Mugatu erreserbatzeko maiztasuna",
|
||||
"booking_tentative": "Erreserba hau behin-behinekoa da",
|
||||
"limit_future_bookings": "Mugatu etorkizuneko erreserbak",
|
||||
"all_bookings_filter_label": "Erreserba guztiak"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Un evento è ancora in attesa della tua approvazione",
|
||||
"booking_submitted_subject": "Prenotazione inviata: {{eventType}} con {{name}} il {{date}}",
|
||||
"your_meeting_has_been_booked": "Il tuo meeting è stata prenotato",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Il tuo {{eventType}} con {{name}} è stato riprogrammato il {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Il tuo {{title}} è stato riprogrammato per il {{date}}.",
|
||||
"event_has_been_rescheduled": "Aggiornato: il tuo evento è stato riprogrammato",
|
||||
"request_reschedule_title_attendee": "Richiesta di riprogrammare la prenotazione",
|
||||
"request_reschedule_subtitle": "{{organizer}} ha annullato la prenotazione e ti ha chiesto di scegliere un altro orario.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "イベントはまだあなたの承認待ちです",
|
||||
"booking_submitted_subject": "予約を送信しました: {{date}} の {{name}} との {{eventType}}",
|
||||
"your_meeting_has_been_booked": "ミーティングが予約されました",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{name}} との {{eventType}} は {{date}} にスケジュールが変更されました。",
|
||||
"event_type_has_been_rescheduled_on_time_date": "あなたの {{title}} は {{date}} に再スケジュールされました。",
|
||||
"event_has_been_rescheduled": "更新 - イベントのスケジュールが変更されました",
|
||||
"request_reschedule_title_attendee": "予約のスケジュール変更をリクエスト",
|
||||
"request_reschedule_subtitle": "{{organizer}} が予約をキャンセルしました。別の時間を選択することをあなたにリクエストしています。",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "이벤트가 아직 승인을 기다리고 있습니다.",
|
||||
"booking_submitted_subject": "예약 제출됨: {{date}}에 {{name}}에 의한 {{eventType}}",
|
||||
"your_meeting_has_been_booked": "회의가 예약되었습니다.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{name}}의 {{eventType}} 일정이 {{date}}(으)로 변경되었습니다.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "귀하의 {{title}} 일정이 {{date}}로 변경되었습니다.",
|
||||
"event_has_been_rescheduled": "업데이트 - 이벤트 일정이 변경되었습니다.",
|
||||
"request_reschedule_title_attendee": "예약 일정 변경 요청",
|
||||
"request_reschedule_subtitle": "{{organizer}}에서 예약을 취소했고 다른 시간을 선택할 것을 요청했습니다.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Een gebeurtenis wacht nog op uw goedkeuring",
|
||||
"booking_submitted_subject": "Boeking verzonden: {{eventType}} met {{name}} om {{date}}",
|
||||
"your_meeting_has_been_booked": "Uw afspraak is geboekt",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Uw {{eventType}} met {{name}} is verplaatst naar {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Uw {{title}} is verplaatst naar {{date}}.",
|
||||
"event_has_been_rescheduled": "Bijgewerkt - Uw gebeurtenis is verplaatst",
|
||||
"request_reschedule_title_attendee": "Vezoek om uw boeking te verplaatsen",
|
||||
"request_reschedule_subtitle": "{{organizer}} heeft de boeking geannuleerd en u verzocht een andere tijd te kiezen.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "En hendelse venter fortsatt på din godkjenning",
|
||||
"booking_submitted_subject": "Booking sendt inn: {{eventType}} med {{name}} kl. {{date}}",
|
||||
"your_meeting_has_been_booked": "Møtet ditt er booket",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Din {{eventType}} med {{name}} har blitt flyttet til {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Din {{title}} har blitt flyttet til {{date}}.",
|
||||
"event_has_been_rescheduled": "Oppdatert – Hendelsen din har blitt endret",
|
||||
"request_reschedule_title_attendee": "Be om å få endre tidspunkt for bookingen din",
|
||||
"request_reschedule_subtitle": "{{organizer}} har kansellert bookingen og bedt deg om å velge et annet tidspunkt.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Wydarzenie nadal oczekuje na Twoje zatwierdzenie",
|
||||
"booking_submitted_subject": "Rezerwacja potwierdzona: {{eventType}} z {{name}} w {{date}}",
|
||||
"your_meeting_has_been_booked": "Twoje spotkanie zostało zarezerwowane",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Wydarzenie {{eventType}} z {{name}} przełożono na {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Twój {{title}} został przełożony na {{date}}.",
|
||||
"event_has_been_rescheduled": "Zaktualizowano - Twoje wydarzenie zostało przełożone",
|
||||
"request_reschedule_title_attendee": "Poproś o przełożenie rezerwacji",
|
||||
"request_reschedule_subtitle": "{{organizer}} anulował(a) rezerwację i prosi Cię o wybranie innego terminu.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Um evento está aguardando a sua aprovação",
|
||||
"booking_submitted_subject": "Reserva enviada: {{eventType}} com {{name}} em {{date}}",
|
||||
"your_meeting_has_been_booked": "A sua reunião foi agendada",
|
||||
"event_type_has_been_rescheduled_on_time_date": "O evento {{eventType}} com {{name}} foi reagendado para as {{time}} ({{timeZone}}) de {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Seu {{title}} foi remarcado para {{date}}.",
|
||||
"event_has_been_rescheduled": "Atualização - O seu evento foi reagendado",
|
||||
"request_reschedule_title_attendee": "Solicitar reagendamento da reserva",
|
||||
"request_reschedule_subtitle": "{{organizer}} cancelou a reserva e solicitou que você escolhesse outro horário.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Um evento ainda aguarda a sua aprovação",
|
||||
"booking_submitted_subject": "Reserva submetida: {{eventType}} com {{name}} em {{date}}",
|
||||
"your_meeting_has_been_booked": "A sua reunião foi reservada",
|
||||
"event_type_has_been_rescheduled_on_time_date": "O evento {{eventType}} com {{name}} foi reagendado para as {{time}} ({{timeZone}}) de {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Seu {{title}} foi remarcado para {{date}}.",
|
||||
"event_has_been_rescheduled": "O seu evento foi reagendado.",
|
||||
"request_reschedule_title_attendee": "Solicitar o reagendamento da sua reserva",
|
||||
"request_reschedule_subtitle": "{{organizer}} cancelou a reserva e pediu para que escolhesse outro horário.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Un eveniment încă așteaptă aprobarea dvs.",
|
||||
"booking_submitted_subject": "Rezervare transmisă: {{eventType}} cu {{name}} în {{date}}",
|
||||
"your_meeting_has_been_booked": "Întâlnirea dvs. a fost programată",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{eventType}} cu {{name}} a fost reprogramat/ă pe {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{title}} dvs. a fost reprogramat la {{date}}.",
|
||||
"event_has_been_rescheduled": "Evenimentul dvs. a fost reprogramat.",
|
||||
"request_reschedule_title_attendee": "Solicitați reprogramarea rezervării",
|
||||
"request_reschedule_subtitle": "{{organizer}} a anulat rezervarea și v-a cerut să alegeți o altă dată.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Событие все еще ждет вашего подтверждения",
|
||||
"booking_submitted_subject": "Бронирование отправлено: {{eventType}} с {{name}}, {{date}}",
|
||||
"your_meeting_has_been_booked": "Ваша встреча была забронирована",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Перенесено: {{eventType}} с {{name}} на {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Ваше {{title}} было перенесено на {{date}}.",
|
||||
"event_has_been_rescheduled": "Ваше событие было перенесено.",
|
||||
"request_reschedule_title_attendee": "Запросить перенос бронирования",
|
||||
"request_reschedule_subtitle": "{{organizer}} отменил(-а) бронирование и попросил(-а) вас выбрать другое время.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Događaj još čeka na vaše odobrenje",
|
||||
"booking_submitted_subject": "Rezervacija podneta: {{eventType}} sa {{name}} datuma {{date}}",
|
||||
"your_meeting_has_been_booked": "Sastanak je zakazan",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Vaš {{eventType}} sa {{name}} je odložen za {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Ваш {{титле}} је померен за {{дате}}.",
|
||||
"event_has_been_rescheduled": "Ažurirano - Vaš dogadjaj je odložen",
|
||||
"request_reschedule_title_attendee": "Zahtev za promenu vremena rezervacije",
|
||||
"request_reschedule_subtitle": "{{organizer}} je otkazao/la rezervaciju i tražio/la je da odaberete drugo vreme.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "En bokning väntar fortfarande på ditt godkännande",
|
||||
"booking_submitted_subject": "Bokning skickad: {{eventType}} med {{name}} {{date}}",
|
||||
"your_meeting_has_been_booked": "Ditt möte har bokats",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Din {{eventType}} med {{name}} har bokats om till {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Din {{title}} har ändrats till {{date}}.",
|
||||
"event_has_been_rescheduled": "Uppdatering - Din bokning har blivit omplanerad",
|
||||
"request_reschedule_title_attendee": "Begäran om att boka om din bokning",
|
||||
"request_reschedule_subtitle": "{{organizer}} har avbokat bokningen och bett dig att välja en annan tid.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Bir etkinlik hâlâ onayınızı bekliyor",
|
||||
"booking_submitted_subject": "Rezervasyon Gönderildi: {{date}} tarihinde {{name}} ile {{eventType}}",
|
||||
"your_meeting_has_been_booked": "Toplantı rezervasyonunuz yapıldı",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{name}} ile yapacağınız {{eventType}} etkinliğiniz {{date}} olarak yeniden planlandı.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{title}}, {{date}} olarak yeniden planlandı.",
|
||||
"event_has_been_rescheduled": "Güncellendi - Etkinliğiniz yeniden planlandı",
|
||||
"request_reschedule_title_attendee": "Rezervasyonunuz için yeniden planlama isteğinde bulunun",
|
||||
"request_reschedule_subtitle": "{{organizer}} rezervasyonu iptal etti ve sizden başka bir saat seçmenizi istiyor.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Захід досі чекає на ваше схвалення",
|
||||
"booking_submitted_subject": "Бронювання надіслано: {{eventType}} «{{name}}», {{date}}",
|
||||
"your_meeting_has_been_booked": "Вашу нараду заброньовано",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{eventType}} «{{name}}» перенесено. Новий час: {{time}} ({{timeZone}}), {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Ваш {{title}} перенесено на {{date}}.",
|
||||
"event_has_been_rescheduled": "Оновлено – ваш захід перенесено",
|
||||
"request_reschedule_title_attendee": "Надіслати запит на перенесення бронювання",
|
||||
"request_reschedule_subtitle": "{{organizer}} скасував(-ла) бронювання та попросив(-ла) вас вибрати інший час.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "Một sự kiện vẫn đang chờ bạn phê duyệt",
|
||||
"booking_submitted_subject": "Đặt chỗ đã được gửi: {{eventType}} với {{name}} lúc {{date}}",
|
||||
"your_meeting_has_been_booked": "Cuộc họp của bạn đã được đặt",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{eventType}} của bạn với {{name}} đã được chuyển sang ngày {{date}}.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{title}} của bạn đã được lên lịch lại vào {{date}}.",
|
||||
"event_has_been_rescheduled": "Đã cập nhật - Sự kiện của bạn đã được đổi",
|
||||
"request_reschedule_title_attendee": "Yêu cầu xếp lại lịch đặt chỗ của bạn",
|
||||
"request_reschedule_subtitle": "{{organizer}} đã huỷ đặt chỗ đó và yêu cầu bạn chọn thời gian khác.",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "有活动仍在等待您的批准",
|
||||
"booking_submitted_subject": "预约已提交: 名称为 {{name}} 的 {{eventType}},日期为 {{date}}",
|
||||
"your_meeting_has_been_booked": "您的会议已预定",
|
||||
"event_type_has_been_rescheduled_on_time_date": "您与 {{name}} 的 {{eventType}} 已被重新安排到 {{date}} {{time}} ({{timeZone}})。",
|
||||
"event_type_has_been_rescheduled_on_time_date": "您的 {{title}} 已改期至 {{date}}。",
|
||||
"event_has_been_rescheduled": "已更新 - 您的活动已被重新安排",
|
||||
"request_reschedule_title_attendee": "请求重新安排您的预约",
|
||||
"request_reschedule_subtitle": "{{organizer}} 取消了预约并要求您选择其他时间。",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"event_still_awaiting_approval": "活動還在等待核准",
|
||||
"booking_submitted_subject": "已提出預約:{{date}} 與 {{name}} 的 {{eventType}}",
|
||||
"your_meeting_has_been_booked": "會議已經預定",
|
||||
"event_type_has_been_rescheduled_on_time_date": "您和 {{name}} 的 {{eventType}} 的預定時間已改為 {{date}}。",
|
||||
"event_type_has_been_rescheduled_on_time_date": "您的 {{title}} 已改期至 {{date}}。",
|
||||
"event_has_been_rescheduled": "已更新 - 已經重新預定活動時間",
|
||||
"request_reschedule_title_attendee": "要求重新安排您的預約",
|
||||
"request_reschedule_subtitle": "{{organizer}} 已取消預約,並要求您選擇其他時間。",
|
||||
|
||||
@@ -383,11 +383,11 @@ nav#nav--settings > a svg {
|
||||
}
|
||||
|
||||
nav#nav--settings > a.active {
|
||||
@apply border-neutral-500 bg-neutral-50 text-neutral-700 hover:bg-neutral-50 hover:text-neutral-700;
|
||||
@apply border-neutral-500 bg-neutral-50 text-gray-700 hover:bg-neutral-50 hover:text-gray-700;
|
||||
}
|
||||
|
||||
nav#nav--settings > a.active svg {
|
||||
@apply text-neutral-500;
|
||||
@apply text-gray-500;
|
||||
}
|
||||
|
||||
hr {
|
||||
@@ -490,4 +490,4 @@ hr {
|
||||
|
||||
::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
SchedulingType,
|
||||
} from "@prisma/client";
|
||||
import { diff } from "jest-diff";
|
||||
import nock from "nock";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
@@ -14,7 +13,7 @@ import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/client";
|
||||
import { getSchedule, Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
||||
|
||||
import { prismaMock } from "../../../../tests/config/singleton";
|
||||
import { prismaMock, CalendarManagerMock } from "../../../../tests/config/singleton";
|
||||
|
||||
// TODO: Mock properly
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(null);
|
||||
@@ -218,6 +217,62 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe("getSchedule", () => {
|
||||
describe("Calendar event", () => {
|
||||
test("correctly identifies unavailable slots from calendar", async () => {
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
||||
|
||||
const scenarioData = {
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
slotInterval: 45,
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
users: [
|
||||
{
|
||||
...TestData.users.example,
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
},
|
||||
],
|
||||
apps: [TestData.apps.googleCalendar],
|
||||
};
|
||||
// An event with one accepted booking
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
addBusyTimesInGoogleCalendar([
|
||||
{
|
||||
start: `${plus2DateString}T04:45:00.000Z`,
|
||||
end: `${plus2DateString}T23:00:00.000Z`,
|
||||
},
|
||||
]);
|
||||
const scheduleForDayWithAGoogleCalendarBooking = await getSchedule(
|
||||
{
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
|
||||
// As per Google Calendar Availability, only 4PM(4-4:45PM) GMT slot would be available
|
||||
expect(scheduleForDayWithAGoogleCalendarBooking).toHaveTimeSlots([`04:00:00.000Z`], {
|
||||
dateString: plus2DateString,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("User Event", () => {
|
||||
test("correctly identifies unavailable slots from Cal Bookings in different status", async () => {
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
@@ -232,6 +287,7 @@ describe("getSchedule", () => {
|
||||
id: 1,
|
||||
// If `slotInterval` is set, it supersedes `length`
|
||||
slotInterval: 45,
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
@@ -555,15 +611,12 @@ describe("getSchedule", () => {
|
||||
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
addBusyTimesInGoogleCalendar(
|
||||
[
|
||||
{
|
||||
start: `${plus3DateString}T04:00:00.000Z`,
|
||||
end: `${plus3DateString}T05:59:59.000Z`,
|
||||
},
|
||||
],
|
||||
scenarioData
|
||||
);
|
||||
addBusyTimesInGoogleCalendar([
|
||||
{
|
||||
start: `${plus3DateString}T04:00:00.000Z`,
|
||||
end: `${plus3DateString}T05:59:59.000Z`,
|
||||
},
|
||||
]);
|
||||
|
||||
const scheduleForEventOnADayWithNonCalBooking = await getSchedule(
|
||||
{
|
||||
@@ -632,15 +685,12 @@ describe("getSchedule", () => {
|
||||
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
addBusyTimesInGoogleCalendar(
|
||||
[
|
||||
{
|
||||
start: `${plus3DateString}T04:00:00.000Z`,
|
||||
end: `${plus3DateString}T05:59:59.000Z`,
|
||||
},
|
||||
],
|
||||
scenarioData
|
||||
);
|
||||
addBusyTimesInGoogleCalendar([
|
||||
{
|
||||
start: `${plus3DateString}T04:00:00.000Z`,
|
||||
end: `${plus3DateString}T05:59:59.000Z`,
|
||||
},
|
||||
]);
|
||||
|
||||
const scheduleForEventOnADayWithCalBooking = await getSchedule(
|
||||
{
|
||||
@@ -712,62 +762,6 @@ describe("getSchedule", () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("correctly identifies unavailable slots from calendar", async () => {
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
||||
|
||||
const scenarioData = {
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
slotInterval: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
users: [
|
||||
{
|
||||
...TestData.users.example,
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
},
|
||||
],
|
||||
apps: [TestData.apps.googleCalendar],
|
||||
};
|
||||
// An event with one accepted booking
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
addBusyTimesInGoogleCalendar(
|
||||
[
|
||||
{
|
||||
start: `${plus2DateString}T04:30:00.000Z`,
|
||||
end: `${plus2DateString}T23:00:00.000Z`,
|
||||
},
|
||||
],
|
||||
scenarioData
|
||||
);
|
||||
const scheduleForDayWithAGoogleCalendarBooking = await getSchedule(
|
||||
{
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
|
||||
// As per Google Calendar Availability, only 4PM GMT slot would be available
|
||||
expect(scheduleForDayWithAGoogleCalendarBooking).toHaveTimeSlots([`04:00:00.000Z`], {
|
||||
dateString: plus2DateString,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Team Event", () => {
|
||||
@@ -783,6 +777,7 @@ describe("getSchedule", () => {
|
||||
{
|
||||
id: 1,
|
||||
slotInterval: 45,
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
@@ -795,6 +790,7 @@ describe("getSchedule", () => {
|
||||
{
|
||||
id: 2,
|
||||
slotInterval: 45,
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 102,
|
||||
@@ -903,6 +899,7 @@ describe("getSchedule", () => {
|
||||
{
|
||||
id: 1,
|
||||
slotInterval: 45,
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
@@ -916,6 +913,7 @@ describe("getSchedule", () => {
|
||||
{
|
||||
id: 2,
|
||||
slotInterval: 45,
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 102,
|
||||
@@ -1080,7 +1078,6 @@ function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser[]) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
prismaMock.eventType.findUnique.mockImplementation(({ where }) => {
|
||||
console.log("eventTypesWithUsers", eventTypesWithUsers);
|
||||
return new Promise((resolve) => {
|
||||
const eventType = eventTypesWithUsers.find((e) => e.id === where.id) as unknown as PrismaEventType & {
|
||||
users: PrismaUser[];
|
||||
@@ -1208,7 +1205,6 @@ const getDate = (param: { dateIncrement?: number; monthIncrement?: number; yearI
|
||||
const date = _date < 10 ? "0" + _date : _date;
|
||||
const month = _month < 10 ? "0" + _month : _month;
|
||||
|
||||
console.log(`Date, month, year for ${JSON.stringify(param)}`, date, month, year);
|
||||
return {
|
||||
date,
|
||||
month,
|
||||
@@ -1218,38 +1214,13 @@ const getDate = (param: { dateIncrement?: number; monthIncrement?: number; yearI
|
||||
};
|
||||
|
||||
/**
|
||||
* Remember that this fn must be called only if you expect your test to lookup for busy times in Google Calendar.
|
||||
* Calling it unnecessarily will result in a test failure. This is how nock works because it would expect a call to the requests and that too only once.
|
||||
* TODO: Improve this to validate the arguments passed to getBusyCalendarTimes if they are valid or not.
|
||||
*/
|
||||
function addBusyTimesInGoogleCalendar(
|
||||
busy: {
|
||||
start: string;
|
||||
end: string;
|
||||
}[],
|
||||
data: ScenarioData
|
||||
}[]
|
||||
) {
|
||||
if (!data.users.find((u) => u.credentials && u.selectedCalendars)) {
|
||||
throw new Error(
|
||||
"Google Calendar mocking requires atleast one user with both `credentials` and `selectedCalendars`"
|
||||
);
|
||||
}
|
||||
if (!data.apps?.find((app) => app.slug === "google-calendar")) {
|
||||
throw new Error('Google Calendar mocking requires an app with slug "google-calendar"');
|
||||
}
|
||||
logger.silly("Adding busy times in Google Calendar", busy);
|
||||
nock("https://oauth2.googleapis.com").post("/token").reply(200, {
|
||||
access_token: "access_token",
|
||||
expiry_date: Infinity,
|
||||
});
|
||||
|
||||
// Google Calendar with 11th July having many events
|
||||
nock("https://www.googleapis.com")
|
||||
.post("/calendar/v3/freeBusy")
|
||||
.reply(200, {
|
||||
calendars: [
|
||||
{
|
||||
busy,
|
||||
},
|
||||
],
|
||||
});
|
||||
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue(busy);
|
||||
}
|
||||
|
||||
+1
-1
Submodule apps/website updated: 02c47a7782...8f5d66f2cc
@@ -2,7 +2,7 @@ import z from "zod";
|
||||
|
||||
import { InstalledAppVariants } from "../utils";
|
||||
|
||||
const variantSchema = z.nativeEnum(InstalledAppVariants);
|
||||
const variantSchema = z.enum(InstalledAppVariants);
|
||||
|
||||
export default function getInstalledAppPath(
|
||||
{ variant, slug }: { variant?: string; slug?: string },
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function FormInputFields(props: Props) {
|
||||
<label
|
||||
id="slug-label"
|
||||
htmlFor="slug"
|
||||
className="flex text-sm font-medium text-neutral-700 dark:text-white">
|
||||
className="flex text-sm font-medium text-gray-700 dark:text-white">
|
||||
{field.label}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm, UseFormReturn } from "react-hook-form";
|
||||
|
||||
import { ShellMain } from "@calcom/features/shell/Shell";
|
||||
import useApp from "@calcom/lib/hooks/useApp";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -27,7 +28,6 @@ import {
|
||||
Icon,
|
||||
Meta,
|
||||
SettingsToggle,
|
||||
ShellMain,
|
||||
showToast,
|
||||
TextAreaField,
|
||||
TextField,
|
||||
@@ -109,7 +109,7 @@ const Actions = ({
|
||||
StartIcon={Icon.FiCode}
|
||||
tooltip={t("embed")}
|
||||
/>
|
||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||
<DropdownMenuSeparator />
|
||||
<FormAction
|
||||
routingForm={form}
|
||||
action="_delete"
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ function Button({ config, type, label, onClick, readonly }: ButtonProps) {
|
||||
if (type === "delRule" || type == "delGroup") {
|
||||
return (
|
||||
<button className="ml-5">
|
||||
<Icon.FiTrash className="m-0 h-4 w-4 text-neutral-500" onClick={onClick} />
|
||||
<Icon.FiTrash className="m-0 h-4 w-4 text-gray-500" onClick={onClick} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
|
||||
import { Controller, useFieldArray, UseFormReturn } from "react-hook-form";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import {
|
||||
BooleanToggleGroupField,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
FormCard,
|
||||
Icon,
|
||||
SelectField,
|
||||
Shell,
|
||||
TextAreaField,
|
||||
TextField,
|
||||
} from "@calcom/ui";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// TODO: i18n
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
|
||||
import Shell, { ShellMain } from "@calcom/features/shell/Shell";
|
||||
import useApp from "@calcom/lib/hooks/useApp";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -13,8 +14,6 @@ import {
|
||||
Icon,
|
||||
List,
|
||||
ListLinkItem,
|
||||
Shell,
|
||||
ShellMain,
|
||||
Tooltip,
|
||||
} from "@calcom/ui";
|
||||
|
||||
@@ -145,7 +144,7 @@ export default function RoutingForms({
|
||||
{t("Copy Typeform Redirect Url")}
|
||||
</FormAction>
|
||||
) : null}
|
||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||
<DropdownMenuSeparator />
|
||||
<FormAction
|
||||
action="_delete"
|
||||
routingForm={form}
|
||||
|
||||
@@ -10,11 +10,12 @@ import {
|
||||
Utils as QbUtils,
|
||||
} from "react-awesome-query-builder";
|
||||
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import { Button, Shell } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder";
|
||||
// types
|
||||
import { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder";
|
||||
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import {
|
||||
SelectField,
|
||||
FormCard,
|
||||
SelectWithValidation as Select,
|
||||
Shell,
|
||||
TextArea,
|
||||
TextField,
|
||||
Badge,
|
||||
|
||||
@@ -112,7 +112,7 @@ function RoutingForm({ form, profile, ...restProps }: inferSSRProps<typeof getSe
|
||||
{form.name}
|
||||
</h1>
|
||||
{form.description ? (
|
||||
<p className="min-h-10 text-sm text-neutral-500 ltr:mr-4 rtl:ml-4 dark:text-white">
|
||||
<p className="min-h-10 text-sm text-gray-500 ltr:mr-4 rtl:ml-4 dark:text-white">
|
||||
{form.description}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -115,7 +115,7 @@ export const SearchDialog = (props: ISearchDialog) => {
|
||||
<h3 className="leading-16 font-cal text-xl text-gray-900" id="modal-title">
|
||||
{t("add_gif_to_confirmation")}
|
||||
</h3>
|
||||
<p className="mb-3 text-sm font-light text-neutral-500">{t("find_gif_spice_confirmation")}</p>
|
||||
<p className="mb-3 text-sm font-light text-gray-500">{t("find_gif_spice_confirmation")}</p>
|
||||
<div className="flex items-center border-b border-solid">
|
||||
{renderTab(SearchIcon, t("search_giphy"), MODE_SEARCH)}
|
||||
{renderTab(LinkIcon, t("add_link_from_giphy"), MODE_URL)}
|
||||
@@ -176,7 +176,7 @@ export const SearchDialog = (props: ISearchDialog) => {
|
||||
{errorMessage && <Alert severity="error" title={errorMessage} className="my-4" />}
|
||||
{gifImage && selectedMode === MODE_SEARCH && (
|
||||
<div className="mt-4 flex items-center justify-between space-x-2 rtl:space-x-reverse">
|
||||
<div className="text-sm font-light text-neutral-500">Not the perfect GIF?</div>
|
||||
<div className="text-sm font-light text-gray-500">Not the perfect GIF?</div>
|
||||
<Button
|
||||
size="sm"
|
||||
color="secondary"
|
||||
|
||||
@@ -273,7 +273,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
auth: myGoogleAuth,
|
||||
calendarId: calendarId ? calendarId : defaultCalendarId,
|
||||
eventId: uid,
|
||||
sendNotifications: true,
|
||||
sendNotifications: false,
|
||||
sendUpdates: "all",
|
||||
},
|
||||
function (err: GoogleCalError | null, event) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user