Fix destination calendar overflow on installed and move DestinationCalendarSelector to feature package (#4778)
* Standardize destination calendar selector * Move DestinationCalendarSelector to feature package * Render integration name * Add custom components to label and selected * Render destinationCalendar on page load * Change name to just Outlook * Small fixes * Merge branch 'main' into hotfix/install-calendar-overflow * Merge branch 'main' into hotfix/install-calendar-overflow * Fix type errors * Fix type errors * Update apps/web/pages/settings/my-account/calendars.tsx * More type fixes Co-authored-by: Omar López <zomars@me.com>pull/4766/head^2
parent
5c01467caa
commit
5ab5af753a
|
@ -3,6 +3,7 @@ import Link from "next/link";
|
|||
import { Fragment } from "react";
|
||||
|
||||
import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import showToast from "@calcom/lib/notification";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
@ -18,7 +19,6 @@ import { QueryCell } from "@lib/QueryCell";
|
|||
|
||||
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
|
||||
import AdditionalCalendarSelector from "@components/v2/apps/AdditionalCalendarSelector";
|
||||
import DestinationCalendarSelector from "@components/v2/apps/DestinationCalendarSelector";
|
||||
import IntegrationListItem from "@components/v2/apps/IntegrationListItem";
|
||||
|
||||
type Props = {
|
||||
|
@ -287,7 +287,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
|
|||
<h1 className="text-sm font-semibold">{t("create_events_on")}</h1>
|
||||
<p className="text-sm font-normal">{t("set_calendar")}</p>
|
||||
</div>
|
||||
<div className="flex justify-end md:w-6/12">
|
||||
<div className="justify-end md:w-6/12">
|
||||
<DestinationCalendarSelector
|
||||
onChange={mutation.mutate}
|
||||
hidePlaceholder
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Controller, useFormContext } from "react-hook-form";
|
|||
import short from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
@ -14,7 +15,6 @@ import { Icon } from "@calcom/ui";
|
|||
import {
|
||||
Button,
|
||||
CustomInputItem,
|
||||
DestinationCalendarSelector,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
Label,
|
||||
|
|
|
@ -3,6 +3,7 @@ import Link from "next/link";
|
|||
import { useRouter } from "next/router";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
@ -14,7 +15,6 @@ import Meta from "@calcom/ui/v2/core/Meta";
|
|||
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
|
||||
import { SkeletonContainer, SkeletonText, SkeletonButton } from "@calcom/ui/v2/core/skeleton";
|
||||
import { List, ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/modules/List";
|
||||
import DestinationCalendarSelector from "@calcom/ui/v2/modules/event-types/DestinationCalendarSelector";
|
||||
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
@ -63,7 +63,7 @@ const CalendarsView = () => {
|
|||
<Icon.FiCalendar className="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-3">
|
||||
<div className="flex w-full flex-col space-y-3">
|
||||
<div>
|
||||
<h4 className=" pb-2 text-base font-semibold leading-5 text-black">
|
||||
{t("add_to_calendar")}
|
||||
|
|
|
@ -3,10 +3,10 @@ import type { AppMeta } from "@calcom/types/App";
|
|||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
name: "Office 365 / Outlook.com Calendar",
|
||||
name: "Outlook Calendar",
|
||||
description: _package.description,
|
||||
type: "office365_calendar",
|
||||
title: "Office 365 / Outlook.com Calendar",
|
||||
title: "Outlook Calendar",
|
||||
imageSrc: "/api/app-store/office365calendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import classNames from "classnames";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { components } from "react-select";
|
||||
import { SingleValueProps, OptionProps, SingleValue, ActionMeta } from "react-select";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { DestinationCalendar } from "@calcom/prisma/client";
|
||||
|
@ -16,6 +18,30 @@ interface Props {
|
|||
maxWidth?: number;
|
||||
}
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
value: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
const SingleValueComponent = ({ ...props }: SingleValueProps<Option>) => {
|
||||
const { label, subtitle } = props.data;
|
||||
return (
|
||||
<components.SingleValue {...props} className="flex space-x-1">
|
||||
<p>{label}</p> <p className=" text-neutral-500">{subtitle}</p>
|
||||
</components.SingleValue>
|
||||
);
|
||||
};
|
||||
|
||||
const OptionComponent = ({ ...props }: OptionProps<Option>) => {
|
||||
const { label } = props.data;
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<span>{label}</span>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
const DestinationCalendarSelector = ({
|
||||
onChange,
|
||||
isLoading,
|
||||
|
@ -26,13 +52,18 @@ const DestinationCalendarSelector = ({
|
|||
}: Props): JSX.Element | null => {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.useQuery(["viewer.connectedCalendars"]);
|
||||
const [selectedOption, setSelectedOption] = useState<{ value: string; label: string } | null>(null);
|
||||
const [selectedOption, setSelectedOption] = useState<{
|
||||
value: string;
|
||||
label: string;
|
||||
subtitle: string;
|
||||
} | null>(null);
|
||||
|
||||
// Extra styles to show prefixed text in react-select
|
||||
const content = (hidePlaceholder = false) => {
|
||||
if (!hidePlaceholder) {
|
||||
return {
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
":before": {
|
||||
content: `'${t("select_destination_calendar")}:'`,
|
||||
|
@ -51,12 +82,19 @@ const DestinationCalendarSelector = ({
|
|||
.find((cal) => cal.externalId === value);
|
||||
|
||||
if (selected) {
|
||||
const selectedIntegration = query.data?.connectedCalendars.find((integration) =>
|
||||
integration.calendars?.some((calendar) => calendar.externalId === selected.externalId)
|
||||
);
|
||||
|
||||
setSelectedOption({
|
||||
value: `${selected.integration}:${selected.externalId}`,
|
||||
label: selected.name || "",
|
||||
label: `${selected.name} ` || "",
|
||||
subtitle: `(${selectedIntegration?.integration.title?.replace(/calendar/i, "")} - ${
|
||||
selectedIntegration?.primary?.name
|
||||
})`,
|
||||
});
|
||||
}
|
||||
}, [query.data?.connectedCalendars, value]);
|
||||
}, [query.data?.connectedCalendars]);
|
||||
|
||||
if (!query.data?.connectedCalendars.length) {
|
||||
return null;
|
||||
|
@ -64,11 +102,16 @@ const DestinationCalendarSelector = ({
|
|||
const options =
|
||||
query.data.connectedCalendars.map((selectedCalendar) => ({
|
||||
key: selectedCalendar.credentialId,
|
||||
label: `${selectedCalendar.integration.title} (${selectedCalendar.primary?.name})`,
|
||||
label: `${selectedCalendar.integration.title?.replace(/calendar/i, "")} (${
|
||||
selectedCalendar.primary?.name
|
||||
})`,
|
||||
options: (selectedCalendar.calendars ?? [])
|
||||
.filter((cal) => cal.readOnly === false)
|
||||
.map((cal) => ({
|
||||
label: cal.name || "",
|
||||
label: ` ${cal.name} `,
|
||||
subtitle: `(${selectedCalendar?.integration.title?.replace(/calendar/i, "")} - ${
|
||||
selectedCalendar?.primary?.name
|
||||
})`,
|
||||
value: `${cal.integration}:${cal.externalId}`,
|
||||
})),
|
||||
})) ?? [];
|
||||
|
@ -113,14 +156,14 @@ const DestinationCalendarSelector = ({
|
|||
className={classNames(
|
||||
"mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-sm border-gray-300 text-sm"
|
||||
)}
|
||||
onChange={(option) => {
|
||||
setSelectedOption(option);
|
||||
if (!option) {
|
||||
onChange={(newValue) => {
|
||||
setSelectedOption(newValue);
|
||||
if (!newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Split only the first `:`, since Apple uses the full URL as externalId */
|
||||
const [integration, externalId] = option.value.split(/:(.+)/);
|
||||
const [integration, externalId] = newValue.value.split(/:(.+)/);
|
||||
|
||||
onChange({
|
||||
integration,
|
||||
|
@ -129,6 +172,8 @@ const DestinationCalendarSelector = ({
|
|||
}}
|
||||
isLoading={isLoading}
|
||||
value={selectedOption}
|
||||
components={{ SingleValue: SingleValueComponent, Option: OptionComponent }}
|
||||
isMulti={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
|
@ -2,89 +2,67 @@
|
|||
{
|
||||
"/*": "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"
|
||||
},
|
||||
|
@ -106,4 +84,4 @@
|
|||
"slug": "fathom",
|
||||
"type": "fathom_analytics"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export { default as CreateEventType } from "./CreateEventType";
|
||||
export { default as DestinationCalendarSelector } from "./DestinationCalendarSelector";
|
||||
export { default as CustomInputItem } from "./CustomInputItem";
|
||||
export { default as CheckedTeamSelect } from "./CheckedTeamSelect";
|
||||
|
|
Loading…
Reference in New Issue