diff --git a/airbyte-webapp/src/components/ui/Button/types.tsx b/airbyte-webapp/src/components/ui/Button/types.tsx index 09e46b431047..f05e9a6d5f99 100644 --- a/airbyte-webapp/src/components/ui/Button/types.tsx +++ b/airbyte-webapp/src/components/ui/Button/types.tsx @@ -1,7 +1,7 @@ import React from "react"; type ButtonSize = "xs" | "sm" | "lg"; -type ButtonVariant = "primary" | "secondary" | "danger" | "light" | "clear" | "dark"; +export type ButtonVariant = "primary" | "secondary" | "danger" | "light" | "clear" | "dark"; export interface ButtonProps extends React.ButtonHTMLAttributes { clickable?: boolean; diff --git a/airbyte-webapp/src/hooks/services/Experiment/experiments.ts b/airbyte-webapp/src/hooks/services/Experiment/experiments.ts index 8fdc7c54e01d..86806f34a0f1 100644 --- a/airbyte-webapp/src/hooks/services/Experiment/experiments.ts +++ b/airbyte-webapp/src/hooks/services/Experiment/experiments.ts @@ -28,4 +28,5 @@ export interface Experiments { "connection.autoDetectSchemaChanges": boolean; "connection.columnSelection": boolean; "connection.newTableDesign": boolean; + "workspace.freeConnectorsProgram.visible": boolean; } diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index 7669fa6b29f4..f14cf07b329c 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -743,5 +743,12 @@ "jobs.noAttemptsFailure": "Failed to start job.", - "cloudApi.loginCallbackUrlError": "There was an error connecting to the developer portal. Please try again." + "cloudApi.loginCallbackUrlError": "There was an error connecting to the developer portal. Please try again.", + + "freeConnectorProgram.enrollmentModal.title": "Free connector program", + "freeConnectorProgram.enrollmentModal.free": "Alpha and Beta Connectors are free while you're in the program.The whole Connection is free until both Connectors have move into General Availability (GA)", + "freeConnectorProgram.enrollmentModal.emailNotification": "We will let you know through email before a Connector you use moves to GA", + "freeConnectorProgram.enrollmentModal.cardOnFile": "When both Connectors are in GA, the Connection will no longer be free. You'll need to have a credit card on file to enroll so Airbyte can handle a Connection's transition to paid service.", + "freeConnectorProgram.enrollmentModal.cancelButtonText": "Cancel", + "freeConnectorProgram.enrollmentModal.enrollButtonText": "Enroll now!" } diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/EnrollmentModal.module.scss b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/EnrollmentModal.module.scss new file mode 100644 index 000000000000..c91b5cf89d8c --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/EnrollmentModal.module.scss @@ -0,0 +1,34 @@ +@use "scss/colors"; +@use "scss/variables"; + +.header { + overflow: hidden; + position: relative; + background: colors.$blue-100; + height: 120px; + border-top-left-radius: variables.$border-radius-lg; + border-top-right-radius: variables.$border-radius-lg; +} + +.headerBackgroundImageContainer { + position: absolute; + left: 50%; + transform: translateX(-50%); +} + +.pill { + z-index: 2; +} + +.contentWrapper { + width: variables.$width-modal-sm; + padding: 0 variables.$spacing-xl variables.$spacing-2xl; +} + +.contentHeader { + margin: variables.$spacing-xl 0; +} + +.iconContainer { + flex: 0 0 82px; +} diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/EnrollmentModal.tsx b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/EnrollmentModal.tsx new file mode 100644 index 000000000000..066f8af84f4d --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/EnrollmentModal.tsx @@ -0,0 +1,123 @@ +import React, { useEffect, useRef, useState } from "react"; +import { FormattedMessage } from "react-intl"; + +import { Button } from "components/ui/Button"; +import { FlexContainer, FlexItem } from "components/ui/Flex"; +import { Heading } from "components/ui/Heading"; +import { ModalFooter } from "components/ui/Modal/ModalFooter"; +import { Text } from "components/ui/Text"; + +import { StripeCheckoutSessionCreate, StripeCheckoutSessionRead } from "packages/cloud/lib/domain/stripe"; + +import { ReactComponent as CardSVG } from "./cards.svg"; +import { ReactComponent as ConnectorGridSvg } from "./connectorGrid.svg"; +import styles from "./EnrollmentModal.module.scss"; +import { ReactComponent as FreeSVG } from "./free.svg"; +import { ReactComponent as MailSVG } from "./mail.svg"; + +const STRIPE_SUCCESS_QUERY = "stripeCheckoutSuccess"; + +interface EnrollmentModalContentProps { + closeModal: () => void; + createCheckout: (p: StripeCheckoutSessionCreate) => Promise; + workspaceId: string; +} + +export const EnrollmentModalContent: React.FC = ({ + closeModal, + createCheckout, + workspaceId, +}) => { + const isMountedRef = useRef(false); + const [isLoading, setIsLoading] = useState(false); + + const startStripeCheckout = async () => { + setIsLoading(true); + // Use the current URL as a success URL but attach the STRIPE_SUCCESS_QUERY to it + const successUrl = new URL(window.location.href); + successUrl.searchParams.set(STRIPE_SUCCESS_QUERY, "true"); + const { stripeUrl } = await createCheckout({ + workspaceId, + successUrl: successUrl.href, + cancelUrl: window.location.href, + stripeMode: "setup", + }); + + // Forward to stripe as soon as we created a checkout session successfully + if (isMountedRef.current) { + window.location.assign(stripeUrl); + } + }; + + // If the user closes the modal while the request is processing, we don't want to redirect them + useEffect(() => { + isMountedRef.current = true; + return () => { + isMountedRef.current = false; + }; + }, []); + + return ( + <> + +
+ +
+
Pill #1
+
Pill #2
+
+
+ + + + + + + + + + {content}, + p2: (content: React.ReactNode) => {content}, + }} + /> + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + ); +}; diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/cards.svg b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/cards.svg new file mode 100644 index 000000000000..a86f40a65816 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/cards.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/connectorGrid.svg b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/connectorGrid.svg new file mode 100644 index 000000000000..da8c9978e4e2 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/connectorGrid.svg @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/free.svg b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/free.svg new file mode 100644 index 000000000000..55c619b2c1d6 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/free.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/index.ts b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/index.ts new file mode 100644 index 000000000000..eb0c733b2117 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/index.ts @@ -0,0 +1 @@ +export * from "./useShowEnrollmentModal"; diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/mail.svg b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/mail.svg new file mode 100644 index 000000000000..d3c1f86a68a5 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/mail.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/useShowEnrollmentModal.tsx b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/useShowEnrollmentModal.tsx new file mode 100644 index 000000000000..95d89effb2b8 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/useShowEnrollmentModal.tsx @@ -0,0 +1,22 @@ +import { useModalService } from "hooks/services/Modal"; +import { useStripeCheckout } from "packages/cloud/services/stripe/StripeService"; +import { useCurrentWorkspaceId } from "services/workspaces/WorkspacesService"; + +import { EnrollmentModalContent } from "./EnrollmentModal"; + +export const useShowEnrollmentModal = () => { + const { openModal, closeModal } = useModalService(); + const { mutateAsync: createCheckout } = useStripeCheckout(); + const workspaceId = useCurrentWorkspaceId(); + + return { + showEnrollmentModal: () => { + openModal({ + title: null, + content: () => ( + + ), + }); + }, + }; +}; diff --git a/airbyte-webapp/src/packages/cloud/lib/domain/stripe/types.ts b/airbyte-webapp/src/packages/cloud/lib/domain/stripe/types.ts index 355aeedfe7ef..d8df4cb8e771 100644 --- a/airbyte-webapp/src/packages/cloud/lib/domain/stripe/types.ts +++ b/airbyte-webapp/src/packages/cloud/lib/domain/stripe/types.ts @@ -3,6 +3,7 @@ export interface StripeCheckoutSessionCreate { successUrl: string; cancelUrl: string; quantity?: number; + stripeMode: "payment" | "setup"; } export interface StripeCheckoutSessionRead { diff --git a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/RemainingCredits.tsx b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/RemainingCredits.tsx index 2ca1824542df..21e2944019e1 100644 --- a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/RemainingCredits.tsx +++ b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/RemainingCredits.tsx @@ -109,6 +109,7 @@ const RemainingCredits: React.FC = ({ selfServiceCheckoutEnabled }) => { workspaceId: currentWorkspace.workspaceId, successUrl: successUrl.href, cancelUrl: window.location.href, + stripeMode: "payment", }); analytics.track(Namespace.CREDITS, Action.CHECKOUT_START, { actionDescription: "Checkout Start",