Compare commits

..
18 Commits
Author SHA1 Message Date
Leo Giovanetti 5714471b9a Temporary fix (#6626)
release.yml / release (push) Canceled after 0s
2023-01-22 17:27:27 +00:00
Mohammed CherfaouiandAlex van Andel a99e93a4bd fix tooltip and modal stacking issues (#6491)
* fix tooltip and modal stacking issues

* use z-index in larger screens and less

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-22 20:31:25 +05:30
Udit Takkar 7f044ff92f fix: use items-center (#6618) 2023-01-22 13:36:07 +01:00
Alex van Andel 17a0a61313 v2.5.3
release.yml / release (push) Canceled after 0s
2023-01-22 00:06:41 +00:00
Alex van Andel 12b7739c9a Bugfix/dropdown menu trigger as child remove class names (#6614)
* Fix UsernameTextfield to take right height

* Remove className side-effect

* Incorrect resolution version fixed

* Converted mobile DropdownMenuTrigger styles into Button
2023-01-21 17:03:59 -07:00
Alex van Andel ff283a25d2 Reduce the avatar size to 'sm' for now 2023-01-21 22:09:32 +00:00
sean-brydon 4b343f20a4 Update booking filters design (#6543)
* Update booking filters

* Add filter on YOUR bookings

* Fix pending members showing up in list
2023-01-21 18:15:59 +01:00
Udit Takkar 5bb6904ffd feat: add LocationSelect component (#6571)
* feat: add LocationSelect component

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* fix: type error

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* chore: type error

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-21 16:52:21 +00:00
sean-brydon 0e04f5d338 Fix FAB (#6611) 2023-01-21 16:14:08 +00:00
Nafees Nazik da0ea5d9d0 fix: case-sensitivity in apps path (#6552)
* fix: lowercase slug

* fix: make fallback blocking
2023-01-21 16:13:55 +00:00
Peer Richelsen 1a19261b8f remove 1 on 1 icon (#6609)
* removed 1-on-1 badge

* changed user to users for group events
2023-01-21 16:07:45 +00:00
Luis Cadillo 55a6c65db6 fix: remove annoying outline when <Button /> clicked (#6537)
* fix: remove annoying outline when <Button /> clicked

* Delete yarn.lock
2023-01-21 16:07:30 +00:00
Alex van Andel c9ed3388ae Regenerated lockfile without upgrade (#6610) 2023-01-21 15:49:35 +00:00
2588b7ef84 Harsh/add member invite (#6598)
Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <harsh.singh@gocomet.com>
2023-01-21 16:32:00 +01:00
7ce83542e2 [CAL-811] Avatar icon not redirecting user back to the main page (#6586)
* Remove cursor-pointer, remove old Avatar* files

* Fixed styling for checkedSelect + some cleanup

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-21 11:41:49 +00:00
Alex van Andel 0bb0883e64 Fix string equality check in git-setup.sh 2023-01-21 06:24:29 +00:00
zomars d3be62fbdd Yarn lock fixes 2023-01-20 16:28:22 -07:00
zomars 736308e43a Test fixes 2023-01-20 15:10:09 -07:00
69 changed files with 362 additions and 556 deletions
@@ -123,7 +123,8 @@ const BookingDescription: FC<Props> = (props) => {
<div
className={classNames(
"flex flex-nowrap text-sm font-medium",
isBookingPage && "dark:text-darkgray-600 text-gray-600"
isBookingPage && "dark:text-darkgray-600 text-gray-600",
!eventType.metadata?.multipleDuration && "items-center"
)}>
<Icon.FiClock
className={classNames(
@@ -3,7 +3,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { components } from "react-select";
import { z } from "zod";
import {
@@ -14,32 +13,25 @@ import {
LocationObject,
LocationType,
} from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Form, Icon, PhoneInput } from "@calcom/ui";
import { Button, Dialog, DialogContent, DialogFooter, Form, Icon, PhoneInput } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import CheckboxField from "@components/ui/form/CheckboxField";
import Select from "@components/ui/form/Select";
import LocationSelect, { LocationOption } from "@components/ui/form/LocationSelect";
type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number];
type OptionTypeBase = {
label: string;
value: EventLocationType["type"];
disabled?: boolean;
};
interface ISetLocationDialog {
saveLocation: (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => void;
selection?: OptionTypeBase;
selection?: LocationOption;
booking?: BookingItem;
defaultValues?: LocationObject[];
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
isOpenDialog: boolean;
setSelectedLocation?: (param: OptionTypeBase | undefined) => void;
setSelectedLocation?: (param: LocationOption | undefined) => void;
setEditingLocationType?: (param: string) => void;
}
@@ -211,7 +203,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
})();
return (
<Dialog open={isOpenDialog}>
<Dialog open={isOpenDialog} onOpenChange={(open) => setShowLocationModal(open)}>
<DialogContent disableOverflow>
<div className="flex flex-row space-x-3">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
@@ -296,38 +288,11 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select<{ label: string; value: string; icon?: string }>
<LocationSelect
maxMenuHeight={300}
name="location"
defaultValue={selection}
options={locationOptions}
components={{
Option: (props) => (
<components.Option {...props}>
<div className="flex items-center gap-3">
{props.data.icon && (
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" />
)}
<span
className={classNames(
"text-sm font-medium",
props.isSelected ? "text-white" : "text-gray-900"
)}>
{props.data.label}
</span>
</div>
</components.Option>
),
}}
formatOptionLabel={(e) => (
<div className="flex items-center gap-3">
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => (
<p className="text-xs font-medium text-gray-600">{e.label}</p>
)}
isSearchable
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
onChange={(val) => {
+23 -10
View File
@@ -17,11 +17,21 @@ import { Button, Icon, Label, Select, SettingsToggle, Skeleton, TextField } from
import { slugify } from "@lib/slugify";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import LocationSelect, {
SingleValueLocationOption,
LocationOption,
} from "@components/ui/form/LocationSelect";
type OptionTypeBase = {
label: string;
value: EventLocationType["type"];
disabled?: boolean;
const getLocationFromType = (
type: EventLocationType["type"],
locationOptions: Pick<EventTypeSetupProps, "locationOptions">["locationOptions"]
) => {
for (const locationOption of locationOptions) {
const option = locationOption.options.find((option) => option.value === type);
if (option) {
return option;
}
}
};
export const EventSetupTab = (
@@ -32,7 +42,7 @@ export const EventSetupTab = (
const { eventType, locationOptions, team } = props;
const [showLocationModal, setShowLocationModal] = useState(false);
const [editingLocationType, setEditingLocationType] = useState<string>("");
const [selectedLocation, setSelectedLocation] = useState<OptionTypeBase | undefined>(undefined);
const [selectedLocation, setSelectedLocation] = useState<LocationOption | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration);
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({
@@ -51,7 +61,8 @@ export const EventSetupTab = (
);
const openLocationModal = (type: EventLocationType["type"]) => {
setSelectedLocation(locationOptions.find((option) => option.value === type));
const option = getLocationFromType(type, locationOptions);
setSelectedLocation(option);
setShowLocationModal(true);
};
@@ -131,14 +142,14 @@ export const EventSetupTab = (
<div className="w-full">
{validLocations.length === 0 && (
<div className="flex">
<Select
<LocationSelect
placeholder={t("select")}
options={locationOptions}
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
onChange={(e) => {
onChange={(e: SingleValueLocationOption) => {
if (e?.value) {
const newLocationType: EventLocationType["type"] = e.value;
const newLocationType = e.value;
const eventLocationType = getEventLocationType(newLocationType);
if (!eventLocationType) {
return;
@@ -362,7 +373,9 @@ export const EventSetupTab = (
saveLocation={saveLocation}
defaultValues={formMethods.getValues("locations")}
selection={
selectedLocation ? { value: selectedLocation.value, label: t(selectedLocation.label) } : undefined
selectedLocation
? { value: selectedLocation.value, label: t(selectedLocation.label), icon: selectedLocation.icon }
: undefined
}
setSelectedLocation={setSelectedLocation}
setEditingLocationType={setEditingLocationType}
@@ -245,8 +245,13 @@ function EventTypeSingleLayout({
<VerticalDivider className="hidden lg:block" />
<Dropdown>
<DropdownMenuTrigger className="block h-9 w-9 justify-center rounded-md border border-gray-200 bg-transparent text-gray-700 lg:hidden">
<Icon.FiMoreHorizontal className="group-hover:text-gray-800" />
<DropdownMenuTrigger asChild>
<Button
className="lg:hidden"
StartIcon={Icon.FiMoreHorizontal}
variant="icon"
color="secondary"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem className="focus:ring-gray-100">
-51
View File
@@ -1,51 +0,0 @@
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import * as Tooltip from "@radix-ui/react-tooltip";
import { Maybe } from "@calcom/trpc/server";
import classNames from "@lib/classNames";
import { defaultAvatarSrc } from "@lib/profile";
export type AvatarProps = {
className?: string;
size?: number;
imageSrc?: Maybe<string>;
title?: string;
alt: string;
gravatarFallbackMd5?: string;
};
/**
* @deprecated Use AvatarSSR instead. Once, there is no usage of Avatar, AvatarSSR can be renamed.
*/
export default function Avatar(props: AvatarProps) {
const { imageSrc, gravatarFallbackMd5, size, alt, title } = props;
const className = classNames("rounded-full", props.className, size && `h-${size} w-${size}`);
const avatar = (
<AvatarPrimitive.Root>
<AvatarPrimitive.Image
src={imageSrc ?? undefined}
alt={alt}
className={classNames("rounded-full", `h-auto w-${size}`, props.className)}
/>
<AvatarPrimitive.Fallback delayMs={600}>
{gravatarFallbackMd5 && (
// eslint-disable-next-line @next/next/no-img-element
<img src={defaultAvatarSrc({ md5: gravatarFallbackMd5 })} alt={alt} className={className} />
)}
</AvatarPrimitive.Fallback>
</AvatarPrimitive.Root>
);
return title ? (
<Tooltip.Tooltip delayDuration={300}>
<Tooltip.TooltipTrigger className="cursor-default">{avatar}</Tooltip.TooltipTrigger>
<Tooltip.Content className="rounded-sm bg-black p-2 text-sm text-white">
<Tooltip.Arrow />
{title}
</Tooltip.Content>
</Tooltip.Tooltip>
) : (
<>{avatar}</>
);
}
-47
View File
@@ -1,47 +0,0 @@
import Link from "next/link";
import React from "react";
import classNames from "@lib/classNames";
import { AvatarSSR } from "@components/ui/AvatarSSR";
export type AvatarGroupProps = {
border?: string; // this needs to be the color of the parent container background, i.e.: border-white dark:border-gray-900
size: number;
truncateAfter?: number;
items: {
image: string;
title?: string;
alt?: string;
href?: string;
}[];
className?: string;
};
export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
return (
<ul className={classNames(props.className)}>
{props.items.slice(0, props.truncateAfter).map((item, idx) => {
if (item.image != null) {
const avatar = (
<AvatarSSR
className={props.border}
imageSrc={item.image}
title={item.title}
alt={item.alt || ""}
size={props.size}
/>
);
return (
<li key={idx} className="-ltr:mr-2 inline-block rtl:ml-2">
{item.href ? <Link href={item.href}>{avatar}</Link> : avatar}
</li>
);
}
})}
</ul>
);
};
export default AvatarGroup;
-62
View File
@@ -1,62 +0,0 @@
import { User } from "@prisma/client";
import * as Tooltip from "@radix-ui/react-tooltip";
import classNames from "@lib/classNames";
export type AvatarProps = (
| {
user: Pick<User, "name" | "username" | "avatar"> & { emailMd5?: string };
}
| {
user?: null;
imageSrc: string;
}
) & {
className?: string;
size?: number;
title?: string;
href?: string;
alt: string;
};
// defaultAvatarSrc from profile.tsx can't be used as it imports crypto
function defaultAvatarSrc(md5: string) {
return `https://www.gravatar.com/avatar/${md5}?s=160&d=mp&r=PG`;
}
// An SSR Supported version of Avatar component.
export function AvatarSSR(props: AvatarProps) {
const { size, title } = props;
let imgSrc = "";
let alt: string = props.alt;
if (props.user) {
const user = props.user;
const nameOrUsername = user.name || user.username || "";
alt = alt || nameOrUsername;
if (user.avatar) {
imgSrc = user.avatar;
} else if (user.emailMd5) {
imgSrc = defaultAvatarSrc(user.emailMd5);
}
} else {
imgSrc = props.imageSrc;
}
const className = classNames("rounded-full", props.className, size && `h-${size} w-${size}`);
const avatar = imgSrc ? <img alt={alt} className={className} src={imgSrc} /> : null;
return title ? (
<Tooltip.Tooltip delayDuration={300}>
<Tooltip.TooltipTrigger asChild>{avatar}</Tooltip.TooltipTrigger>
<Tooltip.Content className="rounded-sm bg-black p-2 text-sm text-white">
<Tooltip.Arrow />
{title}
</Tooltip.Content>
</Tooltip.Tooltip>
) : (
<>{avatar}</>
);
}
@@ -131,7 +131,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
autoCapitalize="none"
autoCorrect="none"
className={classNames(
"mb-0 mt-0 h-6 rounded-md ltr:rounded-l-none rtl:rounded-r-none",
"mb-0 mt-0 rounded-md ltr:rounded-l-none rtl:rounded-r-none",
markAsError
? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none focus:ring-0"
: ""
@@ -2,9 +2,8 @@ import React from "react";
import { Props } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { Icon, Avatar } from "@calcom/ui";
import Avatar from "@components/ui/Avatar";
import Select from "@components/ui/form/Select";
type CheckedSelectOption = {
@@ -43,7 +42,8 @@ export const CheckedSelect = ({
{value.map((option) => (
<div key={option.value} className="border p-2 font-medium">
<Avatar
className="inline h-6 w-6 rounded-full ltr:mr-2 rtl:ml-2"
className="inline ltr:mr-2 rtl:ml-2"
size="sm"
imageSrc={option.avatar}
alt={option.label}
/>
@@ -0,0 +1,64 @@
import { components, GroupBase, Props, SingleValue } from "react-select";
import type { EventLocationType } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import Select from "@components/ui/form/Select";
export type LocationOption = {
label: string;
value: EventLocationType["type"];
icon?: string;
disabled?: boolean;
};
export type SingleValueLocationOption = SingleValue<LocationOption>;
export type GroupOptionType = GroupBase<LocationOption>;
const OptionWithIcon = ({
icon,
isSelected,
label,
}: {
icon?: string;
isSelected?: boolean;
label: string;
}) => {
return (
<div className="flex items-center gap-3">
{icon && <img src={icon} alt="cover" className="h-3.5 w-3.5" />}
<span className={classNames("text-sm font-medium", isSelected ? "text-white" : "text-gray-900")}>
{label}
</span>
</div>
);
};
export default function LocationSelect(props: Props<LocationOption, false, GroupOptionType>) {
return (
<Select<LocationOption>
name="location"
components={{
Option: (props) => (
<components.Option {...props}>
<OptionWithIcon icon={props.data.icon} label={props.data.label} isSelected={props.isSelected} />
</components.Option>
),
SingleValue: (props) => (
<components.SingleValue {...props}>
<OptionWithIcon icon={props.data.icon} label={props.data.label} />
</components.SingleValue>
),
}}
formatOptionLabel={(e) => (
<div className="flex items-center gap-3">
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => <p className="text-xs font-medium text-gray-600">{e.label}</p>}
{...props}
/>
);
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.5.2",
"version": "2.5.3",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
+2 -4
View File
@@ -26,13 +26,11 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
import prisma from "@calcom/prisma";
import { baseEventTypeSelect } from "@calcom/prisma/selects";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Icon, HeadSeo, AvatarGroup } from "@calcom/ui";
import { Icon, HeadSeo, AvatarGroup, Avatar } from "@calcom/ui";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import { AvatarSSR } from "@components/ui/AvatarSSR";
import { ssrInit } from "@server/lib/ssr";
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
@@ -132,7 +130,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
)}>
{isSingleUser && ( // When we deal with a single user, not dynamic group
<div className="mb-8 text-center">
<AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername} />
<Avatar imageSrc={user.avatar} size="xl" alt={nameOrUsername} />
<h1 className="font-cal mb-1 text-3xl text-gray-900 dark:text-white">
{nameOrUsername}
{user.verified && (
+2 -2
View File
@@ -54,7 +54,7 @@ export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
return {
paths,
fallback: false,
fallback: "blocking",
};
};
@@ -62,7 +62,7 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (typeof ctx.params?.slug !== "string") return { notFound: true };
const app = await prisma.app.findUnique({
where: { slug: ctx.params.slug },
where: { slug: ctx.params.slug.toLowerCase() },
});
if (!app) return { notFound: true };
@@ -426,7 +426,6 @@
"password_hint_num": "يحتوي على رقم واحد على الأقل",
"invalid_password_hint": "يجب أن تتألف كلمة المرور على الأقل من 7 أحرف، ورقم واحد على الأقل، ومزيج من الحروف الكبيرة والصغيرة",
"incorrect_password": "كلمة المرور غير صحيحة.",
"1_on_1": "اجتماع 1 مع 1",
"24_h": "24 ساعة",
"use_setting": "استخدام الإعداد",
"am_pm": "صباحًا/مساءً",
@@ -426,7 +426,6 @@
"password_hint_num": "Obsahuje alespoň 1 číslo",
"invalid_password_hint": "Heslo musí být minimálně 7 znaků dlouhé, obsahovat alespoň jedno číslo a obsahovat kombinaci velkých a malých písmen",
"incorrect_password": "Heslo není správné.",
"1_on_1": "Jeden na jednoho",
"24_h": "24h",
"use_setting": "Použít nastavení",
"am_pm": "am/pm",
@@ -425,7 +425,6 @@
"password_hint_num": "Enthält mindestens 1 Zahl",
"invalid_password_hint": "Das Passwort muss mindestens 7 Zeichen lang sein und mindestens eine Nummer beinhalten, sowie eine Mischung aus Groß- und Kleinbuchstaben sein",
"incorrect_password": "Passwort ist falsch",
"1_on_1": "1-on-1",
"24_h": "24 Std",
"use_setting": "Benutze Einstellung",
"am_pm": "am/pm",
@@ -23,6 +23,7 @@
"rejection_reason_description": "Are you sure you want to reject the booking? We'll let the person who tried to book know. You can provide a reason below.",
"rejection_confirmation": "Reject the booking",
"manage_this_event": "Manage this event",
"invite_team_member": "Invite team member",
"your_event_has_been_scheduled": "Your event has been scheduled",
"your_event_has_been_scheduled_recurring": "Your recurring event has been scheduled",
"accept_our_license": "Accept our license by changing the .env variable <1>NEXT_PUBLIC_LICENSE_CONSENT</1> to '{{agree}}'.",
@@ -429,7 +430,6 @@
"password_hint_num": "Contain at least 1 number",
"invalid_password_hint": "The password must be a minimum of 7 characters long containing at least one number and have a mixture of uppercase and lowercase letters",
"incorrect_password": "Password is incorrect.",
"1_on_1": "1-on-1",
"24_h": "24h",
"use_setting": "Use setting",
"am_pm": "am/pm",
@@ -1465,6 +1465,7 @@
"individual":"Individual",
"all_bookings_filter_label":"All Bookings",
"all_users_filter_label":"All Users",
"your_bookings_filter_label":"Your Bookings",
"meeting_url_variable": "Meeting url",
"meeting_url_info": "The event meeting conference url",
"date_overrides": "Date overrides",
@@ -426,7 +426,6 @@
"password_hint_num": "Debe contener al menos 1 número",
"invalid_password_hint": "La contraseña debe tener un mínimo de 7 caracteres que contengan al menos un número y una mezcla de letras mayúsculas y minúsculas",
"incorrect_password": "La Contraseña es Incorrecta.",
"1_on_1": "1 a 1",
"24_h": "24hs",
"use_setting": "Usar Ajuste",
"am_pm": "am/pm",
@@ -429,7 +429,6 @@
"password_hint_num": "Contient au moins 1 chiffre",
"invalid_password_hint": "Le mot de passe doit contenir au moins 7 caractères, un chiffre et un mélange de lettres, majuscules et minuscules.",
"incorrect_password": "Le mot de passe est incorrect.",
"1_on_1": "tête-à-tête",
"24_h": "24 h",
"use_setting": "Utiliser le paramètre",
"am_pm": "AM/PM",
@@ -426,7 +426,6 @@
"password_hint_num": "לכלול לפחות ספרה אחת",
"invalid_password_hint": "הסיסמה חייבת להיות באורך של 7 תווים לפחות ולכלול לפחות ספרה אחת ושילוב של אותיות רישיות וקטנות",
"incorrect_password": "הסיסמה שגויה.",
"1_on_1": "אחד על אחד",
"24_h": "24 שעות",
"use_setting": "השתמש בהגדרה",
"am_pm": "לפנה\"צ/אחה\"צ",
@@ -426,7 +426,6 @@
"password_hint_num": "Deve contenere almeno 1 numero",
"invalid_password_hint": "La password deve avere una lunghezza minima di 7 caratteri e deve contenere almeno 1 numero e una combinazione di caratteri maiuscoli e minuscoli",
"incorrect_password": "La password non è corretta.",
"1_on_1": "1-su-1",
"24_h": "24 ore",
"use_setting": "Usa impostazione",
"am_pm": "am/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "少なくとも 1 文字は数字を含めてください",
"invalid_password_hint": "パスワードには少なくとも 1 文字以上数字を含め、アルファベットの大文字と小文字を両方使用した上で 7 文字以上の長さに設定してください",
"incorrect_password": "パスワードが正しくありません。",
"1_on_1": "1対1",
"24_h": "24時間",
"use_setting": "設定を使用",
"am_pm": "午前/午後",
@@ -426,7 +426,6 @@
"password_hint_num": "1개 이상 숫자 포함",
"invalid_password_hint": "비밀번호는 7자 이상이어야 하고 숫자가 하나 이상 포함되어야 하며 대문자와 소문자가 혼합되어야 합니다",
"incorrect_password": "비밀번호가 잘못되었습니다",
"1_on_1": "일대일",
"24_h": "24시간",
"use_setting": "사용 설정",
"am_pm": "am/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "Minimaal 1 cijfer bevatten",
"invalid_password_hint": "Het wachtwoord moet minimaal 7 tekens lang zijn, minimaal 1 cijfer bevatten en bestaan uit een mix van hoofd- en kleine letters",
"incorrect_password": "Uw wachtwoord is incorrect.",
"1_on_1": "1 op 1",
"24_h": "24u",
"use_setting": "Instelling gebruiken",
"am_pm": "am/pm",
@@ -429,7 +429,6 @@
"password_hint_num": "Inneholde minst 1 tall",
"invalid_password_hint": "Passordet må være minst 7 tegn langt og inneholde minst ett tall og ha en blanding av store og små bokstaver",
"incorrect_password": "Passordet er feil.",
"1_on_1": "1-til-1",
"24_h": "24t",
"use_setting": "Bruk innstilling",
"am_pm": "am/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "Zawrzyj co najmniej 1 cyfrę",
"invalid_password_hint": "Hasło musi składać się z co najmniej 7 znaków, zawierać co najmniej jedną cyfrę oraz kombinację wielkich i małych liter",
"incorrect_password": "Hasło jest nieprawidłowe.",
"1_on_1": "1-na-1",
"24_h": "24 godz.",
"use_setting": "Użyj ustawień",
"am_pm": "rano/po południu",
@@ -426,7 +426,6 @@
"password_hint_num": "Contém pelo menos um número",
"invalid_password_hint": "A senha precisa ter pelo menos sete caracteres com pelo menos um número e ser uma combinação de letras maiúsculas e minúsculas",
"incorrect_password": "Senha incorreta.",
"1_on_1": "1 em 1",
"24_h": "24h",
"use_setting": "Usar configurações",
"am_pm": "manhã/tarde",
@@ -426,7 +426,6 @@
"password_hint_num": "Contém pelo menos 1 número",
"invalid_password_hint": "A palavra-passe deve ter no mínimo 7 caracteres, com pelo menos um número e uma mistura de letras maiúsculas e minúsculas",
"incorrect_password": "Palavra-Passe incorreta",
"1_on_1": "1 para 1",
"24_h": "24h",
"use_setting": "Usar Configuração",
"am_pm": "am/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "Conține cel puțin 1 cifră",
"invalid_password_hint": "Parola trebuie să aibă cel puțin 7 caractere, să conțină cel puțin o cifră și o combinație de litere mari și litere mici",
"incorrect_password": "Parola este incorectă.",
"1_on_1": "1-la-1",
"24_h": "24h",
"use_setting": "Folosește setarea",
"am_pm": "am/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "Не менее 1 цифры",
"invalid_password_hint": "Пароль должен быть не короче 7 символов и содержать заглавные и строчные буквы, а также не менее одной цифры",
"incorrect_password": "Неверный пароль",
"1_on_1": "1-на-1",
"24_h": "24 часа",
"use_setting": "Использовать настройки",
"am_pm": "am/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "Sadrži barem 1 broj",
"invalid_password_hint": "Lozinka mora da bude dugačka najmanje 7 znakova, da sadrži najmanje jedan broj i da ima mešavinu velikih i malih slova",
"incorrect_password": "Lozinka je netačna.",
"1_on_1": "1 na 1",
"24_h": "24 sata",
"use_setting": "Koristi podešavanje",
"am_pm": "am/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "Innehåller minst 1 siffra",
"invalid_password_hint": "Lösenordet måste innehålla minst 7 tecken och har minst en siffra samt en blandning av versaler och gemener",
"incorrect_password": "Lösenordet är felaktigt.",
"1_on_1": "1 och 1",
"24_h": "24 timmar",
"use_setting": "Använd inställning",
"am_pm": "am/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "En az 1 rakam içermelidir",
"invalid_password_hint": "Şifre en az 7 karakter uzunluğunda olmalı, en az bir rakam içermeli ve büyük ve küçük harflerin karışımından oluşmalıdır",
"incorrect_password": "Şifre hatalı.",
"1_on_1": "Bire bir",
"24_h": "24 sa",
"use_setting": "Ayarı kullan",
"am_pm": "öö/ös",
@@ -426,7 +426,6 @@
"password_hint_num": "Містить принаймні 1 цифру",
"invalid_password_hint": "Пароль має містити не менше ніж 7 символів: принаймні одну цифру та комбінацію великих і малих літер",
"incorrect_password": "Пароль неправильний.",
"1_on_1": "1 на 1",
"24_h": "24 год",
"use_setting": "Використовувати налаштування",
"am_pm": "дп/пп",
@@ -426,7 +426,6 @@
"password_hint_num": "Chứa ít nhất 1 con số",
"invalid_password_hint": "Mật khẩu phải có tối thiểu 7 ký tự chứa ít nhất một con số và phải kết hợp chữ cái in hoa lẫn in thường",
"incorrect_password": "Mật khẩu không đúng.",
"1_on_1": "1 với 1",
"24_h": "24h",
"use_setting": "Sử dụng cài đặt",
"am_pm": "\u001dam/pm",
@@ -426,7 +426,6 @@
"password_hint_num": "包含至少 1 个数字",
"invalid_password_hint": "密码必须是至少 7 个字符长,包含至少一个数字,并且是大写和小写字母混合",
"incorrect_password": "密码不正确。",
"1_on_1": "1对1",
"24_h": "24 小时制",
"use_setting": "使用设置",
"am_pm": "12小时制",
@@ -426,7 +426,6 @@
"password_hint_num": "至少包含 1 個數字",
"invalid_password_hint": "密碼長度至少要有 7 個字元,其中包含至少一個數字和大小寫字母組合",
"incorrect_password": "密碼不正確。",
"1_on_1": "1 對 1",
"24_h": "24 小時",
"use_setting": "使用設定",
"am_pm": "上午/下午",
+1 -1
View File
@@ -25,7 +25,7 @@ for module in "$@"; do
git reset
else
# If the module is the API, display a link to request access
if [ "$module" == "api" ]; then
if [ "$module" = "api" ]; then
echo "You don't have access to: '${module}' module. You can request access in: https://console.cal.com"
else
# If the module is not the API, display normal message
+23 -22
View File
@@ -60,28 +60,29 @@ const config: Config = {
transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"],
testEnvironment: "jsdom",
},
{
displayName: "@calcom/api",
roots: ["<rootDir>/apps/api"],
testMatch: ["**/test/lib/**/*.(spec|test).(ts|tsx|js)"],
setupFilesAfterEnv: ["<rootDir>/tests/config/singleton.ts"],
transform: {
"^.+\\.ts?$": "ts-jest",
},
globals: {
"ts-jest": {
tsconfig: "<rootDir>/apps/api/tsconfig.json",
},
},
transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"],
testEnvironment: "node",
clearMocks: true,
moduleNameMapper: {
"^@lib/(.*)$": "<rootDir>/apps/api/lib/$1",
"^@api/(.*)$": "<rootDir>/apps/api/pages/api/$1",
},
// setupFilesAfterEnv: ["<rootDir>/apps/api/jest.setup.ts"], // Uncomment when API becomes public
},
// FIXME: Prevent this breaking Jest when API module is missing
// {
// displayName: "@calcom/api",
// roots: ["<rootDir>/apps/api"],
// testMatch: ["**/test/lib/**/*.(spec|test).(ts|tsx|js)"],
// setupFilesAfterEnv: ["<rootDir>/tests/config/singleton.ts"],
// transform: {
// "^.+\\.ts?$": "ts-jest",
// },
// globals: {
// "ts-jest": {
// tsconfig: "<rootDir>/apps/api/tsconfig.json",
// },
// },
// transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"],
// testEnvironment: "node",
// clearMocks: true,
// moduleNameMapper: {
// "^@lib/(.*)$": "<rootDir>/apps/api/lib/$1",
// "^@api/(.*)$": "<rootDir>/apps/api/pages/api/$1",
// },
// // setupFilesAfterEnv: ["<rootDir>/apps/api/jest.setup.ts"], // Uncomment when API becomes public
// },
],
watchPlugins: [
"jest-watch-typeahead/filename",
+1
View File
@@ -84,6 +84,7 @@
"turbo": "^1.4.3"
},
"resolutions": {
"@apidevtools/json-schema-ref-parser": "9.0.9",
"@types/node": "16.9.1",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6"
@@ -16,7 +16,11 @@ export const EventTypeAppCard = (props: {
return (
<ErrorBoundary message={`There is some problem with ${app.name} App`}>
<EventTypeAppContext.Provider value={[getAppData, setAppData]}>
<DynamicComponent slug={app.slug} componentMap={EventTypeAddonMap} {...props} />
<DynamicComponent
slug={app.slug === "stripe" ? "stripepayment" : app.slug}
componentMap={EventTypeAddonMap}
{...props}
/>
</EventTypeAppContext.Provider>
</ErrorBoundary>
);
@@ -7,13 +7,11 @@
"logo": "icon.svg",
"url": "https://example.com/link",
"variant": "other",
"categories": [
"other"
],
"categories": ["other"],
"publisher": "Cal.com, Inc.",
"email": "support@cal.com",
"description": "It is a template for an app that can be installed and provides no other feature.",
"isTemplate": true,
"__createdUsingCli": true,
"__template": "link-as-an-app"
}
}
@@ -11,4 +11,4 @@
"@calcom/types": "*"
},
"description": "It is a template for an app that can be installed and provides no other feature."
}
}
@@ -23,6 +23,6 @@
]
}
},
"isTemplate":true,
"isTemplate": true,
"__createdUsingCli": true
}
@@ -11,6 +11,6 @@
"email": "support@cal.com",
"description": "A template showcasing how an app with EventType AppCard can be built e.g. Giphy, QR Code",
"extendsFeature": "EventType",
"isTemplate":true,
"isTemplate": true,
"__createdUsingCli": true
}
@@ -20,6 +20,6 @@
}
},
"description": "It is a template showing how to add a static URL EventType location e.g. Around, Whereby",
"isTemplate":true,
"isTemplate": true,
"__createdUsingCli": true
}
@@ -10,6 +10,6 @@
"publisher": "Cal.com Inc",
"email": "support@cal.com",
"description": "It is a template showing how an App can have settings in installed section that configures the app globally e.g. Weather in your Calendar",
"isTemplate":true,
"isTemplate": true,
"__createdUsingCli": true
}
@@ -11,6 +11,6 @@
"publisher": "Cal.com, Inc.",
"email": "support@cal.com",
"description": "It's a template showing how an app, that is just a link to some webpage, can be made e.g. Pipedream, Amie, Vimcal.",
"isTemplate":true,
"isTemplate": true,
"__createdUsingCli": true
}
+8 -31
View File
@@ -10,6 +10,13 @@ import { EventTypeModel } from "@calcom/prisma/zod";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { App, AppMeta } from "@calcom/types/App";
type LocationOption = {
label: string;
value: EventLocationType["type"];
icon?: string;
disabled?: boolean;
};
export type EventTypeApps = NonNullable<NonNullable<z.infer<typeof EventTypeMetaDataSchema>>["apps"]>;
export type EventTypeAppsList = keyof EventTypeApps;
@@ -48,36 +55,6 @@ export const InstalledAppVariants = [
export const ALL_APPS = Object.values(ALL_APPS_MAP);
type OptionTypeBase = {
label: string;
value: EventLocationType["type"];
disabled?: boolean;
};
function translateLocations(locations: OptionTypeBase[], t: TFunction) {
return locations.map((l) => ({
...l,
label: t(l.label),
}));
}
export function getLocationOptions(integrations: ReturnType<typeof getApps>, t: TFunction) {
const locations: OptionTypeBase[] = [];
defaultLocations.forEach((l) => {
locations.push({
label: l.label,
value: l.type,
});
});
integrations.forEach((app) => {
if (app.locationOption) {
locations.push(app.locationOption);
}
});
return translateLocations(locations, t);
}
export function getLocationGroupedOptions(integrations: ReturnType<typeof getApps>, t: TFunction) {
const apps: Record<string, { label: string; value: string; disabled?: boolean; icon?: string }[]> = {};
integrations.forEach((app) => {
@@ -147,7 +124,7 @@ export function getLocationGroupedOptions(integrations: ReturnType<typeof getApp
function getApps(userCredentials: CredentialData[]) {
const apps = ALL_APPS.map((appMeta) => {
const credentials = userCredentials.filter((credential) => credential.type === appMeta.type);
let locationOption: OptionTypeBase | null = null;
let locationOption: LocationOption | null = null;
/** If the app is a globally installed one, let's inject it's key */
if (appMeta.isGlobal) {
@@ -17,9 +17,9 @@ export const PeopleFilter = () => {
return (
<AnimatedPopover
text={userNames && userNames.length > 0 ? `${userNames.join(", ")}` : t("all_users_filter_label")}>
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100">
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100 hover:cursor-pointer hover:bg-gray-50">
<div className="flex h-6 w-6 items-center justify-center ltr:mr-2 rtl:ml-2">
<Icon.FiUser className="h-full w-full" />
<Icon.FiUser className="h-5 w-5" />
</div>
<label htmlFor="allUsers" className="mr-auto self-center truncate text-sm font-medium text-gray-700">
{t("all_users_filter_label")}
@@ -38,7 +38,9 @@ export const PeopleFilter = () => {
</div>
{data &&
data.map((user) => (
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100" key={`${user.id}`}>
<div
className="item-center flex px-4 py-[6px] focus-within:bg-gray-100 focus-within:bg-gray-100 hover:cursor-pointer hover:bg-gray-50"
key={`${user.id}`}>
<Avatar
imageSrc={user.avatar}
size="sm"
@@ -49,7 +51,7 @@ export const PeopleFilter = () => {
/>
<label
htmlFor={user.name ?? "NamelessUser"}
className="ml-2 mr-auto self-center truncate text-sm font-medium text-gray-700">
className="ml-2 mr-auto self-center truncate text-sm font-medium text-gray-700 hover:cursor-pointer">
{user.name}
</label>
@@ -1,3 +1,5 @@
import { useSession } from "next-auth/react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { AnimatedPopover, Avatar, Icon } from "@calcom/ui";
@@ -6,6 +8,7 @@ import { useFilterQuery } from "../lib/useFilterQuery";
export const TeamsMemberFilter = () => {
const { t } = useLocale();
const session = useSession();
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeByKey } = useFilterQuery();
const { data } = trpc.viewer.teams.list.useQuery();
@@ -17,9 +20,9 @@ export const TeamsMemberFilter = () => {
return (
<AnimatedPopover
text={teamNames && teamNames.length > 0 ? `${teamNames.join(", ")}` : t("all_bookings_filter_label")}>
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100">
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100 hover:cursor-pointer hover:bg-gray-50">
<div className="flex h-6 w-6 items-center justify-center ltr:mr-2 rtl:ml-2">
<Icon.FiLayers className="h-full w-full" />
<Icon.FiLayers className="h-5 w-5" />
</div>
<label
htmlFor="allBookings"
@@ -30,16 +33,43 @@ export const TeamsMemberFilter = () => {
<input
id="allBookings"
type="checkbox"
checked={!query.teamIds}
checked={!query.teamIds && !query.userIds?.includes(session.data?.user.id || 0)}
onChange={() => {
removeByKey("teamIds"); // Always clear on toggle or not toggle (seems weird but when you know the behviour it works well )
}}
className="text-primary-600 focus:ring-primary-500 inline-flex h-4 w-4 place-self-center justify-self-end rounded border-gray-300 "
/>
</div>
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100 hover:cursor-pointer hover:bg-gray-50">
<div className="flex h-6 w-6 items-center justify-center ltr:mr-2 rtl:ml-2">
<Icon.FiUser className="h-5 w-5" />
</div>
<label
htmlFor="yourBookings"
className="mr-auto self-center truncate text-sm font-medium text-gray-700">
{t("your_bookings_filter_label")}
</label>
<input
id="yourBookings"
type="checkbox"
disabled={session.status === "loading"}
checked={query.userIds?.includes(session.data?.user.id || 0)}
onChange={(e) => {
if (e.target.checked) {
pushItemToKey("userIds", session.data?.user.id || 0);
} else if (!e.target.checked) {
removeItemByKeyAndValue("userIds", session.data?.user.id || 0);
}
}}
className="text-primary-600 focus:ring-primary-500 inline-flex h-4 w-4 place-self-center justify-self-end rounded border-gray-300 "
/>
</div>
{data &&
data.map((team) => (
<div className="item-center flex px-4 py-[6px] focus-within:bg-gray-100" key={`${team.id}`}>
<div
className="item-center flex px-4 py-[6px] focus-within:bg-gray-100 hover:cursor-pointer hover:bg-gray-50"
key={`${team.id}`}>
<Avatar
imageSrc={team.logo}
size="sm"
@@ -50,7 +80,7 @@ export const TeamsMemberFilter = () => {
/>
<label
htmlFor={team.name}
className="ml-2 mr-auto self-center truncate text-sm font-medium text-gray-700">
className="ml-2 mr-auto select-none self-center truncate text-sm font-medium text-gray-700 hover:cursor-pointer">
{team.name}
</label>
@@ -30,7 +30,13 @@ const AddNewTeamMembers = () => {
return <AddNewTeamMembersForm defaultValues={{ members: teamQuery.data.members }} teamId={teamId} />;
};
const AddNewTeamMembersForm = ({ defaultValues, teamId }: { defaultValues: FormValues; teamId: number }) => {
export const AddNewTeamMembersForm = ({
defaultValues,
teamId,
}: {
defaultValues: FormValues;
teamId: number;
}) => {
const { t, i18n } = useLocale();
const [memberInviteModal, setMemberInviteModal] = useState(false);
const utils = trpc.useContext();
@@ -149,13 +149,11 @@ export default function MemberListItem(props: Props) {
</Tooltip>
{editMode && (
<Dropdown>
<DropdownMenuTrigger
asChild
className="h-[36px] w-[36px] bg-transparent px-0 py-0 hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0">
<DropdownMenuTrigger asChild>
<Button
className="radix-state-open:rounded-r-md"
color="secondary"
variant="icon"
className="rounded-r-md"
StartIcon={Icon.FiMoreHorizontal}
/>
</DropdownMenuTrigger>
@@ -1,7 +1,9 @@
import { MembershipRole } from "@prisma/client";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal";
import classNames from "@calcom/lib/classNames";
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -36,9 +38,20 @@ interface Props {
}
export default function TeamListItem(props: Props) {
const { t } = useLocale();
const { t, i18n } = useLocale();
const utils = trpc.useContext();
const team = props.team;
const [openMemberInvitationModal, setOpenMemberInvitationModal] = useState(false);
const teamQuery = trpc.viewer.teams.get.useQuery({ teamId: team?.id });
const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation({
async onSuccess() {
await utils.viewer.teams.get.invalidate();
setOpenMemberInvitationModal(false);
},
onError: (error) => {
showToast(error.message, "error");
},
});
const acceptOrLeaveMutation = trpc.viewer.teams.acceptOrLeave.useMutation({
onSuccess: () => {
@@ -82,6 +95,22 @@ export default function TeamListItem(props: Props) {
return (
<li className="divide-y">
<MemberInvitationModal
isOpen={openMemberInvitationModal}
onExit={() => {
setOpenMemberInvitationModal(false);
}}
onSubmit={(values) => {
inviteMemberMutation.mutate({
teamId: team.id,
language: i18n.language,
role: values.role,
usernameOrEmail: values.emailOrUsername,
sendEmailInvitation: values.sendInviteEmail,
});
}}
members={teamQuery?.data?.members || []}
/>
<div
className={classNames(
"flex items-center justify-between",
@@ -156,8 +185,9 @@ export default function TeamListItem(props: Props) {
</Tooltip>
)}
<Dropdown>
<DropdownMenuTrigger asChild className="radix-state-open:rounded-r-md">
<DropdownMenuTrigger asChild>
<Button
className="radix-state-open:rounded-r-md"
type="button"
color="secondary"
variant="icon"
@@ -187,6 +217,16 @@ export default function TeamListItem(props: Props) {
</DropdownItem>
</DropdownMenuItem>
)}
<DropdownMenuItem>
<DropdownItem
type="button"
onClick={() => {
setOpenMemberInvitationModal(true);
}}
StartIcon={Icon.FiSend}>
{t("invite_team_member") as string}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuSeparator />
{isOwner && (
<DropdownMenuItem>
@@ -1,12 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { SchedulingType } from "@prisma/client";
import { isValidPhoneNumber } from "libphonenumber-js";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { z } from "zod";
import classNames from "@calcom/lib/classNames";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
@@ -293,10 +291,7 @@ export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
) : (
<Dropdown>
<DropdownMenuTrigger asChild>
<Button
EndIcon={Icon.FiChevronDown}
variant="fab"
className="radix-state-open:!bg-brand-500 radix-state-open:ring-2 radix-state-open:ring-brand-500 ring-offset-2 focus:border-none">
<Button variant="fab" StartIcon={Icon.FiPlus}>
{t("new")}
</Button>
</DropdownMenuTrigger>
@@ -331,80 +326,3 @@ export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
</>
);
}
type CreateEventTypeTrigger = {
isIndividualTeam?: boolean;
// EventTypeParent can be a profile (as first option) or a team for the rest.
options: EventTypeParent[];
hasTeams: boolean;
// set true for use on the team settings page
canAddEvents: boolean;
openModal: (option: EventTypeParent) => void;
};
export function CreateEventTypeTrigger(props: CreateEventTypeTrigger) {
const { t } = useLocale();
return (
<>
{!props.hasTeams || props.isIndividualTeam ? (
<Button
onClick={() => props.openModal(props.options[0])}
data-testid="new-event-type"
StartIcon={Icon.FiPlus}
disabled={!props.canAddEvents}>
{t("new_event_type_btn")}
</Button>
) : (
<Dropdown>
<DropdownMenuTrigger asChild>
<Button EndIcon={Icon.FiChevronDown}>{t("new_event_type_btn")}</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{t("new_event_subtitle")}</DropdownMenuLabel>
<DropdownMenuSeparator />
{props.options.map((option) => (
<CreateEventTeamsItem
key={option.slug}
option={option}
openModal={() => props.openModal(option)}
/>
))}
</DropdownMenuContent>
</Dropdown>
)}
</>
);
}
function CreateEventTeamsItem(props: {
openModal: (option: EventTypeParent) => void;
option: EventTypeParent;
}) {
const session = useSession();
const membershipQuery = trpc.viewer.teams.getMembershipbyUser.useQuery({
memberId: session.data?.user?.id as number,
teamId: props.option.teamId as number,
});
const isDisabled = membershipQuery.data?.role === "MEMBER";
return (
<DropdownMenuItem
key={props.option.slug}
className={classNames(
"cursor-pointer px-3 py-2 focus:outline-none",
isDisabled ? "cursor-default !text-gray-300" : "hover:bg-neutral-100"
)}
disabled={isDisabled}
onSelect={() => props.openModal(props.option)}>
<Avatar
alt={props.option.name || ""}
imageSrc={props.option.image}
size="sm"
className="inline ltr:mr-2 rtl:ml-2"
/>
{props.option.name ? props.option.name : props.option.slug}
</DropdownMenuItem>
);
}
@@ -55,19 +55,13 @@ export const EventTypeDescription = ({ eventType, className }: EventTypeDescript
</Badge>
</li>
)}
{eventType.schedulingType ? (
{eventType.schedulingType && (
<li>
<Badge variant="gray" size="lg" StartIcon={Icon.FiUser}>
<Badge variant="gray" size="lg" StartIcon={Icon.FiUsers}>
{eventType.schedulingType === SchedulingType.ROUND_ROBIN && t("round_robin")}
{eventType.schedulingType === SchedulingType.COLLECTIVE && t("collective")}
</Badge>
</li>
) : (
<li>
<Badge variant="gray" size="lg" StartIcon={Icon.FiUser}>
{t("1_on_1")}
</Badge>
</li>
)}
{recurringEvent?.count && recurringEvent.count > 0 && (
<li className="hidden xl:block">
@@ -72,8 +72,14 @@ export function ScheduleListItem({
</Link>
</div>
<Dropdown>
<DropdownMenuTrigger asChild className="mx-5">
<Button type="button" variant="icon" color="secondary" StartIcon={Icon.FiMoreHorizontal} />
<DropdownMenuTrigger asChild>
<Button
className="mx-5"
type="button"
variant="icon"
color="secondary"
StartIcon={Icon.FiMoreHorizontal}
/>
</DropdownMenuTrigger>
{!isLoading && data && (
<DropdownMenuContent>
@@ -165,7 +165,7 @@ const SettingsSidebarContainer = ({
return (
<nav
className={classNames(
"no-scrollbar fixed bottom-0 left-0 top-0 z-10 flex max-h-screen w-56 flex-col space-y-1 overflow-x-hidden overflow-y-scroll bg-gray-50 px-2 pb-3 transition-transform lg:sticky lg:flex",
"no-scrollbar fixed bottom-0 left-0 top-0 flex max-h-screen w-56 flex-col space-y-1 overflow-x-hidden overflow-y-scroll bg-gray-50 px-2 pb-3 transition-transform max-lg:z-10 lg:sticky lg:flex",
className,
navigationIsOpenedOnMobile
? "translate-x-0 opacity-100"
+2 -2
View File
@@ -1,7 +1,7 @@
import { Prisma, PrismaClient } from "@prisma/client";
import { StripeData } from "@calcom/app-store/stripepayment/lib/server";
import { getEventTypeAppData, getLocationOptions } from "@calcom/app-store/utils";
import { getEventTypeAppData, getLocationGroupedOptions } from "@calcom/app-store/utils";
import { LocationObject } from "@calcom/core/location";
import { parseBookingLimit, parseRecurringEvent } from "@calcom/lib";
import getEnabledApps from "@calcom/lib/apps/getEnabledApps";
@@ -251,7 +251,7 @@ export default async function getEventTypeById({
const currentUser = eventType.users.find((u) => u.id === userId);
const t = await getTranslation(currentUser?.locale ?? "en", "common");
const integrations = await getEnabledApps(credentials);
const locationOptions = getLocationOptions(integrations, t);
const locationOptions = getLocationGroupedOptions(integrations, t);
const eventTypeObject = Object.assign({}, eventType, {
periodStartDate: eventType.periodStartDate?.toString() ?? null,
+33 -97
View File
@@ -2,264 +2,200 @@
{
"/*": "This file is auto-generated and updated by `yarn app-store create/edit`. Don't edit it manually",
"dirName": "routing-forms",
"categories": [
"other"
],
"categories": ["other"],
"slug": "routing-forms",
"type": "routing-forms_other"
},
{
"dirName": "whereby",
"categories": [
"video"
],
"categories": ["video"],
"slug": "whereby",
"type": "whereby_video"
},
{
"dirName": "around",
"categories": [
"video"
],
"categories": ["video"],
"slug": "around",
"type": "around_video"
},
{
"dirName": "riverside",
"categories": [
"video"
],
"categories": ["video"],
"slug": "riverside",
"type": "riverside_video"
},
{
"dirName": "typeform",
"categories": [
"other"
],
"categories": ["other"],
"slug": "typeform",
"type": "typeform_other"
},
{
"dirName": "ping",
"categories": [
"video"
],
"categories": ["video"],
"slug": "ping",
"type": "ping_video"
},
{
"dirName": "campfire",
"categories": [
"video"
],
"categories": ["video"],
"slug": "campfire",
"type": "campfire_video"
},
{
"dirName": "rainbow",
"categories": [
"web3"
],
"categories": ["web3"],
"slug": "rainbow",
"type": "rainbow_web3"
},
{
"dirName": "raycast",
"categories": [
"other"
],
"categories": ["other"],
"slug": "raycast",
"type": "raycast_other"
},
{
"dirName": "n8n",
"categories": [
"automation"
],
"categories": ["automation"],
"slug": "n8n",
"type": "n8n_automation"
},
{
"dirName": "exchangecalendar",
"categories": [
"calendar"
],
"categories": ["calendar"],
"slug": "exchange",
"type": "exchange_calendar"
},
{
"dirName": "qr_code",
"categories": [
"other"
],
"categories": ["other"],
"slug": "qr_code",
"type": "qr_code_other"
},
{
"dirName": "weather_in_your_calendar",
"categories": [
"other"
],
"categories": ["other"],
"slug": "weather_in_your_calendar",
"type": "weather_in_your_calendar_other"
},
{
"dirName": "fathom",
"categories": [
"analytics"
],
"categories": ["analytics"],
"slug": "fathom",
"type": "fathom_analytics"
},
{
"dirName": "plausible",
"categories": [
"analytics"
],
"categories": ["analytics"],
"slug": "plausible",
"type": "plausible_analytics"
},
{
"dirName": "wordpress",
"categories": [
"other"
],
"categories": ["other"],
"slug": "wordpress",
"type": "wordpress_other"
},
{
"dirName": "ga4",
"categories": [
"analytics"
],
"categories": ["analytics"],
"slug": "ga4",
"type": "ga4_analytics"
},
{
"dirName": "pipedream",
"categories": [
"automation"
],
"categories": ["automation"],
"slug": "pipedream",
"type": "pipedream_automation"
},
{
"dirName": "sirius_video",
"categories": [
"video"
],
"categories": ["video"],
"slug": "sirius_video",
"type": "sirius_video_video"
},
{
"dirName": "sendgrid",
"categories": [
"other"
],
"categories": ["other"],
"slug": "sendgrid",
"type": "sendgrid_other_calendar"
},
{
"dirName": "closecom",
"categories": [
"other"
],
"categories": ["other"],
"slug": "closecom",
"type": "closecom_other_calendar"
},
{
"dirName": "whatsapp",
"categories": [
"video"
],
"categories": ["video"],
"slug": "whatsapp",
"type": "whatsapp_video"
},
{
"dirName": "telegram",
"categories": [
"video"
],
"categories": ["video"],
"slug": "telegram",
"type": "telegram_video"
},
{
"dirName": "signal",
"categories": [
"video"
],
"categories": ["video"],
"slug": "signal",
"type": "signal_video"
},
{
"dirName": "vimcal",
"categories": [
"calendar"
],
"categories": ["calendar"],
"slug": "vimcal",
"type": "vimcal_other"
},
{
"dirName": "amie",
"categories": [
"calendar"
],
"categories": ["calendar"],
"slug": "amie",
"type": "amie_other"
},
{
"dirName": "booking-pages-tag",
"categories": [
"analytics"
],
"categories": ["analytics"],
"slug": "booking-pages-tag",
"type": "booking-pages-tag_other",
"isTemplate": true
},
{
"dirName": "event-type-app-card",
"categories": [
"other"
],
"categories": ["other"],
"slug": "event-type-app-card",
"type": "event-type-app-card_other",
"isTemplate": true
},
{
"dirName": "event-type-location-video-static",
"categories": [
"video"
],
"categories": ["video"],
"slug": "event-type-location-video-static",
"type": "event-type-location-video-static_other",
"isTemplate": true
},
{
"dirName": "general-app-settings",
"categories": [
"other"
],
"categories": ["other"],
"slug": "general-app-settings",
"type": "general-app-settings_other",
"isTemplate": true
},
{
"dirName": "link-as-an-app",
"categories": [
"other"
],
"categories": ["other"],
"slug": "link-as-an-app",
"type": "link-as-an-app_other",
"isTemplate": true
},
{
"dirName": "basic",
"categories": [
"other"
],
"categories": ["other"],
"slug": "basic",
"type": "basic_other",
"isTemplate": true
}
]
]
@@ -661,6 +661,7 @@ export const viewerTeamsRouter = router({
user: {
id: ctx.user.id,
},
accepted: true,
},
},
},
+18 -8
View File
@@ -1,5 +1,6 @@
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import * as Tooltip from "@radix-ui/react-tooltip";
import Link from "next/link";
import classNames from "@calcom/lib/classNames";
import { defaultAvatarSrc } from "@calcom/lib/defaultAvatarImage";
@@ -9,10 +10,11 @@ import { Maybe } from "@trpc/server";
export type AvatarProps = {
className?: string;
size: "xs" | "sm" | "md" | "mdLg" | "lg";
size: "xs" | "sm" | "md" | "mdLg" | "lg" | "xl";
imageSrc?: Maybe<string>;
title?: string;
alt: string;
href?: string;
gravatarFallbackMd5?: string;
fallback?: React.ReactNode;
accepted?: boolean;
@@ -25,20 +27,24 @@ const sizesPropsBySize = {
md: "w-8 h-8", // 32px
mdLg: "w-10 h-10", //40px
lg: "w-16 h-16", // 64px
xl: "w-24 h-24", // 96px
} as const;
export function Avatar(props: AvatarProps) {
const { imageSrc, gravatarFallbackMd5, size, alt, title } = props;
const sizeClassname = sizesPropsBySize[size];
const rootClass = classNames("rounded-full aspect-square ", sizeClassname);
const avatar = (
const { imageSrc, gravatarFallbackMd5, size, alt, title, href } = props;
const rootClass = classNames("aspect-square rounded-full", sizesPropsBySize[size]);
let avatar = (
<AvatarPrimitive.Root
className={classNames(
sizeClassname,
"dark:bg-darkgray-300 item-center relative inline-flex aspect-square justify-center overflow-hidden rounded-full"
"dark:bg-darkgray-300 item-center relative inline-flex aspect-square justify-center overflow-hidden rounded-full",
props.className
)}>
<>
<AvatarPrimitive.Image src={imageSrc ?? undefined} alt={alt} className={rootClass} />
<AvatarPrimitive.Image
src={imageSrc ?? undefined}
alt={alt}
className={classNames("aspect-square rounded-full", sizesPropsBySize[size])}
/>
<AvatarPrimitive.Fallback delayMs={600} asChild={props.asChild}>
<>
{props.fallback && !gravatarFallbackMd5 && props.fallback}
@@ -62,6 +68,10 @@ export function Avatar(props: AvatarProps) {
</AvatarPrimitive.Root>
);
if (href) {
avatar = <Link href={href}>{avatar}</Link>;
}
return title ? (
<Tooltip.Provider>
<Tooltip.Tooltip delayDuration={300}>
@@ -8,6 +8,7 @@ export type AvatarGroupProps = {
image: string;
title?: string;
alt?: string;
href?: string;
}[];
className?: string;
accepted?: boolean;
@@ -51,6 +52,7 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
alt={item.alt || ""}
accepted={props.accepted}
size={props.size}
href={item.href}
/>
</li>
);
+10 -5
View File
@@ -39,7 +39,7 @@ const buttonClasses = cva(
variant: {
button: "",
icon: "flex justify-center",
fab: "h-14 w-14 sm:h-9 sm:w-auto rounded-full justify-center sm:rounded-md sm:px-4 sm:py-2.5 radix-state-open:rotate-45 sm:radix-state-open:rotate-0 transition-transform radix-state-open:shadown-none radix-state-open:ring-0 !shadow-none",
fab: "rounded-full justify-center sm:rounded-md radix-state-open:rotate-45 sm:radix-state-open:rotate-0 transition-transform radix-state-open:shadown-none radix-state-open:ring-0 !shadow-none",
},
color: {
primary: "text-white dark:text-black",
@@ -75,7 +75,7 @@ const buttonClasses = cva(
disabled: [undefined, false],
color: "primary",
className:
"bg-brand-500 hover:bg-brand-400 focus:outline-none focus:ring-2 focus:ring-offset focus:ring-brand-500 dark:hover:bg-darkgray-600 dark:bg-darkgray-900",
"bg-brand-500 hover:bg-brand-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset focus-visible:ring-brand-500 dark:hover:bg-darkgray-600 dark:bg-darkgray-900",
}),
// Secondary variants
{
@@ -94,7 +94,7 @@ const buttonClasses = cva(
disabled: [undefined, false],
color: "secondary",
className:
"border border-gray-300 dark:border-darkgray-300 bg-white dark:bg-darkgray-100 hover:bg-gray-50 hover:border-gray-400 focus:bg-gray-100 dark:hover:bg-darkgray-200 dark:focus:bg-darkgray-200 focus:outline-none focus:ring-2 focus:ring-offset focus:ring-gray-900 dark:focus:ring-white",
"border border-gray-300 dark:border-darkgray-300 bg-white dark:bg-darkgray-100 hover:bg-gray-50 hover:border-gray-400 focus-visible:bg-gray-100 dark:hover:bg-darkgray-200 dark:focus-visible:bg-darkgray-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset focus-visible:ring-gray-900 dark:focus-visible:ring-white",
}),
// Minimal variants
{
@@ -113,7 +113,7 @@ const buttonClasses = cva(
disabled: [undefined, false],
color: "minimal",
className:
"hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-darkgray-200 dark:focus:bg-darkgray-200 focus:outline-none focus:ring-2 focus:ring-offset focus:ring-gray-900 dark:focus:ring-white",
"hover:bg-gray-100 focus-visible:bg-gray-100 dark:hover:bg-darkgray-200 dark:focus-visible:bg-darkgray-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset focus-visible:ring-gray-900 dark:focus-visible:ring-white",
}),
// Destructive variants
{
@@ -132,7 +132,7 @@ const buttonClasses = cva(
disabled: [false, undefined],
color: "destructive",
className:
"border dark:text-white text-gray-900 hover:text-red-700 focus:text-red-700 dark:hover:text-red-700 dark:focus:text-red-700 hover:border-red-100 focus:border-red-100 hover:bg-red-100 focus:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset focus:ring-red-700",
"border dark:text-white text-gray-900 hover:text-red-700 focus-visible:text-red-700 dark:hover:text-red-700 dark:focus-visible:text-red-700 hover:border-red-100 focus-visible:border-red-100 hover:bg-red-100 focus-visible:bg-red-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset focus-visible:ring-red-700",
}),
// https://github.com/joe-bell/cva/issues/95 created an issue about using !p-2 on the icon variants as i would expect this to take priority
{
@@ -145,6 +145,11 @@ const buttonClasses = cva(
size: "sm",
className: "h-6 w-6 !p-1",
},
{
variant: "fab",
size: "base",
className: "h-14 sm:h-9 sm:w-auto sm:px-4 sm:py-2.5",
},
],
defaultVariants: {
variant: "button",
+3 -9
View File
@@ -53,12 +53,7 @@ export function Dialog(props: DialogProps) {
}
}
return (
<DialogPrimitive.Root {...dialogProps}>
<DialogPrimitive.Overlay className="fadeIn fixed inset-0 z-40 bg-black bg-opacity-50 transition-opacity" />
{children}
</DialogPrimitive.Root>
);
return <DialogPrimitive.Root {...dialogProps}>{children}</DialogPrimitive.Root>;
}
type DialogContentProps = React.ComponentProps<typeof DialogPrimitive["Content"]> & {
size?: "xl" | "lg" | "md";
@@ -75,12 +70,11 @@ export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps
({ children, title, Icon, disableOverflow, type = "creation", ...props }, forwardedRef) => {
return (
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className="fadeIn fixed inset-0 z-40 bg-gray-500 bg-opacity-75 transition-opacity" />
{/*zIndex one less than Toast */}
<DialogPrimitive.Overlay className="fadeIn fixed inset-0 bg-gray-500 bg-opacity-80 transition-opacity" />
<DialogPrimitive.Content
{...props}
className={classNames(
"fadeIn fixed left-1/2 top-1/2 z-[9998] min-w-[360px] -translate-x-1/2 -translate-y-1/2 rounded bg-white text-left shadow-xl focus-visible:outline-none sm:w-full sm:align-middle",
"fadeIn fixed left-1/2 top-1/2 min-w-[360px] -translate-x-1/2 -translate-y-1/2 rounded bg-white text-left shadow-xl focus-visible:outline-none sm:w-full sm:align-middle",
props.size == "xl"
? "p-8 sm:max-w-[90rem]"
: props.size == "lg"
@@ -14,11 +14,10 @@ export const DropdownMenuTrigger = forwardRef<HTMLButtonElement, DropdownMenuTri
({ className = "", ...props }, forwardedRef) => (
<DropdownMenuPrimitive.Trigger
{...props}
className={
props.asChild
? classNames("rounded-md ring-0", className)
: `inline-flex items-center rounded-md bg-transparent px-3 py-2 text-sm font-medium text-gray-700 ring-0 hover:bg-gray-50 focus:bg-gray-100 group-hover:text-black ${className}`
}
className={classNames(
!props.asChild &&
`inline-flex items-center rounded-md bg-transparent px-3 py-2 text-sm font-medium text-gray-700 ring-0 hover:bg-gray-50 focus:bg-gray-100 group-hover:text-black ${className}`
)}
ref={forwardedRef}
/>
)
@@ -42,9 +42,9 @@ export const AnimatedPopover = ({
<Popover.Trigger asChild>
<div
ref={ref}
className="item-center mb-2 flex h-9 justify-between whitespace-nowrap rounded-md border border-gray-300 py-2 px-3
text-sm placeholder:text-gray-400 hover:cursor-pointer hover:border-gray-400
focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-neutral-800 focus:ring-offset-1">
className="item-center mb-2 flex h-9 max-h-72 justify-between overflow-y-scroll whitespace-nowrap rounded-md border border-gray-300
py-2 px-3 text-sm placeholder:text-gray-400
hover:cursor-pointer hover:border-gray-400 focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-neutral-800 focus:ring-offset-1">
<div className="max-w-36 flex items-center">
<div className="truncate">
{text}
@@ -61,7 +61,7 @@ export const AnimatedPopover = ({
<Popover.Content side="bottom" align={align} asChild>
<div
className={classNames(
"absolute z-50 mt-2 w-56 rounded-md bg-white shadow-sm ring-1 ring-black ring-opacity-5 focus-within:outline-none",
"absolute z-50 mt-2 w-56 rounded-md bg-white py-[2px] shadow-sm ring-1 ring-black ring-opacity-5 focus-within:outline-none",
align === "end" && "-translate-x-[228px]"
)}>
{children}
+1 -1
View File
@@ -31,7 +31,7 @@ export function Tooltip({
className={classNames(
side === "top" && "-mt-7",
side === "right" && "ml-2",
"relative z-40 rounded-md bg-gray-900 px-2 py-1 text-xs font-semibold text-white shadow-lg dark:bg-white dark:text-gray-600"
"relative rounded-md bg-gray-900 px-2 py-1 text-xs font-semibold text-white shadow-lg dark:bg-white dark:text-gray-600"
)}
side={side}
align="center"
+21 -1
View File
@@ -9,6 +9,16 @@
dependencies:
"@jridgewell/trace-mapping" "^0.3.0"
"@apidevtools/json-schema-ref-parser@9.0.9":
version "9.0.9"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==
dependencies:
"@jsdevtools/ono" "^7.1.3"
"@types/json-schema" "^7.0.6"
call-me-maybe "^1.0.1"
js-yaml "^4.1.0"
"@aws-crypto/ie11-detection@^2.0.0":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz#9c39f4a5558196636031a933ec1b4792de959d6a"
@@ -4540,6 +4550,11 @@
resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-5.4.2.tgz#fedb8b4b98cf0750daf5802fa2a661edbf83892b"
integrity sha512-QIDIZ9a0NfDStgD47VaTgwiPjlw1p4QPLwjOB/9+/DqIztoQopPNNAd+HdtQMHgE+ibP3dJacd8/TVL/A1RaaA==
"@jsdevtools/ono@^7.1.3":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==
"@json-rpc-tools/provider@^1.5.5":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@json-rpc-tools/provider/-/provider-1.7.6.tgz#8a17c34c493fa892632e278fd9331104e8491ec6"
@@ -7957,7 +7972,7 @@
resolved "https://registry.yarnpkg.com/@types/json-logic-js/-/json-logic-js-1.2.1.tgz#064e777b77b0fcb77f00c2c50fec1387cb33eb47"
integrity sha512-g/g+wj/7sgazpiCHiyAtndoNiy/LodLkNG4I9MILAl0UinKKwv3GiPKbtvcE1hIoezQqgDamXfx8Lht62/hHqw==
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
@@ -24521,6 +24536,11 @@ synckit@^0.8.4:
"@pkgr/utils" "^2.3.1"
tslib "^2.4.0"
tailwind-merge@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.8.1.tgz#0e56c8afbab2491f72e06381043ffec8b720ba04"
integrity sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww==
tailwind-scrollbar@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tailwind-scrollbar/-/tailwind-scrollbar-2.0.1.tgz#f08721173d513f1406a44601640b79726e3f9728"