Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions app/client/src/ce/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2578,3 +2578,28 @@ export const REQUEST_NEW_INTEGRATIONS = {
},
SUCCESS_TOAST_MESSAGE: () => "Thank you! We are looking into your request.",
};

export const PREMIUM_DATASOURCES = {
RELEVANT_EMAIL_DESCRIPTION: () =>
"Unblock advanced integrations. Let our team guide you in selecting the plan that fits your needs. Schedule a call now to see how Appsmith can transform your workflows!",
NON_RELEVANT_EMAIL_DESCRIPTION: () =>
"Unblock advanced integrations. Let our team guide you in selecting the plan that fits your needs. Give us your email and the Appsmith team will reach out to you soon.",
COMING_SOON_SUFFIX: () => "Coming soon",
COMING_SOON_DESCRIPTION: () =>
"The Appsmith Team is actively working on it. We’ll let you know when this integration is live. ",
LEARN_MORE: () => "Learn more about Premium",
SCHEDULE_CALL: () => "Schedule a call",
SUBMIT: () => "Submit",
NOTIFY_ME: () => "Notify me",
SUCCESS_TOAST_MESSAGE: () =>
"Thank you! The Appsmith Team will contact you shortly.",
FORM_EMAIL: {
LABEL: () => "Email",
DESCRIPTION: () =>
"Appsmith might use this email to follow up on your integration interest.",
NAME: "email",
ERROR: () => "Please enter email",
},
PREMIUM_TAG: () => "Premium",
SOON_TAG: () => "Soon",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { Button, Flex, ModalHeader, toast } from "@appsmith/ads";
import { createMessage, PREMIUM_DATASOURCES } from "ee/constants/messages";
import type { AppState } from "ee/reducers";
import React, { useCallback } from "react";
import { connect } from "react-redux";
import {
Field,
formValueSelector,
getFormSyncErrors,
reduxForm,
type FormErrors,
type InjectedFormProps,
} from "redux-form";
import { getCurrentUser } from "selectors/usersSelectors";
import styled from "styled-components";
import { isEmail, isRelevantEmail } from "utils/formhelpers";
import ReduxFormTextField from "components/utils/ReduxFormTextField";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { ENTERPRISE_PRICING_PAGE } from "constants/ThirdPartyConstants";

const FormWrapper = styled.form`
display: flex;
flex-direction: column;
gap: var(--ads-spaces-7);
`;

const PremiumDatasourceContactForm = (
props: PremiumDatasourceContactFormProps,
) => {
const onSubmit = () => {
submitEvent();
toast.show(createMessage(PREMIUM_DATASOURCES.SUCCESS_TOAST_MESSAGE), {
kind: "success",
});
props.closeModal();
};
Comment thread
AmanAgarwal041 marked this conversation as resolved.

const validRelevantEmail = isRelevantEmail(props.email || "");

const onClickLearnMore = useCallback(() => {
AnalyticsUtil.logEvent(
validRelevantEmail
? "PREMIUM_MODAL_RELEVANT_LEARN_MORE"
: "PREMIUM_MODAL_NOT_RELEVANT_LEARN_MORE",
{
integration_name: props.integrationName,
email: props.email,
},
);
window.open(ENTERPRISE_PRICING_PAGE, "_blank");
}, [props.email, props.integrationName]);

const submitEvent = useCallback(() => {
AnalyticsUtil.logEvent(
props.isEnterprise
? "SOON_NOTIFY_REQUEST"
: validRelevantEmail
? "PREMIUM_MODAL_RELEVANT_SCHEDULE_CALL"
: "PREMIUM_MODAL_NOT_RELEVANT_SUBMIT",
{
integration_name: props.integrationName,
email: props.email,
},
);
}, [props.email, props.integrationName, props.isEnterprise]);

return (
<>
<ModalHeader>{`${props.integrationName} ${props.isEnterprise ? `- ${createMessage(PREMIUM_DATASOURCES.COMING_SOON_SUFFIX)}` : ""}`}</ModalHeader>
<FormWrapper onSubmit={props.handleSubmit(onSubmit)}>
<p>
{props.isEnterprise
? createMessage(PREMIUM_DATASOURCES.COMING_SOON_DESCRIPTION)
: validRelevantEmail
? createMessage(PREMIUM_DATASOURCES.RELEVANT_EMAIL_DESCRIPTION)
: createMessage(
PREMIUM_DATASOURCES.NON_RELEVANT_EMAIL_DESCRIPTION,
)}
</p>
<Field
component={ReduxFormTextField}
description={createMessage(
PREMIUM_DATASOURCES.FORM_EMAIL.DESCRIPTION,
)}
label={createMessage(PREMIUM_DATASOURCES.FORM_EMAIL.LABEL)}
name={PREMIUM_DATASOURCES.FORM_EMAIL.NAME}
size="md"
type="email"
/>
<Flex gap="spaces-7" justifyContent="flex-end" marginTop="spaces-3">
{!props.isEnterprise && (
<Button
aria-label="Close"
kind="secondary"
Comment thread
AmanAgarwal041 marked this conversation as resolved.
Outdated
onClick={onClickLearnMore}
size="md"
>
{createMessage(PREMIUM_DATASOURCES.LEARN_MORE)}
</Button>
)}
<Button isDisabled={props.invalid} size="md" type="submit">
{props.isEnterprise
? createMessage(PREMIUM_DATASOURCES.NOTIFY_ME)
: validRelevantEmail
? createMessage(PREMIUM_DATASOURCES.SCHEDULE_CALL)
: createMessage(PREMIUM_DATASOURCES.SUBMIT)}
</Button>
</Flex>
</FormWrapper>
</>
);
};

const PREMIUM_INTEGRATION_CONTACT_FORM = "PREMIUM_INTEGRATION_CONTACT_FORM";

const selector = formValueSelector(PREMIUM_INTEGRATION_CONTACT_FORM);

interface PremiumDatasourceContactFormValues {
email?: string;
}

type PremiumDatasourceContactFormProps = PremiumDatasourceContactFormValues & {
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
integrationName: string;
isEnterprise: boolean;
} & InjectedFormProps<
PremiumDatasourceContactFormValues,
{
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
integrationName: string;
isEnterprise: boolean;
}
>;

const validate = (values: PremiumDatasourceContactFormValues) => {
const errors: Partial<PremiumDatasourceContactFormValues> = {};

if (!values.email || !isEmail(values.email)) {
errors.email = createMessage(PREMIUM_DATASOURCES.FORM_EMAIL.ERROR);
}

return errors;
};
Comment thread
AmanAgarwal041 marked this conversation as resolved.

export default connect((state: AppState) => {
const currentUser = getCurrentUser(state);

return {
email: selector(state, "email"),
initialValues: {
email: currentUser?.email,
},
formSyncErrors: getFormSyncErrors(PREMIUM_INTEGRATION_CONTACT_FORM)(state),
};
}, null)(
reduxForm<
PremiumDatasourceContactFormValues,
{
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
integrationName: string;
isEnterprise: boolean;
}
>({
validate,
form: PREMIUM_INTEGRATION_CONTACT_FORM,
enableReinitialize: true,
})(PremiumDatasourceContactForm),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useState } from "react";
import {
ApiCard,
CardContentWrapper,
} from "../../../../pages/Editor/IntegrationEditor/NewApi";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { getAssetUrl } from "ee/utils/airgapHelpers";
import { Modal, ModalContent, Tag } from "@appsmith/ads";
import styled from "styled-components";
import ContactForm from "./ContactForm";
import { createMessage, PREMIUM_DATASOURCES } from "ee/constants/messages";

const PREMIUM_INTEGRATIONS = [
{
name: "Zendesk",
icon: "https://assets.appsmith.com/zendesk-icon.png",
},
{
name: "Salesforce",
icon: "https://assets.appsmith.com/salesforce-icon.png",
},
];
Comment thread
AmanAgarwal041 marked this conversation as resolved.
Outdated

const ModalContentWrapper = styled(ModalContent)`
max-width: 518px;
`;

export default function PremiumDatasources({
isEnterprise,
}: {
isEnterprise?: boolean;
}) {
const [selectedIntegration, setSelectedIntegration] = useState<string>("");
const handleOnClick = (name: string) => {
AnalyticsUtil.logEvent(
isEnterprise ? "SOON_INTEGRATION_CTA" : "PREMIUM_INTEGRATION_CTA",
{
integration_name: name,
},
);
setSelectedIntegration(name);
};

const onOpenChange = (isOpen: boolean) => {
if (!isOpen) {
setSelectedIntegration("");
}
};

return (
<>
{PREMIUM_INTEGRATIONS.map((integration) => (
<ApiCard
className={`t--create-${integration.name}`}
key={integration.name}
onClick={() => {
handleOnClick(integration.name);
}}
>
<CardContentWrapper>
<img
alt={integration.name}
className={"content-icon saasImage"}
src={getAssetUrl(integration.icon)}
/>
<p className="t--plugin-name textBtn">{integration.name}</p>
<Tag isClosable={false} kind={"premium"}>
{isEnterprise
? createMessage(PREMIUM_DATASOURCES.SOON_TAG)
: createMessage(PREMIUM_DATASOURCES.PREMIUM_TAG)}
</Tag>
</CardContentWrapper>
</ApiCard>
))}
<Modal onOpenChange={onOpenChange} open={!!selectedIntegration}>
<ModalContentWrapper>
<ContactForm
closeModal={() => setSelectedIntegration("")}
integrationName={selectedIntegration}
isEnterprise={!!isEnterprise}
/>
</ModalContentWrapper>
</Modal>
</>
);
}
12 changes: 11 additions & 1 deletion app/client/src/ce/utils/analyticsUtilTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ export type EventName =
| "MALFORMED_USAGE_PULSE"
| "REQUEST_INTEGRATION_CTA"
| "REQUEST_INTEGRATION_SUBMITTED"
| "TABLE_WIDGET_V2_HTML_CELL_USAGE";
| "TABLE_WIDGET_V2_HTML_CELL_USAGE"
| PREMIUM_DATASOURCES_EVENTS;

type HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS =
| "TEMPLATE_DROPDOWN_CLICK"
Expand Down Expand Up @@ -469,3 +470,12 @@ export type CUSTOM_WIDGET_EVENTS =
| "CUSTOM_WIDGET_BUILDER_DEBUGGER_VISIBILITY_CHANGED"
| "CUSTOM_WIDGET_API_TRIGGER_EVENT"
| "CUSTOM_WIDGET_API_UPDATE_MODEL";

export type PREMIUM_DATASOURCES_EVENTS =
| "PREMIUM_INTEGRATION_CTA"
| "PREMIUM_MODAL_RELEVANT_LEARN_MORE"
| "PREMIUM_MODAL_NOT_RELEVANT_LEARN_MORE"
| "PREMIUM_MODAL_RELEVANT_SCHEDULE_CALL"
| "PREMIUM_MODAL_NOT_RELEVANT_SUBMIT"
| "SOON_INTEGRATION_CTA"
| "SOON_NOTIFY_REQUEST";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "ce/pages/IntegrationEditor/PremiumDatasources";
import { default as PremiumDatasources } from "ce/pages/IntegrationEditor/PremiumDatasources";
export default PremiumDatasources;
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import AIDataSources from "./AIDataSources";
import Debugger from "../DataSourceEditor/Debugger";
import { isPluginActionCreating } from "PluginActionEditor/store";
import RequestNewIntegration from "./RequestNewIntegration";
import PremiumDatasources from "ee/pages/IntegrationEditor/PremiumDatasources";

const NewIntegrationsContainer = styled.div`
${thinScrollbar};
Expand Down Expand Up @@ -170,7 +171,9 @@ function CreateNewDatasource({
parentEntityType={parentEntityType}
showMostPopularPlugins={showMostPopularPlugins}
showUnsupportedPluginDialog={showUnsupportedPluginDialog}
/>
>
{showMostPopularPlugins && <PremiumDatasources />}
</NewQueryScreen>
</div>
);
}
Expand Down Expand Up @@ -354,7 +357,7 @@ class CreateNewDatasourceTab extends React.Component<
</>
)}
</NewIntegrationsContainer>
{isRequestNewIntegrationEnabled && <RequestNewIntegration />}
{true && <RequestNewIntegration />}
Comment thread
AmanAgarwal041 marked this conversation as resolved.
Outdated
{showDebugger && <Debugger />}
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { type ReactNode } from "react";
import styled from "styled-components";
import { connect } from "react-redux";
import { initialize } from "redux-form";
Expand Down Expand Up @@ -132,6 +132,7 @@ interface DatasourceHomeScreenProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
showUnsupportedPluginDialog: (callback: any) => void;
isAirgappedInstance?: boolean;
children?: ReactNode;
}

interface ReduxDispatchProps {
Expand Down Expand Up @@ -294,6 +295,7 @@ class DatasourceHomeScreen extends React.Component<Props> {
</DatasourceCard>
);
})}
{this.props.children}
</DatasourceCardsContainer>
</DatasourceHomePage>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class QueryHomeScreen extends React.Component<QueryHomeScreenProps> {
parentEntityType={parentEntityType}
showMostPopularPlugins={showMostPopularPlugins}
showUnsupportedPluginDialog={showUnsupportedPluginDialog}
/>
>
{this.props.children}
</DataSourceHome>
</QueryHomePage>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function RequestModal({ children }: { children: ReactNode }) {

export default function RequestNewIntegration() {
return (
<RequestNewIntegrationWrapper gap="spaces-5">
<RequestNewIntegrationWrapper gap="spaces-5" justifyContent="flex-end">
<p>{createMessage(REQUEST_NEW_INTEGRATIONS.UNABLE_TO_FIND)}</p>
<RequestModal>
<Button
Expand Down
Loading