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/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index dd335f0962..bcd6a3218c 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -25,4 +25,7 @@ -keepclassmembers class ** { @org.greenrobot.eventbus.Subscribe ; } --keep enum org.greenrobot.eventbus.ThreadMode { *; } \ No newline at end of file +-keep enum org.greenrobot.eventbus.ThreadMode { *; } + +-dontwarn com.samsung.** +-keep class com.samsung.** { *; } diff --git a/app/graphql/generated.ts b/app/graphql/generated.ts index dc4e460ba4..bb0ca56107 100644 --- a/app/graphql/generated.ts +++ b/app/graphql/generated.ts @@ -90,6 +90,7 @@ export type Scalars = { export type Account = { readonly btcWallet?: Maybe; readonly callbackEndpoints: ReadonlyArray; + readonly callbackPortalUrl: Scalars['String']['output']; readonly csvTransactions: Scalars['String']['output']; readonly defaultWallet: PublicWallet; /** @deprecated Shifting property to 'defaultWallet.id' */ @@ -403,6 +404,7 @@ export type ConsumerAccount = Account & { readonly __typename: 'ConsumerAccount'; readonly btcWallet?: Maybe; readonly callbackEndpoints: ReadonlyArray; + readonly callbackPortalUrl: Scalars['String']['output']; /** return CSV stream, base64 encoded, of the list of transactions in the wallet */ readonly csvTransactions: Scalars['String']['output']; readonly defaultWallet: PublicWallet; @@ -8146,6 +8148,7 @@ export type AccountResolvers; btcWallet?: Resolver, ParentType, ContextType>; callbackEndpoints?: Resolver, ParentType, ContextType>; + callbackPortalUrl?: Resolver; csvTransactions?: Resolver>; defaultWallet?: Resolver; defaultWalletId?: Resolver; @@ -8304,6 +8307,7 @@ export type CentAmountPayloadResolvers = { btcWallet?: Resolver, ParentType, ContextType>; callbackEndpoints?: Resolver, ParentType, ContextType>; + callbackPortalUrl?: Resolver; csvTransactions?: Resolver>; defaultWallet?: Resolver; defaultWalletId?: Resolver; 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 @@ -245,7 +239,7 @@ export const HomeScreen: React.FC = () => { } | undefined = undefined - const TRANSACTIONS_TO_SHOW = 2 + const TRANSACTIONS_TO_SHOW = 1 if (isAuthed && transactions.length > 0) { recentTransactionsData = { 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" }) } } diff --git a/app/screens/send-bitcoin-screen/confirm-fees-modal.tsx b/app/screens/send-bitcoin-screen/confirm-fees-modal.tsx index 2c757ebab3..70e1c8af80 100644 --- a/app/screens/send-bitcoin-screen/confirm-fees-modal.tsx +++ b/app/screens/send-bitcoin-screen/confirm-fees-modal.tsx @@ -62,21 +62,4 @@ const useStyles = makeStyles(({ colors }) => ({ titleContainer: { marginBottom: 12, }, - checkBox: { - paddingLeft: 0, - backgroundColor: "transparent", - }, - checkBoxTouchable: { - marginTop: 12, - }, - checkBoxContainer: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - backgroundColor: colors.grey5, - borderRadius: 8, - }, - checkBoxText: { - flex: 1, - }, })) diff --git a/supergraph.graphql b/supergraph.graphql index d03246bb0c..cbc0810b6c 100644 --- a/supergraph.graphql +++ b/supergraph.graphql @@ -25,6 +25,7 @@ interface Account @join__type(graph: GALOY) { callbackEndpoints: [CallbackEndpoint!]! + callbackPortalUrl: String! csvTransactions(walletIds: [WalletId!]!): String! defaultWallet: PublicWallet! defaultWalletId: WalletId! @deprecated(reason: "Shifting property to 'defaultWallet.id'") @@ -383,6 +384,7 @@ type ConsumerAccount implements Account id: ID! welcomeProfile: WelcomeProfile @join__field(graph: CIRCLES) callbackEndpoints: [CallbackEndpoint!]! @join__field(graph: GALOY) + callbackPortalUrl: String! @join__field(graph: GALOY) """ return CSV stream, base64 encoded, of the list of transactions in the wallet @@ -610,17 +612,6 @@ type GraphQLApplicationError implements Error scalar Hex32Bytes @join__type(graph: GALOY) -type InAppNotification - @join__type(graph: GALOY) -{ - id: ID! - title: String! - body: String! - deepLink: String - createdAt: Timestamp! - readAt: Timestamp -} - union InitiationVia @join__type(graph: GALOY) @join__unionMember(graph: GALOY, member: "InitiationViaIntraLedger") @@ -1175,6 +1166,14 @@ type MerchantPayload scalar Minutes @join__type(graph: GALOY) +type MobileSession + @join__type(graph: GALOY) +{ + expiresAt: Timestamp! + id: ID! + issuedAt: Timestamp! +} + type MobileVersions @join__type(graph: GALOY) { @@ -1189,7 +1188,7 @@ type Mutation { apiKeyCreate(input: ApiKeyCreateInput!): ApiKeyCreatePayload! @join__field(graph: GALOY) apiKeyRevoke(input: ApiKeyRevokeInput!): ApiKeyRevokePayload! @join__field(graph: GALOY) - userInAppNotificationMarkAsRead(input: UserInAppNotificationMarkAsReadInput!): UserInAppNotificationMarkAsReadPayload! @join__field(graph: GALOY) + statefulNotificationAcknowledge(input: StatefulNotificationAcknowledgeInput!): StatefulNotificationAcknowledgePayload! @join__field(graph: GALOY) accountDelete: AccountDeletePayload! @join__field(graph: GALOY) accountDisableNotificationCategory(input: AccountDisableNotificationCategoryInput!): AccountUpdateNotificationSettingsPayload! @join__field(graph: GALOY) accountDisableNotificationChannel(input: AccountDisableNotificationChannelInput!): AccountUpdateNotificationSettingsPayload! @join__field(graph: GALOY) @@ -1509,21 +1508,21 @@ type OneDayAccountLimit implements AccountLimit scalar OneTimeAuthCode @join__type(graph: GALOY) -"""Information about pagination in a connection.""" +"""Information about pagination in a connection""" type PageInfo @join__type(graph: GALOY) { - """When paginating forwards, the cursor to continue.""" - endCursor: String + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! """When paginating forwards, are there more items?""" hasNextPage: Boolean! - """When paginating backwards, are there more items?""" - hasPreviousPage: Boolean! - """When paginating backwards, the cursor to continue.""" startCursor: String + + """When paginating forwards, the cursor to continue.""" + endCursor: String } scalar PaymentHash @@ -1829,6 +1828,53 @@ A string amount (of a currency) that can be negative (e.g. in a transaction) scalar SignedDisplayMajorAmount @join__type(graph: GALOY) +type StatefulNotification + @join__type(graph: GALOY) +{ + id: ID! + title: String! + body: String! + deepLink: String + createdAt: Timestamp! + acknowledgedAt: Timestamp +} + +input StatefulNotificationAcknowledgeInput + @join__type(graph: GALOY) +{ + notificationId: ID! +} + +type StatefulNotificationAcknowledgePayload + @join__type(graph: GALOY) +{ + notification: StatefulNotification! +} + +type StatefulNotificationConnection + @join__type(graph: GALOY) +{ + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [StatefulNotificationEdge!]! + + """A list of nodes.""" + nodes: [StatefulNotification!]! +} + +"""An edge in a connection.""" +type StatefulNotificationEdge + @join__type(graph: GALOY) +{ + """The item at the end of the edge""" + node: StatefulNotification! + + """A cursor for use in pagination""" + cursor: String! +} + type Subscription @join__type(graph: GALOY) { @@ -2069,7 +2115,7 @@ type User { id: ID! apiKeys: [ApiKey!]! - inAppNotifications(onlyUnread: Boolean): [InAppNotification!]! + statefulNotifications(first: Int!, after: String): StatefulNotificationConnection! """ Get single contact details. @@ -2094,6 +2140,9 @@ type User """ language: Language! + """List of mobile sessions""" + mobileSessions: [MobileSession!]! + """Phone number with international calling code.""" phone: Phone supportChat: [SupportMessage!]! @@ -2184,18 +2233,6 @@ type UserEmailRegistrationValidatePayload me: User } -input UserInAppNotificationMarkAsReadInput - @join__type(graph: GALOY) -{ - notificationId: ID! -} - -type UserInAppNotificationMarkAsReadPayload - @join__type(graph: GALOY) -{ - notification: InAppNotification! -} - input UserLoginInput @join__type(graph: GALOY) {