diff --git a/airbyte-webapp/src/components/ui/Toast/Toast.module.scss b/airbyte-webapp/src/components/ui/Toast/Toast.module.scss index 42d4ac5840fe7..d14a776aa30b1 100644 --- a/airbyte-webapp/src/components/ui/Toast/Toast.module.scss +++ b/airbyte-webapp/src/components/ui/Toast/Toast.module.scss @@ -43,6 +43,7 @@ $toast-bottom-margin: 27px; position: fixed; box-sizing: border-box; bottom: $toast-bottom-margin; + margin-left: calc(vars.$width-size-menu / 2); left: 50%; transform: translate(-50%, 0); z-index: z-indices.$notification; 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 index b580fb1d4adf0..bdbee6b827e50 100644 --- a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/EnrollmentModal.tsx +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/EnrollmentModal.tsx @@ -12,6 +12,7 @@ import { Text } from "components/ui/Text"; import { StripeCheckoutSessionCreate, StripeCheckoutSessionRead } from "packages/cloud/lib/domain/stripe"; +import { STRIPE_SUCCESS_QUERY } from "../hooks/useFreeConnectorProgram"; import { ReactComponent as CardSVG } from "./cards.svg"; import { ReactComponent as ConnectorGridSvg } from "./connectorGrid.svg"; import styles from "./EnrollmentModal.module.scss"; @@ -19,8 +20,6 @@ import { ReactComponent as FreeAlphaBetaPillsSVG } from "./free-alpha-beta-pills 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; 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 index 632c615399e8c..bf1849e28d7a6 100644 --- a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/useShowEnrollmentModal.tsx +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/EnrollmentModal/useShowEnrollmentModal.tsx @@ -1,4 +1,9 @@ +import { useIntl } from "react-intl"; + +import { ToastType } from "components/ui/Toast"; + import { useModalService } from "hooks/services/Modal"; +import { useNotificationService } from "hooks/services/Notification"; import { useAuthService } from "packages/cloud/services/auth/AuthService"; import { useStripeCheckout } from "packages/cloud/services/stripe/StripeService"; import { useCurrentWorkspaceId } from "services/workspaces/WorkspacesService"; @@ -10,6 +15,19 @@ export const useShowEnrollmentModal = () => { const { mutateAsync: createCheckout } = useStripeCheckout(); const workspaceId = useCurrentWorkspaceId(); const { emailVerified, sendEmailVerification } = useAuthService(); + const { formatMessage } = useIntl(); + const { registerNotification } = useNotificationService(); + + const verifyEmail = () => + sendEmailVerification() + .then(() => { + registerNotification({ + id: "fcp/verify-email", + text: formatMessage({ id: "freeConnectorProgram.enrollmentModal.validationEmailConfirmation" }), + type: ToastType.INFO, + }); + }) + .catch(); // don't crash the page on error return { showEnrollmentModal: () => { @@ -21,7 +39,7 @@ export const useShowEnrollmentModal = () => { createCheckout={createCheckout} closeModal={closeModal} emailVerified={emailVerified} - sendEmailVerification={sendEmailVerification} + sendEmailVerification={verifyEmail} /> ), }); diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/InlineEnrollmentCallout.tsx b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/InlineEnrollmentCallout.tsx index bde66bf2d6693..09a425906a9da 100644 --- a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/InlineEnrollmentCallout.tsx +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/InlineEnrollmentCallout.tsx @@ -5,6 +5,7 @@ import { Callout } from "components/ui/Callout"; import { Text } from "components/ui/Text"; import { useShowEnrollmentModal } from "./EnrollmentModal"; +import { useFreeConnectorProgram } from "./hooks/useFreeConnectorProgram"; import styles from "./InlineEnrollmentCallout.module.scss"; export const EnrollLink: React.FC> = ({ children }) => { @@ -17,7 +18,9 @@ export const EnrollLink: React.FC> = ({ children }) = ); }; export const InlineEnrollmentCallout: React.FC = () => { - return ( + const { userDidEnroll } = useFreeConnectorProgram(); + + return userDidEnroll ? null : ( { const { showEnrollmentModal } = useShowEnrollmentModal(); + const { userDidEnroll } = useFreeConnectorProgram(); - return ( + return userDidEnroll ? null : ( diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/hooks/useFreeConnectorProgram.ts b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/hooks/useFreeConnectorProgram.ts index 014a43014a6cd..2a2cdbe460d4e 100644 --- a/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/hooks/useFreeConnectorProgram.ts +++ b/airbyte-webapp/src/packages/cloud/components/experiments/FreeConnectorProgram/hooks/useFreeConnectorProgram.ts @@ -1,12 +1,21 @@ +import { useState } from "react"; +import { useIntl } from "react-intl"; import { useQuery } from "react-query"; +import { useSearchParams } from "react-router-dom"; +import { useEffectOnce } from "react-use"; + +import { ToastType } from "components/ui/Toast"; import { useExperiment } from "hooks/services/Experiment"; +import { useNotificationService } from "hooks/services/Notification"; import { useConfig } from "packages/cloud/services/config"; import { useDefaultRequestMiddlewares } from "services/useDefaultRequestMiddlewares"; import { useCurrentWorkspaceId } from "services/workspaces/WorkspacesService"; import { webBackendGetFreeConnectorProgramInfoForWorkspace } from "../lib/api"; +export const STRIPE_SUCCESS_QUERY = "fcpEnrollmentSuccess"; + export const useFreeConnectorProgram = () => { const workspaceId = useCurrentWorkspaceId(); const { cloudApiUrl } = useConfig(); @@ -14,8 +23,25 @@ export const useFreeConnectorProgram = () => { const middlewares = useDefaultRequestMiddlewares(); const requestOptions = { config, middlewares }; const freeConnectorProgramEnabled = useExperiment("workspace.freeConnectorsProgram.visible", false); + const [searchParams, setSearchParams] = useSearchParams(); + const [userDidEnroll, setUserDidEnroll] = useState(false); + const { formatMessage } = useIntl(); + const { registerNotification } = useNotificationService(); - return useQuery(["freeConnectorProgramInfo", workspaceId], () => + useEffectOnce(() => { + if (searchParams.has(STRIPE_SUCCESS_QUERY)) { + // Remove the stripe parameter from the URL + setSearchParams({}, { replace: true }); + setUserDidEnroll(true); + registerNotification({ + id: "fcp/enrolled", + text: formatMessage({ id: "freeConnectorProgram.enroll.success" }), + type: ToastType.SUCCESS, + }); + } + }); + + const enrollmentStatusQuery = useQuery(["freeConnectorProgramInfo", workspaceId], () => webBackendGetFreeConnectorProgramInfoForWorkspace({ workspaceId }, requestOptions).then( ({ hasEligibleConnector, hasPaymentAccountSaved }) => { const userIsEligibleToEnroll = !hasPaymentAccountSaved && hasEligibleConnector; @@ -27,4 +53,9 @@ export const useFreeConnectorProgram = () => { } ) ); + + return { + enrollmentStatusQuery, + userDidEnroll, + }; }; diff --git a/airbyte-webapp/src/packages/cloud/locales/en.json b/airbyte-webapp/src/packages/cloud/locales/en.json index 69b6a4cc69af1..25bb28b00a51c 100644 --- a/airbyte-webapp/src/packages/cloud/locales/en.json +++ b/airbyte-webapp/src/packages/cloud/locales/en.json @@ -183,11 +183,13 @@ "freeConnectorProgram.title": "Free Connector Program", "freeConnectorProgram.enrollNow": "Enroll now!", "freeConnectorProgram.enroll.description": "Enroll in the Free Connector Program to use Alpha and Beta connectors for free.", + "freeConnectorProgram.enroll.success": "Successfully enrolled in the Free Connector Program", "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.unvalidatedEmailWarning": "You need to verify your email address before you can enroll in the Free Connector Program. Re-send verification email.", + "freeConnectorProgram.enrollmentModal.validationEmailConfirmation": "Verification email sent", "freeConnectorProgram.enrollmentModal.cancelButtonText": "Cancel", "freeConnectorProgram.enrollmentModal.enrollButtonText": "Enroll now!", "freeConnectorProgram.enrollmentModal.unvalidatedEmailButtonText": "Resend email validation", diff --git a/airbyte-webapp/src/packages/cloud/services/auth/AuthService.tsx b/airbyte-webapp/src/packages/cloud/services/auth/AuthService.tsx index 1fa84ac07a4da..fdccf84cfdca5 100644 --- a/airbyte-webapp/src/packages/cloud/services/auth/AuthService.tsx +++ b/airbyte-webapp/src/packages/cloud/services/auth/AuthService.tsx @@ -273,6 +273,7 @@ export const AuthenticationProvider: React.FC> type: ToastType.ERROR, }); } + throw error; } }, async verifyEmail(code: string): Promise { diff --git a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/CreditsPage.tsx b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/CreditsPage.tsx index 7395779491c11..447d98f5b8760 100644 --- a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/CreditsPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/CreditsPage.tsx @@ -20,8 +20,8 @@ import styles from "./CreditsPage.module.scss"; const CreditsPage: React.FC = () => { const { emailVerified } = useAuthService(); useTrackPage(PageTrackingCodes.CREDITS); - const { data: freeConnectorProgramInfo } = useFreeConnectorProgram(); - const { showEnrollmentUi } = freeConnectorProgramInfo || {}; + const { enrollmentStatusQuery } = useFreeConnectorProgram(); + const { showEnrollmentUi } = enrollmentStatusQuery.data || {}; return ( = ({ className }) => { const [isEmailResend, setIsEmailResend] = useState(false); const onResendVerificationMail = async () => { - await sendEmailVerification(); + // the shared error handling inside `sendEmailVerification` suffices + await sendEmailVerification().catch(); setIsEmailResend(true); }; diff --git a/airbyte-webapp/src/pages/connections/ConnectionPage/ConnectionPageTitle.tsx b/airbyte-webapp/src/pages/connections/ConnectionPage/ConnectionPageTitle.tsx index a98a76ec19053..86e7194be2777 100644 --- a/airbyte-webapp/src/pages/connections/ConnectionPage/ConnectionPageTitle.tsx +++ b/airbyte-webapp/src/pages/connections/ConnectionPage/ConnectionPageTitle.tsx @@ -26,8 +26,8 @@ export const ConnectionPageTitle: React.FC = () => { const { connection } = useConnectionEditService(); - const { data: freeConnectorProgramInfo } = useFreeConnectorProgram(); - const displayEnrollmentCallout = freeConnectorProgramInfo?.showEnrollmentUi; + const { enrollmentStatusQuery } = useFreeConnectorProgram(); + const { showEnrollmentUi } = enrollmentStatusQuery.data || {}; const steps = useMemo(() => { const steps = [ @@ -80,7 +80,7 @@ export const ConnectionPageTitle: React.FC = () => {
- {displayEnrollmentCallout && } + {showEnrollmentUi && }
diff --git a/airbyte-webapp/src/scss/_z-indices.scss b/airbyte-webapp/src/scss/_z-indices.scss index 71ec0051a4ca0..752c0b00ecb38 100644 --- a/airbyte-webapp/src/scss/_z-indices.scss +++ b/airbyte-webapp/src/scss/_z-indices.scss @@ -1,11 +1,11 @@ -$tooltip: 9999 + 3; +$tooltip: 9999 + 4; +$notification: 9999 + 3; $datepicker: 9999 + 2; $modal: 9999 + 1; $sidebar: 9999; -$panelSplitter: 0; -$dropdownMenu: 2; -$notification: 20; -$schemaChangesBackdrop: 3; $schemaChangesBackdropContent: 4; +$schemaChangesBackdrop: 3; +$dropdownMenu: 2; $switchSliderBefore: 1; $tableScroll: 1; +$panelSplitter: 0;