diff --git a/apps/daimo-mobile/src/logic/daimoContacts.ts b/apps/daimo-mobile/src/logic/daimoContacts.ts index b20c63869..f2a95751a 100644 --- a/apps/daimo-mobile/src/logic/daimoContacts.ts +++ b/apps/daimo-mobile/src/logic/daimoContacts.ts @@ -246,7 +246,7 @@ function eAccAddrToContact(addr: Address): EAccountContact { return eAccToContact(eAcc); } -export function landlineAccountToContact( +function landlineAccountToContact( landlineAccount: LandlineAccount ): LandlineBankAccountContact { return { diff --git a/apps/daimo-mobile/src/view/TabNav.tsx b/apps/daimo-mobile/src/view/TabNav.tsx index 00f52f4a0..4248fe3ea 100644 --- a/apps/daimo-mobile/src/view/TabNav.tsx +++ b/apps/daimo-mobile/src/view/TabNav.tsx @@ -29,7 +29,7 @@ import { QRScreen } from "./screen/QRScreen"; import { SeedPhraseScreen } from "./screen/SeedPhraseScreen"; import { SettingsScreen } from "./screen/SettingsScreen"; import { YourInvitesScreen } from "./screen/YourInvitesScreen"; -import { BitrefillWebView } from "./screen/deposit/BitrefillWebview"; +import { BitrefillWebView } from "./screen/deposit/BitrefillWebView"; import DepositScreen from "./screen/deposit/DepositScreen"; import { ErrorScreen } from "./screen/errorScreens"; import { AddDeviceScreen } from "./screen/keyRotation/AddDeviceScreen"; diff --git a/apps/daimo-mobile/src/view/screen/deposit/BitrefillWebview.tsx b/apps/daimo-mobile/src/view/screen/deposit/BitrefillWebView.tsx similarity index 100% rename from apps/daimo-mobile/src/view/screen/deposit/BitrefillWebview.tsx rename to apps/daimo-mobile/src/view/screen/deposit/BitrefillWebView.tsx diff --git a/apps/daimo-mobile/src/view/screen/deposit/DaimoPayWebView.tsx b/apps/daimo-mobile/src/view/screen/deposit/DaimoPayWebView.tsx new file mode 100644 index 000000000..bd5c76f95 --- /dev/null +++ b/apps/daimo-mobile/src/view/screen/deposit/DaimoPayWebView.tsx @@ -0,0 +1,93 @@ +import { debugJson } from "@daimo/common"; +import React, { useMemo, useCallback } from "react"; +import { View, StyleSheet } from "react-native"; +import { WebView, WebViewMessageEvent } from "react-native-webview"; + +import { Account } from "../../../storage/account"; + +type DaimoPayWebViewProps = { + account: Account; + visible: boolean; + onClose: () => void; +}; + +export function DaimoPayWebView({ + account, + visible, + onClose, +}: DaimoPayWebViewProps) { + const styles = useMemo(() => getStyles(), []); + + // The /embed webpage emits a message when the user closes the modal. + // When this happens, close the webview. + const handleMessage = useCallback( + (event: WebViewMessageEvent) => { + try { + const data = JSON.parse(event.nativeEvent.data); + console.log(`[DAIMO PAY] Received message: ${debugJson(data)}`); + if (data?.source !== "daimo-pay") return; + if (data.type === "modalClosed") { + onClose(); + } + } catch (err) { + console.error("[DAIMO PAY] Unable to parse postMessage payload", err); + } + }, + [onClose] + ); + + if (!visible) return null; + + const url = buildDaimoPayUrl(account); + + return ( + + console.error("[DAIMO PAY] WebView error", e)} + /> + + ); +} + +function buildDaimoPayUrl(account: Account) { + const baseUrl = "https://miniapp.daimo.com/embed"; + const params = new URLSearchParams({ + toAddress: account.address, + refundAddress: "0xDa130a3573e1a5F54f1B7C2F324bf5d4F89b3c27", + toChain: account.homeChainId.toString(), + toToken: account.homeCoinAddress, + intent: "Deposit to Daimo", + paymentOptions: "Coinbase,Solana", + }); + + return `${baseUrl}?${params.toString()}`; +} + +const getStyles = () => + StyleSheet.create({ + overlay: { + position: "absolute", + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: "transparent", + zIndex: 1000, + }, + header: { + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", + padding: 16, + zIndex: 1001, + }, + closeButton: { + padding: 8, + borderRadius: 8, + }, + }); diff --git a/apps/daimo-mobile/src/view/screen/deposit/DepositScreen.tsx b/apps/daimo-mobile/src/view/screen/deposit/DepositScreen.tsx index e673871ab..449679edd 100644 --- a/apps/daimo-mobile/src/view/screen/deposit/DepositScreen.tsx +++ b/apps/daimo-mobile/src/view/screen/deposit/DepositScreen.tsx @@ -1,19 +1,11 @@ -import { - LandlineAccount, - PlatformType, - daimoDomainAddress, - timeAgo, -} from "@daimo/common"; +import { daimoDomainAddress } from "@daimo/common"; import { daimoChainFromId } from "@daimo/contract"; import Octicons from "@expo/vector-icons/Octicons"; import { Image } from "expo-image"; -import React, { useCallback, useContext, useMemo, useState } from "react"; +import React, { useContext, useMemo, useState, useEffect } from "react"; import { ActivityIndicator, ImageSourcePropType, - Linking, - Platform, - Pressable, ScrollView, StyleSheet, TouchableHighlight, @@ -21,23 +13,14 @@ import { useWindowDimensions, } from "react-native"; +import { DaimoPayWebView } from "./DaimoPayWebView"; import IconDepositWallet from "../../../../assets/icon-deposit-wallet.png"; import IconWithdrawWallet from "../../../../assets/icon-withdraw-wallet.png"; -import LandlineLogo from "../../../../assets/logos/landline-logo.png"; import { DispatcherContext } from "../../../action/dispatch"; import { useNav } from "../../../common/nav"; import { env } from "../../../env"; -import { i18NLocale, i18n } from "../../../i18n"; -import { useAccount } from "../../../logic/accountManager"; -import { - DaimoContact, - getContactProfilePicture, - landlineAccountToContact, -} from "../../../logic/daimoContacts"; -import { useTime } from "../../../logic/time"; -import { getRpcFunc } from "../../../logic/trpc"; +import { i18n } from "../../../i18n"; import { Account } from "../../../storage/account"; -import { InfoBox } from "../../shared/InfoBox"; import { ScreenHeader } from "../../shared/ScreenHeader"; import Spacer from "../../shared/Spacer"; import { TextBody, TextMeta } from "../../shared/text"; @@ -55,162 +38,50 @@ export default function DepositScreen() { // maybe is in here is the problem? function DepositScreenInner({ account }: { account: Account }) { const { ss } = useTheme(); + const [showDaimoPay, setShowDaimoPay] = useState(false); + const nav = useNav(); + + // Automatically close Daimo Pay webview when the user navigates away. + useEffect(() => { + const unsubscribe = nav.addListener("blur", () => setShowDaimoPay(false)); + return unsubscribe; + }, [nav]); + return ( - - + - - ); -} - -function LandlineList() { - const account = useAccount(); - if (account == null) return null; - const showLandline = !!account.landlineSessionURL; - if (!showLandline) return null; - - const isLandlineConnected = account.landlineAccounts.length > 0; - - return isLandlineConnected ? : ; -} - -function LandlineConnect() { - const account = useAccount(); - - const openLandline = useCallback(() => { - if (!account) return; - Linking.openURL(account.landlineSessionURL); - }, [account?.landlineSessionURL]); - - if (account == null) return null; - - return ( - - ); -} -function LandlineAccountList() { - const account = useAccount(); - const nav = useNav(); - const nowS = useTime(); - - if (account == null) return null; - - const landlineAccounts = account.landlineAccounts; - - const goToSendTransfer = (landlineAccount: LandlineAccount) => { - const recipient = landlineAccountToContact(landlineAccount); - nav.navigate("DepositTab", { - screen: "LandlineTransfer", - params: { recipient }, - }); - }; - - return ( - <> - {landlineAccounts.map((acc, idx) => { - const accCreatedAtS = new Date(acc.createdAt).getTime() / 1000; - const recipient = landlineAccountToContact(acc) as DaimoContact; - return ( - goToSendTransfer(acc)} - /> - ); - })} - + setShowDaimoPay(false)} + /> + ); } -// An on-demand exchange is one that requires fetching a URL at the time of -// user's interaction, rather than have it pre-fetched. These exchanges are -// first fetched with a loading spinner, then the URL is opened. -// Binance is the only on-demand exchange for now because generated links -// expire quickly and 301 redirects don't work for opening universal links -// in Binance app, so we can't include it in the recommendedExchanges API-side. -function getOnDemandExchanges( - account: Account, - setProgress: (progress: Progress) => void -) { - const rpcFunc = getRpcFunc(daimoChainFromId(account.homeChainId)); - - const platform = ["ios", "android"].includes(Platform.OS) - ? (Platform.OS as PlatformType) - : "other"; - - if (platform === "other") { - return []; - } - - const progressId = "loading-binance-deposit"; - - const onClick = async () => { - setProgress(progressId); - const url = await rpcFunc.getExchangeURL.query({ - addr: account.address, - platform, - exchange: "binance", - direction: "depositFromExchange", - }); - - if (url == null) { - console.error(`[DEPOSIT] no binance url for ${account.name}`); - setProgress("idle"); - } else { - Linking.openURL(url); - setProgress("started"); - } - }; - - return [ - { - cta: i18.binance.cta(), - title: i18.binance.title(), - logo: { - uri: `${daimoDomainAddress}/assets/deposit/binance.png`, - }, - loadingId: progressId, - isExternal: true, - sortId: 2, - onClick, - }, - ]; -} - type Progress = "idle" | "loading-binance-deposit" | "started"; -function DepositList({ account }: { account: Account }) { +function DepositList({ + account, + setShowDaimoPay, +}: { + account: Account; + setShowDaimoPay: React.Dispatch>; +}) { const { color, ss } = useTheme(); const styles = useMemo(() => getStyles(color, ss), [color, ss]); const { chainConfig } = env(daimoChainFromId(account.homeChainId)); const isTestnet = chainConfig.chainL2.testnet; - const [progress, setProgress] = useState("idle"); - - const openExchange = (url: string) => { - Linking.openURL(url); - setProgress("started"); - }; - const dispatcher = useContext(DispatcherContext); const openAddressDeposit = () => { @@ -230,36 +101,23 @@ function DepositList({ account }: { account: Account }) { ]; if (!isTestnet) { - options.push( - ...account.recommendedExchanges.map((rec) => ({ - title: rec.title || i18.loading(), - cta: rec.cta, - logo: rec.logo || defaultLogo, - isExternal: true, - sortId: rec.sortId || 0, - onClick: () => openExchange(rec.url), - })), - ...getOnDemandExchanges(account, setProgress) - ); - options.sort((a, b) => (a.sortId || 0) - (b.sortId || 0)); + options.push({ + cta: "Deposit with Daimo Pay", + title: "Daimo Pay", + logo: { uri: "https://pay.daimo.com/daimo-pay-logo.svg" }, + sortId: 1, + onClick: () => { + setShowDaimoPay(true); + }, + }); } return ( Deposit - {progress === "started" && ( - <> - - - - )} {options.map((option) => ( - + ))} ); @@ -307,62 +165,6 @@ function WithdrawList({ account }: { account: Account }) { ); } -type LandlineOptionRowProps = { - title: string; - cta: string; - logo: ImageSourcePropType; - isAccount?: boolean; - onClick: () => void; -}; - -function LandlineOptionRow({ - title, - cta, - logo, - isAccount, - onClick, -}: LandlineOptionRowProps) { - const { color, touchHighlightUnderlay, ss } = useTheme(); - const styles = useMemo(() => getStyles(color, ss), [color, ss]); - const width = useWindowDimensions().width; - - return ( - [ - { - ...styles.checklistAction, - backgroundColor: pressed - ? touchHighlightUnderlay.subtle.underlayColor - : undefined, - }, - ]} - > - - - - {cta} - - {title} - - - - {isAccount ? ( - - {i18.landline.startTransfer()} - - ) : ( - - {i18.go()} - {" "} - - - )} - - - ); -} - type OptionRowProps = { title?: string; cta: string;