Merge branch 'main' of https://github.com/calcom/cal.com
@@ -1,28 +0,0 @@
|
||||
name: TODO to Issue
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
MANUAL_COMMIT_REF:
|
||||
description: "The SHA of the commit to get the diff for"
|
||||
required: true
|
||||
MANUAL_BASE_REF:
|
||||
description: "By default, the commit entered above is compared to the one directly before it; to go back further, enter an earlier SHA here"
|
||||
required: false
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
todos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Add TODOs to issues"
|
||||
uses: "alstr/todo-to-issue-action@v4"
|
||||
env:
|
||||
MANUAL_COMMIT_REF: ${{ inputs.MANUAL_COMMIT_REF }}
|
||||
MANUAL_BASE_REF: ${{ inputs.MANUAL_BASE_REF }}
|
||||
with:
|
||||
IDENTIFIERS: '[{"name": "TODO", "labels": ["🙋🏻♂️help wanted"]}, {"name": "FIXME", "labels": ["🐛 bug"]}]'
|
||||
@@ -86,7 +86,12 @@ To develop locally:
|
||||
yarn
|
||||
```
|
||||
|
||||
5. Start developing and watch for code changes:
|
||||
5. Set up your `.env` file:
|
||||
|
||||
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
|
||||
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
|
||||
|
||||
6. Start developing and watch for code changes:
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
|
||||
@@ -2,7 +2,7 @@ Copyright (c) 2020-present Cal.com, Inc.
|
||||
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee directory of this repository (Enterprise Edition) is licensed under the license defined in "ee/LICENSE".
|
||||
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee directory of this repository (Commercial License) is licensed under the license defined in "ee/LICENSE".
|
||||
* All third party components incorporated into the Cal.com Software are licensed under the original license provided by the owner of the applicable component.
|
||||
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ const Component = ({
|
||||
tos,
|
||||
privacy,
|
||||
isProOnly,
|
||||
teamsPlanRequired,
|
||||
descriptionItems,
|
||||
isTemplate,
|
||||
dependencies,
|
||||
@@ -152,6 +153,7 @@ const Component = ({
|
||||
type={type}
|
||||
isProOnly={isProOnly}
|
||||
disableInstall={disableInstall}
|
||||
teamsPlanRequired={teamsPlanRequired}
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
if (useDefaultComponent) {
|
||||
props = {
|
||||
@@ -192,6 +194,7 @@ const Component = ({
|
||||
type={type}
|
||||
isProOnly={isProOnly}
|
||||
disableInstall={disableInstall}
|
||||
teamsPlanRequired={teamsPlanRequired}
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
if (useDefaultComponent) {
|
||||
props = {
|
||||
@@ -241,7 +244,9 @@ const Component = ({
|
||||
</div>
|
||||
<h4 className="text-emphasis mt-8 font-semibold ">{t("pricing")}</h4>
|
||||
<span className="text-default">
|
||||
{price === 0 ? (
|
||||
{teamsPlanRequired ? (
|
||||
t("teams_plan_required")
|
||||
) : price === 0 ? (
|
||||
t("free_to_use_apps")
|
||||
) : (
|
||||
<>
|
||||
@@ -358,6 +363,7 @@ export default function App(props: {
|
||||
privacy?: string;
|
||||
licenseRequired: AppType["licenseRequired"];
|
||||
isProOnly: AppType["isProOnly"];
|
||||
teamsPlanRequired: AppType["teamsPlanRequired"];
|
||||
descriptionItems?: Array<string | { iframe: IframeHTMLAttributes<HTMLIFrameElement> }>;
|
||||
isTemplate?: boolean;
|
||||
disableInstall?: boolean;
|
||||
|
||||
@@ -18,9 +18,9 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
||||
const { t } = useLocale();
|
||||
const session = useSession();
|
||||
const router = useRouter();
|
||||
const isAdmin = session.data?.user.role === "ADMIN";
|
||||
|
||||
if (session.status === "loading") return <></>;
|
||||
|
||||
return (
|
||||
<Shell {...rest} actions={actions?.("block")} hideHeadingOnMobile>
|
||||
<div className="flex flex-col xl:flex-row">
|
||||
@@ -29,8 +29,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
||||
<EmptyScreen
|
||||
Icon={AlertCircle}
|
||||
headline={t("no_apps")}
|
||||
description={session.data?.user.role === "ADMIN" ? "You can enable apps in the settings" : ""}
|
||||
buttonText={session.data?.user.role === "ADMIN" ? t("apps_settings") : ""}
|
||||
description={isAdmin ? "You can enable apps in the settings" : ""}
|
||||
buttonText={isAdmin ? t("apps_settings") : ""}
|
||||
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -72,7 +72,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
slotUtcStartDate: slot.time,
|
||||
eventTypeId,
|
||||
slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(),
|
||||
bookingAttendees: bookingAttendees || undefined,
|
||||
bookingUid: slot.bookingUid,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
TableActions,
|
||||
TextAreaField,
|
||||
} from "@calcom/ui";
|
||||
import { Check, Clock, MapPin, RefreshCcw, Send, Slash, X, CreditCard } from "@calcom/ui/components/icon";
|
||||
import { Check, Clock, MapPin, RefreshCcw, Send, Ban, X, CreditCard } from "@calcom/ui/components/icon";
|
||||
|
||||
import useMeQuery from "@lib/hooks/useMeQuery";
|
||||
|
||||
@@ -119,7 +119,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
onClick: () => {
|
||||
setRejectionDialogIsOpen(true);
|
||||
},
|
||||
icon: Slash,
|
||||
icon: Ban,
|
||||
disabled: mutation.isLoading,
|
||||
},
|
||||
// For bookings with payment, only confirm if the booking is paid for
|
||||
|
||||
@@ -237,7 +237,7 @@ const BookingPage = ({
|
||||
eventTypeId: eventType.id,
|
||||
slotUtcStartDate: dayjs(queryDate).utc().format(),
|
||||
slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(),
|
||||
bookingAttendees: currentSlotBooking ? currentSlotBooking.attendees.length : undefined,
|
||||
bookingUid: currentSlotBooking?.uid,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -322,7 +322,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
disabled={seatsLocked.disabled}
|
||||
defaultValue={value || 2}
|
||||
min={1}
|
||||
className="w-24"
|
||||
addOnSuffix={<>{t("seats")}</>}
|
||||
onChange={(e) => {
|
||||
onChange(Math.abs(Number(e.target.value)));
|
||||
|
||||
@@ -108,7 +108,7 @@ const MinimumBookingNoticeInput = React.forwardRef<
|
||||
});
|
||||
|
||||
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
|
||||
const { t } = useLocale();
|
||||
const { t, i18n } = useLocale();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
|
||||
const PERIOD_TYPES = [
|
||||
@@ -149,12 +149,25 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
const bookingLimitsLocked = shouldLockDisableProps("bookingLimits");
|
||||
const durationLimitsLocked = shouldLockDisableProps("durationLimits");
|
||||
const periodTypeLocked = shouldLockDisableProps("periodType");
|
||||
const offsetStartLockedProps = shouldLockDisableProps("offsetStart");
|
||||
|
||||
const optionsPeriod = [
|
||||
{ value: 1, label: t("calendar_days") },
|
||||
{ value: 0, label: t("business_days") },
|
||||
];
|
||||
|
||||
// offsetStart toggle is client-side only, opened by default if offsetStart is set
|
||||
const offsetStartValue = useWatch({
|
||||
control: formMethods.control,
|
||||
name: "offsetStart",
|
||||
});
|
||||
const [offsetToggle, setOffsetToggle] = useState(() => offsetStartValue > 0);
|
||||
|
||||
// Preview how the offset will affect start times
|
||||
const offsetOriginalTime = new Date();
|
||||
offsetOriginalTime.setHours(9, 0, 0, 0);
|
||||
const offsetAdjustedTime = new Date(offsetOriginalTime.getTime() + offsetStartValue * 60 * 1000);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
@@ -432,6 +445,32 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
</SettingsToggle>
|
||||
)}
|
||||
/>
|
||||
<hr className="border-subtle" />
|
||||
<SettingsToggle
|
||||
title={t("offset_toggle")}
|
||||
description={t("offset_toggle_description")}
|
||||
{...offsetStartLockedProps}
|
||||
checked={offsetToggle}
|
||||
onCheckedChange={(active) => {
|
||||
setOffsetToggle(active);
|
||||
if (!active) {
|
||||
formMethods.setValue("offsetStart", 0);
|
||||
}
|
||||
}}>
|
||||
<TextField
|
||||
required
|
||||
type="number"
|
||||
{...offsetStartLockedProps}
|
||||
label={t("offset_start")}
|
||||
defaultValue={eventType.offsetStart}
|
||||
{...formMethods.register("offsetStart")}
|
||||
addOnSuffix={<>{t("minutes")}</>}
|
||||
hint={t("offset_start_description", {
|
||||
originalTime: offsetOriginalTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
|
||||
adjustedTime: offsetAdjustedTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
|
||||
})}
|
||||
/>
|
||||
</SettingsToggle>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ArrowRightIcon } from "@heroicons/react/solid";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { List } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import { AppConnectionItem } from "../components/AppConnectionItem";
|
||||
import { ConnectedCalendarItem } from "../components/ConnectedCalendarItem";
|
||||
@@ -85,7 +84,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
|
||||
onClick={() => nextStep()}
|
||||
disabled={disabledNextButton}>
|
||||
{firstCalendar ? `${t("continue")}` : `${t("next_step_text")}`}
|
||||
<ArrowRightIcon className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ArrowRightIcon } from "@heroicons/react/solid";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { List } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import { AppConnectionItem } from "../components/AppConnectionItem";
|
||||
import { StepConnectionLoader } from "../components/StepConnectionLoader";
|
||||
@@ -59,7 +58,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
|
||||
disabled={!hasAnyInstalledVideoApps}
|
||||
onClick={() => nextStep()}>
|
||||
{t("next_step_text")}
|
||||
<ArrowRightIcon className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ArrowRightIcon } from "@heroicons/react/solid";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
@@ -9,6 +8,7 @@ import type { TRPCClientErrorLike } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
import { Button, Form } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
interface ISetupAvailabilityProps {
|
||||
nextStep: () => void;
|
||||
@@ -81,7 +81,7 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
|
||||
type="submit"
|
||||
className="mt-2 w-full justify-center p-2 text-sm sm:mt-8"
|
||||
disabled={availabilityForm.formState.isSubmitting}>
|
||||
{t("next_step_text")} <ArrowRightIcon className="ml-2 h-4 w-4 self-center" />
|
||||
{t("next_step_text")} <ArrowRight className="ml-2 h-4 w-4 self-center" />
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ArrowRightIcon } from "@heroicons/react/solid";
|
||||
import { useRouter } from "next/router";
|
||||
import type { FormEvent } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
@@ -11,6 +10,7 @@ import turndown from "@calcom/lib/turndownService";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
|
||||
|
||||
@@ -159,7 +159,7 @@ const UserProfile = (props: IUserProfileProps) => {
|
||||
type="submit"
|
||||
className="text-inverted mt-8 flex w-full flex-row justify-center rounded-md border border-black bg-black p-2 text-center text-sm">
|
||||
{t("finish")}
|
||||
<ArrowRightIcon className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ArrowRightIcon } from "@heroicons/react/outline";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -10,6 +9,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, TimezoneSelect } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
|
||||
|
||||
@@ -116,7 +116,7 @@ const UserSettings = (props: IUserSettingsProps) => {
|
||||
className="mt-8 flex w-full flex-row justify-center"
|
||||
disabled={mutation.isLoading}>
|
||||
{t("next_step_text")}
|
||||
<ArrowRightIcon className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { StarIcon as StarIconSolid } from "@heroicons/react/solid";
|
||||
import classNames from "classnames";
|
||||
import { debounce, noop } from "lodash";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -15,6 +14,7 @@ import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
import { Button, Dialog, DialogClose, DialogContent, Input, Label } from "@calcom/ui";
|
||||
import { Star as StarSolid } from "@calcom/ui/components/icon";
|
||||
import { Check, Edit2, ExternalLink } from "@calcom/ui/components/icon";
|
||||
|
||||
export enum UsernameChangeStatusEnum {
|
||||
@@ -231,11 +231,15 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
||||
<span
|
||||
className={classNames(
|
||||
"mx-2 py-2",
|
||||
isInputUsernamePremium ? "text-orange-400" : "",
|
||||
isInputUsernamePremium ? "text-transparent" : "",
|
||||
usernameIsAvailable ? "" : ""
|
||||
)}>
|
||||
{isInputUsernamePremium ? <StarIconSolid className="mt-[2px] h-4 w-4" /> : <></>}
|
||||
{!isInputUsernamePremium && usernameIsAvailable ? <Check className="mt-2 h-4 w-4" /> : <></>}
|
||||
{isInputUsernamePremium ? <StarSolid className="mt-[2px] h-4 w-4 fill-orange-400" /> : <></>}
|
||||
{!isInputUsernamePremium && usernameIsAvailable ? (
|
||||
<Check className="mt-[2px] h-4 w-4" />
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -111,12 +111,6 @@ const nextConfig = {
|
||||
transform: "lucide-react/dist/esm/icons/{{ kebabCase member }}",
|
||||
preventFullImport: true,
|
||||
},
|
||||
"@heroicons/react/solid": {
|
||||
transform: "@heroicons/react/solid/esm/{{ member }}",
|
||||
},
|
||||
"@heroicons/react/outline": {
|
||||
transform: "@heroicons/react/outline/esm/{{ member }}",
|
||||
},
|
||||
"@calcom/features/insights/components": {
|
||||
transform: "@calcom/features/insights/components/{{member}}",
|
||||
skipDefaultConversion: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.9.0",
|
||||
"version": "2.9.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
@@ -41,7 +41,6 @@
|
||||
"@daily-co/daily-js": "^0.37.0",
|
||||
"@formkit/auto-animate": "^1.0.0-beta.5",
|
||||
"@glidejs/glide": "^3.5.2",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@hookform/error-message": "^2.0.0",
|
||||
"@hookform/resolvers": "^2.9.7",
|
||||
"@next-auth/prisma-adapter": "^1.0.4",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BadgeCheckIcon } from "@heroicons/react/solid";
|
||||
import classNames from "classnames";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
@@ -28,7 +27,7 @@ import prisma from "@calcom/prisma";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { Avatar, AvatarGroup, HeadSeo } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
import { Verified, ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||
@@ -139,7 +138,9 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
<Avatar imageSrc={user.avatar} size="xl" alt={nameOrUsername} />
|
||||
<h1 className="font-cal text-emphasis mb-1 text-3xl">
|
||||
{nameOrUsername}
|
||||
{user.verified && <BadgeCheckIcon className=" mx-1 -mt-1 inline h-6 w-6 text-blue-500" />}
|
||||
{user.verified && (
|
||||
<Verified className=" mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black" />
|
||||
)}
|
||||
</h1>
|
||||
{!isBioEmpty && (
|
||||
<>
|
||||
|
||||
@@ -72,6 +72,7 @@ function SingleAppPage(props: inferSSRProps<typeof getStaticProps>) {
|
||||
website={data.url}
|
||||
email={data.email}
|
||||
licenseRequired={data.licenseRequired}
|
||||
teamsPlanRequired={data.teamsPlanRequired}
|
||||
isProOnly={data.isProOnly}
|
||||
descriptionItems={source.data?.items as string[] | undefined}
|
||||
isTemplate={data.isTemplate}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CheckIcon, MailOpenIcon, ExclamationIcon } from "@heroicons/react/outline";
|
||||
import { signIn } from "next-auth/react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -9,6 +8,7 @@ import z from "zod";
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, showToast } from "@calcom/ui";
|
||||
import { Check, MailOpen, AlertTriangle } from "@calcom/ui/components/icon";
|
||||
|
||||
import Loader from "@components/Loader";
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
@@ -118,11 +118,11 @@ export default function Verify() {
|
||||
<div className="m-10 flex max-w-2xl flex-col items-start border border-white p-12 text-left">
|
||||
<div className="rounded-full border border-white p-3">
|
||||
{hasPaymentFailed ? (
|
||||
<ExclamationIcon className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||
<AlertTriangle className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||
) : sessionId ? (
|
||||
<CheckIcon className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||
<Check className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||
) : (
|
||||
<MailOpenIcon className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||
<MailOpen className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||
)}
|
||||
</div>
|
||||
<h3 className="font-cal my-6 text-3xl font-normal">
|
||||
|
||||
@@ -51,6 +51,7 @@ export type FormValues = {
|
||||
eventName: string;
|
||||
slug: string;
|
||||
length: number;
|
||||
offsetStart: number;
|
||||
description: string;
|
||||
disableGuests: boolean;
|
||||
requiresConfirmation: boolean;
|
||||
@@ -211,6 +212,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
bookingLimits: eventType.bookingLimits || undefined,
|
||||
durationLimits: eventType.durationLimits || undefined,
|
||||
length: eventType.length,
|
||||
offsetStart: eventType.offsetStart,
|
||||
hidden: eventType.hidden,
|
||||
periodDates: {
|
||||
startDate: periodDates.startDate,
|
||||
@@ -255,6 +257,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
)
|
||||
.optional(),
|
||||
length: z.union([z.string().transform((val) => +val), z.number()]).optional(),
|
||||
offsetStart: z.union([z.string().transform((val) => +val), z.number()]).optional(),
|
||||
bookingFields: eventTypeBookingFields,
|
||||
})
|
||||
// TODO: Add schema for other fields later.
|
||||
|
||||
@@ -14,11 +14,22 @@ import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Type({ slug, user, booking, away }: PageProps) {
|
||||
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
|
||||
return (
|
||||
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<BookerSeo username={user} eventSlug={slug} rescheduleUid={booking?.uid} />
|
||||
<Booker username={user} eventSlug={slug} rescheduleBooking={booking} isAway={away} />
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={booking?.uid}
|
||||
hideBranding={isBrandingHidden}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleBooking={booking}
|
||||
isAway={away}
|
||||
hideBranding={isBrandingHidden}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -64,6 +75,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||
slug,
|
||||
away: false,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -80,6 +92,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
},
|
||||
select: {
|
||||
away: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -103,6 +116,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
user: username,
|
||||
slug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: user?.hideBranding,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,11 +13,22 @@ import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Type({ slug, user, booking, away }: PageProps) {
|
||||
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
|
||||
return (
|
||||
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<BookerSeo username={user} eventSlug={slug} rescheduleUid={booking?.uid} />
|
||||
<Booker username={user} eventSlug={slug} rescheduleBooking={booking} isAway={away} />
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={booking?.uid}
|
||||
hideBranding={isBrandingHidden}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleBooking={booking}
|
||||
isAway={away}
|
||||
hideBranding={isBrandingHidden}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -41,6 +52,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -62,6 +74,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
user: teamSlug,
|
||||
slug: meetingSlug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: team?.hideBranding,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ const ProfileImpersonationView = () => {
|
||||
setValue,
|
||||
reset,
|
||||
getValues,
|
||||
watch,
|
||||
} = formMethods;
|
||||
|
||||
const isDisabled = isSubmitting || !isDirty;
|
||||
@@ -63,11 +64,11 @@ const ProfileImpersonationView = () => {
|
||||
}}>
|
||||
<div className="flex space-x-3">
|
||||
<Switch
|
||||
defaultChecked={!user?.disableImpersonation}
|
||||
onCheckedChange={(e) => {
|
||||
setValue("disableImpersonation", !e, { shouldDirty: true });
|
||||
}}
|
||||
fitToHeight={true}
|
||||
checked={!watch("disableImpersonation")}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<Skeleton as={Label} className="text-emphasis text-sm font-semibold leading-none">
|
||||
|
||||
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 743 B |
@@ -1,3 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke="white" stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ban"><circle cx="12" cy="12" r="10"></circle><line x1="4.93" x2="19.07" y1="4.93" y2="19.07"></line></svg>
|
||||
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 308 B |
@@ -1572,7 +1572,7 @@
|
||||
"sender_name": "اسم المرسل",
|
||||
"already_invited": "تمت دعوة هذا الحاضر بالفعل",
|
||||
"no_recordings_found": "لم يتم العثور على تسجيلات",
|
||||
"new_workflow_subtitle": "سير عمل جديد من أجل...",
|
||||
"new_workflow_subtitle": "سير عمل جديد لـ...",
|
||||
"reporting": "تقديم التقارير",
|
||||
"reporting_feature": "عرض جميع البيانات الواردة وتنزيلها كملف CSV",
|
||||
"teams_plan_required": "خطة Teams مطلوبة",
|
||||
|
||||
@@ -756,6 +756,10 @@
|
||||
"new_event_type_to_book_description": "Create a new event type for people to book times with.",
|
||||
"length": "Length",
|
||||
"minimum_booking_notice": "Minimum Notice",
|
||||
"offset_toggle": "Offset start times",
|
||||
"offset_toggle_description": "Offset timeslots shown to bookers by a specified number of minutes",
|
||||
"offset_start": "Offset by",
|
||||
"offset_start_description": "e.g. this will show time slots to your bookers at {{ adjustedTime }} instead of {{ originalTime }}",
|
||||
"slot_interval": "Time-slot intervals",
|
||||
"slot_interval_default": "Use event length (default)",
|
||||
"delete_event_type": "Delete event type?",
|
||||
|
||||
@@ -151,10 +151,6 @@ const TestData = {
|
||||
},
|
||||
};
|
||||
|
||||
const ctx = {
|
||||
prisma: prismaMock,
|
||||
};
|
||||
|
||||
type App = {
|
||||
slug: string;
|
||||
dirName: string;
|
||||
@@ -186,6 +182,7 @@ type InputEventType = {
|
||||
id: number;
|
||||
title?: string;
|
||||
length?: number;
|
||||
offsetStart?: number;
|
||||
slotInterval?: number;
|
||||
minimumBookingNotice?: number;
|
||||
users?: { id: number }[];
|
||||
@@ -711,6 +708,72 @@ describe("getSchedule", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("Start times are offset (offsetStart)", async () => {
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
||||
|
||||
const scenarioData = {
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
length: 25,
|
||||
offsetStart: 5,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
users: [
|
||||
{
|
||||
...TestData.users.example,
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
},
|
||||
],
|
||||
hosts: [],
|
||||
apps: [TestData.apps.googleCalendar],
|
||||
};
|
||||
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
const schedule = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
});
|
||||
|
||||
expect(schedule).toHaveTimeSlots(
|
||||
[
|
||||
`04:05:00.000Z`,
|
||||
`04:35:00.000Z`,
|
||||
`05:05:00.000Z`,
|
||||
`05:35:00.000Z`,
|
||||
`06:05:00.000Z`,
|
||||
`06:35:00.000Z`,
|
||||
`07:05:00.000Z`,
|
||||
`07:35:00.000Z`,
|
||||
`08:05:00.000Z`,
|
||||
`08:35:00.000Z`,
|
||||
`09:05:00.000Z`,
|
||||
`09:35:00.000Z`,
|
||||
`10:05:00.000Z`,
|
||||
`10:35:00.000Z`,
|
||||
`11:05:00.000Z`,
|
||||
`11:35:00.000Z`,
|
||||
`12:05:00.000Z`,
|
||||
],
|
||||
{
|
||||
dateString: plus2DateString,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("Check for Date overrides", async () => {
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
||||
@@ -1132,6 +1195,7 @@ function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser[]) {
|
||||
seatsPerTimeSlot: null,
|
||||
metadata: {},
|
||||
minimumBookingNotice: 0,
|
||||
offsetStart: 0,
|
||||
};
|
||||
const foundEvents: Record<number, boolean> = {};
|
||||
const eventTypesWithUsers = eventTypes.map((eventType) => {
|
||||
|
||||
@@ -24,6 +24,7 @@ describe("Tests the slot logic", () => {
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
offsetStart: 0,
|
||||
organizerTimeZone: "America/Toronto",
|
||||
})
|
||||
).toHaveLength(24);
|
||||
@@ -46,6 +47,7 @@ describe("Tests the slot logic", () => {
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
offsetStart: 0,
|
||||
organizerTimeZone: "America/Toronto",
|
||||
})
|
||||
).toHaveLength(12);
|
||||
@@ -66,6 +68,7 @@ describe("Tests the slot logic", () => {
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
offsetStart: 0,
|
||||
organizerTimeZone: "America/Toronto",
|
||||
})
|
||||
).toHaveLength(0);
|
||||
@@ -87,6 +90,7 @@ describe("Tests the slot logic", () => {
|
||||
minimumBookingNotice: 0,
|
||||
workingHours,
|
||||
eventLength: 60,
|
||||
offsetStart: 0,
|
||||
organizerTimeZone: "America/Toronto",
|
||||
})
|
||||
).toHaveLength(0);
|
||||
@@ -108,6 +112,7 @@ describe("Tests the slot logic", () => {
|
||||
},
|
||||
],
|
||||
eventLength: 60,
|
||||
offsetStart: 0,
|
||||
organizerTimeZone: "America/Toronto",
|
||||
})
|
||||
).toHaveLength(11);
|
||||
@@ -129,8 +134,30 @@ describe("Tests the slot logic", () => {
|
||||
},
|
||||
],
|
||||
eventLength: 20,
|
||||
offsetStart: 0,
|
||||
organizerTimeZone: "America/Toronto",
|
||||
})
|
||||
).toHaveLength(71);
|
||||
});
|
||||
|
||||
it("can fit 48 25 minute slots with a 5 minute offset for an empty day", async () => {
|
||||
expect(
|
||||
getSlots({
|
||||
inviteeDate: dayjs.utc().add(1, "day"),
|
||||
frequency: 25,
|
||||
minimumBookingNotice: 0,
|
||||
workingHours: [
|
||||
{
|
||||
userId: 1,
|
||||
days: Array.from(Array(7).keys()),
|
||||
startTime: MINUTES_DAY_START,
|
||||
endTime: MINUTES_DAY_END,
|
||||
},
|
||||
],
|
||||
eventLength: 25,
|
||||
offsetStart: 5,
|
||||
organizerTimeZone: "America/Toronto",
|
||||
})
|
||||
).toHaveLength(48);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"dependencies": {
|
||||
"city-timezones": "^1.2.1",
|
||||
"eslint": "^8.34.0",
|
||||
"lucide-react": "^0.130.0",
|
||||
"lucide-react": "^0.171.0",
|
||||
"turbo": "^1.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -47,6 +47,7 @@ export default function OmniInstallAppButton({
|
||||
<InstallAppButton
|
||||
type={app.type}
|
||||
isProOnly={app.isProOnly}
|
||||
teamsPlanRequired={app.teamsPlanRequired}
|
||||
wrapperClassName={classNames("[@media(max-width:260px)]:w-full", className)}
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
if (useDefaultComponent) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import closecom_config_json from "./closecom/config.json";
|
||||
import cron_config_json from "./cron/config.json";
|
||||
import { metadata as dailyvideo__metadata_ts } from "./dailyvideo/_metadata";
|
||||
import discord_config_json from "./discord/config.json";
|
||||
import eightxeight_config_json from "./eightxeight/config.json";
|
||||
import { metadata as exchange2013calendar__metadata_ts } from "./exchange2013calendar/_metadata";
|
||||
import { metadata as exchange2016calendar__metadata_ts } from "./exchange2016calendar/_metadata";
|
||||
import exchangecalendar_config_json from "./exchangecalendar/config.json";
|
||||
@@ -75,6 +76,7 @@ export const appStoreMetadata = {
|
||||
cron: cron_config_json,
|
||||
dailyvideo: dailyvideo__metadata_ts,
|
||||
discord: discord_config_json,
|
||||
eightxeight: eightxeight_config_json,
|
||||
exchange2013calendar: exchange2013calendar__metadata_ts,
|
||||
exchange2016calendar: exchange2016calendar__metadata_ts,
|
||||
exchangecalendar: exchangecalendar_config_json,
|
||||
|
||||
@@ -11,6 +11,7 @@ export const apiHandlers = {
|
||||
closecom: import("./closecom/api"),
|
||||
cron: import("./cron/api"),
|
||||
discord: import("./discord/api"),
|
||||
eightxeight: import("./eightxeight/api"),
|
||||
exchange2013calendar: import("./exchange2013calendar/api"),
|
||||
exchange2016calendar: import("./exchange2016calendar/api"),
|
||||
exchangecalendar: import("./exchangecalendar/api"),
|
||||
|
||||
@@ -7,6 +7,7 @@ import classNames from "@calcom/lib/classNames";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
@@ -49,14 +50,17 @@ export const InstallAppButtonWithoutPlanCheck = (
|
||||
export const InstallAppButton = (
|
||||
props: {
|
||||
isProOnly?: App["isProOnly"];
|
||||
teamsPlanRequired?: App["teamsPlanRequired"];
|
||||
type: App["type"];
|
||||
wrapperClassName?: string;
|
||||
disableInstall?: boolean;
|
||||
} & InstallAppButtonProps
|
||||
) => {
|
||||
const { isLoading, data: user } = trpc.viewer.me.useQuery();
|
||||
const { isLoading: isUserLoading, data: user } = trpc.viewer.me.useQuery();
|
||||
const router = useRouter();
|
||||
const proProtectionElementRef = useRef<HTMLDivElement | null>(null);
|
||||
const { isLoading: isTeamPlanStatusLoading, hasTeamPlan } = useHasTeamPlan();
|
||||
|
||||
useEffect(() => {
|
||||
const el = proProtectionElementRef.current;
|
||||
if (!el) {
|
||||
@@ -72,12 +76,19 @@ export const InstallAppButton = (
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.teamsPlanRequired && !hasTeamPlan) {
|
||||
// TODO: I think we should show the UpgradeTip in a Dialog here. This would solve the problem of no way to go back to the App page from the UpgradeTip page(except browser's back button)
|
||||
router.push(props.teamsPlanRequired.upgradeUrl);
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
}, [isLoading, user, router, props.isProOnly]);
|
||||
}, [isUserLoading, user, router, props.isProOnly, hasTeamPlan, props.teamsPlanRequired]);
|
||||
|
||||
if (isLoading) {
|
||||
if (isUserLoading || isTeamPlanStatusLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
items:
|
||||
- 1.jpeg
|
||||
- 2.jpeg
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
||||
- High definition video conferencing on any device for up to 500 participants
|
||||
- Rich content sharing and collaboration
|
||||
- Enterprise grade reliability and availability for a business of five or 50,000
|
||||
- 100% secured and encrypted
|
||||
- Copy your room link and start scheduling calls in 8x8
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createDefaultInstallation } from "@calcom/app-store/_utils/installation";
|
||||
import type { AppDeclarativeHandler } from "@calcom/types/AppHandler";
|
||||
|
||||
import appConfig from "../config.json";
|
||||
|
||||
const handler: AppDeclarativeHandler = {
|
||||
appType: appConfig.type,
|
||||
variant: appConfig.variant,
|
||||
slug: appConfig.slug,
|
||||
supportsMultipleInstalls: false,
|
||||
handlerType: "add",
|
||||
createCredential: ({ appType, user, slug }) =>
|
||||
createDefaultInstallation({ appType, userId: user.id, slug, key: {} }),
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as add } from "./add";
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "8x8",
|
||||
"slug": "eightxeight",
|
||||
"type": "eightxeight_video",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/eightxeight",
|
||||
"variant": "conferencing",
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"publisher": "shivamklr",
|
||||
"email": "shivamkalra98@gmail.com",
|
||||
"appData": {
|
||||
"location": {
|
||||
"type": "integrations:{SLUG}_video",
|
||||
"label": "{TITLE}",
|
||||
"linkType": "static",
|
||||
"organizerInputPlaceholder": "https://8x8.vc/company",
|
||||
"urlRegExp": "^(http|https)://(www\\.)?8x8.vc/[a-zA-Z0-9]*"
|
||||
}
|
||||
},
|
||||
"description": "The best video conferencing solution for businesses of any size. Fully secure, reliable, packed with features and ridiculously simple to use.\r\r",
|
||||
"isTemplate": false,
|
||||
"__createdUsingCli": true,
|
||||
"__template": "event-type-location-video-static",
|
||||
"dirName": "eightxeight"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * as api from "./api";
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/eightxeight",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "The best video conferencing solution for businesses of any size. Fully secure, reliable, packed with features and ridiculously simple to use.\r\r"
|
||||
}
|
||||
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 288 288" style="enable-background:new 0 0 288 288;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#CB2233;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<rect x="-0.2" y="0.5" class="st0" width="288" height="288"/>
|
||||
<path class="st1" d="M172.7,132.1h-21.9l-5,9.6c-0.5,1.1-1.1,2.2-1.8,3.2h-0.2c0,0-1-1.7-1.8-3.2l-5-9.6h-21.9l17.2,26.5l-17.1,26.5
|
||||
h21.5l5.8-11.1c0.6-1,1.4-3,1.4-3h0.2c0,0,0.8,2,1.4,3l5.9,11.1h21.5l-17.1-26.5L172.7,132.1L172.7,132.1z"/>
|
||||
<path class="st1" d="M75.9,171.7c-5.9,0.1-10.7-4.5-10.9-10.4c0,0,0-0.1,0-0.1c0.1-4.1,1.9-8.1,4.8-11c8.8,4,16.9,7.1,16.9,12.2
|
||||
C86.7,168.4,82.6,171.7,75.9,171.7L75.9,171.7z M76.5,117.3c5.9,0,9.5,3.3,9.5,8.3c-0.1,3.9-1.2,7.7-3.2,11.1
|
||||
c-7.9-3.4-14.5-6.4-14.5-12.4C68.3,120.4,70.8,117.3,76.5,117.3z M98.7,144.5c0.9-1.1,8.8-10.9,8.8-20.6c0-16.8-13.8-26.1-30.5-26.1
|
||||
c-21.1,0-30.3,12.9-30.3,26c0,7.7,3.4,13.1,8.2,17.3c-2.8,2.1-12.5,10.4-12.5,22c0,14.4,11.5,28,33.4,28c22.3,0,33.4-13.8,33.4-27.5
|
||||
C109.3,154.5,104.8,148.7,98.7,144.5L98.7,144.5z"/>
|
||||
<path class="st1" d="M211.7,171.7c-5.9,0.1-10.7-4.5-10.8-10.4c0,0,0-0.1,0-0.1c0.1-4.1,1.8-8.1,4.8-11c8.8,4,16.9,7.1,16.9,12.2
|
||||
C222.5,168.4,218.4,171.7,211.7,171.7L211.7,171.7z M212.3,117.3c5.9,0,9.5,3.3,9.5,8.3c-0.1,3.9-1.2,7.7-3.2,11.1
|
||||
c-7.9-3.4-14.5-6.4-14.5-12.4C204.1,120.4,206.7,117.3,212.3,117.3z M234.5,144.5c0.9-1.1,8.8-10.9,8.8-20.6
|
||||
c0-16.8-13.8-26.1-30.6-26.1c-21.1,0-30.3,12.9-30.3,26c0,7.7,3.4,13.1,8.2,17.3c-2.8,2.1-12.5,10.4-12.5,22c0,14.4,11.5,28,33.4,28
|
||||
c22.3,0,33.4-13.8,33.4-27.5C245.1,154.5,240.6,148.7,234.5,144.5L234.5,144.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1,10 +1,10 @@
|
||||
import { LinkIcon, SearchIcon } from "@heroicons/react/outline";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Alert, Button, Dialog, DialogClose, DialogContent, DialogFooter, Input } from "@calcom/ui";
|
||||
import { Link, Search } from "@calcom/ui/components/icon";
|
||||
|
||||
interface ISearchDialog {
|
||||
isOpenDialog: boolean;
|
||||
@@ -118,8 +118,8 @@ export const SearchDialog = (props: ISearchDialog) => {
|
||||
</h3>
|
||||
<p className="text-subtle mb-3 text-sm font-light">{t("find_gif_spice_confirmation")}</p>
|
||||
<div className="border-emphasis flex items-center border-b border-solid">
|
||||
{renderTab(SearchIcon, t("search_giphy"), MODE_SEARCH)}
|
||||
{renderTab(LinkIcon, t("add_link_from_giphy"), MODE_URL)}
|
||||
{renderTab(Search, t("search_giphy"), MODE_SEARCH)}
|
||||
{renderTab(Link, t("add_link_from_giphy"), MODE_URL)}
|
||||
</div>
|
||||
<form
|
||||
className="flex w-full justify-center space-x-2 space-y-2 rtl:space-x-reverse"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PencilAltIcon, PlusIcon, XIcon } from "@heroicons/react/solid";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { Edit, Plus, X } from "@calcom/ui/components/icon";
|
||||
|
||||
import { SearchDialog } from "./SearchDialog";
|
||||
|
||||
@@ -24,11 +24,11 @@ export default function SelectGifInput(props: ISelectGifInput) {
|
||||
)}
|
||||
<div className="flex">
|
||||
{selectedGif ? (
|
||||
<Button color="minimal" type="button" StartIcon={PencilAltIcon} onClick={() => setShowDialog(true)}>
|
||||
<Button color="minimal" type="button" StartIcon={Edit} onClick={() => setShowDialog(true)}>
|
||||
Change
|
||||
</Button>
|
||||
) : (
|
||||
<Button color="minimal" type="button" StartIcon={PlusIcon} onClick={() => setShowDialog(true)}>
|
||||
<Button color="minimal" type="button" StartIcon={Plus} onClick={() => setShowDialog(true)}>
|
||||
Add from Giphy
|
||||
</Button>
|
||||
)}
|
||||
@@ -37,7 +37,7 @@ export default function SelectGifInput(props: ISelectGifInput) {
|
||||
<Button
|
||||
color="destructive"
|
||||
type="button"
|
||||
StartIcon={XIcon}
|
||||
StartIcon={X}
|
||||
onClick={() => {
|
||||
setSelectedGif("");
|
||||
props.onChange("");
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
key: {},
|
||||
});
|
||||
|
||||
return { url: getInstalledAppPath({ variant: "other", slug: "ping" }) };
|
||||
return { url: getInstalledAppPath({ variant: appConfig.variant, slug: "ping" }) };
|
||||
}
|
||||
|
||||
export default defaultResponder(getHandler);
|
||||
|
||||
@@ -6,4 +6,4 @@ items:
|
||||
- 4.png
|
||||
---
|
||||
|
||||
Quickly share your Cal.com meeting links with Raycast. Requires Raycast.com to be installed. You can create an API token in your Developer Cal.com Settings.
|
||||
Quickly share your Cal.com meeting links with Raycast. Requires Raycast.com to be installed and the Cal.com from the Raycast Store needs to be installed: https://www.raycast.com/cal/. You can create an API token in your Developer Cal.com Settings, and then you can add the API Key by opening up the Cal.com App in Raycast.
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"publisher": "Cal.com",
|
||||
"email": "help@cal.com",
|
||||
"licenseRequired": true,
|
||||
"teamsPlanRequired": {
|
||||
"upgradeUrl": "/apps/routing-forms/forms"
|
||||
},
|
||||
"description": "It would allow a booker to connect with the right person or choose the right event, faster. It would work by taking inputs from the booker and using that data to route to the correct booker/event as configured by Cal user",
|
||||
"__createdUsingCli": true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ClockIcon } from "@heroicons/react/outline";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { useState } from "react";
|
||||
@@ -8,6 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, showToast } from "@calcom/ui";
|
||||
import { Clock } from "@calcom/ui/components/icon";
|
||||
|
||||
interface IConfirmDialogWipe {
|
||||
isOpenDialog: boolean;
|
||||
@@ -82,7 +82,7 @@ export const ConfirmDialog = (props: IConfirmDialogWipe) => {
|
||||
<DialogContent onOpenAutoFocus={(e) => e.preventDefault()}>
|
||||
<div className="flex flex-row space-x-3">
|
||||
<div className="flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
|
||||
<ClockIcon className="m-auto h-6 w-6" />
|
||||
<Clock className="m-auto h-5 w-5" />
|
||||
</div>
|
||||
<div className="pt-1">
|
||||
<DialogHeader title="Wipe My Calendar" />
|
||||
|
||||
@@ -455,9 +455,9 @@ export default class EventManager {
|
||||
return [];
|
||||
}
|
||||
const { uid: bookingRefUid, externalCalendarId: bookingExternalCalendarId } = calendarReference;
|
||||
|
||||
if (!bookingExternalCalendarId) {
|
||||
throw new Error("externalCalendarId");
|
||||
let calenderExternalId: string | null = null;
|
||||
if (bookingExternalCalendarId) {
|
||||
calenderExternalId = bookingExternalCalendarId;
|
||||
}
|
||||
|
||||
let result = [];
|
||||
@@ -465,13 +465,13 @@ export default class EventManager {
|
||||
credential = this.calendarCredentials.filter(
|
||||
(credential) => credential.id === calendarReference?.credentialId
|
||||
)[0];
|
||||
result.push(updateEvent(credential, event, bookingRefUid, bookingExternalCalendarId));
|
||||
result.push(updateEvent(credential, event, bookingRefUid, calenderExternalId));
|
||||
} else {
|
||||
const credentials = this.calendarCredentials.filter(
|
||||
(credential) => credential.type === calendarReference?.type
|
||||
);
|
||||
for (const credential of credentials) {
|
||||
result.push(updateEvent(credential, event, bookingRefUid, bookingExternalCalendarId));
|
||||
result.push(updateEvent(credential, event, bookingRefUid, calenderExternalId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,11 +65,10 @@ export const CallToAction = (props: {
|
||||
marginLeft: 0,
|
||||
}}
|
||||
iconName={startIconName}
|
||||
secondary={secondary}
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
{endIconName && <CallToActionIcon iconName={endIconName} secondary={secondary} />}
|
||||
{endIconName && <CallToActionIcon iconName={endIconName} />}
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -2,15 +2,7 @@ import React from "react";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
export const CallToActionIcon = ({
|
||||
secondary,
|
||||
iconName,
|
||||
style,
|
||||
}: {
|
||||
secondary?: boolean;
|
||||
iconName: string;
|
||||
style?: React.CSSProperties;
|
||||
}) => (
|
||||
export const CallToActionIcon = ({ iconName, style }: { iconName: string; style?: React.CSSProperties }) => (
|
||||
<img
|
||||
src={`${WEBAPP_URL}/emails/${iconName}.png`}
|
||||
srcSet={`${WEBAPP_URL}/emails/${iconName}.svg`}
|
||||
@@ -19,7 +11,6 @@ export const CallToActionIcon = ({
|
||||
height: "1rem",
|
||||
width: "1rem",
|
||||
marginLeft: "0.5rem",
|
||||
...(secondary && { filter: "brightness(50%)" }),
|
||||
...style,
|
||||
}}
|
||||
alt=""
|
||||
|
||||
@@ -112,7 +112,7 @@ ${this.t(
|
||||
)}
|
||||
${this.t(subtitle)}
|
||||
|
||||
${getRichDescription(this.calEvent)}
|
||||
${getRichDescription(this.calEvent, this.t)}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ ${this.t("request_reschedule_subtitle", {
|
||||
})},
|
||||
${this.getWhen()}
|
||||
${this.t("need_to_reschedule_or_cancel")}
|
||||
${getManageLink(this.calEvent)}
|
||||
${getManageLink(this.calEvent, this.t)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ if (only === "all" || only === "ns:second") {
|
||||
iframeAttrs: {
|
||||
id: "cal-booking-place-second-iframe",
|
||||
},
|
||||
theme: "auto",
|
||||
},
|
||||
},
|
||||
]);
|
||||
@@ -333,6 +334,16 @@ Cal("init", "routingFormDark", {
|
||||
|
||||
if (only === "all" || only == "ns:floatingButton") {
|
||||
Cal.ns.floatingButton("floatingButton", {
|
||||
calLink: "pro",
|
||||
calLink: "pro/30min",
|
||||
config: {
|
||||
iframeAttrs: {
|
||||
id: "floatingtest",
|
||||
},
|
||||
name: "John",
|
||||
email: "johndoe@gmail.com",
|
||||
notes: "Test Meeting",
|
||||
guests: ["janedoe@example.com", "test@example.com"],
|
||||
theme: "dark",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ type ModalTargetDatasetProps = {
|
||||
calLink: string;
|
||||
calNamespace: string;
|
||||
calOrigin: string;
|
||||
calConfig: string;
|
||||
};
|
||||
|
||||
type CamelCase<T extends string> = T extends `${infer U}${infer V}` ? `${Uppercase<U>}${V}` : T;
|
||||
|
||||
@@ -8,10 +8,10 @@ import type { Message } from "./embed";
|
||||
import { sdkActionManager } from "./sdk-event";
|
||||
|
||||
type Theme = "dark" | "light";
|
||||
|
||||
export type EmbedThemeConfig = Theme | "auto";
|
||||
export type UiConfig = {
|
||||
hideEventTypeDetails?: boolean;
|
||||
theme?: Theme | "auto";
|
||||
theme?: EmbedThemeConfig;
|
||||
styles?: EmbedStyles & EmbedNonStylesConfig;
|
||||
//TODO: Extract from tailwind the list of all custom variables and support them in auto-completion as well as runtime validation. Followup with listing all variables in Embed Snippet Generator UI.
|
||||
cssVarsPerTheme?: Record<Theme, Record<string, string>>;
|
||||
@@ -31,7 +31,7 @@ const embedStore = {
|
||||
reactNonStylesStateSetters: {} as Record<keyof EmbedNonStylesConfig, setNonStylesConfig>,
|
||||
parentInformedAboutContentHeight: false,
|
||||
windowLoadEventFired: false,
|
||||
setTheme: undefined as ((arg0: string) => void) | undefined,
|
||||
setTheme: undefined as ((arg0: EmbedThemeConfig) => void) | undefined,
|
||||
theme: undefined as UiConfig["theme"],
|
||||
uiConfig: undefined as Omit<UiConfig, "styles" | "theme"> | undefined,
|
||||
setUiConfig: undefined as ((arg0: UiConfig) => void) | undefined,
|
||||
@@ -170,14 +170,14 @@ function isValidNamespace(ns: string | null | undefined) {
|
||||
|
||||
export const useEmbedTheme = () => {
|
||||
const router = useRouter();
|
||||
const [theme, setTheme] = useState(embedStore.theme || (router.query.theme as string));
|
||||
const [theme, setTheme] = useState(embedStore.theme || (router.query.theme as typeof embedStore.theme));
|
||||
useEffect(() => {
|
||||
router.events.on("routeChangeComplete", () => {
|
||||
sdkActionManager?.fire("__routeChanged", {});
|
||||
});
|
||||
}, [router.events]);
|
||||
embedStore.setTheme = setTheme;
|
||||
return theme === "auto" ? null : theme;
|
||||
return theme;
|
||||
};
|
||||
|
||||
export const useEmbedUiConfig = () => {
|
||||
@@ -434,7 +434,7 @@ if (isBrowser) {
|
||||
// Exposes certain global variables/fns that are used by the app to get interface with the embed.
|
||||
embedInit();
|
||||
const url = new URL(document.URL);
|
||||
embedStore.theme = (window?.getEmbedTheme?.() || "auto") as UiConfig["theme"];
|
||||
embedStore.theme = window?.getEmbedTheme?.() as UiConfig["theme"];
|
||||
if (url.searchParams.get("prerender") !== "true" && window?.isEmbed?.()) {
|
||||
log("Initializing embed-iframe");
|
||||
// HACK
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { FloatingButton } from "./FloatingButton/FloatingButton";
|
||||
import { Inline } from "./Inline/inline";
|
||||
import { ModalBox } from "./ModalBox/ModalBox";
|
||||
import type { InterfaceWithParent, interfaceWithParent, UiConfig } from "./embed-iframe";
|
||||
import type { InterfaceWithParent, interfaceWithParent, UiConfig, EmbedThemeConfig } from "./embed-iframe";
|
||||
import css from "./embed.css";
|
||||
import type { EventData, EventDataMap } from "./sdk-action-manager";
|
||||
import { SdkActionManager } from "./sdk-action-manager";
|
||||
@@ -129,6 +129,7 @@ type PrefillAndIframeAttrsConfig = Record<string, string | string[] | Record<str
|
||||
iframeAttrs?: Record<string, string> & {
|
||||
id?: string;
|
||||
};
|
||||
theme?: EmbedThemeConfig;
|
||||
};
|
||||
|
||||
export class Cal {
|
||||
@@ -435,6 +436,7 @@ class CalApi {
|
||||
buttonColor = "rgb(0, 0, 0)",
|
||||
buttonTextColor = "rgb(255, 255, 255)",
|
||||
calOrigin,
|
||||
config,
|
||||
}: {
|
||||
calLink: string;
|
||||
buttonText?: string;
|
||||
@@ -444,6 +446,7 @@ class CalApi {
|
||||
buttonColor?: string;
|
||||
buttonTextColor?: string;
|
||||
calOrigin?: string;
|
||||
config?: PrefillAndIframeAttrsConfig;
|
||||
}) {
|
||||
// validate(arguments[0], {
|
||||
// required: true,
|
||||
@@ -466,6 +469,10 @@ class CalApi {
|
||||
el.dataset.calLink = calLink;
|
||||
el.dataset.calNamespace = this.cal.namespace;
|
||||
el.dataset.calOrigin = calOrigin ?? "";
|
||||
if (config) {
|
||||
el.dataset.calConfig = JSON.stringify(config);
|
||||
}
|
||||
|
||||
if (attributes?.id) {
|
||||
el.id = attributes.id;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@ import StickyBox from "react-sticky-box";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
import { ToggleGroup, useCalcomTheme } from "@calcom/ui";
|
||||
import { ToggleGroup } from "@calcom/ui";
|
||||
import { Calendar, Columns, Grid } from "@calcom/ui/components/icon";
|
||||
|
||||
import { AvailableTimeSlots } from "./components/AvailableTimeSlots";
|
||||
@@ -23,21 +22,20 @@ import { fadeInLeft, getBookerSizeClassNames, useBookerResizeAnimation } from ".
|
||||
import { useBookerStore, useInitializeBookerStore } from "./store";
|
||||
import type { BookerLayout, BookerProps } from "./types";
|
||||
import { useEvent } from "./utils/event";
|
||||
import { useBrandColors } from "./utils/use-brand-colors";
|
||||
|
||||
const PoweredBy = dynamic(() => import("@calcom/ee/components/PoweredBy"));
|
||||
const DatePicker = dynamic(() => import("./components/DatePicker").then((mod) => mod.DatePicker), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const useBrandColors = ({ brandColor, darkBrandColor }: { brandColor?: string; darkBrandColor?: string }) => {
|
||||
const brandTheme = useGetBrandingColours({
|
||||
lightVal: brandColor,
|
||||
darkVal: darkBrandColor,
|
||||
});
|
||||
useCalcomTheme(brandTheme);
|
||||
};
|
||||
|
||||
const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: BookerProps) => {
|
||||
const BookerComponent = ({
|
||||
username,
|
||||
eventSlug,
|
||||
month,
|
||||
rescheduleBooking,
|
||||
hideBranding = false,
|
||||
}: BookerProps) => {
|
||||
const { t } = useLocale();
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const isTablet = useMediaQuery("(max-width: 1024px)");
|
||||
@@ -61,6 +59,7 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
|
||||
useBrandColors({
|
||||
brandColor: event.data?.profile.brandColor,
|
||||
darkBrandColor: event.data?.profile.darkBrandColor,
|
||||
theme: event.data?.profile.theme,
|
||||
});
|
||||
|
||||
useInitializeBookerStore({
|
||||
@@ -209,7 +208,7 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
|
||||
"mt-auto mb-6 pt-6 [&_img]:h-[15px]",
|
||||
layout === "small_calendar" ? "block" : "hidden"
|
||||
)}>
|
||||
<PoweredBy logoOnly />
|
||||
{!hideBranding ? <PoweredBy logoOnly /> : null}
|
||||
</m.span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { useCalcomTheme } from "@calcom/ui";
|
||||
|
||||
export const useBrandColors = ({
|
||||
brandColor,
|
||||
darkBrandColor,
|
||||
theme,
|
||||
}: {
|
||||
brandColor?: string;
|
||||
darkBrandColor?: string;
|
||||
theme?: string | null;
|
||||
}) => {
|
||||
const brandTheme = useGetBrandingColours({
|
||||
lightVal: brandColor,
|
||||
darkVal: darkBrandColor,
|
||||
});
|
||||
useCalcomTheme(brandTheme);
|
||||
const { setTheme } = useTheme(theme);
|
||||
|
||||
useEffect(() => {
|
||||
if (theme) setTheme(theme);
|
||||
}, [setTheme, theme]);
|
||||
};
|
||||
@@ -11,7 +11,7 @@ import { useFilterQuery } from "../lib/useFilterQuery";
|
||||
export const TeamsMemberFilter = () => {
|
||||
const { t } = useLocale();
|
||||
const session = useSession();
|
||||
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeByKey } = useFilterQuery();
|
||||
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeByKey, removeAllQueryParams } = useFilterQuery();
|
||||
const { data } = trpc.viewer.teams.list.useQuery();
|
||||
const [dropdownTitle, setDropdownTitle] = useState<string>(t("all_bookings_filter_label"));
|
||||
|
||||
@@ -38,7 +38,7 @@ export const TeamsMemberFilter = () => {
|
||||
checked={!query.teamIds && !query.userIds?.includes(session.data?.user.id || 0)}
|
||||
onChange={() => {
|
||||
setDropdownTitle(t("all_bookings_filter_label"));
|
||||
removeByKey("teamIds"); // Always clear on toggle or not toggle (seems weird but when you know the behviour it works well )
|
||||
removeAllQueryParams();
|
||||
}}
|
||||
className="text-primary-600 focus:ring-primary-500 border-default inline-flex h-4 w-4 place-self-center justify-self-end rounded "
|
||||
/>
|
||||
@@ -57,7 +57,7 @@ export const TeamsMemberFilter = () => {
|
||||
id="yourBookings"
|
||||
type="checkbox"
|
||||
disabled={session.status === "loading"}
|
||||
checked={query.userIds?.includes(session.data?.user.id || 0)}
|
||||
checked={!!query.userIds?.includes(session.data?.user.id || 0)}
|
||||
onChange={(e) => {
|
||||
setDropdownTitle(t("your_bookings_filter_label"));
|
||||
if (e.target.checked) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
||||
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useEmbedStyles } from "@calcom/embed-core/embed-iframe";
|
||||
@@ -8,6 +6,7 @@ import { daysInMonth, yyyymmdd } from "@calcom/lib/date-fns";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { weekdayNames } from "@calcom/lib/weekday";
|
||||
import { Button, SkeletonText } from "@calcom/ui";
|
||||
import { ChevronLeft, ChevronRight } from "@calcom/ui/components/icon";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
export type DatePickerProps = {
|
||||
@@ -223,7 +222,7 @@ const DatePicker = ({
|
||||
data-testid="decrementMonth"
|
||||
color="minimal"
|
||||
variant="icon"
|
||||
StartIcon={ChevronLeftIcon}
|
||||
StartIcon={ChevronLeft}
|
||||
/>
|
||||
<Button
|
||||
className="group p-1 opacity-70 hover:opacity-100"
|
||||
@@ -231,7 +230,7 @@ const DatePicker = ({
|
||||
data-testid="incrementMonth"
|
||||
color="minimal"
|
||||
variant="icon"
|
||||
StartIcon={ChevronRightIcon}
|
||||
StartIcon={ChevronRight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
The Cal.com Enterprise Edition (EE) license (the “EE License”)
|
||||
The Cal.com Commercial License (the “Commercial License”)
|
||||
Copyright (c) 2020-present Cal.com, Inc
|
||||
|
||||
With regard to the Cal.com Software:
|
||||
@@ -6,15 +6,15 @@ With regard to the Cal.com Software:
|
||||
This software and associated documentation files (the "Software") may only be
|
||||
used in production, if you (and any entity that you represent) have agreed to,
|
||||
and are in compliance with, the Cal.com Subscription Terms available
|
||||
at https://cal.com/terms (the “EE Terms”), or other agreements governing
|
||||
at https://cal.com/terms, or other agreements governing
|
||||
the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"),
|
||||
and otherwise have a valid Cal.com Enterprise Edition subscription ("EE Subscription")
|
||||
for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence,
|
||||
and otherwise have a valid Cal.com Enterprise Edition subscription ("Commercial Subscription")
|
||||
for the correct number of hosts as defined in the "Commercial Terms ("Hosts"). Subject to the foregoing sentence,
|
||||
you are free to modify this Software and publish patches to the Software. You agree
|
||||
that Cal.com and/or its licensors (as applicable) retain all right, title and interest in
|
||||
and to all such modifications and/or patches, and all such modifications and/or
|
||||
patches may only be used, copied, modified, displayed, distributed, or otherwise
|
||||
exploited with a valid EE Subscription for the correct number of hosts.
|
||||
exploited with a valid Commercial Subscription for the correct number of hosts.
|
||||
Notwithstanding the foregoing, you may copy and modify the Software for development
|
||||
and testing purposes, without requiring a subscription. You agree that Cal.com and/or
|
||||
its licensors (as applicable) retain all right, title and interest in and to all such
|
||||
@@ -22,11 +22,11 @@ modifications. You are not granted any other rights beyond what is expressly sta
|
||||
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
|
||||
and/or sell the Software.
|
||||
|
||||
This EE License applies only to the part of this Software that is not distributed under
|
||||
This Commercial License applies only to the part of this Software that is not distributed under
|
||||
the AGPLv3 license. Any part of this Software distributed under the MIT license or which
|
||||
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
|
||||
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
|
||||
in part, is copyrighted under the AGPLv3 license. The full text of this EE License shall
|
||||
in part, is copyrighted under the AGPLv3 license. The full text of this Commercial License shall
|
||||
be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
|
||||
@@ -41,6 +41,7 @@ export default function TeamAvailabilityTimes(props: Props) {
|
||||
inviteeDate: props.selectedDate,
|
||||
workingHours: data?.workingHours || [],
|
||||
minimumBookingNotice: 0,
|
||||
offsetStart: 0,
|
||||
eventLength: props.frequency,
|
||||
organizerTimeZone: `${data?.timeZone}`,
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DropdownItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@calcom/ui";
|
||||
import { Slash, Check, MoreHorizontal, X } from "@calcom/ui/components/icon";
|
||||
import { Ban, Check, MoreHorizontal, X } from "@calcom/ui/components/icon";
|
||||
|
||||
interface Props {
|
||||
team: {
|
||||
@@ -92,7 +92,7 @@ export default function TeamInviteListItem(props: Props) {
|
||||
variant="icon"
|
||||
color="secondary"
|
||||
onClick={declineInvite}
|
||||
StartIcon={Slash}
|
||||
StartIcon={Ban}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -48,7 +48,6 @@ function MembersList(props: MembersListProps) {
|
||||
autoComplete="false"
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
value={query}
|
||||
defaultValue=""
|
||||
placeholder={`${t("search")}...`}
|
||||
/>
|
||||
{membersList?.length && team ? (
|
||||
|
||||
@@ -102,10 +102,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
where: {
|
||||
method: WorkflowMethods.EMAIL,
|
||||
scheduled: false,
|
||||
cancelled: false,
|
||||
scheduledDate: {
|
||||
lte: dayjs().add(72, "hour").toISOString(),
|
||||
},
|
||||
OR: [{ cancelled: false }, { cancelled: null }],
|
||||
},
|
||||
include: {
|
||||
workflowStep: true,
|
||||
|
||||
@@ -36,7 +36,7 @@ export const CheckedTeamSelect = ({
|
||||
}}
|
||||
name={props.name}
|
||||
placeholder={props.placeholder || t("select")}
|
||||
isSearchable={false}
|
||||
isSearchable={true}
|
||||
options={options}
|
||||
value={value}
|
||||
isMulti
|
||||
@@ -48,7 +48,7 @@ export const CheckedTeamSelect = ({
|
||||
className={classNames("mt-3 rounded-md", value.length >= 1 && "border-subtle border")}
|
||||
ref={animationRef}>
|
||||
{value.map((option, index) => (
|
||||
<li key={option.value} className={`flex py-2 px-3 ${index === value.length - 1 ? "" : "border-b"}`}>
|
||||
<li key={option.value} className={`flex py-2 px-3 ${index === value.length - 1 ? "" : "border-subtle border-b"}`}>
|
||||
<Avatar size="sm" imageSrc={option.avatar} alt={option.label} />
|
||||
<p className="text-emphasis ms-3 my-auto text-sm">{option.label}</p>
|
||||
<X
|
||||
|
||||
@@ -54,6 +54,7 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||
weekStart: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
theme: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -80,6 +81,7 @@ export const getPublicEvent = async (username: string, eventSlug: string, prisma
|
||||
metadata: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
theme: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -113,6 +115,7 @@ export const getPublicEvent = async (username: string, eventSlug: string, prisma
|
||||
image: `${WEBAPP_URL}/${users[0].username}/avatar.png`,
|
||||
brandColor: users[0].brandColor,
|
||||
darkBrandColor: users[0].darkBrandColor,
|
||||
theme: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -179,6 +182,7 @@ function getProfileFromEvent(event: Event) {
|
||||
logo: !team ? undefined : team.logo,
|
||||
brandColor: profile.brandColor,
|
||||
darkBrandColor: profile.darkBrandColor,
|
||||
theme: profile.theme,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import useAvatarQuery from "@calcom/trpc/react/hooks/useAvatarQuery";
|
||||
import type { VerticalTabItemProps } from "@calcom/ui";
|
||||
import { Badge, Button, ErrorBoundary, Skeleton, useMeta, VerticalTabItem } from "@calcom/ui";
|
||||
import {
|
||||
@@ -105,14 +106,16 @@ const adminRequiredKeys = ["admin"];
|
||||
|
||||
const useTabs = () => {
|
||||
const session = useSession();
|
||||
const { data: user } = trpc.viewer.me.useQuery();
|
||||
const { data: avatar } = useAvatarQuery();
|
||||
|
||||
const isAdmin = session.data?.user.role === UserPermissionRole.ADMIN;
|
||||
|
||||
tabs.map((tab) => {
|
||||
if (tab.name === "my_account") {
|
||||
tab.name = session.data?.user?.name || "my_account";
|
||||
if (tab.href === "/settings/my-account") {
|
||||
tab.name = user?.name || "my_account";
|
||||
tab.icon = undefined;
|
||||
tab.avatar = WEBAPP_URL + "/" + session.data?.user?.username + "/avatar.png";
|
||||
tab.avatar = avatar?.avatar || WEBAPP_URL + "/" + session?.data?.user?.username + "/avatar.png";
|
||||
}
|
||||
return tab;
|
||||
});
|
||||
@@ -128,7 +131,7 @@ const BackButtonInSidebar = ({ name }: { name: string }) => {
|
||||
return (
|
||||
<Link
|
||||
href="/"
|
||||
className="hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis group-hover:text-default text-emphasis group my-6 flex h-6 max-h-6 w-64 flex-row items-center rounded-md py-2 px-3 text-sm font-medium leading-4"
|
||||
className="hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis group-hover:text-default text-emphasis group my-6 flex h-6 max-h-6 w-full flex-row items-center rounded-md py-2 px-3 text-sm font-medium leading-4"
|
||||
data-testid={`vertical-tab-${name}`}>
|
||||
<ArrowLeft className="h-4 w-4 stroke-[2px] ltr:mr-[10px] rtl:ml-[10px] rtl:rotate-180 md:mt-0" />
|
||||
<Skeleton title={name} as="p" className="max-w-36 min-h-4 truncate">
|
||||
|
||||
@@ -24,6 +24,7 @@ import getBrandColours from "@calcom/lib/getBrandColours";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { isKeyInObject } from "@calcom/lib/isKeyInObject";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import useAvatarQuery from "@calcom/trpc/react/hooks/useAvatarQuery";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import type { SVGComponent } from "@calcom/types/SVGComponent";
|
||||
import {
|
||||
@@ -246,8 +247,9 @@ export default function Shell(props: LayoutProps) {
|
||||
|
||||
function UserDropdown({ small }: { small?: boolean }) {
|
||||
const { t } = useLocale();
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
const { data: user } = useMeQuery();
|
||||
const { data: avatar } = useAvatarQuery();
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
@@ -294,7 +296,7 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={WEBAPP_URL + "/" + user.username + "/avatar.png"}
|
||||
src={avatar?.avatar || WEBAPP_URL + "/" + user.username + "/avatar.png"}
|
||||
alt={user.username || "Nameless User"}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { TFunction } from "next-i18next";
|
||||
import short from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
@@ -10,26 +11,26 @@ const translator = short();
|
||||
|
||||
// The odd indentation in this file is necessary because otherwise the leading tabs will be applied into the event description.
|
||||
|
||||
export const getWhat = (calEvent: CalendarEvent) => {
|
||||
export const getWhat = (calEvent: CalendarEvent, t: TFunction) => {
|
||||
return `
|
||||
${calEvent.organizer.language.translate("what")}:
|
||||
${t("what")}:
|
||||
${calEvent.type}
|
||||
`;
|
||||
};
|
||||
|
||||
export const getWhen = (calEvent: CalendarEvent) => {
|
||||
export const getWhen = (calEvent: CalendarEvent, t: TFunction) => {
|
||||
return calEvent.seatsPerTimeSlot
|
||||
? `
|
||||
${calEvent.organizer.language.translate("organizer_timezone")}:
|
||||
${t("organizer_timezone")}:
|
||||
${calEvent.organizer.timeZone}
|
||||
`
|
||||
: `
|
||||
${calEvent.organizer.language.translate("invitee_timezone")}:
|
||||
${t("invitee_timezone")}:
|
||||
${calEvent.attendees[0].timeZone}
|
||||
`;
|
||||
};
|
||||
|
||||
export const getWho = (calEvent: CalendarEvent) => {
|
||||
export const getWho = (calEvent: CalendarEvent, t: TFunction) => {
|
||||
let attendeesFromCalEvent = [...calEvent.attendees];
|
||||
if (calEvent.seatsPerTimeSlot && !calEvent.seatsShowAttendees) {
|
||||
attendeesFromCalEvent = [];
|
||||
@@ -37,38 +38,38 @@ export const getWho = (calEvent: CalendarEvent) => {
|
||||
const attendees = attendeesFromCalEvent
|
||||
.map((attendee) => {
|
||||
return `
|
||||
${attendee?.name || calEvent.organizer.language.translate("guest")}
|
||||
${attendee?.name || t("guest")}
|
||||
${attendee.email}
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const organizer = `
|
||||
${calEvent.organizer.name} - ${calEvent.organizer.language.translate("organizer")}
|
||||
${calEvent.organizer.name} - ${t("organizer")}
|
||||
${calEvent.organizer.email}
|
||||
`;
|
||||
|
||||
const teamMembers = calEvent.team?.members
|
||||
? calEvent.team.members.map((member) => {
|
||||
return `
|
||||
${member.name} - ${calEvent.organizer.language.translate("team_member")}
|
||||
${member.name} - ${t("team_member")}
|
||||
${member.email}
|
||||
`;
|
||||
})
|
||||
: [];
|
||||
|
||||
return `
|
||||
${calEvent.organizer.language.translate("who")}:
|
||||
${t("who")}:
|
||||
${organizer + attendees + teamMembers.join("")}
|
||||
`;
|
||||
};
|
||||
|
||||
export const getAdditionalNotes = (calEvent: CalendarEvent) => {
|
||||
export const getAdditionalNotes = (calEvent: CalendarEvent, t: TFunction) => {
|
||||
if (!calEvent.additionalNotes) {
|
||||
return "";
|
||||
}
|
||||
return `
|
||||
${calEvent.organizer.language.translate("additional_notes")}:
|
||||
${t("additional_notes")}:
|
||||
${calEvent.additionalNotes}
|
||||
`;
|
||||
};
|
||||
@@ -94,11 +95,11 @@ ${labelValueMap[key]}
|
||||
return responsesString;
|
||||
};
|
||||
|
||||
export const getAppsStatus = (calEvent: CalendarEvent) => {
|
||||
export const getAppsStatus = (calEvent: CalendarEvent, t: TFunction) => {
|
||||
if (!calEvent.appsStatus) {
|
||||
return "";
|
||||
}
|
||||
return `\n${calEvent.organizer.language.translate("apps_status")}
|
||||
return `\n${t("apps_status")}
|
||||
${calEvent.appsStatus.map((app) => {
|
||||
return `\n- ${app.appName} ${
|
||||
app.success >= 1 ? `✅ ${app.success > 1 ? `(x${app.success})` : ""}` : ""
|
||||
@@ -111,11 +112,11 @@ export const getAppsStatus = (calEvent: CalendarEvent) => {
|
||||
`;
|
||||
};
|
||||
|
||||
export const getDescription = (calEvent: CalendarEvent) => {
|
||||
export const getDescription = (calEvent: CalendarEvent, t: TFunction) => {
|
||||
if (!calEvent.description) {
|
||||
return "";
|
||||
}
|
||||
return `\n${calEvent.organizer.language.translate("description")}
|
||||
return `\n${t("description")}
|
||||
${calEvent.description}
|
||||
`;
|
||||
};
|
||||
@@ -153,9 +154,9 @@ const getSeatReferenceId = (calEvent: CalendarEvent): string => {
|
||||
return calEvent.attendeeSeatId ? calEvent.attendeeSeatId : "";
|
||||
};
|
||||
|
||||
export const getManageLink = (calEvent: CalendarEvent) => {
|
||||
export const getManageLink = (calEvent: CalendarEvent, t: TFunction) => {
|
||||
return `
|
||||
${calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${t("need_to_reschedule_or_cancel")}
|
||||
${WEBAPP_URL + "/booking/" + getUid(calEvent) + "?changes=true"}
|
||||
`;
|
||||
};
|
||||
@@ -176,27 +177,29 @@ export const getRescheduleLink = (calEvent: CalendarEvent): string => {
|
||||
return `${WEBAPP_URL}/reschedule/${seatUid ? seatUid : Uid}`;
|
||||
};
|
||||
|
||||
export const getRichDescription = (calEvent: CalendarEvent /*, attendee?: Person*/) => {
|
||||
export const getRichDescription = (calEvent: CalendarEvent, t_?: TFunction /*, attendee?: Person*/) => {
|
||||
const t = t_ ?? calEvent.organizer.language.translate;
|
||||
|
||||
return `
|
||||
${getCancellationReason(calEvent)}
|
||||
${getWhat(calEvent)}
|
||||
${getWhen(calEvent)}
|
||||
${getWho(calEvent)}
|
||||
${calEvent.organizer.language.translate("where")}:
|
||||
${getCancellationReason(calEvent, t)}
|
||||
${getWhat(calEvent, t)}
|
||||
${getWhen(calEvent, t)}
|
||||
${getWho(calEvent, t)}
|
||||
${t("where")}:
|
||||
${getLocation(calEvent)}
|
||||
${getDescription(calEvent)}
|
||||
${getAdditionalNotes(calEvent)}
|
||||
${getDescription(calEvent, t)}
|
||||
${getAdditionalNotes(calEvent, t)}
|
||||
${getUserFieldsResponses(calEvent)}
|
||||
${getAppsStatus(calEvent)}
|
||||
${getAppsStatus(calEvent, t)}
|
||||
${
|
||||
// TODO: Only the original attendee can make changes to the event
|
||||
// Guests cannot
|
||||
!calEvent.seatsPerTimeSlot && getManageLink(calEvent)
|
||||
!calEvent.seatsPerTimeSlot && getManageLink(calEvent, t)
|
||||
}
|
||||
${
|
||||
calEvent.paymentInfo
|
||||
? `
|
||||
${calEvent.organizer.language.translate("pay_now")}:
|
||||
${t("pay_now")}:
|
||||
${calEvent.paymentInfo.link}
|
||||
`
|
||||
: ""
|
||||
@@ -204,10 +207,10 @@ ${calEvent.paymentInfo.link}
|
||||
`.trim();
|
||||
};
|
||||
|
||||
export const getCancellationReason = (calEvent: CalendarEvent) => {
|
||||
export const getCancellationReason = (calEvent: CalendarEvent, t: TFunction) => {
|
||||
if (!calEvent.cancellationReason) return "";
|
||||
return `
|
||||
${calEvent.organizer.language.translate("cancellation_reason")}:
|
||||
${t("cancellation_reason")}:
|
||||
${calEvent.cancellationReason}
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -63,6 +63,7 @@ const commons = {
|
||||
periodType: PeriodType.UNLIMITED,
|
||||
periodDays: null,
|
||||
slotInterval: null,
|
||||
offsetStart: 0,
|
||||
locations: [{ type: DailyLocationType }],
|
||||
customInputs,
|
||||
disableGuests: true,
|
||||
|
||||
@@ -78,6 +78,7 @@ export default async function getEventTypeById({
|
||||
slug: true,
|
||||
description: true,
|
||||
length: true,
|
||||
offsetStart: true,
|
||||
hidden: true,
|
||||
locations: true,
|
||||
eventName: true,
|
||||
|
||||
@@ -21,8 +21,10 @@ export default function useTheme(themeToSet?: Maybe<string>) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Embed theme takes precedence over theme configured in app. If embedTheme isn't set that is it's in 'Auto' mode, then it would use the theme configured in appearance.
|
||||
const finalThemeToSet = embedTheme || themeToSet;
|
||||
// Embed theme takes precedence over theme configured in app.
|
||||
// If embedTheme isn't set i.e. it's not explicitly configured with a theme, then it would use the theme configured in appearance.
|
||||
// If embedTheme is set to "auto" then we consider it as null which then uses system theme.
|
||||
const finalThemeToSet = embedTheme ? (embedTheme === "auto" ? null : embedTheme) : themeToSet;
|
||||
|
||||
if (!finalThemeToSet || finalThemeToSet === activeTheme) return;
|
||||
|
||||
|
||||
@@ -86,5 +86,19 @@ export function useTypedQuery<T extends z.AnyZodObject>(schema: T) {
|
||||
}
|
||||
}
|
||||
|
||||
return { data: parsedQuery, setQuery, removeByKey, pushItemToKey, removeItemByKeyAndValue };
|
||||
// Remove all query params from the URL
|
||||
function removeAllQueryParams() {
|
||||
const { asPath } = router;
|
||||
const newPath = asPath.split("?")[0];
|
||||
router.replace(newPath, undefined, { shallow: true });
|
||||
}
|
||||
|
||||
return {
|
||||
data: parsedQuery,
|
||||
setQuery,
|
||||
removeByKey,
|
||||
pushItemToKey,
|
||||
removeItemByKeyAndValue,
|
||||
removeAllQueryParams,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,17 +12,20 @@ export type GetSlots = {
|
||||
dateOverrides?: DateOverride[];
|
||||
minimumBookingNotice: number;
|
||||
eventLength: number;
|
||||
offsetStart: number;
|
||||
organizerTimeZone: string;
|
||||
};
|
||||
export type TimeFrame = { userIds?: number[]; startTime: number; endTime: number };
|
||||
|
||||
const minimumOfOne = (input: number) => (input < 1 ? 1 : input);
|
||||
const minimumOfZero = (input: number) => (input < 0 ? 0 : input);
|
||||
|
||||
function buildSlots({
|
||||
startOfInviteeDay,
|
||||
computedLocalAvailability,
|
||||
frequency,
|
||||
eventLength,
|
||||
offsetStart,
|
||||
startDate,
|
||||
organizerTimeZone,
|
||||
inviteeTimeZone,
|
||||
@@ -32,6 +35,7 @@ function buildSlots({
|
||||
startDate: Dayjs;
|
||||
frequency: number;
|
||||
eventLength: number;
|
||||
offsetStart: number;
|
||||
organizerTimeZone: string;
|
||||
inviteeTimeZone: string;
|
||||
}) {
|
||||
@@ -42,6 +46,8 @@ function buildSlots({
|
||||
// keep the old safeguards in; may be needed.
|
||||
frequency = minimumOfOne(frequency);
|
||||
eventLength = minimumOfOne(eventLength);
|
||||
offsetStart = minimumOfZero(offsetStart);
|
||||
|
||||
// A day starts at 00:00 unless the startDate is the same as the current day
|
||||
const dayStart = startOfInviteeDay.isSame(startDate, "day")
|
||||
? Math.ceil((startDate.hour() * 60 + startDate.minute()) / frequency) * frequency
|
||||
@@ -84,7 +90,11 @@ function buildSlots({
|
||||
|
||||
for (const [boundaryStart, boundaryEnd] of ranges) {
|
||||
// loop through the day, based on frequency.
|
||||
for (let slotStart = boundaryStart; slotStart < boundaryEnd; slotStart += frequency) {
|
||||
for (
|
||||
let slotStart = boundaryStart + offsetStart;
|
||||
slotStart < boundaryEnd;
|
||||
slotStart += offsetStart + frequency
|
||||
) {
|
||||
computedLocalAvailability.forEach((item) => {
|
||||
// TODO: This logic does not allow for past-midnight bookings.
|
||||
if (slotStart < item.startTime || slotStart > item.endTime + 1 - eventLength) {
|
||||
@@ -139,6 +149,7 @@ const getSlots = ({
|
||||
workingHours,
|
||||
dateOverrides = [],
|
||||
eventLength,
|
||||
offsetStart,
|
||||
organizerTimeZone,
|
||||
}: GetSlots) => {
|
||||
// current date in invitee tz
|
||||
@@ -240,6 +251,7 @@ const getSlots = ({
|
||||
startDate,
|
||||
frequency,
|
||||
eventLength,
|
||||
offsetStart,
|
||||
organizerTimeZone,
|
||||
inviteeTimeZone: timeZone,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
export const slugify = (str: string) => {
|
||||
return str.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
||||
return str
|
||||
.toLowerCase() // Convert to lowercase
|
||||
.trim() // Remove whitespace from both sides
|
||||
.normalize("NFD") // Normalize to decomposed form for handling accents
|
||||
.replace(/\p{Diacritic}/gu, "") // Remove any diacritics (accents) from characters
|
||||
.replace(/[^\p{L}\p{N}\p{Zs}\p{Emoji}]+/gu, "-") // Replace any non-alphanumeric characters (including Unicode) with a dash
|
||||
.replace(/[\s_]+/g, "-") // Replace whitespace and underscores with a single dash
|
||||
.replace(/^-+/, "") // Remove dashes from start
|
||||
.replace(/-+$/, ""); // Remove dashes from end
|
||||
};
|
||||
|
||||
export default slugify;
|
||||
|
||||
@@ -71,6 +71,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
|
||||
position: 1,
|
||||
locations: null,
|
||||
length: 15,
|
||||
offsetStart: 0,
|
||||
hidden: false,
|
||||
userId: null,
|
||||
teamId: null,
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "EventType" ADD COLUMN "offsetStart" INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -57,6 +57,7 @@ model EventType {
|
||||
/// @zod.custom(imports.eventTypeLocations)
|
||||
locations Json?
|
||||
length Int
|
||||
offsetStart Int @default(0)
|
||||
hidden Boolean @default(false)
|
||||
hosts Host[]
|
||||
users User[] @relation("user_eventtype")
|
||||
|
||||
@@ -65,6 +65,7 @@ export const availiblityPageEventTypeSelect = Prisma.validator<Prisma.EventTypeS
|
||||
availability: true,
|
||||
description: true,
|
||||
length: true,
|
||||
offsetStart: true,
|
||||
price: true,
|
||||
currency: true,
|
||||
periodType: true,
|
||||
|
||||
@@ -501,6 +501,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
|
||||
price: true,
|
||||
slug: true,
|
||||
length: true,
|
||||
offsetStart: true,
|
||||
locations: true,
|
||||
hidden: true,
|
||||
availability: true,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { trpc } from "../trpc";
|
||||
|
||||
export function useAvatarQuery() {
|
||||
const avatarQuery = trpc.viewer.avatar.useQuery(undefined, {
|
||||
retry(failureCount) {
|
||||
return failureCount > 3;
|
||||
},
|
||||
});
|
||||
|
||||
return avatarQuery;
|
||||
}
|
||||
|
||||
export default useAvatarQuery;
|
||||
@@ -51,11 +51,15 @@ export const getHandler = async ({ ctx, input }: GetOptions) => {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
// disabling utc casting while fetching WorkingHours
|
||||
return {
|
||||
id: schedule.id,
|
||||
name: schedule.name,
|
||||
isManaged: schedule.userId !== user.id,
|
||||
workingHours: getWorkingHours({ timeZone: schedule.timeZone || undefined }, schedule.availability || []),
|
||||
workingHours: getWorkingHours(
|
||||
{ timeZone: schedule.timeZone || undefined, utcOffset: 0 },
|
||||
schedule.availability || []
|
||||
),
|
||||
schedule: schedule.availability,
|
||||
availability: convertScheduleToAvailability(schedule).map((a) =>
|
||||
a.map((startAndEnd) => ({
|
||||
|
||||
@@ -45,6 +45,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
// eslint-disable-next-line
|
||||
teamId,
|
||||
bookingFields,
|
||||
offsetStart,
|
||||
...rest
|
||||
} = input;
|
||||
|
||||
@@ -102,6 +103,13 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
data.durationLimits = durationLimits;
|
||||
}
|
||||
|
||||
if (offsetStart !== undefined) {
|
||||
if (offsetStart < 0) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Offset start time must be zero or greater." });
|
||||
}
|
||||
data.offsetStart = offsetStart;
|
||||
}
|
||||
|
||||
if (schedule) {
|
||||
// Check that the schedule belongs to the user
|
||||
const userScheduleQuery = await ctx.prisma.schedule.findFirst({
|
||||
|
||||
@@ -21,7 +21,7 @@ interface ReserveSlotOptions {
|
||||
export const reserveSlotHandler = async ({ ctx, input }: ReserveSlotOptions) => {
|
||||
const { prisma, req, res } = ctx;
|
||||
const uid = req?.cookies?.uid || uuid();
|
||||
const { slotUtcStartDate, slotUtcEndDate, eventTypeId, bookingAttendees } = input;
|
||||
const { slotUtcStartDate, slotUtcEndDate, eventTypeId, bookingUid } = input;
|
||||
const releaseAt = dayjs.utc().add(parseInt(MINUTES_TO_BOOK), "minutes").format();
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
@@ -40,8 +40,13 @@ export const reserveSlotHandler = async ({ ctx, input }: ReserveSlotOptions) =>
|
||||
// If this is a seated event then don't reserve a slot
|
||||
if (eventType.seatsPerTimeSlot) {
|
||||
// Check to see if this is the last attendee
|
||||
if (bookingAttendees) {
|
||||
const seatsLeft = eventType.seatsPerTimeSlot - bookingAttendees;
|
||||
const bookingWithAttendees = await prisma.booking.findFirst({
|
||||
where: { uid: bookingUid },
|
||||
select: { attendees: true },
|
||||
});
|
||||
const bookingAttendeesLength = bookingWithAttendees?.attendees?.length;
|
||||
if (bookingAttendeesLength) {
|
||||
const seatsLeft = eventType.seatsPerTimeSlot - bookingAttendeesLength;
|
||||
if (seatsLeft < 1) shouldReserveSlot = false;
|
||||
} else {
|
||||
// If there is no booking yet then don't reserve the slot
|
||||
|
||||
@@ -33,7 +33,7 @@ export const reserveSlotSchema = z
|
||||
slotUtcStartDate: z.string(),
|
||||
// endTime ISOString
|
||||
slotUtcEndDate: z.string(),
|
||||
bookingAttendees: z.number().optional(),
|
||||
bookingUid: z.string().optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => !!data.eventTypeId || !!data.slotUtcStartDate || !!data.slotUtcEndDate,
|
||||
|
||||
@@ -80,6 +80,7 @@ export async function getEventType(input: TGetScheduleInputSchema) {
|
||||
slug: true,
|
||||
minimumBookingNotice: true,
|
||||
length: true,
|
||||
offsetStart: true,
|
||||
seatsPerTimeSlot: true,
|
||||
timeZone: true,
|
||||
slotInterval: true,
|
||||
@@ -281,6 +282,7 @@ export async function getSchedule(input: TGetScheduleInputSchema) {
|
||||
workingHours,
|
||||
dateOverrides,
|
||||
minimumBookingNotice: eventType.minimumBookingNotice,
|
||||
offsetStart: eventType.offsetStart,
|
||||
frequency: eventType.slotInterval || input.duration || eventType.length,
|
||||
organizerTimeZone:
|
||||
eventType.timeZone || eventType?.schedule?.timeZone || userAvailability?.[0]?.timeZone,
|
||||
|
||||
@@ -135,7 +135,6 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) =
|
||||
inviteTeamOptions.isCalcomMember = false;
|
||||
}
|
||||
|
||||
|
||||
await sendTeamInviteEmail({
|
||||
language: translation,
|
||||
from: ctx.user.name,
|
||||
|
||||
@@ -10,12 +10,14 @@ import {
|
||||
scheduleSMSReminder,
|
||||
} from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
|
||||
import { IS_SELF_HOSTED, SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
|
||||
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
||||
import type { PrismaClient } from "@calcom/prisma/client";
|
||||
import { BookingStatus, WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { hasTeamPlanHandler } from "../teams/hasTeamPlan.handler";
|
||||
import type { TUpdateInputSchema } from "./update.schema";
|
||||
import {
|
||||
getSender,
|
||||
@@ -64,6 +66,15 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
const isCurrentUsernamePremium = user && user.metadata && hasKeyInMetadata(user, "isPremium");
|
||||
|
||||
let isTeamsPlan = false;
|
||||
if (!isCurrentUsernamePremium) {
|
||||
const { hasTeamPlan } = await hasTeamPlanHandler({ ctx });
|
||||
isTeamsPlan = !!hasTeamPlan;
|
||||
}
|
||||
const hasPaidPlan = IS_SELF_HOSTED || isCurrentUsernamePremium || isTeamsPlan;
|
||||
|
||||
const activeOnEventTypes = await ctx.prisma.eventType.findMany({
|
||||
where: {
|
||||
id: {
|
||||
@@ -377,13 +388,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
|
||||
//step was edited
|
||||
} else if (JSON.stringify(oldStep) !== JSON.stringify(newStep)) {
|
||||
if (
|
||||
!userWorkflow.teamId &&
|
||||
!userWorkflow.user?.teams.length &&
|
||||
!isSMSAction(oldStep.action) &&
|
||||
isSMSAction(newStep.action) &&
|
||||
!IS_SELF_HOSTED
|
||||
) {
|
||||
if (!hasPaidPlan && !isSMSAction(oldStep.action) && isSMSAction(newStep.action)) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
await ctx.prisma.workflowStep.update({
|
||||
@@ -536,7 +541,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
//added steps
|
||||
const addedSteps = steps.map((s) => {
|
||||
if (s.id <= 0) {
|
||||
if (!userWorkflow.user?.teams.length && isSMSAction(s.action) && !IS_SELF_HOSTED) {
|
||||
if (isSMSAction(s.action) && !hasPaidPlan) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
const { id: _stepId, ...stepToAdd } = s;
|
||||
|
||||
@@ -126,6 +126,9 @@ export interface App {
|
||||
/** only required for "usage-based" billing. % of commission for paid bookings */
|
||||
commission?: number;
|
||||
licenseRequired?: boolean;
|
||||
teamsPlanRequired?: {
|
||||
upgradeUrl: string;
|
||||
};
|
||||
isProOnly?: boolean;
|
||||
appData?: AppData;
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { CheckCircleIcon, ExclamationIcon, InformationCircleIcon, XCircleIcon } from "@heroicons/react/solid";
|
||||
import classNames from "classnames";
|
||||
import type { ReactNode } from "react";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
import { Info } from "../icon";
|
||||
import { CheckCircle2, Info, XCircle, AlertTriangle } from "@calcom/ui/components/icon";
|
||||
|
||||
export interface AlertProps {
|
||||
title?: ReactNode;
|
||||
@@ -34,25 +33,34 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
|
||||
<div className="relative flex flex-col md:flex-row">
|
||||
<div className="flex-shrink-0">
|
||||
{severity === "error" && (
|
||||
<XCircleIcon className={classNames("h-5 w-5 text-red-400", iconClassName)} aria-hidden="true" />
|
||||
<XCircle
|
||||
className={classNames("h-5 w-5 fill-red-400 text-white", iconClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{severity === "warning" && (
|
||||
<ExclamationIcon
|
||||
className={classNames("h-5 w-5 text-yellow-400", iconClassName)}
|
||||
<AlertTriangle
|
||||
className={classNames("h-5 w-5 fill-yellow-400 text-white", iconClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{severity === "info" && (
|
||||
<InformationCircleIcon
|
||||
className={classNames("h-5 w-5 text-sky-400", iconClassName)}
|
||||
<Info
|
||||
className={classNames("h-5 w-5 fill-sky-400 text-white", iconClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{severity === "neutral" && (
|
||||
<Info className={classNames("text-default h-5 w-5", iconClassName)} aria-hidden="true" />
|
||||
<Info
|
||||
className={classNames("text-default h-5 w-5 fill-transparent", iconClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{severity === "success" && (
|
||||
<CheckCircleIcon className={classNames("text-muted h-5 w-5", iconClassName)} aria-hidden="true" />
|
||||
<CheckCircle2
|
||||
className={classNames("fill-muted h-5 w-5 text-white", iconClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-3 flex-grow">
|
||||
|
||||
@@ -97,6 +97,7 @@ export function AppCard({ app, credentials, searchText }: AppCardProps) {
|
||||
<InstallAppButton
|
||||
type={app.type}
|
||||
isProOnly={app.isProOnly}
|
||||
teamsPlanRequired={app.teamsPlanRequired}
|
||||
disableInstall={!!app.dependencies && !app.dependencyData?.some((data) => !data.installed)}
|
||||
wrapperClassName="[@media(max-width:260px)]:w-full"
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
@@ -127,6 +128,7 @@ export function AppCard({ app, credentials, searchText }: AppCardProps) {
|
||||
isProOnly={app.isProOnly}
|
||||
wrapperClassName="[@media(max-width:260px)]:w-full"
|
||||
disableInstall={!!app.dependencies && app.dependencyData?.some((data) => !data.installed)}
|
||||
teamsPlanRequired={app.teamsPlanRequired}
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
if (useDefaultComponent) {
|
||||
props = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
.react-calendar {
|
||||
width: 350px;
|
||||
max-width: 100%;
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: var(--cal-bg);
|
||||
border: 1px solid var(--cal-bg-inverted);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
line-height: 1.125em;
|
||||
}
|
||||
@@ -52,13 +52,13 @@
|
||||
}
|
||||
|
||||
.react-calendar__navigation button:disabled {
|
||||
background-color: #f0f0f0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.react-calendar__navigation button:enabled:hover,
|
||||
.react-calendar__navigation button:enabled:focus {
|
||||
background-color: #e5e7eb;
|
||||
color: #111827;
|
||||
background-color: #1d5ad8;
|
||||
color: var(--cal-text-emphasis);
|
||||
}
|
||||
|
||||
.react-calendar__tile {
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
.react-calendar__tile:enabled:hover,
|
||||
.react-calendar__tile:enabled:focus {
|
||||
background-color: #e6e6e6;
|
||||
background-color: var(--cal-bg-subtle);
|
||||
}
|
||||
|
||||
.react-daterange-picker > .react-daterange-picker__wrapper {
|
||||
@@ -134,7 +134,7 @@
|
||||
|
||||
.react-daterange-picker > .react-daterange-picker__wrapper:focus-within,
|
||||
.react-daterange-picker > .react-daterange-picker__wrapper:focus-within:hover {
|
||||
border-color: #111827;
|
||||
border-color: var(--cal-bg-subtle);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
display: flex;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
color: #111827;
|
||||
color: var(--cal-text);
|
||||
order: -9999;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
@@ -206,26 +206,26 @@ button.react-calendar__tile.react-calendar__month-view__days__day {
|
||||
}
|
||||
|
||||
.react-calendar__tile.react-calendar__tile--hover:not(.react-calendar__tile--active) {
|
||||
background-color: #e5e7eb !important;
|
||||
background-color: var(--cal-bg-subtle) !important;
|
||||
}
|
||||
|
||||
.react-calendar__month-view__days > .react-calendar__tile.react-calendar__tile--hasActive,
|
||||
.react-calendar__month-view__days > .react-calendar__tile.react-calendar__tile--active,
|
||||
.react-calendar__month-view__days > button.react-calendar__tile.react-calendar__tile--active:hover {
|
||||
background-color: #111827;
|
||||
color: #ffffff;
|
||||
background-color: var(--cal-bg-inverted);
|
||||
color: var(--cal-text-inverted);
|
||||
}
|
||||
|
||||
.react-calendar__tile.react-calendar__tile--active.react-calendar__month-view__days__day--weekend {
|
||||
color: #ffffff;
|
||||
color: var(--cal-text-inverted);
|
||||
}
|
||||
|
||||
.react-calendar__tile.react-calendar__month-view__days__day--weekend {
|
||||
color: #111827;
|
||||
color: var(--cal-text-emphasis);
|
||||
}
|
||||
|
||||
.react-calendar__tile.react-calendar__month-view__days__day--neighboringMonth {
|
||||
color: #9ca3af;
|
||||
color: var(--cal-text-muted);
|
||||
}
|
||||
|
||||
.react-calendar__tile--now::before {
|
||||
@@ -234,7 +234,7 @@ button.react-calendar__tile.react-calendar__month-view__days__day {
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
background-color: #111827;
|
||||
background-color: var(--cal-text-emphasis);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 0;
|
||||
@@ -243,19 +243,19 @@ button.react-calendar__tile.react-calendar__month-view__days__day {
|
||||
|
||||
button.react-calendar__tile.react-calendar__month-view__days__day:hover,
|
||||
.react-calendar__tile.react-calendar__year-view__months__month:hover {
|
||||
background-color: #d1d5db;
|
||||
color: #111827;
|
||||
background-color: var(--cal-bg-subtle);
|
||||
color: var(--cal-text-emphasis);
|
||||
}
|
||||
|
||||
.react-daterange-picker > .react-daterange-picker__wrapper:hover {
|
||||
border-color: #9ca3af;
|
||||
border-color: var(--cal-bg-subtle);
|
||||
}
|
||||
|
||||
.react-daterange-picker.react-daterange-picker--disabled > .react-daterange-picker__wrapper:hover {
|
||||
border-color: #e5e7eb;
|
||||
border-color: var(--cal-bg-subtle);
|
||||
}
|
||||
|
||||
.react-calendar__navigation button.react-calendar__navigation__label:enabled:hover,
|
||||
.react-calendar__navigation button.react-calendar__navigation__label:enabled:focus {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CheckCircleIcon } from "@heroicons/react/outline";
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import Link from "next/link";
|
||||
import type { ComponentProps } from "react";
|
||||
@@ -6,6 +5,7 @@ import { forwardRef } from "react";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import type { SVGComponent } from "@calcom/types/SVGComponent";
|
||||
import { CheckCircle } from "@calcom/ui/components/icon";
|
||||
|
||||
import type { ButtonColor } from "../../button/Button";
|
||||
|
||||
@@ -76,7 +76,7 @@ export const DropdownMenuCheckboxItem = forwardRef<HTMLDivElement, DropdownMenuC
|
||||
<DropdownMenuPrimitive.CheckboxItem {...props} ref={forwardedRef}>
|
||||
{children}
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckCircleIcon />
|
||||
<CheckCircle />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
@@ -93,7 +93,7 @@ export const DropdownMenuRadioItem = forwardRef<HTMLDivElement, DropdownMenuRadi
|
||||
<DropdownMenuPrimitive.RadioItem {...props} ref={forwardedRef}>
|
||||
{children}
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckCircleIcon />
|
||||
<CheckCircle />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
);
|
||||
|
||||
@@ -129,7 +129,7 @@ export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function
|
||||
{addOnLeading || addOnSuffix ? (
|
||||
<div
|
||||
dir="ltr"
|
||||
className="group relative mb-1 flex items-center rounded-md focus-within:outline-none focus-within:ring-2 focus-within:ring-neutral-800 focus-within:ring-offset-1">
|
||||
className="group relative mb-1 flex max-w-max items-center rounded-md focus-within:outline-none focus-within:ring-2 focus-within:ring-neutral-800 focus-within:ring-offset-1">
|
||||
{addOnLeading && (
|
||||
<Addon isFilled={addOnFilled} className={classNames("rounded-l-md border-r-0", addOnClassname)}>
|
||||
{addOnLeading}
|
||||
|
||||
@@ -54,11 +54,11 @@ export const Select = <
|
||||
"bg-default border-default !min-h-9 h-9 text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:ring-2 focus-within:ring-emphasis hover:border-emphasis rounded-md border ",
|
||||
state.isMulti
|
||||
? variant === "checkbox"
|
||||
? "px-3 py-2"
|
||||
? "px-3 py-2 h-fit"
|
||||
: state.hasValue
|
||||
? "p-1"
|
||||
: "px-3 py-2"
|
||||
: "py-2 px-3",
|
||||
? "p-1 h-fit"
|
||||
: "px-3 py-2 h-fit"
|
||||
: "py-2 px-3 h-fit",
|
||||
props.isDisabled && "bg-subtle",
|
||||
props.classNames?.control
|
||||
),
|
||||
|
||||