diff --git a/extension/e2e-tests/buyWithOnramp.test.ts b/extension/e2e-tests/buyWithOnramp.test.ts index 1ffd4b794..109e21469 100644 --- a/extension/e2e-tests/buyWithOnramp.test.ts +++ b/extension/e2e-tests/buyWithOnramp.test.ts @@ -20,7 +20,7 @@ test("should show add XLM page and open Coinbase", async ({ screenshot: "add-xlm-page.png", }); - await page.getByText("Buy with Coinbase").click(); + await page.getByText("Buy XLM with Coinbase").click(); const popup = await popupPromise; @@ -42,12 +42,14 @@ test("should show Buy with Coinbase and open Coinbase", async ({ }); const popupPromise = page.context().waitForEvent("page"); await page.getByTestId("account-options-dropdown").click(); - await page.getByText("Buy with Coinbase").click(); + await page.getByText("Add Funds").click(); await expectPageToHaveScreenshot({ page, screenshot: "buy-with-coinbase.png", }); + await page.getByText("Buy with Coinbase").click(); + const popup = await popupPromise; await expect(popup).toHaveURL(/https:\/\/pay\.coinbase\.com\//); diff --git a/extension/e2e-tests/buyWithOnramp.test.ts-snapshots/add-xlm-page-chromium-darwin.png b/extension/e2e-tests/buyWithOnramp.test.ts-snapshots/add-xlm-page-chromium-darwin.png index 810bd540d..444cf5af2 100644 Binary files a/extension/e2e-tests/buyWithOnramp.test.ts-snapshots/add-xlm-page-chromium-darwin.png and b/extension/e2e-tests/buyWithOnramp.test.ts-snapshots/add-xlm-page-chromium-darwin.png differ diff --git a/extension/e2e-tests/buyWithOnramp.test.ts-snapshots/buy-with-coinbase-chromium-darwin.png b/extension/e2e-tests/buyWithOnramp.test.ts-snapshots/buy-with-coinbase-chromium-darwin.png index 0ec0686a7..6d1f01e6e 100644 Binary files a/extension/e2e-tests/buyWithOnramp.test.ts-snapshots/buy-with-coinbase-chromium-darwin.png and b/extension/e2e-tests/buyWithOnramp.test.ts-snapshots/buy-with-coinbase-chromium-darwin.png differ diff --git a/extension/src/helpers/hooks/useGetOnrampToken.tsx b/extension/src/helpers/hooks/useGetOnrampToken.tsx index fa768d39a..ab91a8c38 100644 --- a/extension/src/helpers/hooks/useGetOnrampToken.tsx +++ b/extension/src/helpers/hooks/useGetOnrampToken.tsx @@ -3,6 +3,8 @@ import { captureException } from "@sentry/browser"; import { INDEXER_URL } from "@shared/constants/mercury"; import { openTab } from "popup/helpers/navigate"; +import { emitMetric } from "helpers/metrics"; +import { METRIC_NAMES } from "popup/constants/metricsNames"; import { RequestState, initialState, reducer } from "./fetchHookInterface"; type SuccessReturnType = { token: string | null; error: string | null }; @@ -40,8 +42,8 @@ function useGetOnrampToken({ publicKey, asset }: UseGetOnrampTokenParams) { const token = state.data.token; setTokenError(""); - captureException("Unable to fetch Coinbase session token"); const coinbaseUrl = getCoinbaseUrl({ token, asset }); + emitMetric(METRIC_NAMES.coinbaseOnrampOpened, { asset }); openTab(coinbaseUrl); } diff --git a/extension/src/popup/Router.tsx b/extension/src/popup/Router.tsx index 3181fb44f..e8aa2472e 100644 --- a/extension/src/popup/Router.tsx +++ b/extension/src/popup/Router.tsx @@ -68,7 +68,7 @@ import { Swap } from "popup/views/Swap"; import { ManageNetwork } from "popup/views/ManageNetwork"; import { LeaveFeedback } from "popup/views/LeaveFeedback"; import { AccountMigration } from "popup/views/AccountMigration"; -import { AddXlm } from "popup/views/AddXlm"; +import { AddFunds } from "popup/views/AddFunds"; import "popup/metrics/views"; import { DEV_SERVER } from "@shared/constants/services"; @@ -574,10 +574,10 @@ export const Router = () => ( } > - + } /> diff --git a/extension/src/popup/components/account/AccountOptionsDropdown/index.tsx b/extension/src/popup/components/account/AccountOptionsDropdown/index.tsx index a7afaffa7..f89eb447c 100644 --- a/extension/src/popup/components/account/AccountOptionsDropdown/index.tsx +++ b/extension/src/popup/components/account/AccountOptionsDropdown/index.tsx @@ -3,7 +3,7 @@ import { createPortal } from "react-dom"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; -import { NavButton, Icon, Loader } from "@stellar/design-system"; +import { NavButton, Icon } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; import { navigateTo, openTab } from "popup/helpers/navigate"; @@ -14,13 +14,7 @@ import { saveAssetSelectType, AssetSelectType, } from "popup/ducks/transactionSubmission"; -import { publicKeySelector } from "popup/ducks/accountServices"; -import { useGetOnrampToken } from "helpers/hooks/useGetOnrampToken"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; -import { - WarningMessage, - WarningMessageVariant, -} from "popup/components/WarningMessages"; import { LoadingBackground } from "popup/basics/LoadingBackground"; import { AppDispatch } from "popup/App"; @@ -36,20 +30,6 @@ const DropdownModal = ({ isFunded }: DropdownModalProps) => { const dispatch = useDispatch(); const navigate = useNavigate(); const networkDetails = useSelector(settingsNetworkDetailsSelector); - const publicKey = useSelector(publicKeySelector); - - const { - isLoading: isTokenRequestLoading, - fetchData, - tokenError, - clearTokenError, - } = useGetOnrampToken({ - publicKey, - }); - - const handleBuyClick = async () => { - await fetchData(); - }; return (
@@ -68,13 +48,13 @@ const DropdownModal = ({ isFunded }: DropdownModalProps) => { {isMainnet(networkDetails) && (
navigateTo(ROUTES.addFunds, navigate)} >
- {t("Buy with Coinbase")} + {t("Add funds")}
- {isTokenRequestLoading ? : } +
)} @@ -118,19 +98,6 @@ const DropdownModal = ({ isFunded }: DropdownModalProps) => {
- {tokenError - ? createPortal( - -
{tokenError}
-
, - document.querySelector("#modal-root")!, - ) - : null} ); }; diff --git a/extension/src/popup/components/account/NotFundedMessage/index.tsx b/extension/src/popup/components/account/NotFundedMessage/index.tsx index 425d74d67..59a2e27e2 100644 --- a/extension/src/popup/components/account/NotFundedMessage/index.tsx +++ b/extension/src/popup/components/account/NotFundedMessage/index.tsx @@ -57,7 +57,7 @@ export const NotFundedMessage = ({ isFullWidth onClick={() => isMainnet(networkDetails) - ? navigateTo(ROUTES.addXlm, navigate) + ? navigateTo(ROUTES.addFunds, navigate, "?isAddXlm=true") : navigateTo(ROUTES.viewPublicKey, navigate) } > diff --git a/extension/src/popup/constants/metricsNames.ts b/extension/src/popup/constants/metricsNames.ts index fde2f5625..a288fce03 100644 --- a/extension/src/popup/constants/metricsNames.ts +++ b/extension/src/popup/constants/metricsNames.ts @@ -81,7 +81,7 @@ export const METRIC_NAMES = { viewAddNetwork: "loaded screen: add network", viewEditNetwork: "loaded screen: edit network", viewNetworkSettings: "loaded screen: network settings", - viewAddXlm: "loaded screen: add xlm", + viewAddFunds: "loaded screen: add fund", manageAssetAddAsset: "manage asset: add asset", manageAssetAddToken: "manage asset: add token", @@ -170,4 +170,6 @@ export const METRIC_NAMES = { blockaidDomainScan: "blockaid: scanned domain", blockaidTxScan: "blockaid: scanned transaction", blockaidAssetScan: "blockaid: scanned asset", + + coinbaseOnrampOpened: "coinbase onramp: opened", }; diff --git a/extension/src/popup/constants/routes.ts b/extension/src/popup/constants/routes.ts index 9eeed3db8..52ffd2a39 100644 --- a/extension/src/popup/constants/routes.ts +++ b/extension/src/popup/constants/routes.ts @@ -50,7 +50,7 @@ export enum ROUTES { manageAssetsLists = "/settings/manage-assets-lists", manageAssetsListsModifyAssetList = "/settings/manage-assets-lists/modify-asset-list", advancedSettings = "/settings/advanced-settings", - addXlm = "/add-xlm", + addFunds = "/add-funds", manageAssets = "/manage-assets", searchAsset = "/manage-assets/search-asset", diff --git a/extension/src/popup/metrics/views.ts b/extension/src/popup/metrics/views.ts index 262bf9867..23359dc65 100644 --- a/extension/src/popup/metrics/views.ts +++ b/extension/src/popup/metrics/views.ts @@ -78,7 +78,7 @@ const routeToEventName = { [ROUTES.accountMigrationMigrationComplete]: METRIC_NAMES.viewAccountMigrationMigrationComplete, [ROUTES.advancedSettings]: METRIC_NAMES.viewAdvancedSettings, - [ROUTES.addXlm]: METRIC_NAMES.viewAddXlm, + [ROUTES.addFunds]: METRIC_NAMES.viewAddFunds, }; registerHandler(navigate, (_, a) => { diff --git a/extension/src/popup/views/AddFunds/index.tsx b/extension/src/popup/views/AddFunds/index.tsx new file mode 100644 index 000000000..84eec3f50 --- /dev/null +++ b/extension/src/popup/views/AddFunds/index.tsx @@ -0,0 +1,124 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { useNavigate, useLocation } from "react-router-dom"; +import { Icon, Text } from "@stellar/design-system"; + +import { SubviewHeader } from "popup/components/SubviewHeader"; +import { Loading } from "popup/components/Loading"; +import { View } from "popup/basics/layout/View"; +import { publicKeySelector } from "popup/ducks/accountServices"; +import { ROUTES } from "popup/constants/routes"; +import { navigateTo } from "popup/helpers/navigate"; +import { useGetOnrampToken } from "helpers/hooks/useGetOnrampToken"; + +import CoinbaseLogo from "popup/assets/coinbase-logo.svg"; + +import "./styles.scss"; + +export const AddFunds = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { search } = useLocation(); + const publicKey = useSelector(publicKeySelector); + + const params = new URLSearchParams(search); + const isAddXlm = params.get("isAddXlm") === "true"; + + const { + isLoading: isTokenRequestLoading, + fetchData, + tokenError, + } = useGetOnrampToken({ publicKey, ...(isAddXlm ? { asset: "XLM" } : {}) }); + + const handleOnrampClick = async () => { + await fetchData(); + }; + + const handleTransferClick = () => { + navigateTo(ROUTES.viewPublicKey, navigate); + }; + + if (isTokenRequestLoading) { + return ; + } + + return ( + <> + {t("Choose your method")}} + /> + +
+
+
+ Coinbase Logo +
+ + {isAddXlm + ? t("Buy XLM with Coinbase") + : t("Buy with Coinbase")} + + + {t( + "Transfer from Coinbase, buy with debit and credit cards or bank transfer *", + )} + +
+
+
+
+ +
+
+ + {t("Transfer from another account")} + + + {isAddXlm + ? t("Send XLM to this account address") + : t("Send funds to this account address")} + +
+
+ {tokenError &&
{tokenError}
} +
+
+ + {t("* payment methods may vary based on your location")} + +
+
+
+ + ); +}; diff --git a/extension/src/popup/views/AddFunds/styles.scss b/extension/src/popup/views/AddFunds/styles.scss new file mode 100644 index 000000000..06902a310 --- /dev/null +++ b/extension/src/popup/views/AddFunds/styles.scss @@ -0,0 +1,67 @@ +@use "../../styles/utils.scss" as *; + +.AddFunds { + display: flex; + flex-direction: column; + height: 100%; + + &__content { + display: flex; + flex-direction: column; + flex: 1; + gap: pxToRem(12); + } + + &__onrampLogo { + width: pxToRem(32); + height: pxToRem(32); + } + + &__error { + color: var(--sds-clr-red-09); + font-size: pxToRem(12); + line-height: pxToRem(16); + margin-top: pxToRem(8); + text-align: center; + } + + &__button { + background: var(--sds-clr-gray-03); + border-radius: pxToRem(6); + cursor: pointer; + display: flex; + font-size: pxToRem(14); + font-weight: var(--sds-fw-semi-bold); + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: pxToRem(12); + padding: pxToRem(12) pxToRem(16); + + &__description { + color: var(--sds-clr-gray-11); + } + + &__qr { + align-items: flex; + background: var(--sds-clr-mint-03); + border-radius: pxToRem(100); + display: flex; + height: pxToRem(32); + justify-content: center; + padding: pxToRem(6); + width: pxToRem(32); + + svg { + height: pxToRem(20); + stroke: var(--sds-clr-mint-09); + width: pxToRem(20); + } + } + } + + &__footer { + color: var(--sds-clr-gray-10); + text-align: center; + } +} diff --git a/extension/src/popup/views/AddXlm/index.tsx b/extension/src/popup/views/AddXlm/index.tsx deleted file mode 100644 index ba7297627..000000000 --- a/extension/src/popup/views/AddXlm/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from "react"; -import { useSelector } from "react-redux"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { Button, Icon } from "@stellar/design-system"; - -import { SubviewHeader } from "popup/components/SubviewHeader"; -import { Loading } from "popup/components/Loading"; -import { View } from "popup/basics/layout/View"; -import { publicKeySelector } from "popup/ducks/accountServices"; -import { ROUTES } from "popup/constants/routes"; -import { navigateTo } from "popup/helpers/navigate"; -import { useGetOnrampToken } from "helpers/hooks/useGetOnrampToken"; - -import CoinbaseLogo from "popup/assets/coinbase-logo.svg"; - -import "./styles.scss"; - -export const AddXlm = () => { - const { t } = useTranslation(); - const navigate = useNavigate(); - const publicKey = useSelector(publicKeySelector); - const { - isLoading: isTokenRequestLoading, - fetchData, - tokenError, - } = useGetOnrampToken({ publicKey, asset: "XLM" }); - - const handleOnrampClick = async () => { - await fetchData(); - }; - - const handleTransferClick = () => { - navigateTo(ROUTES.viewPublicKey, navigate); - }; - - if (isTokenRequestLoading) { - return ; - } - - return ( - <> - {t("Choose your method")}} - /> - -
- - - {tokenError &&
{tokenError}
} -
-
- - ); -}; diff --git a/extension/src/popup/views/AddXlm/styles.scss b/extension/src/popup/views/AddXlm/styles.scss deleted file mode 100644 index 2fc2d307b..000000000 --- a/extension/src/popup/views/AddXlm/styles.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "../../styles/utils.scss" as *; - -.AddXlm { - &__content { - display: flex; - flex-direction: column; - gap: pxToRem(12); - } - - &__onrampLogo { - width: pxToRem(16); - height: pxToRem(16); - } - - &__error { - color: var(--sds-clr-red-09); - font-size: pxToRem(12); - line-height: pxToRem(16); - margin-top: pxToRem(8); - text-align: center; - } -} diff --git a/extension/src/popup/views/__tests__/AddXlm.test.tsx b/extension/src/popup/views/__tests__/AddFunds.test.tsx similarity index 61% rename from extension/src/popup/views/__tests__/AddXlm.test.tsx rename to extension/src/popup/views/__tests__/AddFunds.test.tsx index d254323cc..787844c3d 100644 --- a/extension/src/popup/views/__tests__/AddXlm.test.tsx +++ b/extension/src/popup/views/__tests__/AddFunds.test.tsx @@ -10,7 +10,7 @@ import { import { APPLICATION_STATE as ApplicationState } from "@shared/constants/applicationState"; import { ROUTES } from "popup/constants/routes"; import { Wrapper, mockAccounts } from "../../__testHelpers__"; -import { AddXlm } from "../AddXlm"; +import { AddFunds } from "../AddFunds"; const token = "foo"; @@ -43,11 +43,11 @@ const mockFetch = jest.spyOn(global, "fetch").mockResolvedValue({ ok: true, }); -describe("AddXlm view", () => { - it("displays Coinbase onramp button and opens Coinbase's flow", async () => { +describe("AddFunds view", () => { + it("displays Coinbase onramp button and opens Coinbase's default flow", async () => { render( { }, }} > - + + , + ); + + await waitFor(async () => { + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Add funds", + ); + const coinbaseButton = screen.getByTestId("add-coinbase-button"); + await fireEvent.click(coinbaseButton); + expect(newTabSpy).toHaveBeenCalledWith({ + url: `https://pay.coinbase.com/buy/select-asset?sessionToken=${token}&defaultExperience=buy`, + }); + }); + }); + it("displays Coinbase onramp button and opens Coinbase's XLM flow", async () => { + render( + + , ); @@ -69,7 +101,7 @@ describe("AddXlm view", () => { expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( "Add XLM", ); - const coinbaseButton = screen.getByTestId("add-xlm-coinbase-button"); + const coinbaseButton = screen.getByTestId("add-coinbase-button"); await fireEvent.click(coinbaseButton); expect(newTabSpy).toHaveBeenCalledWith({ url: `https://pay.coinbase.com/buy/select-asset?sessionToken=${token}&defaultExperience=buy&defaultAsset=XLM`,