Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5714471b9a | ||
|
|
a99e93a4bd | ||
|
|
7f044ff92f | ||
|
|
17a0a61313 | ||
|
|
12b7739c9a | ||
|
|
ff283a25d2 | ||
|
|
4b343f20a4 | ||
|
|
5bb6904ffd | ||
|
|
0e04f5d338 | ||
|
|
da0ea5d9d0 | ||
|
|
1a19261b8f | ||
|
|
55a6c65db6 | ||
|
|
c9ed3388ae | ||
|
|
2588b7ef84 | ||
|
|
7ce83542e2 | ||
|
|
0bb0883e64 | ||
|
|
d3be62fbdd | ||
|
|
736308e43a |
@@ -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) => {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}</>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.5.2",
|
||||
"version": "2.5.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user