From 7fccaf8795e7313b616e331331bb00b9a4e46955 Mon Sep 17 00:00:00 2001 From: nicolasburtey Date: Wed, 24 Apr 2024 17:22:18 -0600 Subject: [PATCH] feat: adding explicit accept TC steps in the onboarding flow (#3218) * feat: adding explicit accept TC steps in the onboarding flow * chore: address feedback --------- Co-authored-by: Nicolas Burtey --- .storybook/storybook.requires.js | 30 +++-- .storybook/storybook.tsx | 2 +- app/i18n/en/index.ts | 7 + app/i18n/i18n-types.ts | 44 ++++++ app/i18n/raw-i18n/source/en.json | 7 + app/navigation/root-navigator.tsx | 8 ++ app/navigation/stack-param-lists.ts | 3 + .../accept-t-and-c/accept-t-and-c.stories.tsx | 12 ++ app/screens/accept-t-and-c/accept-t-and-c.tsx | 125 ++++++++++++++++++ app/screens/accept-t-and-c/index.ts | 1 + app/screens/earns-screen/earns-section.tsx | 6 +- .../device-account-modal.tsx | 9 +- .../get-started-screen/get-started-screen.tsx | 20 +-- app/screens/home-screen/home-screen.tsx | 8 +- app/screens/map-screen/map-screen.tsx | 8 +- 15 files changed, 237 insertions(+), 53 deletions(-) create mode 100644 app/screens/accept-t-and-c/accept-t-and-c.stories.tsx create mode 100644 app/screens/accept-t-and-c/accept-t-and-c.tsx create mode 100644 app/screens/accept-t-and-c/index.ts diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js index 4d69f5b734..d3b03bab37 100644 --- a/.storybook/storybook.requires.js +++ b/.storybook/storybook.requires.js @@ -1,17 +1,12 @@ /* do not change this file, it is auto generated by storybook. */ -import { argsEnhancers } from "@storybook/addon-actions/dist/modern/preset/addArgs" -import "@storybook/addon-ondevice-actions/register" -import "@storybook/addon-ondevice-backgrounds/register" -import "@storybook/addon-ondevice-controls/register" -import "@storybook/addon-ondevice-knobs/register" -import "@storybook/addon-ondevice-notes/register" + import { configure, addDecorator, addParameters, addArgsEnhancer, clearDecorators, -} from "@storybook/react-native" +} from "@storybook/react-native"; global.STORIES = [ { @@ -21,10 +16,18 @@ global.STORIES = [ importPathMatcher: "^\\.[\\\\/](?:app(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx)?)$", }, -] +]; + +import "@storybook/addon-ondevice-notes/register"; +import "@storybook/addon-ondevice-controls/register"; +import "@storybook/addon-ondevice-knobs/register"; +import "@storybook/addon-ondevice-backgrounds/register"; +import "@storybook/addon-ondevice-actions/register"; + +import { argsEnhancers } from "@storybook/addon-actions/dist/modern/preset/addArgs"; try { - argsEnhancers.forEach((enhancer) => addArgsEnhancer(enhancer)) + argsEnhancers.forEach((enhancer) => addArgsEnhancer(enhancer)); } catch {} const getStories = () => { @@ -61,10 +64,11 @@ const getStories = () => { "./app/rne-theme/colors.stories.tsx": require("../app/rne-theme/colors.stories.tsx"), "./app/rne-theme/text.stories.tsx": require("../app/rne-theme/text.stories.tsx"), "./app/rne-theme/theme.stories.tsx": require("../app/rne-theme/theme.stories.tsx"), + "./app/screens/accept-t-and-c/accept-t-and-c.stories.tsx": require("../app/screens/accept-t-and-c/accept-t-and-c.stories.tsx"), "./app/screens/authentication-screen/authentication-check-screen.stories.tsx": require("../app/screens/authentication-screen/authentication-check-screen.stories.tsx"), "./app/screens/authentication-screen/authentication-screen.stories.tsx": require("../app/screens/authentication-screen/authentication-screen.stories.tsx"), "./app/screens/authentication-screen/pin-screen.stories.tsx": require("../app/screens/authentication-screen/pin-screen.stories.tsx"), - "./app/screens/conversation/conversation.stories.tsx": require("../app/screens/chatbot-screen/chatbot.stories.tsx"), + "./app/screens/chatbot-screen/chatbot.stories.tsx": require("../app/screens/chatbot-screen/chatbot.stories.tsx"), "./app/screens/conversion-flow/conversion-success-screen.stories.tsx": require("../app/screens/conversion-flow/conversion-success-screen.stories.tsx"), "./app/screens/earns-map-screen/earns-map-screen.stories.tsx": require("../app/screens/earns-map-screen/earns-map-screen.stories.tsx"), "./app/screens/earns-screen/earns-quiz.stories.tsx": require("../app/screens/earns-screen/earns-quiz.stories.tsx"), @@ -102,7 +106,7 @@ const getStories = () => { "./app/screens/settings-screen/settings.stories.tsx": require("../app/screens/settings-screen/settings.stories.tsx"), "./app/screens/settings-screen/theme-screen.stories.tsx": require("../app/screens/settings-screen/theme-screen.stories.tsx"), "./app/screens/transaction-detail-screen/transaction-detail-screen.stories.tsx": require("../app/screens/transaction-detail-screen/transaction-detail-screen.stories.tsx"), - } -} + }; +}; -configure(getStories, module, false) +configure(getStories, module, false); diff --git a/.storybook/storybook.tsx b/.storybook/storybook.tsx index 937c40ea8b..b8f73d688c 100644 --- a/.storybook/storybook.tsx +++ b/.storybook/storybook.tsx @@ -22,7 +22,7 @@ RNBootSplash.hide({ fade: true }) const StorybookUI = getStorybookUI({ enableWebsockets: true, onDeviceUI: true, - initialSelection: { kind: "ChatBot Screen", name: "Default" }, + initialSelection: { kind: "Failed device account modal", name: "Default" }, shouldPersistSelection: false, }) diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index dceffe3d88..b6cd299b44 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -28,6 +28,13 @@ const en: BaseTranslation = { howToUseYourCashRegisterExplainer: "Allow people to collect payments via the Cash Register link, without accessing your wallet.\n\nThey can create invoices and payments will be sent directly to your {bankName: string} Wallet.", }, + AcceptTermsAndConditionsScreen: { + title: "Terms and Conditions", + accept: "Accept", + termsAndConditions: "View Terms and Conditions", + prohibitedCountry: "View prohibited countries", + text: "By clicking 'Accept', you agree to our Terms And Conditions. You also agree that you are not a resident or citizen from one of the prohibited countries.", + }, SetAccountModal: { title: "Set default account", description: diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index 53c2976dd5..f999e83d8b 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -122,6 +122,28 @@ type RootTranslation = { */ howToUseYourCashRegisterExplainer: RequiredParams<'bankName'> } + AcceptTermsAndConditionsScreen: { + /** + * T​e​r​m​s​ ​a​n​d​ ​C​o​n​d​i​t​i​o​n​s + */ + title: string + /** + * A​c​c​e​p​t + */ + accept: string + /** + * V​i​e​w​ ​T​e​r​m​s​ ​a​n​d​ ​C​o​n​d​i​t​i​o​n​s + */ + termsAndConditions: string + /** + * V​i​e​w​ ​p​r​o​h​i​b​i​t​e​d​ ​c​o​u​n​t​r​i​e​s + */ + prohibitedCountry: string + /** + * B​y​ ​c​l​i​c​k​i​n​g​ ​'​A​c​c​e​p​t​'​,​ ​y​o​u​ ​a​g​r​e​e​ ​t​o​ ​o​u​r​ ​T​e​r​m​s​ ​A​n​d​ ​C​o​n​d​i​t​i​o​n​s​.​ ​Y​o​u​ ​a​l​s​o​ ​a​g​r​e​e​ ​t​h​a​t​ ​y​o​u​ ​a​r​e​ ​n​o​t​ ​a​ ​r​e​s​i​d​e​n​t​ ​o​r​ ​c​i​t​i​z​e​n​ ​f​r​o​m​ ​o​n​e​ ​o​f​ ​t​h​e​ ​p​r​o​h​i​b​i​t​e​d​ ​c​o​u​n​t​r​i​e​s​. + */ + text: string + } SetAccountModal: { /** * S​e​t​ ​d​e​f​a​u​l​t​ ​a​c​c​o​u​n​t @@ -9184,6 +9206,28 @@ export type TranslationFunctions = { */ howToUseYourCashRegisterExplainer: (arg: { bankName: string }) => LocalizedString } + AcceptTermsAndConditionsScreen: { + /** + * Terms and Conditions + */ + title: () => LocalizedString + /** + * Accept + */ + accept: () => LocalizedString + /** + * View Terms and Conditions + */ + termsAndConditions: () => LocalizedString + /** + * View prohibited countries + */ + prohibitedCountry: () => LocalizedString + /** + * By clicking 'Accept', you agree to our Terms And Conditions. You also agree that you are not a resident or citizen from one of the prohibited countries. + */ + text: () => LocalizedString + } SetAccountModal: { /** * Set default account diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index 7b03b6c169..be7f314dc5 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -20,6 +20,13 @@ "howToUseYourPaycodeExplainer": "You can print your Paycode (technically, this is an lnurl-pay address) and display it in your business to receive payments. Individuals can pay you by scanning it with a Lightning-enabled wallet.\n\nHowever, be aware that some wallets can’t scan a Paycode such as:", "howToUseYourCashRegisterExplainer": "Allow people to collect payments via the Cash Register link, without accessing your wallet.\n\nThey can create invoices and payments will be sent directly to your {bankName: string} Wallet." }, + "AcceptTermsAndConditionsScreen": { + "title": "Terms and Conditions", + "accept": "Accept", + "termsAndConditions": "View Terms and Conditions", + "prohibitedCountry": "View prohibited countries", + "text": "By clicking 'Accept', you agree to our Terms And Conditions. You also agree that you are not a resident or citizen from one of the prohibited countries." + }, "SetAccountModal": { "title": "Set default account", "description": "This account will be initially selected for sending and receiving payments. It can be changed at any time.", diff --git a/app/navigation/root-navigator.tsx b/app/navigation/root-navigator.tsx index d871638104..56594b7abe 100644 --- a/app/navigation/root-navigator.tsx +++ b/app/navigation/root-navigator.tsx @@ -81,6 +81,7 @@ import { PrimaryStackParamList, RootStackParamList, } from "./stack-param-lists" +import { AcceptTermsAndConditionsScreen } from "@app/screens/accept-t-and-c" const RootNavigator = createStackNavigator() @@ -347,6 +348,13 @@ export const RootStack = () => { title: LL.common.transactionLimits(), }} /> + {Story()}], +} + +export const Default = () => diff --git a/app/screens/accept-t-and-c/accept-t-and-c.tsx b/app/screens/accept-t-and-c/accept-t-and-c.tsx new file mode 100644 index 0000000000..134e281dd1 --- /dev/null +++ b/app/screens/accept-t-and-c/accept-t-and-c.tsx @@ -0,0 +1,125 @@ +import * as React from "react" +import { Alert, View } from "react-native" + +import { GaloyPrimaryButton } from "@app/components/atomic/galoy-primary-button" +import { useI18nContext } from "@app/i18n/i18n-react" +import { RootStackParamList } from "@app/navigation/stack-param-lists" +import { RouteProp, useNavigation, useRoute } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" +import { Text, makeStyles } from "@rneui/themed" + +import { GaloySecondaryButton } from "@app/components/atomic/galoy-secondary-button" +import InAppBrowser from "react-native-inappbrowser-reborn" +import { Screen } from "../../components/screen" +import { PhoneLoginInitiateType } from "../phone-auth-screen" +import { DeviceAccountModal } from "../get-started-screen/device-account-modal" + +export const AcceptTermsAndConditionsScreen: React.FC = () => { + const styles = useStyles() + + const navigation = + useNavigation>() + + const { LL } = useI18nContext() + + const route = useRoute>() + const { flow } = route.params || { flow: "phone" } + + const [confirmationModalVisible, setConfirmationModalVisible] = React.useState(false) + const openConfirmationModal = () => setConfirmationModalVisible(true) + const closeConfirmationModal = () => { + setConfirmationModalVisible(false) + } + + const action = () => { + if (flow === "phone") { + navigation.navigate("phoneFlow", { + screen: "phoneLoginInitiate", + params: { + type: PhoneLoginInitiateType.CreateAccount, + }, + }) + } else if (flow === "trial") { + openConfirmationModal() + } else { + Alert.alert("unknown flow") + } + } + + return ( + + + + + {LL.AcceptTermsAndConditionsScreen.text()} + + + + InAppBrowser.open("https://www.blink.sv/en/terms-conditions")} + /> + + + + InAppBrowser.open( + "https://faq.blink.sv/creating-a-blink-account/which-countries-are-unable-to-download-and-activate-blink", + ) + } + /> + + + + + + + + ) +} + +const useStyles = makeStyles(({ colors }) => ({ + screenStyle: { + padding: 20, + flexGrow: 1, + }, + buttonsContainer: { + flex: 1, + justifyContent: "flex-end", + }, + + inputContainer: { + marginBottom: 20, + flexDirection: "row", + alignItems: "stretch", + minHeight: 48, + }, + textContainer: { + marginBottom: 20, + }, + viewWrapper: { flex: 1 }, + + inputContainerStyle: { + flex: 1, + borderWidth: 2, + borderBottomWidth: 2, + paddingHorizontal: 10, + borderColor: colors.primary5, + borderRadius: 8, + }, + errorContainer: { + marginBottom: 20, + }, +})) diff --git a/app/screens/accept-t-and-c/index.ts b/app/screens/accept-t-and-c/index.ts new file mode 100644 index 0000000000..7ab27db601 --- /dev/null +++ b/app/screens/accept-t-and-c/index.ts @@ -0,0 +1 @@ +export * from "./accept-t-and-c" diff --git a/app/screens/earns-screen/earns-section.tsx b/app/screens/earns-screen/earns-section.tsx index baab194212..fb8f53ae8e 100644 --- a/app/screens/earns-screen/earns-section.tsx +++ b/app/screens/earns-screen/earns-section.tsx @@ -17,7 +17,6 @@ import { makeStyles, useTheme } from "@rneui/themed" import { Screen } from "../../components/screen" import type { RootStackParamList } from "../../navigation/stack-param-lists" import { useQuizServer } from "../earns-map-screen/use-quiz-server" -import { PhoneLoginInitiateType } from "../phone-auth-screen" import { SVGs } from "./earn-svg-factory" import { augmentCardWithGqlData, @@ -219,10 +218,7 @@ export const EarnSection = ({ route }: Props) => { { text: "OK", onPress: () => - navigation.navigate("phoneFlow", { - screen: "phoneLoginInitiate", - params: { type: PhoneLoginInitiateType.CreateAccount }, - }), + navigation.navigate("acceptTermsAndConditions", { flow: "phone" }), }, ]) return diff --git a/app/screens/get-started-screen/device-account-modal.tsx b/app/screens/get-started-screen/device-account-modal.tsx index 04a860e9de..fe0197ecb9 100644 --- a/app/screens/get-started-screen/device-account-modal.tsx +++ b/app/screens/get-started-screen/device-account-modal.tsx @@ -22,8 +22,10 @@ import { useNavigation } from "@react-navigation/native" import { StackNavigationProp } from "@react-navigation/stack" import { Text, makeStyles, useTheme } from "@rneui/themed" -import { PhoneLoginInitiateType } from "../phone-auth-screen" import { DeviceAccountFailModal } from "./device-account-fail-modal" +import useAppCheckToken from "./use-device-token" +import { useFeatureFlags } from "@app/config/feature-flags-context" +import { PhoneLoginInitiateType } from "../phone-auth-screen" const generateSecureRandomUUID = async () => { const randomBytes = await generateSecureRandom(16) // Generate 16 random bytes @@ -36,13 +38,11 @@ const DEVICE_ACCOUNT_CREDENTIALS_KEY = "device-account" export type DeviceAccountModalProps = { isVisible: boolean closeModal: () => void - appCheckToken: string | undefined } export const DeviceAccountModal: React.FC = ({ isVisible, closeModal, - appCheckToken, }) => { const { saveToken } = useAppConfig() const { @@ -51,6 +51,9 @@ export const DeviceAccountModal: React.FC = ({ }, } = useAppConfig() + const { deviceAccountEnabled } = useFeatureFlags() + const appCheckToken = useAppCheckToken({ skip: !deviceAccountEnabled }) + const [hasError, setHasError] = React.useState(false) const [loading, setLoading] = React.useState(false) const styles = useStyles() diff --git a/app/screens/get-started-screen/get-started-screen.tsx b/app/screens/get-started-screen/get-started-screen.tsx index cd5b6eca6f..c4cfc055b0 100644 --- a/app/screens/get-started-screen/get-started-screen.tsx +++ b/app/screens/get-started-screen/get-started-screen.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react" +import React from "react" import { Pressable, TouchableOpacity, View } from "react-native" import { GaloyPrimaryButton } from "@app/components/atomic/galoy-primary-button" @@ -18,7 +18,6 @@ import AppLogoLightMode from "../../assets/logo/app-logo-light.svg" import { Screen } from "../../components/screen" import { RootStackParamList } from "../../navigation/stack-param-lists" import { PhoneLoginInitiateType } from "../phone-auth-screen" -import { DeviceAccountModal } from "./device-account-modal" import useAppCheckToken from "./use-device-token" export const GetStartedScreen: React.FC = () => { @@ -41,11 +40,6 @@ export const GetStartedScreen: React.FC = () => { const AppLogo = mode === "dark" ? AppLogoDarkMode : AppLogoLightMode const { LL } = useI18nContext() - const [confirmationModalVisible, setConfirmationModalVisible] = useState(false) - const openConfirmationModal = () => setConfirmationModalVisible(true) - const closeConfirmationModal = () => { - setConfirmationModalVisible(false) - } const { deviceAccountEnabled } = useFeatureFlags() @@ -56,10 +50,7 @@ export const GetStartedScreen: React.FC = () => { action: "log_in", createDeviceAccountEnabled: Boolean(appCheckToken), }) - navigation.navigate("phoneFlow", { - screen: "phoneLoginInitiate", - params: { type: PhoneLoginInitiateType.CreateAccount }, - }) + navigation.navigate("acceptTermsAndConditions", { flow: "phone" }) } const handleLoginWithPhone = () => { @@ -87,7 +78,7 @@ export const GetStartedScreen: React.FC = () => { createDeviceAccountEnabled: Boolean(appCheckToken), }) - openConfirmationModal() + navigation.navigate("acceptTermsAndConditions", { flow: "trial" }) } const handleLoginWithEmail = async () => { @@ -124,11 +115,6 @@ export const GetStartedScreen: React.FC = () => { > - { const activateWallet = () => { setModalVisible(false) - navigation.navigate("phoneFlow", { - screen: "phoneLoginInitiate", - params: { - type: PhoneLoginInitiateType.CreateAccount, - }, - }) + navigation.navigate("acceptTermsAndConditions", { flow: "phone" }) } // debug code. verify that we have 2 wallets. mobile doesn't work well with only one wallet diff --git a/app/screens/map-screen/map-screen.tsx b/app/screens/map-screen/map-screen.tsx index 7dee312814..4d15104e04 100644 --- a/app/screens/map-screen/map-screen.tsx +++ b/app/screens/map-screen/map-screen.tsx @@ -23,7 +23,6 @@ import countryCodes from "../../../utils/countryInfo.json" import { Screen } from "../../components/screen" import { RootStackParamList } from "../../navigation/stack-param-lists" import { toastShow } from "../../utils/toast" -import { PhoneLoginInitiateType } from "../phone-auth-screen" import { LOCATION_PERMISSION, getUserRegion } from "./functions" const EL_ZONTE_COORDS = { @@ -166,12 +165,7 @@ export const MapScreen: React.FC = ({ navigation }) => { if (isAuthed) { navigation.navigate("sendBitcoinDestination", { username: item.username }) } else { - navigation.navigate("phoneFlow", { - screen: "phoneLoginInitiate", - params: { - type: PhoneLoginInitiateType.CreateAccount, - }, - }) + navigation.navigate("acceptTermsAndConditions", { flow: "phone" }) } }