feat: Salesforce Event Type App v3 designs (#21100)
* section component * more section UI * wip * core components styled * all components using sections * match icons to figma * small inputs * fix select small to be the same as small text * center flex rows * enforce inputs to be full spanning grid * tidy up section container for single inputs * fix select and i18n * wip * install available apps * raw heading * app install card * fix app logos width x height * fix gap * match design for other button * fix prop name * push i18n string * fix doc snippets * fix docs * chore: remove yarn.lock changes --------- Co-authored-by: Udit Takkar <udit222001@gmail.com>
This commit is contained in:
1 parent
d0e39cf2dd
commit
073faf9e10
14 files changed
+966
-302
No files matched your search
@@ -24,7 +24,7 @@ export const RenderComponentWithSnippet: React.FC<PropsWithChildren<Props>> = (p
|
||||
// },
|
||||
// });
|
||||
return (
|
||||
<div className="border-gray-6 bg-gray-1 rounded-lg border">
|
||||
<div className="border-gray-6 bg-default rounded-lg border">
|
||||
<div className={className("not-prose p-8 xl:p-12", props.className)}>{props.children}</div>
|
||||
|
||||
<div className="bg-gray-3 border-gray-6 flex items-center justify-start border-b border-t p-2">
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
export const BasicExample = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<Section>
|
||||
<Section.Header
|
||||
title="Basic Section"
|
||||
description="This is a basic section with a title and description">
|
||||
<Switch size="sm" checked={isOpen} onCheckedChange={setIsOpen} />
|
||||
</Section.Header>
|
||||
{isOpen && (
|
||||
<Section.Content>
|
||||
<p>This is the main content of the section.</p>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const BasicExampleSnippet = `
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
|
||||
const BasicSection = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Section.Header
|
||||
title="Basic Section"
|
||||
description="This is a basic section with a title and description"
|
||||
>
|
||||
<Section.HeaderRight>
|
||||
<Switch size="sm" checked={isOpen} onCheckedChange={setIsOpen} />
|
||||
</Section.HeaderRight>
|
||||
</Section.Header>
|
||||
{isOpen && (
|
||||
<Section.Content>
|
||||
<p>This is the main content of the section.</p>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
`;
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Section
|
||||
---
|
||||
import { Section } from "@calcom/ui/components/section"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { BasicExample } from "./section.basic"
|
||||
import { WithIconExample } from "./section.withIcon"
|
||||
import { SubSectionExample } from "./section.subsection"
|
||||
|
||||
## Basic Usage
|
||||
|
||||
The Section component provides a structured way to organize content with a consistent visual hierarchy. It includes several subcomponents for different parts of the section.
|
||||
|
||||
<BasicExample/>
|
||||
|
||||
## With Icon
|
||||
|
||||
Sections can include an icon in the header to provide visual context.
|
||||
|
||||
<WithIconExample/>
|
||||
|
||||
## SubSections
|
||||
|
||||
Sections can contain subsections for organizing related content in a nested structure.
|
||||
|
||||
<SubSectionExample/>
|
||||
|
||||
## Components
|
||||
|
||||
The Section component is composed of several subcomponents:
|
||||
|
||||
- `Section` - The root container
|
||||
- `Section.Header` - Container for the section header
|
||||
- `Section.Title` - The section title
|
||||
- `Section.Description` - Optional description text
|
||||
- `Section.Content` - The main content area
|
||||
- `Section.SubSection` - A nested section
|
||||
- `Section.SubSectionHeader` - Header for a subsection
|
||||
- `Section.SubSectionContent` - Content area for a subsection
|
||||
@@ -0,0 +1,151 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Input, Switch } from "@calcom/ui/components/form";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
export const SubSectionExample = () => {
|
||||
const [isMainOpen, setIsMainOpen] = useState(true);
|
||||
const [isFirstSubsectionOpen, setIsFirstSubsectionOpen] = useState(true);
|
||||
const [isSecondSubsectionOpen, setIsSecondSubsectionOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<Section>
|
||||
<Section.Header
|
||||
icon="calendar"
|
||||
title="Section with Subsections"
|
||||
description="This section contains nested subsections">
|
||||
<Switch size="sm" checked={isMainOpen} onCheckedChange={setIsMainOpen} />
|
||||
</Section.Header>
|
||||
{isMainOpen && (
|
||||
<Section.Content>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="zap" title="Automatic Sync (Justify Start)" justify="start">
|
||||
<Switch size="sm" />
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="user" title="Create new records for guests added">
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={isFirstSubsectionOpen}
|
||||
onCheckedChange={setIsFirstSubsectionOpen}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isFirstSubsectionOpen && (
|
||||
<Section.SubSectionContent>
|
||||
<p>This is the content of the first subsection.</p>
|
||||
</Section.SubSectionContent>
|
||||
)}
|
||||
</Section.SubSection>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="settings" title="Open Settings">
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={isSecondSubsectionOpen}
|
||||
onCheckedChange={setIsSecondSubsectionOpen}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isSecondSubsectionOpen && (
|
||||
<Section.SubSectionContent>
|
||||
<div className="flex gap-3 px-3 py-1.5">
|
||||
<div className="text-subtle w-full text-sm font-medium">Field Name</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">Field Type</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">Value</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">When to Write</div>
|
||||
</div>
|
||||
<Section.SubSectionNested>
|
||||
<div className="flex gap-3">
|
||||
<Input value="Customer Name" className="w-full" />
|
||||
<Input value="Text" className="w-full" />
|
||||
<Input value="John Doe" className="w-full" />
|
||||
<Input value="On Create" className="w-full" />
|
||||
</div>
|
||||
</Section.SubSectionNested>
|
||||
</Section.SubSectionContent>
|
||||
)}
|
||||
</Section.SubSection>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubSectionExampleSnippet = `
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
|
||||
const SectionWithSubsections = () => {
|
||||
const [isMainOpen, setIsMainOpen] = useState(true);
|
||||
const [isFirstSubsectionOpen, setIsFirstSubsectionOpen] = useState(true);
|
||||
const [isSecondSubsectionOpen, setIsSecondSubsectionOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Section.Header
|
||||
icon="calendar"
|
||||
title="Section with Subsections"
|
||||
description="This section contains nested subsections">
|
||||
<Switch size="sm" checked={isMainOpen} onCheckedChange={setIsMainOpen} />
|
||||
</Section.Header>
|
||||
{isMainOpen && (
|
||||
<Section.Content>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="zap" title="Automatic Sync (Justify Start)" justify="start">
|
||||
<Switch size="sm" />
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="user" title="Create new records for guests added">
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={isFirstSubsectionOpen}
|
||||
onCheckedChange={setIsFirstSubsectionOpen}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isFirstSubsectionOpen && (
|
||||
<Section.SubSectionContent>
|
||||
<p>This is the content of the first subsection.</p>
|
||||
</Section.SubSectionContent>
|
||||
)}
|
||||
</Section.SubSection>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="settings" title="Open Settings">
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={isSecondSubsectionOpen}
|
||||
onCheckedChange={setIsSecondSubsectionOpen}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isSecondSubsectionOpen && (
|
||||
<Section.SubSectionContent>
|
||||
<div className="flex gap-3 px-3 py-1.5">
|
||||
<div className="text-subtle w-full text-sm font-medium">Field Name</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">Field Type</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">Value</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">When to Write</div>
|
||||
</div>
|
||||
<Section.SubSectionNested>
|
||||
<div className="flex gap-3">
|
||||
<Input value="Customer Name" className="w-full" />
|
||||
<Input value="Text" className="w-full" />
|
||||
<Input value="John Doe" className="w-full" />
|
||||
<Input value="On Create" className="w-full" />
|
||||
</div>
|
||||
</Section.SubSectionNested>
|
||||
</Section.SubSectionContent>
|
||||
)}
|
||||
</Section.SubSection>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
`;
|
||||
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
export const WithIconExample = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<Section>
|
||||
<Section.Header
|
||||
icon="calendar"
|
||||
title="Section with Icon"
|
||||
description="This section includes an icon in the header. Toggle to see auto-animation.">
|
||||
<Switch size="sm" checked={isOpen} onCheckedChange={setIsOpen} />
|
||||
</Section.Header>
|
||||
{isOpen && (
|
||||
<Section.Content>
|
||||
<p>
|
||||
This section demonstrates how to include an icon in the header and uses FormKit auto-animate for
|
||||
smooth transitions when toggling content visibility.
|
||||
</p>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithIconExampleSnippet = `
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
|
||||
const SectionWithIcon = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Section.Header
|
||||
icon="calendar"
|
||||
title="Section with Icon"
|
||||
description="This section includes an icon in the header. Toggle to see auto-animation.">
|
||||
<Switch size="sm" checked={isOpen} onCheckedChange={setIsOpen} />
|
||||
</Section.Header>
|
||||
{isOpen && (
|
||||
<Section.Content>
|
||||
<p>
|
||||
This section demonstrates how to include an icon in the header and uses FormKit auto-animate for
|
||||
smooth transitions when toggling content visibility.
|
||||
</p>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
`;
|
||||
@@ -2812,6 +2812,7 @@
|
||||
"salesforce_skip_entry_creation": "Skip creating {{entry}} record if they do not exist in Salesforce",
|
||||
"salesforce_if_account_does_not_exist": "If the contact does not exist under an account, create new lead from attendee",
|
||||
"salesforce_create_new_contact_under_account": "Create a new contact under an account based on email domain of attendee and existing contacts",
|
||||
"salesforce_change_record_owner_on_booking":"Change record owner on booking",
|
||||
"mass_assign_attributes": "Mass assign attributes",
|
||||
"reroute_preview_custom_message": "It results in showing custom message. Try changing the response to route to an event",
|
||||
"reroute_preview_external_redirect": "It results in redirecting to {{externalUrl}}. Try changing the response to route to an event",
|
||||
@@ -2908,6 +2909,7 @@
|
||||
"routing_form_insights_assignment_reason": "Assignment Reason",
|
||||
"access_denied": "Access Denied",
|
||||
"salesforce_route_to_owner": "Contact owner will be the Round Robin host if available",
|
||||
"salesforce_add_attendees_as":"Add new attendees as",
|
||||
"salesforce_do_not_route_to_owner": "Contact owner will not be forced (can still be host if it matches the attributes and Round Robin criteria)",
|
||||
"salesforce_route_to_custom_lookup_field": "Route to a user that matches a lookup field on an account",
|
||||
"salesforce_option": "Salesforce Option",
|
||||
|
||||
@@ -5,12 +5,11 @@ import { useTranslation } from "react-i18next";
|
||||
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
|
||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
import type { CredentialOwner } from "../types";
|
||||
import OmniInstallAppButton from "./OmniInstallAppButton";
|
||||
@@ -49,45 +48,36 @@ export default function AppCard({
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"border-subtle",
|
||||
app?.isInstalled && "mb-4 rounded-md border",
|
||||
!app.enabled && "grayscale"
|
||||
)}>
|
||||
<div className={classNames(app.isInstalled ? "p-4 text-sm sm:p-4" : "px-5 py-4 text-sm sm:px-5")}>
|
||||
<div className="flex w-full flex-col gap-2 sm:flex-row sm:gap-0">
|
||||
{/* Don't know why but w-[42px] isn't working, started happening when I started using next/dynamic */}
|
||||
<Link
|
||||
href={`/apps/${app.slug}`}
|
||||
className={classNames(app?.isInstalled ? "mr-[11px]" : "mr-3", "h-auto w-10 rounded-sm")}>
|
||||
<img
|
||||
className={classNames(
|
||||
app?.logo.includes("-dark") && "dark:invert",
|
||||
app?.isInstalled ? "min-w-[42px]" : "min-w-[40px]",
|
||||
"w-full"
|
||||
)}
|
||||
src={app?.logo}
|
||||
alt={app?.name}
|
||||
/>
|
||||
</Link>
|
||||
<div className="flex flex-col pe-3">
|
||||
<div className="text-emphasis">
|
||||
<span className={classNames(app?.isInstalled && "text-base", "font-semibold leading-4")}>
|
||||
{app?.name}
|
||||
</span>
|
||||
<Section className={classNames(!app?.isInstalled && "rounded-xl")}>
|
||||
<Section.Header
|
||||
rawHeading={
|
||||
<div>
|
||||
<div className="flex w-full items-center gap-1">
|
||||
<Section.Title>{app?.name}</Section.Title>
|
||||
{!app?.isInstalled && (
|
||||
<span className="bg-emphasis ml-1 rounded px-1 py-0.5 text-xs font-medium leading-3 tracking-[0.01em]">
|
||||
{app?.categories[0].charAt(0).toUpperCase() + app?.categories[0].slice(1)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p title={app?.description} className="text-default line-clamp-1 pt-1 text-sm font-normal">
|
||||
{description || app?.description}
|
||||
</p>
|
||||
<Section.Description>{app?.description}</Section.Description>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center space-x-2">
|
||||
{app.credentialOwner && !isPlatform && (
|
||||
}
|
||||
iconSlot={
|
||||
<Link href={`/apps/${app.slug}`} className="flex h-8 w-8 items-center justify-center">
|
||||
<img
|
||||
className={classNames(
|
||||
app?.logo.includes("-dark") && "dark:invert",
|
||||
"max-h-full max-w-full object-contain"
|
||||
)}
|
||||
src={app?.logo}
|
||||
alt={app?.name}
|
||||
/>
|
||||
</Link>
|
||||
}>
|
||||
<div>
|
||||
<div>
|
||||
{/* {app.credentialOwner && !isPlatform && (
|
||||
<div className="ml-auto">
|
||||
<Badge variant="gray">
|
||||
<div className="flex items-center">
|
||||
@@ -101,10 +91,11 @@ export default function AppCard({
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
{app?.isInstalled || app.credentialOwner ? (
|
||||
<div className="ml-auto flex items-center">
|
||||
<Switch
|
||||
size="sm"
|
||||
disabled={!app.enabled || managedDisabled || disableSwitch}
|
||||
onCheckedChange={(enabled) => {
|
||||
if (switchOnClick) {
|
||||
@@ -128,32 +119,31 @@ export default function AppCard({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{hideAppCardOptions ? null : (
|
||||
<div ref={animationRef}>
|
||||
{app?.isInstalled && switchChecked && <hr className="border-subtle" />}
|
||||
|
||||
{app?.isInstalled && switchChecked ? (
|
||||
app.isSetupAlready === undefined || app.isSetupAlready ? (
|
||||
<div className="relative p-4 pt-5 text-sm [&_input]:mb-0 [&_input]:leading-4">
|
||||
{!hideSettingsIcon && !isPlatform && (
|
||||
<Link href={`/apps/${app.slug}/setup`} className="absolute right-4 top-4">
|
||||
<Icon name="settings" className="text-default h-4 w-4" aria-hidden="true" />
|
||||
</Section.Header>
|
||||
{hideAppCardOptions
|
||||
? null
|
||||
: app?.isInstalled &&
|
||||
switchChecked && (
|
||||
<div ref={animationRef}>
|
||||
{app.isSetupAlready === undefined || app.isSetupAlready ? (
|
||||
<div className="relative text-sm [&_input]:mb-0 [&_input]:leading-4">
|
||||
{!hideSettingsIcon && !isPlatform && (
|
||||
<Link href={`/apps/${app.slug}/setup`} className="absolute right-4 top-4">
|
||||
<Icon name="settings" className="text-default h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-64 w-full flex-col items-center justify-center gap-4 ">
|
||||
<p>{t("this_app_is_not_setup_already")}</p>
|
||||
<Link href={`/apps/${app.slug}/setup`}>
|
||||
<Button StartIcon="settings">{t("setup")}</Button>
|
||||
</Link>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-64 w-full flex-col items-center justify-center gap-4 ">
|
||||
<p>{t("this_app_is_not_setup_already")}</p>
|
||||
<Link href={`/apps/${app.slug}/setup`}>
|
||||
<Button StartIcon="settings">{t("setup")}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -10,9 +10,10 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { InputField } from "@calcom/ui/components/form";
|
||||
import { InputField, Label } from "@calcom/ui/components/form";
|
||||
import { Select } from "@calcom/ui/components/form";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
import { SalesforceRecordEnum } from "../lib/enums";
|
||||
@@ -74,14 +75,21 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
// Used when creating events under leads or contacts under account
|
||||
const CreateContactUnderAccount = () => {
|
||||
return (
|
||||
<Switch
|
||||
label={t("salesforce_create_new_contact_under_account")}
|
||||
labelOnLeading
|
||||
checked={createNewContactUnderAccount}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("createNewContactUnderAccount", checked);
|
||||
}}
|
||||
/>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="at-sign"
|
||||
title={t("salesforce_create_new_contact_under_account")}
|
||||
labelFor="create-new-contact-under-account">
|
||||
<Switch
|
||||
size="sm"
|
||||
labelOnLeading
|
||||
checked={createNewContactUnderAccount}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("createNewContactUnderAccount", checked);
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -95,107 +103,135 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}}
|
||||
switchChecked={enabled}
|
||||
hideSettingsIcon>
|
||||
<>
|
||||
<div className="mb-4 ml-2">
|
||||
<label className="text-emphasis mb-2 align-text-top text-sm font-medium">
|
||||
{t("salesforce_create_record_as")}
|
||||
</label>
|
||||
<Select
|
||||
className="mt-2 w-60"
|
||||
options={recordOptions}
|
||||
value={createEventOnSelectedOption}
|
||||
onChange={(e) => {
|
||||
if (e) {
|
||||
setCreateEventOnSelectedOption(e);
|
||||
setAppData("createEventOn", e.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Switch
|
||||
label={t("salesforce_ignore_guests")}
|
||||
labelOnLeading
|
||||
checked={ignoreGuests}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("ignoreGuests", checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Section.Content>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="zap"
|
||||
title={t("salesforce_add_attendees_as")}
|
||||
justify="start"
|
||||
labelFor="add-attendees-as">
|
||||
<Select
|
||||
size="sm"
|
||||
id="add-attendees-as"
|
||||
className="w-[200px]"
|
||||
options={recordOptions}
|
||||
value={createEventOnSelectedOption}
|
||||
onChange={(e) => {
|
||||
if (e) {
|
||||
setCreateEventOnSelectedOption(e);
|
||||
setAppData("createEventOn", e.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="user-plus"
|
||||
title={t("salesforce_ignore_guests")}
|
||||
labelFor="ignore-guests">
|
||||
<Switch
|
||||
size="sm"
|
||||
labelOnLeading
|
||||
checked={ignoreGuests}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("ignoreGuests", checked);
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
|
||||
{createEventOnSelectedOption.value === SalesforceRecordEnum.CONTACT ? (
|
||||
<div>
|
||||
<Switch
|
||||
label={t("skip_contact_creation", { appName: "Salesforce" })}
|
||||
labelOnLeading
|
||||
checked={isSkipContactCreationEnabled}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("skipContactCreation", checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="user-plus"
|
||||
title={t("skip_contact_creation", { appName: "Salesforce" })}
|
||||
labelFor="skip-contact-creation">
|
||||
<Switch
|
||||
size="sm"
|
||||
labelOnLeading
|
||||
checked={isSkipContactCreationEnabled}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("skipContactCreation", checked);
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
) : null}
|
||||
|
||||
{createEventOnSelectedOption.value === SalesforceRecordEnum.LEAD ? (
|
||||
<div>
|
||||
<Switch
|
||||
label={t("salesforce_create_event_on_contact")}
|
||||
labelOnLeading
|
||||
checked={createEventOnLeadCheckForContact}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("createEventOnLeadCheckForContact", checked);
|
||||
}}
|
||||
/>
|
||||
<div className="mt-4">
|
||||
<CreateContactUnderAccount />
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="user-plus"
|
||||
title={t("salesforce_create_event_on_contact")}
|
||||
labelFor="create-event-on-contact">
|
||||
<Switch size="sm" labelOnLeading checked={createEventOnLeadCheckForContact} />
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
<CreateContactUnderAccount />
|
||||
</>
|
||||
) : null}
|
||||
{createEventOnSelectedOption.value === SalesforceRecordEnum.ACCOUNT ? (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<CreateContactUnderAccount />
|
||||
</div>
|
||||
<div>
|
||||
<Switch
|
||||
label={t("salesforce_if_account_does_not_exist")}
|
||||
labelOnLeading
|
||||
checked={createLeadIfAccountNull}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("createLeadIfAccountNull", checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<CreateContactUnderAccount />
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="user-plus"
|
||||
title={t("salesforce_if_account_does_not_exist")}
|
||||
labelFor="create-lead-if-account-null">
|
||||
<Switch
|
||||
size="sm"
|
||||
labelOnLeading
|
||||
checked={createLeadIfAccountNull}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("createLeadIfAccountNull", checked);
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<div className="mt-4">
|
||||
<Switch
|
||||
label={t("on_booking_write_to_event_object")}
|
||||
labelOnLeading
|
||||
checked={onBookingWriteToEventObject}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("onBookingWriteToEventObject", checked);
|
||||
}}
|
||||
/>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="badge-check" title={t("on_booking_write_to_event_object")}>
|
||||
<Switch
|
||||
size="sm"
|
||||
labelOnLeading
|
||||
checked={onBookingWriteToEventObject}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("onBookingWriteToEventObject", checked);
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{onBookingWriteToEventObject ? (
|
||||
<div className="ml-2 mt-2">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>{t("field_name")}</div>
|
||||
<div>{t("value")}</div>
|
||||
<Section.SubSectionContent>
|
||||
<div className="text-subtle flex gap-3 px-3 py-[6px] text-sm font-medium">
|
||||
<div className="flex-1">{t("field_name")}</div>
|
||||
<div className="flex-1">{t("value")}</div>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
<div>
|
||||
<Section.SubSectionNested>
|
||||
{Object.keys(onBookingWriteToEventObjectMap).map((key) => (
|
||||
<div className="mt-2 grid grid-cols-3 gap-4" key={key}>
|
||||
<div>
|
||||
<InputField value={key} readOnly />
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<div className="flex-1">
|
||||
<InputField value={key} readOnly size="sm" className="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<InputField value={onBookingWriteToEventObjectMap[key]} readOnly />
|
||||
<div className="flex-1">
|
||||
<InputField
|
||||
value={onBookingWriteToEventObjectMap[key]}
|
||||
readOnly
|
||||
size="sm"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex w-10 justify-center">
|
||||
<Button
|
||||
StartIcon="trash"
|
||||
StartIcon="x"
|
||||
variant="icon"
|
||||
color="destructive"
|
||||
size="sm"
|
||||
color="minimal"
|
||||
onClick={() => {
|
||||
const newObject = onBookingWriteToEventObjectMap;
|
||||
delete onBookingWriteToEventObjectMap[key];
|
||||
@@ -205,9 +241,11 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-2 grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<div className="mt-2 flex gap-4">
|
||||
<div className="flex-1">
|
||||
<InputField
|
||||
size="sm"
|
||||
className="w-full"
|
||||
value={newOnBookingWriteToEventObjectField.field}
|
||||
onChange={(e) =>
|
||||
setNewOnBookingWriteToEventObjectField({
|
||||
@@ -217,8 +255,10 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-1">
|
||||
<InputField
|
||||
size="sm"
|
||||
className="w-full"
|
||||
value={newOnBookingWriteToEventObjectField.value}
|
||||
onChange={(e) =>
|
||||
setNewOnBookingWriteToEventObjectField({
|
||||
@@ -228,11 +268,13 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
</div>
|
||||
</Section.SubSectionNested>
|
||||
<Button
|
||||
className="mt-2"
|
||||
className="text-subtle mt-2 w-fit"
|
||||
size="sm"
|
||||
color="secondary"
|
||||
disabled={
|
||||
!(newOnBookingWriteToEventObjectField.field && newOnBookingWriteToEventObjectField.value)
|
||||
}
|
||||
@@ -255,11 +297,11 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}}>
|
||||
{t("add_new_field")}
|
||||
</Button>
|
||||
</div>
|
||||
</Section.SubSectionContent>
|
||||
) : null}
|
||||
</div>
|
||||
</Section.SubSection>
|
||||
|
||||
<div className="mt-4">
|
||||
<Section.SubSection>
|
||||
<WriteToObjectSettings
|
||||
bookingAction={BookingActionEnum.ON_BOOKING}
|
||||
optionLabel={t("salesforce_on_booking_write_to_record", { record: createEventOn })}
|
||||
@@ -270,89 +312,128 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}}
|
||||
updateWriteToObjectData={(data) => setAppData("onBookingWriteToRecordFields", data)}
|
||||
/>
|
||||
</div>
|
||||
</Section.SubSection>
|
||||
|
||||
<div className="mt-4">
|
||||
<Switch
|
||||
label="Change record owner on booking"
|
||||
labelOnLeading
|
||||
checked={onBookingChangeRecordOwner}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("onBookingChangeRecordOwner", checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{onBookingChangeRecordOwner ? (
|
||||
<div className="ml-2 mt-2">
|
||||
<p className="mb-2">{t("salesforce_owner_name_to_change")}</p>
|
||||
<InputField
|
||||
value={onBookingChangeRecordOwnerName}
|
||||
onChange={(e) => setAppData("onBookingChangeRecordOwnerName", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{eventType.schedulingType === SchedulingType.ROUND_ROBIN ? (
|
||||
<div className="mt-4">
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="user-plus"
|
||||
title={t("salesforce_change_record_owner_on_booking")}
|
||||
labelFor="change-record-owner-on-booking">
|
||||
<Switch
|
||||
label={t("salesforce_book_directly_with_attendee_owner")}
|
||||
size="sm"
|
||||
labelOnLeading
|
||||
checked={isRoundRobinLeadSkipEnabled}
|
||||
checked={onBookingChangeRecordOwner}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("roundRobinLeadSkip", checked);
|
||||
if (checked) {
|
||||
// temporary solution, enabled should always be already set
|
||||
setAppData("enabled", checked);
|
||||
}
|
||||
setAppData("onBookingChangeRecordOwner", checked);
|
||||
}}
|
||||
/>
|
||||
{isRoundRobinLeadSkipEnabled ? (
|
||||
<>
|
||||
<div className="my-4 ml-2">
|
||||
<label className="text-emphasis mb-2 align-text-top text-sm font-medium">
|
||||
{t("salesforce_check_owner_of")}
|
||||
</label>
|
||||
<Select
|
||||
className="mt-2 w-60"
|
||||
options={checkOwnerOptions}
|
||||
value={checkOwnerSelectedOption}
|
||||
onChange={(e) => {
|
||||
if (e) {
|
||||
setCheckOwnerSelectedOption(e);
|
||||
setAppData("roundRobinSkipCheckRecordOn", e.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{checkOwnerSelectedOption.value === SalesforceRecordEnum.CONTACT ? (
|
||||
<div className="my-4">
|
||||
<Switch
|
||||
label={t("salesforce_round_robin_skip_fallback_to_lead_owner")}
|
||||
labelOnLeading
|
||||
checked={roundRobinSkipFallbackToLeadOwner}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("roundRobinSkipFallbackToLeadOwner", checked);
|
||||
</Section.SubSectionHeader>
|
||||
{onBookingChangeRecordOwner ? (
|
||||
<Section.SubSectionContent classNames={{ container: "p-3" }}>
|
||||
<div>
|
||||
<Label
|
||||
htmlFor="on-booking-change-record-owner-name"
|
||||
className="text-subtle text-sm font-medium">
|
||||
{t("salesforce_owner_name_to_change")}
|
||||
</Label>
|
||||
<InputField
|
||||
id="on-booking-change-record-owner-name"
|
||||
size="sm"
|
||||
value={onBookingChangeRecordOwnerName}
|
||||
onChange={(e) => setAppData("onBookingChangeRecordOwnerName", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Section.SubSectionContent>
|
||||
) : null}
|
||||
</Section.SubSection>
|
||||
|
||||
{eventType.schedulingType === SchedulingType.ROUND_ROBIN ? (
|
||||
<>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="users"
|
||||
title={t("salesforce_book_directly_with_attendee_owner")}
|
||||
labelFor="book-directly-with-attendee-owner">
|
||||
<Switch
|
||||
size="sm"
|
||||
id="book-directly-witha-attendee-owner"
|
||||
checked={isRoundRobinLeadSkipEnabled}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("roundRobinLeadSkip", checked);
|
||||
if (checked) {
|
||||
// temporary solution, enabled should always be already set
|
||||
setAppData("enabled", checked);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isRoundRobinLeadSkipEnabled ? (
|
||||
<Section.SubSectionContent classNames={{ container: "p-3" }}>
|
||||
<div>
|
||||
<Label
|
||||
htmlFor="round-robin-skip-check-record-on"
|
||||
className="text-subtle text-sm font-medium">
|
||||
{t("salesforce_check_owner_of")}
|
||||
</Label>
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-60"
|
||||
options={checkOwnerOptions}
|
||||
value={checkOwnerSelectedOption}
|
||||
onChange={(e) => {
|
||||
if (e) {
|
||||
setCheckOwnerSelectedOption(e);
|
||||
setAppData("roundRobinSkipCheckRecordOn", e.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="my-4">
|
||||
<Switch
|
||||
label={t("salesforce_if_free_email_domain_skip_owner_check")}
|
||||
labelOnLeading
|
||||
checked={ifFreeEmailDomainSkipOwnerCheck}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("ifFreeEmailDomainSkipOwnerCheck", checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<Alert className="mt-2" severity="neutral" title={t("skip_rr_description")} />
|
||||
</div>
|
||||
</Section.SubSectionContent>
|
||||
) : null}
|
||||
</Section.SubSection>
|
||||
<>
|
||||
{isRoundRobinLeadSkipEnabled ? (
|
||||
<>
|
||||
{checkOwnerSelectedOption.value === SalesforceRecordEnum.CONTACT ? (
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="users"
|
||||
title={t("salesforce_round_robin_skip_fallback_to_lead_owner")}
|
||||
labelFor="round-robin-skip-fallback-to-lead-owner">
|
||||
<Switch
|
||||
id="round-robin-skip-fallback-to-lead-owner"
|
||||
size="sm"
|
||||
checked={roundRobinSkipFallbackToLeadOwner}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("roundRobinSkipFallbackToLeadOwner", checked);
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
) : null}
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="users"
|
||||
title={t("salesforce_if_free_email_domain_skip_owner_check")}
|
||||
labelFor="if-free-email-domain-skip-owner-check">
|
||||
<Switch
|
||||
id="if-free-email-domain-skip-owner-check"
|
||||
size="sm"
|
||||
checked={ifFreeEmailDomainSkipOwnerCheck}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("ifFreeEmailDomainSkipOwnerCheck", checked);
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
<Alert severity="info" title={t("skip_rr_description")} />
|
||||
</Section.SubSection>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<div className="mt-4">
|
||||
<Section.SubSection>
|
||||
<WriteToObjectSettings
|
||||
bookingAction={BookingActionEnum.ON_CANCEL}
|
||||
optionLabel={t("salesforce_on_cancel_write_to_event")}
|
||||
@@ -363,27 +444,39 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}}
|
||||
updateWriteToObjectData={(data) => setAppData("onCancelWriteToEventRecordFields", data)}
|
||||
/>
|
||||
</div>
|
||||
</Section.SubSection>
|
||||
|
||||
<div className="ml-2 mt-4">
|
||||
<Switch
|
||||
label="Send no show attendee data to event object"
|
||||
checked={sendNoShowAttendeeData}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("sendNoShowAttendeeData", checked);
|
||||
}}
|
||||
/>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader
|
||||
icon="calendar"
|
||||
title="Send no show attendee data to event object"
|
||||
labelFor="send-no-show-attendee-data">
|
||||
<Switch
|
||||
id="send-no-show-attendee-data"
|
||||
size="sm"
|
||||
checked={sendNoShowAttendeeData}
|
||||
onCheckedChange={(checked) => {
|
||||
setAppData("sendNoShowAttendeeData", checked);
|
||||
}}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{sendNoShowAttendeeData ? (
|
||||
<div className="mt-2">
|
||||
<p className="mb-2">Field name to check (must be checkbox data type)</p>
|
||||
<Section.SubSectionContent classNames={{ container: "p-3" }}>
|
||||
<Label
|
||||
htmlFor="send-no-show-attendee-data-field-name"
|
||||
className="text-subtle text-sm font-medium">
|
||||
Field name to check (must be checkbox data type)
|
||||
</Label>
|
||||
<InputField
|
||||
id="send-no-show-attendee-data-field-name"
|
||||
size="sm"
|
||||
value={sendNoShowAttendeeDataField}
|
||||
onChange={(e) => setAppData("sendNoShowAttendeeDataField", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Section.SubSectionContent>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
</Section.SubSection>
|
||||
</Section.Content>
|
||||
</AppCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button } from "@calcom/ui/components/button";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { InputField } from "@calcom/ui/components/form";
|
||||
import { Select } from "@calcom/ui/components/form";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
import { WhenToWriteToRecord, SalesforceFieldType, DateFieldTypeData } from "../../lib/enums";
|
||||
@@ -81,37 +82,45 @@ const WriteToObjectSettings = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Switch
|
||||
label={optionLabel}
|
||||
labelOnLeading
|
||||
checked={optionEnabled}
|
||||
onCheckedChange={optionSwitchOnChange}
|
||||
/>
|
||||
<Section.SubSectionHeader icon="star" labelFor="write-to-object-settings" title={optionLabel}>
|
||||
<Switch
|
||||
checked={optionEnabled}
|
||||
onCheckedChange={optionSwitchOnChange}
|
||||
id="write-to-object-settings"
|
||||
size="sm"
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
|
||||
{optionEnabled ? (
|
||||
<div className="ml-2 mt-2">
|
||||
<div className="grid grid-cols-5 gap-4">
|
||||
<div>{t("field_name")}</div>
|
||||
<div>{t("field_type")}</div>
|
||||
<div>{t("value")}</div>
|
||||
<div>{t("when_to_write")}</div>
|
||||
<Section.SubSectionContent>
|
||||
<div className="text-subtle flex gap-3 px-3 py-[6px] text-sm font-medium">
|
||||
<div className="flex-1">{t("field_name")}</div>
|
||||
<div className="flex-1">{t("field_type")}</div>
|
||||
<div className="flex-1">{t("value")}</div>
|
||||
<div className="flex-1">{t("when_to_write")}</div>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
<div>
|
||||
<Section.SubSectionNested>
|
||||
{Object.keys(writeToObjectData).map((key) => (
|
||||
<div className="mt-2 grid grid-cols-5 gap-4" key={key}>
|
||||
<div>
|
||||
<InputField value={key} readOnly />
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<div className="flex-1">
|
||||
<InputField value={key} readOnly size="sm" className="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-1">
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
value={fieldTypeOptions.find(
|
||||
(option) => option.value === writeToObjectData[key].fieldType
|
||||
)}
|
||||
isDisabled={true}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-1">
|
||||
{writeToObjectData[key].fieldType === SalesforceFieldType.DATE ? (
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
value={dateFieldValueOptions.find(
|
||||
(option) => option.value === writeToObjectData[key].value
|
||||
)}
|
||||
@@ -119,28 +128,38 @@ const WriteToObjectSettings = ({
|
||||
/>
|
||||
) : writeToObjectData[key].fieldType === SalesforceFieldType.CHECKBOX ? (
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
value={checkboxFieldValueOptions.find(
|
||||
(option) => option.value === writeToObjectData[key].value
|
||||
)}
|
||||
isDisabled={true}
|
||||
/>
|
||||
) : (
|
||||
<InputField value={writeToObjectData[key].value as string} readOnly />
|
||||
<InputField
|
||||
value={writeToObjectData[key].value as string}
|
||||
readOnly
|
||||
size="sm"
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-1">
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
value={whenToWriteToRecordOptions.find(
|
||||
(option) => option.value === writeToObjectData[key].whenToWrite
|
||||
)}
|
||||
isDisabled={true}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex w-10 justify-center">
|
||||
<Button
|
||||
StartIcon="trash"
|
||||
size="sm"
|
||||
StartIcon="x"
|
||||
variant="icon"
|
||||
color="destructive"
|
||||
color="minimal"
|
||||
onClick={() => {
|
||||
const newObject = writeToObjectData;
|
||||
delete writeToObjectData[key];
|
||||
@@ -150,9 +169,11 @@ const WriteToObjectSettings = ({
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-2 grid grid-cols-5 gap-4">
|
||||
<div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<InputField
|
||||
size="sm"
|
||||
className="w-full"
|
||||
value={newOnWriteToRecordEntry.field}
|
||||
onChange={(e) =>
|
||||
setNewOnWriteToRecordEntry({
|
||||
@@ -162,8 +183,10 @@ const WriteToObjectSettings = ({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-1">
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
options={fieldTypeOptions}
|
||||
value={fieldTypeSelectedOption}
|
||||
onChange={(e) => {
|
||||
@@ -181,9 +204,11 @@ const WriteToObjectSettings = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-1">
|
||||
{newOnWriteToRecordEntry.fieldType === SalesforceFieldType.DATE ? (
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
options={dateFieldValueOptions}
|
||||
value={dateFieldSelectedOption}
|
||||
onChange={(e) => {
|
||||
@@ -198,6 +223,8 @@ const WriteToObjectSettings = ({
|
||||
/>
|
||||
) : newOnWriteToRecordEntry.fieldType === SalesforceFieldType.CHECKBOX ? (
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
options={checkboxFieldValueOptions}
|
||||
value={checkboxFieldSelectedOption}
|
||||
onChange={(e) => {
|
||||
@@ -212,6 +239,8 @@ const WriteToObjectSettings = ({
|
||||
/>
|
||||
) : (
|
||||
<InputField
|
||||
size="sm"
|
||||
className="w-full"
|
||||
value={newOnWriteToRecordEntry.value as string}
|
||||
onChange={(e) =>
|
||||
setNewOnWriteToRecordEntry({
|
||||
@@ -222,8 +251,10 @@ const WriteToObjectSettings = ({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-1">
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
options={whenToWriteToRecordOptions}
|
||||
value={whenToWriteSelectedOption}
|
||||
onChange={(e) => {
|
||||
@@ -237,10 +268,13 @@ const WriteToObjectSettings = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
</div>
|
||||
</Section.SubSectionNested>
|
||||
<Button
|
||||
className="mt-2"
|
||||
className="text-subtle mt-2 w-fit"
|
||||
StartIcon="plus"
|
||||
color="minimal"
|
||||
size="sm"
|
||||
disabled={
|
||||
!(
|
||||
@@ -274,7 +308,7 @@ const WriteToObjectSettings = ({
|
||||
}}>
|
||||
{t("add_new_field")}
|
||||
</Button>
|
||||
</div>
|
||||
</Section.SubSectionContent>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { trpc } from "@calcom/trpc/react";
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
export type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
|
||||
EventTypeAppCardComponentProps["eventType"];
|
||||
@@ -91,7 +92,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="before:border-0">
|
||||
<div className="flex flex-col gap-4 before:border-0">
|
||||
{(isManagedEventType || isChildrenManagedEventType) && (
|
||||
<Alert
|
||||
severity={appsDisableProps.isLocked ? "neutral" : "info"}
|
||||
@@ -151,14 +152,13 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{/* TODO: Add back after salesforce v3 dev */}
|
||||
{!appsDisableProps.disabled && (
|
||||
<div className="bg-muted mt-6 rounded-md p-8">
|
||||
<div className="bg-muted mt-4 rounded-2xl p-4">
|
||||
{!isPending && notInstalledApps?.length ? (
|
||||
<>
|
||||
<h2 className="text-emphasis mb-2 text-xl font-semibold leading-5 tracking-[0.01em]">
|
||||
{t("available_apps_lower_case")}
|
||||
</h2>
|
||||
<p className="text-default mb-6 text-sm font-normal">
|
||||
<div className="mb-4 flex flex-col">
|
||||
<Section.Title>{t("available_apps_lower_case")}</Section.Title>
|
||||
<Section.Description>
|
||||
<ServerTrans
|
||||
t={t}
|
||||
i18nKey="available_apps_desc"
|
||||
@@ -168,10 +168,10 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
</Link>,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
</Section.Description>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="bg-default border-subtle divide-subtle divide-y rounded-md border before:border-0">
|
||||
<div className="bg-default border-muted flex flex-col gap-4 rounded-xl border p-3">
|
||||
{notInstalledApps?.map((app) => (
|
||||
<EventTypeAppCard
|
||||
getAppData={getAppDataGetter(app.slug as EventTypeAppsList)}
|
||||
|
||||
@@ -52,8 +52,8 @@ export const Select = <
|
||||
styles={{
|
||||
control: (base) => ({
|
||||
...base,
|
||||
minHeight: size === "sm" ? "28px" : "36px",
|
||||
height: grow ? "auto" : size === "sm" ? "28px" : "36px",
|
||||
minHeight: size === "sm" ? "28px" : "32px",
|
||||
height: grow ? "auto" : size === "sm" ? "28px" : "32px",
|
||||
}),
|
||||
}}
|
||||
classNames={{
|
||||
@@ -67,7 +67,7 @@ export const Select = <
|
||||
innerClassNames?.option
|
||||
),
|
||||
placeholder: (state) => cx("text-muted", state.isFocused && variant !== "checkbox" && "hidden"),
|
||||
dropdownIndicator: () => "text-default",
|
||||
dropdownIndicator: () => cx("text-default w-4 h-4"),
|
||||
control: (state) =>
|
||||
cx(
|
||||
inputStyles({ size }),
|
||||
@@ -78,8 +78,8 @@ export const Select = <
|
||||
? "p-1 h-fit"
|
||||
: "px-3 h-fit"
|
||||
: size === "sm"
|
||||
? "h-7 px-2"
|
||||
: "h-9 px-3",
|
||||
? "h-7 px-2 py-1"
|
||||
: "h-8 px-3 py-2",
|
||||
props.isDisabled && "bg-subtle",
|
||||
"rounded-[10px]",
|
||||
"[&:focus-within]:border-emphasis [&:focus-within]:shadow-outline-gray-focused [&:focus-within]:ring-0 !flex",
|
||||
@@ -108,11 +108,12 @@ export const Select = <
|
||||
cx(
|
||||
state.selectProps.menuIsOpen
|
||||
? hasMultiLastIcons
|
||||
? "[&>*:last-child]:rotate-180 [&>*:last-child]:transition-transform"
|
||||
: "rotate-180 transition-transform"
|
||||
: "text-default" // Woo it adds another SVG here on multi for some reason
|
||||
? "[&>*:last-child]:rotate-180 [&>*:last-child]:transition-transform [&>*:last-child]:w-4 [&>*:last-child]:h-4"
|
||||
: "rotate-180 transition-transform w-4 h-4"
|
||||
: "text-default w-4 h-4"
|
||||
),
|
||||
multiValueRemove: () => "text-default py-auto",
|
||||
|
||||
...classNames,
|
||||
}}
|
||||
{...restProps}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./section";
|
||||
@@ -0,0 +1,230 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
|
||||
import cn from "../../classNames";
|
||||
import type { IconName } from "../icon";
|
||||
import { Icon as IconComponent } from "../icon";
|
||||
|
||||
const Root = ({
|
||||
children,
|
||||
ref,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [animateRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
return (
|
||||
<div ref={animateRef} className={cn("bg-muted flex flex-col gap-4 rounded-2xl p-4", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Title = ({
|
||||
children,
|
||||
ref,
|
||||
as,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
ref?: React.Ref<HTMLHeadingElement>;
|
||||
as?: React.ElementType;
|
||||
}) => {
|
||||
const Component = as ? Slot : "h2";
|
||||
return (
|
||||
<Component ref={ref} className="text-emphasis text-base font-semibold leading-none">
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
const Description = ({
|
||||
children,
|
||||
ref,
|
||||
as,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
ref?: React.Ref<HTMLHeadingElement>;
|
||||
as?: React.ElementType;
|
||||
}) => {
|
||||
const Component = as ? Slot : "h3";
|
||||
return (
|
||||
<Component ref={ref} className="text-subtle line-clamp-1 text-sm">
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
const Icon = ({
|
||||
ref,
|
||||
name,
|
||||
size = "md",
|
||||
iconSlot,
|
||||
}: {
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
size?: "sm" | "md";
|
||||
name?: IconName;
|
||||
iconSlot?: React.ReactNode;
|
||||
} & ({ name: IconName; iconSlot?: never } | { name?: never; iconSlot: React.ReactNode })) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-default border-subtle border-subtle flex items-center justify-center border",
|
||||
size === "sm" && "rounded-md p-1",
|
||||
size === "md" && "rounded-[10px] p-1.5"
|
||||
)}>
|
||||
{iconSlot ? (
|
||||
iconSlot
|
||||
) : (
|
||||
<IconComponent name={name!} className={cn(size === "sm" && "h-4 w-4", size === "md" && "h-6 w-6")} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = ({
|
||||
children,
|
||||
ref,
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
iconSlot,
|
||||
rawHeading,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
icon?: IconName;
|
||||
iconSlot?: React.ReactNode;
|
||||
title?: string;
|
||||
description?: string;
|
||||
rawHeading?: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
{icon && !iconSlot && <Icon name={icon} />}
|
||||
{iconSlot && <Icon iconSlot={iconSlot} />}
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{title && <Title>{title}</Title>}
|
||||
{description && <Description>{description}</Description>}
|
||||
{rawHeading && rawHeading}
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Content = ({ children, ref }: { children: React.ReactNode; ref?: React.Ref<HTMLDivElement> }) => {
|
||||
return (
|
||||
<div ref={ref} className="flex flex-col gap-4">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SubSection = ({ children, ref }: { children: React.ReactNode; ref?: React.Ref<HTMLDivElement> }) => {
|
||||
const [animateRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
return (
|
||||
<div ref={animateRef} className="border-muted bg-default flex flex-col gap-4 rounded-xl border p-3">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SubSectionHeader = ({
|
||||
children,
|
||||
ref,
|
||||
icon,
|
||||
title,
|
||||
classNames,
|
||||
justify = "between",
|
||||
labelFor,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
icon?: IconName;
|
||||
title: string;
|
||||
labelFor?: string;
|
||||
classNames?: {
|
||||
container?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
};
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
justify?: "between" | "start";
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex items-center gap-2",
|
||||
justify === "between" ? "justify-between" : "justify-start",
|
||||
classNames?.container
|
||||
)}>
|
||||
<div className={cn("flex items-center gap-2", classNames?.title)}>
|
||||
{icon && <Icon name={icon} size="sm" />}
|
||||
<div className={cn("flex", classNames?.title)}>
|
||||
<h4 className="text-default text-sm font-medium leading-none" id={labelFor}>
|
||||
{title}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SubSectionContent = ({
|
||||
children,
|
||||
ref,
|
||||
invert,
|
||||
classNames,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
classNames?: {
|
||||
container?: string;
|
||||
};
|
||||
invert?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-muted flex flex-col rounded-lg px-[6px] py-1",
|
||||
invert && "bg-default border-subtle border",
|
||||
classNames?.container
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SubSectionNested = ({
|
||||
children,
|
||||
ref,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
}) => {
|
||||
return (
|
||||
<div ref={ref} className="bg-default border-subtle flex flex-col gap-2 rounded-xl border p-2">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Section = Object.assign(Root, {
|
||||
Header: Header,
|
||||
Content: Content,
|
||||
Title: Title,
|
||||
Description: Description,
|
||||
SubSection: SubSection,
|
||||
SubSectionHeader: SubSectionHeader,
|
||||
SubSectionContent: SubSectionContent,
|
||||
SubSectionNested: SubSectionNested,
|
||||
});
|
||||
@@ -55,6 +55,7 @@
|
||||
"./components/unpublished-entity": "./components/unpublished-entity/index.ts",
|
||||
"./components/table/TableNew": "./components/table/TableNew.tsx",
|
||||
"./components/app-list-card/AppListCard": "./components/app-list-card/AppListCard.tsx",
|
||||
"./components/section": "./components/section/index.ts",
|
||||
"./__mocks__/ui": "./__mocks__/ui.tsx"
|
||||
},
|
||||
"license": "MIT",
|
||||
|
||||
Reference in new issue
Block a user