Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: added premium tags datasources for knowing users request to add new integrations #38110

Merged
merged 15 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
10 changes: 10 additions & 0 deletions app/client/src/ce/constants/PremiumDatasourcesConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const PREMIUM_INTEGRATIONS = [
{
name: "Zendesk",
icon: "https://assets.appsmith.com/zendesk-icon.png",
},
{
name: "Salesforce",
icon: "https://assets.appsmith.com/salesforce-icon.png",
},
];
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 @@ -2582,3 +2582,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.",
LEARN_MORE: () => "Learn more about Premium",
SCHEDULE_CALL: () => "Schedule a call",
SUBMIT: () => "Submit",
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",
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. ",
NOTIFY_ME: () => "Notify me",
};
2 changes: 2 additions & 0 deletions app/client/src/ce/entities/FeatureFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const FEATURE_FLAG = {
"release_table_html_column_type_enabled",
release_gs_all_sheets_options_enabled:
"release_gs_all_sheets_options_enabled",
ab_premium_datasources_view_enabled: "ab_premium_datasources_view_enabled",
} as const;

export type FeatureFlag = keyof typeof FEATURE_FLAG;
Expand Down Expand Up @@ -89,6 +90,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
release_evaluation_scope_cache: false,
release_table_html_column_type_enabled: false,
release_gs_all_sheets_options_enabled: false,
ab_premium_datasources_view_enabled: false,
};

export const AB_TESTING_EVENT_KEYS = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
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, useSelector } 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 } from "utils/formhelpers";
import ReduxFormTextField from "components/utils/ReduxFormTextField";
import { PRICING_PAGE_URL } from "constants/ThirdPartyConstants";
import { getAppsmithConfigs } from "ee/configs";
import { getInstanceId, isFreePlan } from "ee/selectors/tenantSelectors";
import { pricingPageUrlSource } from "ee/utils/licenseHelpers";
import { RampFeature, RampSection } from "utils/ProductRamps/RampsControlList";
import {
getContactFormModalDescription,
getContactFormModalTitle,
getContactFormSubmitButtonText,
handleLearnMoreClick,
handleSubmitEvent,
shouldLearnMoreButtonBeVisible,
} from "ee/utils/PremiumDatasourcesHelpers";

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

const PremiumDatasourceContactForm = (
props: PremiumDatasourceContactFormProps,
) => {
const instanceId = useSelector(getInstanceId);
const appsmithConfigs = getAppsmithConfigs();
const isFreePlanInstance = useSelector(isFreePlan);

const redirectPricingURL = PRICING_PAGE_URL(
appsmithConfigs.pricingUrl,
pricingPageUrlSource,
instanceId,
RampFeature.PremiumDatasources,
RampSection.PremiumDatasourcesContactModal,
);

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

const onClickLearnMore = useCallback(() => {
handleLearnMoreClick(
props.integrationName,
props.email || "",
redirectPricingURL,
);
}, [redirectPricingURL, props.email, props.integrationName]);

const submitEvent = useCallback(() => {
handleSubmitEvent(
props.integrationName,
props.email || "",
!isFreePlanInstance,
);
}, [props.email, props.integrationName, isFreePlanInstance]);

return (
<>
<ModalHeader>
{getContactFormModalTitle(props.integrationName, !isFreePlanInstance)}
</ModalHeader>
<FormWrapper onSubmit={props.handleSubmit(onSubmit)}>
<p>
{getContactFormModalDescription(
props.email || "",
!isFreePlanInstance,
)}
</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">
{shouldLearnMoreButtonBeVisible(!isFreePlanInstance) && (
<Button
aria-label="Learn more"
kind="secondary"
onClick={onClickLearnMore}
size="md"
>
{createMessage(PREMIUM_DATASOURCES.LEARN_MORE)}
</Button>
)}
<Button isDisabled={props.invalid} size="md" type="submit">
{getContactFormSubmitButtonText(
props.email || "",
!isFreePlanInstance,
)}
</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;
} & InjectedFormProps<
PremiumDatasourceContactFormValues,
{
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
integrationName: string;
}
>;

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

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

return errors;
};
AmanAgarwal041 marked this conversation as resolved.
Show resolved Hide 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;
}
>({
validate,
form: PREMIUM_INTEGRATION_CONTACT_FORM,
enableReinitialize: true,
})(PremiumDatasourceContactForm),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useState } from "react";
import {
ApiCard,
CardContentWrapper,
} from "../../../../pages/Editor/IntegrationEditor/NewApi";
import { getAssetUrl } from "ee/utils/airgapHelpers";
import { Modal, ModalContent, Tag } from "@appsmith/ads";
import styled from "styled-components";
import ContactForm from "./ContactForm";
import { PREMIUM_INTEGRATIONS } from "ee/constants/PremiumDatasourcesConstants";
import {
getTagText,
handlePremiumDatasourceClick,
} from "ee/utils/PremiumDatasourcesHelpers";
import { isFreePlan } from "ee/selectors/tenantSelectors";
import { useSelector } from "react-redux";

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

export default function PremiumDatasources() {
const [selectedIntegration, setSelectedIntegration] = useState<string>("");
const isFreePlanInstance = useSelector(isFreePlan);
const handleOnClick = (name: string) => {
handlePremiumDatasourceClick(name, !isFreePlanInstance);
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"}>
{getTagText(!isFreePlanInstance)}
</Tag>
</CardContentWrapper>
</ApiCard>
))}
<Modal onOpenChange={onOpenChange} open={!!selectedIntegration}>
<ModalContentWrapper>
<ContactForm
closeModal={() => setSelectedIntegration("")}
integrationName={selectedIntegration}
/>
</ModalContentWrapper>
</Modal>
</>
);
}
Loading
Loading