From 3b09a63b6470119a168ab60e65f12150a7c95214 Mon Sep 17 00:00:00 2001 From: 2624789 <2624789@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:20:47 -0500 Subject: [PATCH 01/44] Use RNSecureKeyStore for auth token persistent storage --- app/hooks/use-logout.ts | 1 + app/store/persistent-state/index.tsx | 8 +++-- .../persistent-state/state-migrations.ts | 3 ++ app/utils/storage/secureStorage.ts | 36 +++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/hooks/use-logout.ts b/app/hooks/use-logout.ts index 9e1a51f448..e43bd921af 100644 --- a/app/hooks/use-logout.ts +++ b/app/hooks/use-logout.ts @@ -34,6 +34,7 @@ const useLogout = () => { await KeyStoreWrapper.removeIsBiometricsEnabled() await KeyStoreWrapper.removePin() await KeyStoreWrapper.removePinAttempts() + await KeyStoreWrapper.removeSecurePersitentState() logLogout() if (stateToDefault) { diff --git a/app/store/persistent-state/index.tsx b/app/store/persistent-state/index.tsx index 43959f1a98..8c7e47a9ba 100644 --- a/app/store/persistent-state/index.tsx +++ b/app/store/persistent-state/index.tsx @@ -2,6 +2,7 @@ import { createContext, useContext, PropsWithChildren } from "react" import * as React from "react" import { loadJson, saveJson } from "@app/utils/storage" +import KeyStoreWrapper from "@app/utils/storage/secureStorage" import { defaultPersistentState, @@ -13,11 +14,14 @@ const PERSISTENT_STATE_KEY = "persistentState" const loadPersistentState = async (): Promise => { const data = await loadJson(PERSISTENT_STATE_KEY) - return migrateAndGetPersistentState(data) + const secureData = await KeyStoreWrapper.getSecurePersitentState() + return migrateAndGetPersistentState({ ...data, ...secureData }) } const savePersistentState = async (state: PersistentState) => { - return saveJson(PERSISTENT_STATE_KEY, state) + const { galoyAuthToken, ...data } = state + saveJson(PERSISTENT_STATE_KEY, data) + KeyStoreWrapper.setSecurePersitentState({ galoyAuthToken }) } // TODO: should not be exported diff --git a/app/store/persistent-state/state-migrations.ts b/app/store/persistent-state/state-migrations.ts index 64af8ecb4b..4fbc507e09 100644 --- a/app/store/persistent-state/state-migrations.ts +++ b/app/store/persistent-state/state-migrations.ts @@ -108,6 +108,9 @@ const stateMigrations: StateMigrations = { } export type PersistentState = PersistentState_6 +export type SecurePersistentState = { + galoyAuthToken: string +} export const defaultPersistentState: PersistentState = { schemaVersion: 6, diff --git a/app/utils/storage/secureStorage.ts b/app/utils/storage/secureStorage.ts index 53d10e3483..6b27259429 100644 --- a/app/utils/storage/secureStorage.ts +++ b/app/utils/storage/secureStorage.ts @@ -1,9 +1,45 @@ import RNSecureKeyStore, { ACCESSIBLE } from "react-native-secure-key-store" +import { SecurePersistentState } from "@app/store/persistent-state/state-migrations" + export default class KeyStoreWrapper { private static readonly IS_BIOMETRICS_ENABLED = "isBiometricsEnabled" private static readonly PIN = "PIN" private static readonly PIN_ATTEMPTS = "pinAttempts" + private static readonly SECURE_STATE = "secureState" + + public static async getSecurePersitentState(): Promise< + SecurePersistentState | Record + > { + try { + const data = await RNSecureKeyStore.get(KeyStoreWrapper.SECURE_STATE) + return JSON.parse(data) + } catch { + return {} + } + } + + public static async setSecurePersitentState( + state: SecurePersistentState, + ): Promise { + try { + await RNSecureKeyStore.set(KeyStoreWrapper.SECURE_STATE, JSON.stringify(state), { + accessible: ACCESSIBLE.ALWAYS_THIS_DEVICE_ONLY, + }) + return true + } catch { + return false + } + } + + public static async removeSecurePersitentState(): Promise { + try { + await RNSecureKeyStore.remove(KeyStoreWrapper.SECURE_STATE) + return true + } catch { + return false + } + } public static async getIsBiometricsEnabled(): Promise { try { From 1d9ec4ef820997d90d48d158e11b5c5c04c031b7 Mon Sep 17 00:00:00 2001 From: 2624789 <2624789@users.noreply.github.com> Date: Mon, 25 Mar 2024 08:23:44 -0500 Subject: [PATCH 02/44] Fix typo --- app/hooks/use-logout.ts | 2 +- app/store/persistent-state/index.tsx | 4 ++-- app/utils/storage/secureStorage.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/hooks/use-logout.ts b/app/hooks/use-logout.ts index e43bd921af..76f548a41b 100644 --- a/app/hooks/use-logout.ts +++ b/app/hooks/use-logout.ts @@ -34,7 +34,7 @@ const useLogout = () => { await KeyStoreWrapper.removeIsBiometricsEnabled() await KeyStoreWrapper.removePin() await KeyStoreWrapper.removePinAttempts() - await KeyStoreWrapper.removeSecurePersitentState() + await KeyStoreWrapper.removeSecurePersistentState() logLogout() if (stateToDefault) { diff --git a/app/store/persistent-state/index.tsx b/app/store/persistent-state/index.tsx index 8c7e47a9ba..e8c34fdb61 100644 --- a/app/store/persistent-state/index.tsx +++ b/app/store/persistent-state/index.tsx @@ -14,14 +14,14 @@ const PERSISTENT_STATE_KEY = "persistentState" const loadPersistentState = async (): Promise => { const data = await loadJson(PERSISTENT_STATE_KEY) - const secureData = await KeyStoreWrapper.getSecurePersitentState() + const secureData = await KeyStoreWrapper.getSecurePersistentState() return migrateAndGetPersistentState({ ...data, ...secureData }) } const savePersistentState = async (state: PersistentState) => { const { galoyAuthToken, ...data } = state saveJson(PERSISTENT_STATE_KEY, data) - KeyStoreWrapper.setSecurePersitentState({ galoyAuthToken }) + KeyStoreWrapper.setSecurePersistentState({ galoyAuthToken }) } // TODO: should not be exported diff --git a/app/utils/storage/secureStorage.ts b/app/utils/storage/secureStorage.ts index 6b27259429..555cd3262f 100644 --- a/app/utils/storage/secureStorage.ts +++ b/app/utils/storage/secureStorage.ts @@ -8,7 +8,7 @@ export default class KeyStoreWrapper { private static readonly PIN_ATTEMPTS = "pinAttempts" private static readonly SECURE_STATE = "secureState" - public static async getSecurePersitentState(): Promise< + public static async getSecurePersistentState(): Promise< SecurePersistentState | Record > { try { @@ -19,7 +19,7 @@ export default class KeyStoreWrapper { } } - public static async setSecurePersitentState( + public static async setSecurePersistentState( state: SecurePersistentState, ): Promise { try { @@ -32,7 +32,7 @@ export default class KeyStoreWrapper { } } - public static async removeSecurePersitentState(): Promise { + public static async removeSecurePersistentState(): Promise { try { await RNSecureKeyStore.remove(KeyStoreWrapper.SECURE_STATE) return true From 3159fbdb81fe56d9069f43366691b75d543c7226 Mon Sep 17 00:00:00 2001 From: Sandipan Date: Fri, 22 Mar 2024 01:06:48 +0530 Subject: [PATCH 03/44] ci: nixify concourse setup (#3127) --- ci/pipeline.yml | 237 ++++++----------------- ci/tasks/build-android.sh | 27 --- ci/tasks/build-ios.sh | 37 ---- ci/tasks/build.sh | 53 +++++ ci/tasks/bump-and-commit-build-number.sh | 47 ++--- ci/tasks/upload-to-gcs.sh | 3 +- ci/values.yml | 1 + ios/fastlane/Fastfile | 22 ++- ios/fastlane/README.md | 64 ------ package.json | 4 +- 10 files changed, 154 insertions(+), 341 deletions(-) delete mode 100755 ci/tasks/build-android.sh delete mode 100755 ci/tasks/build-ios.sh create mode 100755 ci/tasks/build.sh delete mode 100644 ios/fastlane/README.md diff --git a/ci/pipeline.yml b/ci/pipeline.yml index b33408347b..c0a99a3ade 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -24,34 +24,25 @@ source: repository: #@ release_pipeline_image() #@ end +#@ def claim_lock(): +put: mobile-concourse-lock +params: { claim: mobile-concourse } +#@ end + +#@ def release_lock(): +put: mobile-concourse-lock +params: { release: mobile-concourse-lock } +#@ end + groups: - name: all jobs: - test-unit - check-code - install-deps - - dev-build-android - - dev-build-ios - - e2e-test-android - - e2e-test-ios + - dev-build - prerelease - - prod-build-android - - prod-build-ios - - build-pipeline-image -- name: development - jobs: - - test-unit - - check-code - - install-deps - - dev-build-android - - dev-build-ios -- name: e2e-builds - jobs: - - e2e-test-android - - e2e-test-ios - - prerelease - - prod-build-android - - prod-build-ios + - prod-build - name: image jobs: - build-pipeline-image @@ -113,7 +104,7 @@ jobs: run: path: pipeline-tasks/ci/tasks/check-code.sh -- name: dev-build-android +- name: dev-build serial: true plan: - in_parallel: @@ -124,79 +115,35 @@ jobs: - check-code - get: pipeline-tasks - get: build-number-android - - task: build - tags: [ mac-m1 ] - config: - platform: darwin - inputs: - - name: repo - - name: pipeline-tasks - - name: build-number-android - outputs: - - name: repo - - name: artifacts - run: - path: /bin/sh - args: - - "-c" - - "arch -arm64 pipeline-tasks/ci/tasks/build-android.sh" - params: - VERSION_FILE: repo/.git/ref - ANDROID_KEYSTORE: #@ data.values.android_keystore - LANG: en_US.UTF-8 - - task: upload-to-gcs - config: - platform: linux - image_resource: #@ task_image_config() - inputs: - - name: repo - - name: artifacts - - name: pipeline-tasks - run: - path: pipeline-tasks/ci/tasks/upload-to-gcs.sh - params: - INPUTS: android/app/build/outputs/* - VERSION_FILE: repo/.git/ref - GCS_DIRECTORY: dev/android - bucket: #@ data.values.build_artifacts_bucket_name - json_key: #@ data.values.build_artifacts_bucket_creds - -- name: dev-build-ios - serial: true - plan: - - in_parallel: - - get: repo - trigger: true - passed: - - test-unit - - check-code - - get: pipeline-tasks - get: build-number-ios + - #@ claim_lock() - task: build tags: [ mac-m1 ] + ensure: #@ release_lock() config: platform: darwin inputs: - name: repo - name: pipeline-tasks + - name: build-number-android - name: build-number-ios outputs: - name: repo - name: artifacts run: path: /bin/sh - args: - - "-c" - - "arch -arm64 pipeline-tasks/ci/tasks/build-ios.sh" + args: [ "-c", "arch -arm64 pipeline-tasks/ci/tasks/build.sh" ] params: + CI: true VERSION_FILE: repo/.git/ref + LANG: en_US.UTF-8 + ANDROID_KEYSTORE: #@ data.values.android_keystore MATCH_PASSWORD: #@ data.values.fastlane_match_password MATCH_KEYCHAIN_PASSWORD: #@ data.values.fastlane_match_keychain_password GITHUB_SSH_KEY: #@ data.values.github_private_key APPSTORE_API_KEY: #@ data.values.appstore_api_key APPSTORE_API_KEY_ID: #@ data.values.appstore_api_key_id APPSTORE_ISSUER_ID: #@ data.values.appstore_issuer_id - LANG: en_US.UTF-8 - task: upload-to-gcs config: platform: linux @@ -208,38 +155,11 @@ jobs: run: path: pipeline-tasks/ci/tasks/upload-to-gcs.sh params: - INPUTS: ios/Blink.ipa - GCS_DIRECTORY: dev/ios VERSION_FILE: repo/.git/ref + GCS_DIRECTORY: dev bucket: #@ data.values.build_artifacts_bucket_name json_key: #@ data.values.build_artifacts_bucket_creds -- name: e2e-test-android - serial: true - plan: - - in_parallel: - - get: built-dev-apk - trigger: true - - get: repo - passed: ["dev-build-android"] - trigger: true - - get: bundled-deps - passed: ["test-unit"] - - get: pipeline-tasks - -- name: e2e-test-ios - serial: true - plan: - - in_parallel: - - get: built-dev-ipa - trigger: true - - get: repo - passed: ["dev-build-ios"] - trigger: true - - get: bundled-deps - passed: ["test-unit"] - - get: pipeline-tasks - - name: prerelease serial: true plan: @@ -247,16 +167,14 @@ jobs: - get: daily trigger: true - get: repo - passed: ["e2e-test-android", "e2e-test-ios"] + passed: ["dev-build"] params: fetch_tags: true - get: built-dev-ipa - passed: [ e2e-test-ios ] - get: built-dev-apk - passed: [ e2e-test-android ] - get: pipeline-tasks - get: testflight-version - - task: choose-commit-prerelease + - task: check-same-commit config: platform: linux image_resource: #@ release_task_image_config() @@ -295,127 +213,73 @@ jobs: params: file: testflight-version/version -- name: prod-build-android +- name: prod-build serial: true plan: - in_parallel: - get: repo passed: ["prerelease"] - get: build-number-android + - get: build-number-ios - get: pipeline-tasks - get: built-dev-apk passed: [ "prerelease" ] trigger: true + - get: built-dev-ipa + passed: [ "prerelease" ] + trigger: true - get: testflight-version passed: [ "prerelease" ] - - task: bump-and-commit-build-number + - task: check-same-commit config: platform: linux - image_resource: #@ task_image_config() - inputs: - - name: pipeline-tasks - - name: build-number-android - path: build-number - - name: testflight-version - path: version - outputs: - - name: build-number-android - path: build-number - run: - path: pipeline-tasks/ci/tasks/bump-and-commit-build-number.sh - params: - PLATFORM: android - - task: build - tags: [ mac-m1 ] - config: - platform: darwin + image_resource: #@ release_task_image_config() inputs: - name: repo - name: pipeline-tasks - - name: build-number-android - - name: testflight-version + - name: built-dev-ipa + - name: built-dev-apk outputs: - name: repo - name: artifacts run: - path: /bin/sh - args: - - "-c" - - "arch -arm64 pipeline-tasks/ci/tasks/build-android.sh" - params: - VERSION_FILE: testflight-version/version - ANDROID_KEYSTORE: #@ data.values.android_keystore - - task: upload-to-gcs - config: - platform: linux - image_resource: #@ task_image_config() - inputs: - - name: repo - - name: artifacts - - name: pipeline-tasks - - name: testflight-version - run: - path: pipeline-tasks/ci/tasks/upload-to-gcs.sh - params: - INPUTS: android/app/build/outputs/* - GCS_DIRECTORY: prod/android - VERSION_FILE: testflight-version/version - bucket: #@ data.values.build_artifacts_bucket_name - json_key: #@ data.values.build_artifacts_bucket_creds - - put: build-number-android - params: - repository: build-number-android - rebase: true - -- name: prod-build-ios - serial: true - plan: - - in_parallel: - - get: repo - passed: ["prerelease"] - - get: pipeline-tasks - - get: build-number-ios - - get: built-dev-ipa - passed: [ "prerelease" ] - trigger: true - - get: testflight-version - passed: [ "prerelease" ] + path: pipeline-tasks/ci/tasks/choose-commit-prerelease.sh - task: bump-and-commit-build-number config: platform: linux image_resource: #@ task_image_config() inputs: - name: pipeline-tasks + - name: build-number-android - name: build-number-ios - path: build-number - name: testflight-version - path: version outputs: + - name: build-number-android - name: build-number-ios - path: build-number run: path: pipeline-tasks/ci/tasks/bump-and-commit-build-number.sh - params: - PLATFORM: ios + - #@ claim_lock() - task: build tags: [ mac-m1 ] + ensure: #@ release_lock() config: platform: darwin inputs: - name: repo - name: pipeline-tasks + - name: build-number-android - name: build-number-ios - - name: testflight-version outputs: - name: repo - name: artifacts run: path: /bin/sh - args: - - "-c" - - "arch -arm64 pipeline-tasks/ci/tasks/build-ios.sh" + args: [ "-c", "arch -arm64 pipeline-tasks/ci/tasks/build.sh" ] params: - VERSION_FILE: testflight-version/version + CI: true + VERSION_FILE: repo/.git/ref + LANG: en_US.UTF-8 + ANDROID_KEYSTORE: #@ data.values.android_keystore MATCH_PASSWORD: #@ data.values.fastlane_match_password MATCH_KEYCHAIN_PASSWORD: #@ data.values.fastlane_match_keychain_password GITHUB_SSH_KEY: #@ data.values.github_private_key @@ -434,11 +298,14 @@ jobs: run: path: pipeline-tasks/ci/tasks/upload-to-gcs.sh params: - INPUTS: ios/Blink.ipa - GCS_DIRECTORY: prod/ios VERSION_FILE: testflight-version/version + GCS_DIRECTORY: prod bucket: #@ data.values.build_artifacts_bucket_name json_key: #@ data.values.build_artifacts_bucket_creds + - put: build-number-android + params: + repository: build-number-android + rebase: true - put: build-number-ios params: repository: build-number-ios @@ -591,6 +458,14 @@ resources: branch: #@ data.values.git_build_number_branch private_key: #@ data.values.github_private_key +- name: mobile-concourse-lock + type: pool + source: + uri: #@ data.values.concourse_locks_git_uri + branch: main + pool: mobile-concourse + private_key: #@ data.values.github_private_key + resource_types: - name: gcs-resource type: docker-image diff --git a/ci/tasks/build-android.sh b/ci/tasks/build-android.sh deleted file mode 100755 index 2f1f0a92e8..0000000000 --- a/ci/tasks/build-android.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/zsh - -set -eu -export PATH=$(cat /Users/m1/concourse/path) - -export BUILD_NUMBER=$(cat build-number-android/android) -export PUBLIC_VERSION=$(cat $VERSION_FILE) - -GIT_REF=$(cat repo/.git/ref) - -pushd repo -git checkout $GIT_REF - -nix develop -c yarn install - -pushd android -nix develop -c bundle install -popd - -echo $ANDROID_KEYSTORE | base64 -d > android/app/release.keystore - -sed -i'' -e "s/versionCode .*$/versionCode $BUILD_NUMBER/g" android/app/build.gradle -nix develop -c sh -c 'cd android && bundle exec fastlane android build --verbose' -popd - -mkdir -p artifacts/android/app/build/outputs -cp -r repo/android/app/build/outputs/* artifacts/android/app/build/outputs diff --git a/ci/tasks/build-ios.sh b/ci/tasks/build-ios.sh deleted file mode 100755 index 51d076384a..0000000000 --- a/ci/tasks/build-ios.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/zsh - -set -eu -export PATH=$(cat /Users/m1/concourse/path) - -# Make sure ssh agent is running -eval "$(ssh-agent -s)" -cat < id_rsa -$GITHUB_SSH_KEY -EOF -chmod 600 id_rsa -ssh-add ./id_rsa -rm id_rsa - -BUILD_NUMBER=$(cat build-number-ios/ios) -export PUBLIC_VERSION=$(cat $VERSION_FILE) - -GIT_REF=$(cat repo/.git/ref) - -pushd repo -git checkout $GIT_REF - -nix develop -c yarn install - -# Kill existing Metro -lsof -ti:8080,8081 | xargs kill || true -tmpfile=$(mktemp /tmp/wwdr-cert.cer.XXXXXXXXX) || true -curl -f -o $tmpfile https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer && security import $tmpfile ~/Library/Keychains/login.keychain-db || true - -sed -i'' -e "s/MARKETING_VERSION.*/MARKETING_VERSION = $PUBLIC_VERSION;/g" ios/GaloyApp.xcodeproj/project.pbxproj -nix develop -c sh -c 'cd ios && bundle exec fastlane ios build --verbose' -# Kill spawned Metro -lsof -ti:8080,8081 | xargs kill || true -popd - -mkdir -p artifacts/ios -cp repo/ios/Blink.ipa artifacts/ios diff --git a/ci/tasks/build.sh b/ci/tasks/build.sh new file mode 100755 index 0000000000..079375fa76 --- /dev/null +++ b/ci/tasks/build.sh @@ -0,0 +1,53 @@ +#!/bin/zsh + +set -eu +export CI_ROOT="$(pwd)" + +export PATH=$(cat /Users/m1/concourse/path) +export PUBLIC_VERSION=$(cat $VERSION_FILE) + +# Make sure ssh agent is running - to access GaloyMoney ios keystore from github +eval "$(ssh-agent -s)" +cat < id_rsa +$GITHUB_SSH_KEY +EOF +chmod 600 id_rsa && ssh-add ./id_rsa && rm id_rsa + +tmpfile=$(mktemp /tmp/wwdr-cert.cer.XXXXXXXXX) || true +curl -f -o $tmpfile https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer && security import $tmpfile ~/Library/Keychains/login.keychain-db || true + +# Checkout correct commit +GIT_REF=$(cat repo/.git/ref) + +pushd repo +git checkout $GIT_REF + +nix develop -c yarn install +nix develop -c sh -c 'cd android && bundle install' +nix develop -c sh -c 'cd ios && bundle install' + +lsof -ti:8080,8081 | xargs kill -9 || true +(nix develop -c yarn start) & +until lsof -ti:8080,8081; do sleep 1; echo "waiting for metro to come up..." ; done + +# Android Build +export BUILD_NUMBER=$(cat ${CI_ROOT}/build-number-android/android) +sed -i'' -e "s/versionCode .*$/versionCode $BUILD_NUMBER/g" android/app/build.gradle + +echo $ANDROID_KEYSTORE | base64 -d > android/app/release.keystore +nix develop -c sh -c 'cd android && bundle exec fastlane android build --verbose' + +# iOS Build +export BUILD_NUMBER=$(cat ${CI_ROOT}/build-number-ios/ios) +sed -i'' -e "s/MARKETING_VERSION.*/MARKETING_VERSION = $PUBLIC_VERSION;/g" ios/GaloyApp.xcodeproj/project.pbxproj + +nix develop -c sh -c 'cd ios && bundle exec fastlane ios build --verbose' + +lsof -ti:8080,8081 | xargs kill -9 || true +popd + +mkdir -p artifacts/android/app/build/outputs +cp -r repo/android/app/build/outputs/* artifacts/android/app/build/outputs + +mkdir -p artifacts/ios +cp repo/ios/Blink.ipa artifacts/ios diff --git a/ci/tasks/bump-and-commit-build-number.sh b/ci/tasks/bump-and-commit-build-number.sh index b54efc8c47..a5a1be72e7 100755 --- a/ci/tasks/bump-and-commit-build-number.sh +++ b/ci/tasks/bump-and-commit-build-number.sh @@ -4,33 +4,34 @@ set -eu export VERSION=$(cat ./version/version) -pushd build-number +for PLATFORM in android ios; do + pushd build-number-${PLATFORM} + current=$(cat $PLATFORM) -current=$(cat $PLATFORM) + echo "Current Build Number: $current" -echo "Current Build Number: $current" + new=$(($current + 1)) -new=$(($current + 1)) + echo "Updated Build Number: $new" -echo "Updated Build Number: $new" + echo $new > $PLATFORM -echo $new > $PLATFORM + if [[ -z $(git config --global user.email) ]]; then + git config --global user.email "bot@galoy.io" + fi + if [[ -z $(git config --global user.name) ]]; then + git config --global user.name "CI Bot" + fi -if [[ -z $(git config --global user.email) ]]; then - git config --global user.email "bot@galoy.io" -fi -if [[ -z $(git config --global user.name) ]]; then - git config --global user.name "CI Bot" -fi + mkdir -p ${PLATFORM}-builds + cd ${PLATFORM}-builds + echo $new > $VERSION -mkdir -p ${PLATFORM}-builds -cd ${PLATFORM}-builds -echo $new > $VERSION - -( - cd $(git rev-parse --show-toplevel) - git add ${PLATFORM} - git add ${PLATFORM}-builds/$VERSION - git status - git commit -m "chore(build-number): bump ${PLATFORM} build number from $current to $new" -) + ( + cd $(git rev-parse --show-toplevel) + git add ${PLATFORM} + git add ${PLATFORM}-builds/$VERSION + git status + git commit -m "chore(build-number): bump ${PLATFORM} build number from $current to $new" + ) + popd diff --git a/ci/tasks/upload-to-gcs.sh b/ci/tasks/upload-to-gcs.sh index 7fa86a613d..56fe679848 100755 --- a/ci/tasks/upload-to-gcs.sh +++ b/ci/tasks/upload-to-gcs.sh @@ -9,4 +9,5 @@ gcloud auth activate-service-account --key-file key.json pushd artifacts -gsutil cp -r $INPUTS gs://$bucket/galoy-mobile/$GCS_DIRECTORY/galoy-mobile-$(date +%s)-v${version}/ +gsutil cp -r android/app/build/outputs/* gs://$bucket/galoy-mobile/$GCS_DIRECTORY/android/galoy-mobile-$(date +%s)-v${version}/ +gsutil cp -r ios/Blink.ipa gs://$bucket/galoy-mobile/$GCS_DIRECTORY/ios/galoy-mobile-$(date +%s)-v${version}/ diff --git a/ci/values.yml b/ci/values.yml index baa423ad81..b7f56c870b 100644 --- a/ci/values.yml +++ b/ci/values.yml @@ -1,6 +1,7 @@ #@data/values --- git_uri: git@github.com:GaloyMoney/galoy-mobile.git +concourse_locks_git_uri: git@github.com:GaloyMoney/concourse-locks.git git_mobile_deployments_uri: git@github.com:GaloyMoney/galoy-mobile-deployments.git git_version_branch: main diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 924252abdb..31ae0ff3c4 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -28,12 +28,20 @@ def app_review_info end platform :ios do - before_all do - setup_circle_ci - end - desc "Build Releasable IPA" lane :build do + keychain_name = "blink" + keychain_password = "blink" # not sensitive because local + + create_keychain( + name: keychain_name, + password: keychain_password, + default_keychain: true, + unlock: true, + timeout: 3600, + lock_when_sleeps: false + ) + app_store_connect_api_key( key_id: ENV["APPSTORE_API_KEY_ID"], issuer_id: ENV["APPSTORE_ISSUER_ID"], @@ -42,8 +50,10 @@ platform :ios do ) increment_build_number(build_number: ENV["BUILD_NUMBER"], xcodeproj: "GaloyApp.xcodeproj") - match(type: "appstore", readonly: is_ci) - gym(scheme: "GaloyApp") + match(type: "appstore", readonly: is_ci, keychain_name: keychain_name, keychain_password: keychain_password) + gym(scheme: "GaloyApp", clean:true, export_xcargs: "-allowProvisioningUpdates") + ensure + delete_keychain(name: keychain_name) end desc "Upload to App Store TestFlight" diff --git a/ios/fastlane/README.md b/ios/fastlane/README.md deleted file mode 100644 index 0cf2039712..0000000000 --- a/ios/fastlane/README.md +++ /dev/null @@ -1,64 +0,0 @@ -fastlane documentation ----- - -# Installation - -Make sure you have the latest version of the Xcode command line tools installed: - -```sh -xcode-select --install -``` - -For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) - -# Available Actions - -## iOS - -### ios beta - -```sh -[bundle exec] fastlane ios beta -``` - -Push a new beta build to TestFlight - -### ios browserstack - -```sh -[bundle exec] fastlane ios browserstack -``` - -End to end testing on browserstack - -### ios increment - -```sh -[bundle exec] fastlane ios increment -``` - -increment path version - -### ios build_ipa - -```sh -[bundle exec] fastlane ios build_ipa -``` - -build ipa - -### ios build_e2e - -```sh -[bundle exec] fastlane ios build_e2e -``` - -Build for end to end tests - ----- - -This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. - -More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). - -The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/package.json b/package.json index 1a254e1ba8..1e6f844fd4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.1", "private": true, "scripts": { - "start": "yarn watchman:del && react-native start", + "start": "react-native start", "ios": "react-native run-ios", "android": "react-native run-android", "watchman:del": "if which watchman >/dev/null; then watchman watch-del $(pwd) >/dev/null; fi", @@ -26,7 +26,7 @@ "dev:gengql": "yarn node utils/create-gql.ts", "adb": "adb reverse tcp:9090 tcp:9090 && adb reverse tcp:3000 tcp:3000 && adb reverse tcp:4000 tcp:4000 && adb reverse tcp:9001 tcp:9001 && adb reverse tcp:8081 tcp:8081 && adb reverse tcp:4002 tcp:4002 && adb reverse tcp:4455 tcp:4455", "patch": "patch-package", - "postinstall": "jetify && yarn patch-package && if which pod >/dev/null; then (cd ios; pod install --allow-root --no-ansi); fi", + "postinstall": "jetify && yarn patch-package && if which pod >/dev/null; then (cd ios; pod install --allow-root); fi", "hack:types-react-native": "rimraf node_modules/@types/react-native/node_modules/@types", "prepare": "npm-run-all patch hack:*", "client:dev": "yalc add @galoymoney/client", From b23a52114983b6ed3449c77617c41fe0a903e3de Mon Sep 17 00:00:00 2001 From: sandipndev Date: Fri, 22 Mar 2024 12:20:17 +0530 Subject: [PATCH 04/44] ci: fix for prod builds --- ci/pipeline.yml | 1 + ci/tasks/bump-and-commit-build-number.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index c0a99a3ade..d3dd7fb1f6 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -253,6 +253,7 @@ jobs: - name: build-number-android - name: build-number-ios - name: testflight-version + path: version outputs: - name: build-number-android - name: build-number-ios diff --git a/ci/tasks/bump-and-commit-build-number.sh b/ci/tasks/bump-and-commit-build-number.sh index a5a1be72e7..e6819275b9 100755 --- a/ci/tasks/bump-and-commit-build-number.sh +++ b/ci/tasks/bump-and-commit-build-number.sh @@ -35,3 +35,4 @@ for PLATFORM in android ios; do git commit -m "chore(build-number): bump ${PLATFORM} build number from $current to $new" ) popd +done From c986671c272655e9f523ba66dc310ab034a6a6b4 Mon Sep 17 00:00:00 2001 From: sandipndev Date: Fri, 22 Mar 2024 12:48:40 +0530 Subject: [PATCH 05/44] ci: fix version file name for prod --- ci/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index d3dd7fb1f6..29ada4ad2f 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -278,7 +278,7 @@ jobs: args: [ "-c", "arch -arm64 pipeline-tasks/ci/tasks/build.sh" ] params: CI: true - VERSION_FILE: repo/.git/ref + VERSION_FILE: testflight-version/version LANG: en_US.UTF-8 ANDROID_KEYSTORE: #@ data.values.android_keystore MATCH_PASSWORD: #@ data.values.fastlane_match_password From a120fee2d1789ed6fdf3854abd66d6162e57dfd6 Mon Sep 17 00:00:00 2001 From: UncleSamtoshi <88598461+UncleSamtoshi@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:18:55 -0500 Subject: [PATCH 06/44] feat: add price and marketing settings (#3124) --- app/i18n/en/index.ts | 8 ++++ app/i18n/i18n-types.ts | 40 +++++++++++++++++++ app/i18n/raw-i18n/source/en.json | 8 ++++ .../settings-screen/notifications-screen.tsx | 2 + 4 files changed, 58 insertions(+) diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index 6befbad16b..c30f331d2b 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -2336,6 +2336,14 @@ const en: BaseTranslation = { Payments: { title: "Payments", description: "Notifications related to sending and receiving payments.", + }, + Marketing: { + title: "Features and updates", + description: "Notifications about new features and updates.", + }, + Price: { + title: "Price changes", + description: "Notifications about the price of Bitcoin.", } } }, diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index 9ad37f1614..de788faceb 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -7282,6 +7282,26 @@ type RootTranslation = { */ description: string } + Marketing: { + /** + * F​e​a​t​u​r​e​s​ ​a​n​d​ ​u​p​d​a​t​e​s + */ + title: string + /** + * N​o​t​i​f​i​c​a​t​i​o​n​s​ ​a​b​o​u​t​ ​n​e​w​ ​f​e​a​t​u​r​e​s​ ​a​n​d​ ​u​p​d​a​t​e​s​. + */ + description: string + } + Price: { + /** + * P​r​i​c​e​ ​c​h​a​n​g​e​s + */ + title: string + /** + * N​o​t​i​f​i​c​a​t​i​o​n​s​ ​a​b​o​u​t​ ​t​h​e​ ​p​r​i​c​e​ ​o​f​ ​B​i​t​c​o​i​n​. + */ + description: string + } } } AccountScreen: { @@ -16099,6 +16119,26 @@ export type TranslationFunctions = { */ description: () => LocalizedString } + Marketing: { + /** + * Features and updates + */ + title: () => LocalizedString + /** + * Notifications about new features and updates. + */ + description: () => LocalizedString + } + Price: { + /** + * Price changes + */ + title: () => LocalizedString + /** + * Notifications about the price of Bitcoin. + */ + description: () => LocalizedString + } } } AccountScreen: { diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index 1d154dea97..72cbf3e107 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -2262,6 +2262,14 @@ "Payments": { "title": "Payments", "description": "Notifications related to sending and receiving payments." + }, + "Marketing": { + "title": "Features and updates", + "description": "Notifications about new features and updates." + }, + "Price": { + "title": "Price changes", + "description": "Notifications about the price of Bitcoin." } } }, diff --git a/app/screens/settings-screen/notifications-screen.tsx b/app/screens/settings-screen/notifications-screen.tsx index 072dfa0b0b..831fdabf49 100644 --- a/app/screens/settings-screen/notifications-screen.tsx +++ b/app/screens/settings-screen/notifications-screen.tsx @@ -117,6 +117,8 @@ gql` const NotificationCategories = { Circles: "Circles", Payments: "Payments", + Price: "Price", + Marketing: "Marketing", } as const type NotificationCategoryType = From 0274c5673dd5ef366542a2d67a879a126d3af072 Mon Sep 17 00:00:00 2001 From: sandipndev Date: Sat, 23 Mar 2024 22:43:30 +0530 Subject: [PATCH 07/44] ci: testflight version was missing --- ci/pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 29ada4ad2f..b6b885c7bd 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -270,6 +270,7 @@ jobs: - name: pipeline-tasks - name: build-number-android - name: build-number-ios + - name: testflight-version outputs: - name: repo - name: artifacts From 06c91edcb506e8f217732003483289fa0b40a31c Mon Sep 17 00:00:00 2001 From: Sandipan Date: Sun, 24 Mar 2024 04:35:53 +0530 Subject: [PATCH 08/44] test(e2e): detox tests (#3126) * chore: tilt ci process is spinning up backend deps ci: fix android/mac setup to open sims, upload rec ci: test chore: try macos ci: test if everything works ci: upload recordings test try emulator creation manually export JAVA_HOME="/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home" ci: single e2e test export JAVA_HOME="/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home" * test(e2e): auth flow * test(e2e): intraledger flow ci: run e2e for all prs * test(e2e): conversion flow * test(e2e): send flow chore: better chore: better chore: external invoices chore: send onchain ci: update chore: fix-code chore: recv test: destroy backend save check code save chore: update quickstart which also fix funding issue fix: e2e change ios device more time tests save test test pls god save ci: separate test: fix * gha fix? builds * Update app/screens/send-bitcoin-screen/send-bitcoin-details-screen.tsx --------- Co-authored-by: nicolasburtey --- .github/workflows/e2e.yml | 154 +++++++++--- .gitignore | 2 + Makefile | 12 + .../galoy-slider-button.tsx | 2 + .../balance-header/balance-header.tsx | 3 +- app/components/code-input/code-input.tsx | 2 +- app/components/screen/screen.tsx | 1 + .../transaction-item/transaction-item.tsx | 4 +- .../wallet-overview/wallet-overview.tsx | 3 +- .../conversion-details-screen.tsx | 5 + app/screens/home-screen/home-screen.tsx | 2 + .../receive-bitcoin-screen/receive-screen.tsx | 1 + .../confirm-destination-modal.tsx | 2 +- .../send-bitcoin-completed-screen.tsx | 9 +- .../send-bitcoin-details-screen.tsx | 8 +- app/screens/settings-screen/account-id.tsx | 5 +- .../settings-screen/account-screen.tsx | 2 + .../transaction-history-screen.tsx | 1 + dev/.env | 2 - dev/Tiltfile | 19 +- dev/bin/dev-ln-setup.sh | 39 +++ dev/bin/dev-setup.sh | 71 ++++++ dev/bin/{blocks.sh => one-block-every-5s.sh} | 2 +- dev/bin/setup.sh | 25 -- dev/vendir.lock.yml | 6 +- dev/vendir.yml | 2 +- dev/vendor/galoy-quickstart/dev/BUCK | 34 ++- dev/vendor/galoy-quickstart/dev/Tiltfile | 195 ++++++++++----- .../apollo-federation/supergraph.graphql | 121 ++++++++-- .../notifications/fake_service_account.json | 10 + .../config/notifications/notifications.yml | 4 + .../galoy-quickstart/dev/config/price.yml | 13 + .../dev/core-bundle/dummy-env.json | 19 ++ .../dev/core-bundle/integration-env.json | 61 +++++ .../dev/core-bundle/serve-env.json | 47 ++++ .../dev/docker-compose.deps.yml | 17 +- .../galoy-quickstart/docker-compose.yml | 31 ++- .../graphql/gql/api-key-create.gql | 1 + .../galoy-quickstart/graphql/gql/api-keys.gql | 1 + .../graphql/gql/authorization.gql | 5 + .../graphql/gql/ln-invoice-cancel.gql | 9 + .../ln-invoice-payment-status-by-hash-sub.gql | 10 + .../gql/ln-invoice-payment-status-by-hash.gql | 7 + ...-payment-status-by-payment-request-sub.gql | 10 + ...oice-payment-status-by-payment-request.gql | 7 + .../gql/ln-invoice-payment-status-sub.gql | 8 - .../graphql/gql/ln-invoice-payment-status.gql | 5 - .../graphql/gql/merchant-map-suggest.gql | 17 ++ ...sactions-for-wallet-by-payment-request.gql | 58 +++++ .../graphql/schemas/admin/schema.graphql | 115 +++++---- .../graphql/schemas/public/schema.graphql | 94 +++++++- e2e/detox/01-auth.test.ts | 138 +++++++++++ e2e/detox/01-phone-flow.test.ts | 72 ------ e2e/detox/02-conversion.test.ts | 60 +++++ e2e/detox/03-payment-send.test.ts | 228 ++++++++++++++++++ e2e/detox/04-payment-receive.test.ts | 198 +++++++++++++++ e2e/detox/jest.config.js | 8 +- e2e/detox/utils/commandline.ts | 140 +++++++++++ e2e/detox/utils/common-flows.ts | 70 ++++++ e2e/detox/utils/config.ts | 11 +- e2e/detox/utils/controls.ts | 39 +++ flake.nix | 5 +- 62 files changed, 1914 insertions(+), 338 deletions(-) delete mode 100644 dev/.env create mode 100755 dev/bin/dev-ln-setup.sh create mode 100755 dev/bin/dev-setup.sh rename dev/bin/{blocks.sh => one-block-every-5s.sh} (94%) delete mode 100755 dev/bin/setup.sh create mode 100644 dev/vendor/galoy-quickstart/dev/config/notifications/fake_service_account.json create mode 100644 dev/vendor/galoy-quickstart/dev/config/notifications/notifications.yml create mode 100644 dev/vendor/galoy-quickstart/dev/config/price.yml create mode 100644 dev/vendor/galoy-quickstart/dev/core-bundle/dummy-env.json create mode 100644 dev/vendor/galoy-quickstart/dev/core-bundle/integration-env.json create mode 100644 dev/vendor/galoy-quickstart/dev/core-bundle/serve-env.json create mode 100644 dev/vendor/galoy-quickstart/graphql/gql/authorization.gql create mode 100644 dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-cancel.gql create mode 100644 dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-hash-sub.gql create mode 100644 dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-hash.gql create mode 100644 dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-payment-request-sub.gql create mode 100644 dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-payment-request.gql delete mode 100644 dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-sub.gql delete mode 100644 dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status.gql create mode 100644 dev/vendor/galoy-quickstart/graphql/gql/merchant-map-suggest.gql create mode 100644 dev/vendor/galoy-quickstart/graphql/gql/transactions-for-wallet-by-payment-request.gql create mode 100644 e2e/detox/01-auth.test.ts delete mode 100644 e2e/detox/01-phone-flow.test.ts create mode 100644 e2e/detox/02-conversion.test.ts create mode 100644 e2e/detox/03-payment-send.test.ts create mode 100644 e2e/detox/04-payment-receive.test.ts create mode 100644 e2e/detox/utils/commandline.ts create mode 100644 e2e/detox/utils/common-flows.ts create mode 100644 e2e/detox/utils/controls.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 023e2dcf45..c1eb042892 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -9,9 +9,10 @@ concurrency: cancel-in-progress: true jobs: - e2e: - name: E2E Tests + e2e-android: + name: Android runs-on: self-hosted + timeout-minutes: 45 steps: - uses: actions/checkout@v2 @@ -19,7 +20,6 @@ jobs: # In case this is run on a runner without nix # - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@v2 - - uses: nicknovitski/nix-develop@v1 # Caching - uses: actions/cache@v2 @@ -33,68 +33,55 @@ jobs: # Installations - name: Install Node Modules and Pods - run: yarn install + run: nix develop -c yarn install env: LANG: en_US.UTF-8 # Metro - name: Start Metro run: | - yarn start & + nix develop -c sh -c 'yarn start' & echo "METRO_PID=$!" >> $GITHUB_ENV # Builds - - run: yarn e2e:build ios.sim.debug - - run: yarn e2e:build android.emu.debug + - run: nix develop -c yarn e2e:build android.emu.debug # Start Tilt - name: Tilt CI uses: nick-fields/retry@v3 with: timeout_minutes: 10 - retry_wait_seconds: 15 - max_attempts: 5 - command: cd dev && tilt ci - - # Tests on iOS Simulator - - name: Run Detox Tests on iOS Simulator - uses: nick-fields/retry@v3 - with: - timeout_minutes: 10 - max_attempts: 5 - command: | - rm -rf ios-recordings || true - yarn e2e:test ios.sim.debug -d --take-screenshots all --record-videos all --record-logs all --artifacts-location ios-recordings - - - run: killall Simulator - if: always() - continue-on-error: true - - - uses: actions/upload-artifact@v4 - if: always() - with: - name: ios-recordings - path: ios-recordings + retry_wait_seconds: 30 + max_attempts: 3 + command: nix develop -c sh -c 'cd dev && tilt ci' + - name: Tilt Server + run: | + lsof -ti:10350 | xargs kill -9 || true + nix develop -c sh -c 'cd dev && tilt up' & + echo "TILT_SERVER_PID=$!" >> $GITHUB_ENV # Tests on Android Emulator - name: Start Android Emulator run: | - make emulator & - adb wait-for-device + nix develop -c sh -c 'emulator -avd Pixel_API_34 -gpu swiftshader -wipe-data -no-boot-anim' & + nix develop -c adb wait-for-device - name: Run Detox Tests on Android Emulator uses: nick-fields/retry@v3 with: - timeout_minutes: 10 - max_attempts: 5 + timeout_minutes: 15 + retry_wait_seconds: 1 + max_attempts: 3 command: | rm -rf android-recordings || true - yarn e2e:test android.emu.debug -d --take-screenshots all --record-videos all --record-logs all --artifacts-location android-recordings + nix develop -c tilt trigger dev-setup + nix develop -c tilt wait --timeout 5m --for=condition=Ready uiresources dev-setup + nix develop -c yarn e2e:test android.emu.debug -d --take-screenshots all --record-videos all --record-logs all --artifacts-location android-recordings - name: Kill Android Emulator if: always() continue-on-error: true - run: adb emu kill + run: nix develop -c adb emu kill - uses: actions/upload-artifact@v4 if: always() @@ -108,10 +95,97 @@ jobs: continue-on-error: true run: kill $METRO_PID - - name: Destroy backend + - name: Cleanup + if: always() + continue-on-error: true + run: | + kill $METRO_PID || true + kill $TILT_SERVER_PID || true + nix develop -c sh -c 'cd dev && tilt down' || true + docker rm -f $(docker ps -aq) || true + lsof -ti:10350,8080,8081 | xargs kill -9 || true + + e2e-ios: + name: iOS + runs-on: self-hosted + timeout-minutes: 45 + + steps: + - uses: actions/checkout@v2 + + # In case this is run on a runner without nix + # - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@v2 + + # Caching + - uses: actions/cache@v2 + with: + path: | + node_modules + ios/Pods + key: ${{ runner.os }}-deps-${{ hashFiles('**/yarn.lock', '**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-deps- + + # Installations + - name: Install Node Modules and Pods + run: nix develop -c yarn install + env: + LANG: en_US.UTF-8 + + # Metro + - name: Start Metro + run: | + nix develop -c sh -c 'yarn start' & + echo "METRO_PID=$!" >> $GITHUB_ENV + + # Builds + - run: nix develop -c yarn e2e:build ios.sim.debug + + # Start Tilt + - name: Tilt CI + uses: nick-fields/retry@v3 + with: + timeout_minutes: 10 + retry_wait_seconds: 30 + max_attempts: 3 + command: nix develop -c sh -c 'cd dev && tilt ci' + - name: Tilt Server + run: | + lsof -ti:10350 | xargs kill -9 || true + nix develop -c sh -c 'cd dev && tilt up' & + echo "TILT_SERVER_PID=$!" >> $GITHUB_ENV + + # Tests on iOS Simulator + - name: Run Detox Tests on iOS Simulator + uses: nick-fields/retry@v3 + with: + timeout_minutes: 15 + retry_wait_seconds: 1 + max_attempts: 3 + command: | + rm -rf ios-recordings || true + nix develop -c tilt trigger dev-setup + nix develop -c tilt wait --timeout 5m --for=condition=Ready uiresources dev-setup + nix develop -c yarn e2e:test ios.sim.debug -d -R 5 --take-screenshots all --record-videos all --record-logs all --artifacts-location ios-recordings + + - run: killall Simulator + if: always() + continue-on-error: true + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: ios-recordings + path: ios-recordings + + # Cleanup + - name: Cleanup if: always() continue-on-error: true run: | - cd dev && tilt down || true - docker volume prune -af || true - docker rm -f $(docker ps -aq) + kill $METRO_PID || true + kill $TILT_SERVER_PID || true + nix develop -c sh -c 'cd dev && tilt down' || true + docker rm -f $(docker ps -aq) || true + lsof -ti:10350,8080,8081 | xargs kill -9 || true diff --git a/.gitignore b/.gitignore index 7b3efdf71c..98b3d0749e 100644 --- a/.gitignore +++ b/.gitignore @@ -89,6 +89,8 @@ android/app/src/main/assets .env.local *.tmp.ci artifacts +ios-recordings +android-recordings .direnv ios/.xcode.env.local diff --git a/Makefile b/Makefile index 739c24a1b5..fcb390194e 100644 --- a/Makefile +++ b/Makefile @@ -27,3 +27,15 @@ tilt-down: emulator: emulator -avd Pixel_API_34 -gpu swiftshader -wipe-data -no-boot-anim + +reset-e2e: + tilt trigger dev-setup + tilt wait --timeout 5m --for=condition=Ready uiresources dev-setup + +e2e-ios: reset-e2e + yarn e2e:build ios.sim.debug + yarn e2e:test ios.sim.debug + +e2e-android: reset-e2e + yarn e2e:build android.emu.debug + yarn e2e:test android.emu.debug diff --git a/app/components/atomic/galoy-slider-button/galoy-slider-button.tsx b/app/components/atomic/galoy-slider-button/galoy-slider-button.tsx index 134f429ad7..66f1dc97e6 100644 --- a/app/components/atomic/galoy-slider-button/galoy-slider-button.tsx +++ b/app/components/atomic/galoy-slider-button/galoy-slider-button.tsx @@ -14,6 +14,7 @@ import Animated, { withSpring, } from "react-native-reanimated" +import { testProps } from "@app/utils/testProps" import { Text, makeStyles, useTheme } from "@rneui/themed" import { GaloyIcon } from "../galoy-icon" @@ -105,6 +106,7 @@ const GaloySliderButton = ({ { @@ -96,7 +97,7 @@ export const BalanceHeader: React.FC = ({ loading }) => { } return ( - + {hideAmount ? ( = ({ = ({ @@ -71,6 +72,7 @@ const TransactionItem: React.FC = ({ isFirst = false, isLast = false, isOnHomeScreen = false, + testId = "transaction-item", }) => { const styles = useStyles({ isFirst, @@ -146,7 +148,7 @@ const TransactionItem: React.FC = ({ return ( navigation.navigate("transactionDetail", { diff --git a/app/components/wallet-overview/wallet-overview.tsx b/app/components/wallet-overview/wallet-overview.tsx index 9496fb3b66..919ad1934e 100644 --- a/app/components/wallet-overview/wallet-overview.tsx +++ b/app/components/wallet-overview/wallet-overview.tsx @@ -120,7 +120,7 @@ const WalletOverview: React.FC = ({ loading, setIsStablesatModalVisible } **** ) : ( - + {btcInUnderlyingCurrency} {btcInDisplayCurrencyFormatted} @@ -148,6 +148,7 @@ const WalletOverview: React.FC = ({ loading, setIsStablesatModalVisible } ) : null} diff --git a/app/screens/conversion-flow/conversion-details-screen.tsx b/app/screens/conversion-flow/conversion-details-screen.tsx index af2f804089..e8a0c53769 100644 --- a/app/screens/conversion-flow/conversion-details-screen.tsx +++ b/app/screens/conversion-flow/conversion-details-screen.tsx @@ -24,6 +24,7 @@ import { toUsdMoneyAmount, toWalletAmount, } from "@app/types/amounts" +import { testProps } from "@app/utils/testProps" import { NavigationProp, useNavigation } from "@react-navigation/native" import { makeStyles, Text, useTheme } from "@rneui/themed" @@ -216,24 +217,28 @@ export const ConversionDetailsScreen = () => { setAmountToBalancePercentage(25)} > 25% setAmountToBalancePercentage(50)} > 50% setAmountToBalancePercentage(75)} > 75% setAmountToBalancePercentage(100)} > diff --git a/app/screens/home-screen/home-screen.tsx b/app/screens/home-screen/home-screen.tsx index 562475ac71..881a8a8162 100644 --- a/app/screens/home-screen/home-screen.tsx +++ b/app/screens/home-screen/home-screen.tsx @@ -254,6 +254,7 @@ export const HomeScreen: React.FC = () => { subtitle isOnHomeScreen={true} isLast={index === array.length - 1} + testId={`transaction-by-index-${index}`} /> ), )} @@ -350,6 +351,7 @@ export const HomeScreen: React.FC = () => { /> { keyboardOffset="navigationHeader" keyboardShouldPersistTaps="handled" style={styles.screenStyle} + {...testProps("receive-screen")} > = ( uncheckedIcon={"square-outline"} onPress={() => setConfirmationEnabled(!confirmationEnabled)} /> - + {LL.SendBitcoinDestinationScreen.confirmUsernameModal.checkBox({ lnAddress, })} diff --git a/app/screens/send-bitcoin-screen/send-bitcoin-completed-screen.tsx b/app/screens/send-bitcoin-screen/send-bitcoin-completed-screen.tsx index c9733f9cfd..297fec4b42 100644 --- a/app/screens/send-bitcoin-screen/send-bitcoin-completed-screen.tsx +++ b/app/screens/send-bitcoin-screen/send-bitcoin-completed-screen.tsx @@ -11,6 +11,7 @@ import { } from "@app/components/success-animation" import { setFeedbackModalShown } from "@app/graphql/client-only-query" import { useFeedbackModalShownQuery } from "@app/graphql/generated" +import { useAppConfig } from "@app/hooks" import { useI18nContext } from "@app/i18n/i18n-react" import { RootStackParamList } from "@app/navigation/stack-param-lists" import { logAppFeedback } from "@app/utils/analytics" @@ -66,7 +67,13 @@ const SendBitcoinCompletedScreen: React.FC = ({ route }) => { InAppReview.RequestInAppReview() } + const { appConfig } = useAppConfig() + const requestFeedback = useCallback(() => { + if (!appConfig || appConfig.galoyInstance.id === "Local") { + return + } + if (InAppReview.isAvailable()) { Alert.alert( "", @@ -88,7 +95,7 @@ const SendBitcoinCompletedScreen: React.FC = ({ route }) => { ) setFeedbackModalShown(client, true) } - }, [LL, client]) + }, [LL, client, appConfig]) const FEEDBACK_DELAY = 3000 const CALLBACK_DELAY = 3000 diff --git a/app/screens/send-bitcoin-screen/send-bitcoin-details-screen.tsx b/app/screens/send-bitcoin-screen/send-bitcoin-details-screen.tsx index 2be70d3d46..c88aa767cb 100644 --- a/app/screens/send-bitcoin-screen/send-bitcoin-details-screen.tsx +++ b/app/screens/send-bitcoin-screen/send-bitcoin-details-screen.tsx @@ -302,6 +302,7 @@ const SendBitcoinDetailsScreen: React.FC = ({ route }) => { return ( { chooseWallet(wallet) }} @@ -484,7 +485,11 @@ const SendBitcoinDetailsScreen: React.FC = ({ route }) => { {LL.common.from()} - + { const result = await getOnChainTxFee() const fees = result.data?.onChainTxFee.amount + if (fees) { setOnChainTxFee(fees) } else { diff --git a/app/screens/settings-screen/account-id.tsx b/app/screens/settings-screen/account-id.tsx index c3a93ddc34..0235c2686f 100644 --- a/app/screens/settings-screen/account-id.tsx +++ b/app/screens/settings-screen/account-id.tsx @@ -4,6 +4,7 @@ import { View } from "react-native" import { GaloyIconButton } from "@app/components/atomic/galoy-icon-button" import { useAccountScreenQuery } from "@app/graphql/generated" import { useI18nContext } from "@app/i18n/i18n-react" +import { testProps } from "@app/utils/testProps" import { toastShow } from "@app/utils/toast" import Clipboard from "@react-native-clipboard/clipboard" import { Text, makeStyles } from "@rneui/themed" @@ -30,7 +31,9 @@ export const AccountId: React.FC = () => { return ( - {LL.AccountScreen.yourAccountId()} + + {LL.AccountScreen.yourAccountId()} + diff --git a/app/screens/settings-screen/account-screen.tsx b/app/screens/settings-screen/account-screen.tsx index d57e090e4f..74a1eaa5a4 100644 --- a/app/screens/settings-screen/account-screen.tsx +++ b/app/screens/settings-screen/account-screen.tsx @@ -24,6 +24,7 @@ import useLogout from "@app/hooks/use-logout" import { useI18nContext } from "@app/i18n/i18n-react" import { RootStackParamList } from "@app/navigation/stack-param-lists" import { toBtcMoneyAmount, toUsdMoneyAmount } from "@app/types/amounts" +import { testProps } from "@app/utils/testProps" import { useNavigation } from "@react-navigation/native" import { StackNavigationProp } from "@react-navigation/stack" import { Text, makeStyles, useTheme } from "@rneui/themed" @@ -592,6 +593,7 @@ export const AccountScreen = () => { preset="scroll" keyboardShouldPersistTaps="handled" keyboardOffset="navigationHeader" + {...testProps("account-screen-scroll-view")} > {accountSettingsList.map((setting) => ( diff --git a/app/screens/transaction-history/transaction-history-screen.tsx b/app/screens/transaction-history/transaction-history-screen.tsx index 11e344b0a1..4df18ba0d6 100644 --- a/app/screens/transaction-history/transaction-history-screen.tsx +++ b/app/screens/transaction-history/transaction-history-screen.tsx @@ -105,6 +105,7 @@ export const TransactionHistoryScreen: React.FC = () => { isLast={index === section.data.length - 1} txid={item.id} subtitle + testId={`transaction-by-index-${index}`} /> )} renderSectionHeader={({ section: { title } }) => ( diff --git a/dev/.env b/dev/.env deleted file mode 100644 index 9ed68f86c3..0000000000 --- a/dev/.env +++ /dev/null @@ -1,2 +0,0 @@ -ALICE_PHONE="+919876540001" -BOB_PHONE="+919876540002" diff --git a/dev/Tiltfile b/dev/Tiltfile index f57b9f8d08..c321c109cf 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -33,19 +33,28 @@ dc_resource('otel-agent', labels = ["otel"]) dc_resource('quickstart-test', labels = ['quickstart'], auto_init=False) local_resource( - name='init-setup', + name='dev-ln-setup', labels = ['dev-setup'], - cmd='bin/setup.sh', + cmd='bin/dev-ln-setup.sh', resource_deps = galoy_services + [ "init-lightning" ] ) local_resource( - name='blocks', + name='dev-setup', labels = ['dev-setup'], - serve_cmd='bin/blocks.sh', + cmd='bin/dev-setup.sh', resource_deps = [ - "init-setup" + "dev-ln-setup" + ] +) + +local_resource( + name='one-block-every-5s', + labels = ['dev-setup'], + serve_cmd='bin/one-block-every-5s.sh', + resource_deps = [ + "dev-setup" ] ) diff --git a/dev/bin/dev-ln-setup.sh b/dev/bin/dev-ln-setup.sh new file mode 100755 index 0000000000..8e16a886c4 --- /dev/null +++ b/dev/bin/dev-ln-setup.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -e + +REPO_ROOT=$(git rev-parse --show-toplevel) + +source ${REPO_ROOT}/dev/vendor/galoy-quickstart/bin/helpers.sh +source ${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh + +function performTransactions { + address="$(lnd_cli newaddress p2wkh | jq -r '.address')" + bitcoin_cli sendtoaddress "$address" 0.1 + bitcoin_cli -generate 3 + + address="$(lnd_outside_cli newaddress p2wkh | jq -r '.address')" + bitcoin_cli sendtoaddress "$address" 0.1 + bitcoin_cli -generate 3 + + btc_amount=100000 + invoice_response="$(lnd_cli addinvoice --amt $btc_amount)" + payment_request="$(echo $invoice_response | jq -r '.payment_request')" + lnd_outside_cli payinvoice -f --pay_req "$payment_request" + local result1=$? + + btc_amount=20000 + invoice_response="$(lnd_outside_cli addinvoice --amt $btc_amount)" + payment_request="$(echo $invoice_response | jq -r '.payment_request')" + lnd_cli payinvoice -f --pay_req "$payment_request" + local result2=$? + + return $(($result1 + $result2)) +} + +until performTransactions; do + echo "One or more transactions failed. Retrying in 5 seconds..." + sleep 5 +done + +echo "lnd <--> lnd-outside channel created with funds" diff --git a/dev/bin/dev-setup.sh b/dev/bin/dev-setup.sh new file mode 100755 index 0000000000..8fad78f694 --- /dev/null +++ b/dev/bin/dev-setup.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +set -e + +REPO_ROOT=$(git rev-parse --show-toplevel) + +source ${REPO_ROOT}/dev/vendor/galoy-quickstart/bin/helpers.sh +source ${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh + +ALICE_SUFFIX=$(LC_ALL=C cat /dev/urandom | od -An -tu1 | tr -d ' ' | tr -cd '0-9' | head -c 6) +ALICE_PHONE="+919836$ALICE_SUFFIX" +ALICE_USERNAME="alice_$ALICE_SUFFIX" + +login_user "alice" "$ALICE_PHONE" "000000" +echo "alice logged in" +ALICE_TOKEN=$(read_value "alice") + +fund_wallet_from_onchain "alice" "alice.btc_wallet_id" "1" +fund_wallet_from_onchain "alice" "alice.usd_wallet_id" "1" + +# Alice account should report non zero balances +btc_balance=0 +usd_balance=0 +while [ "$btc_balance" -eq 0 ] || [ "$usd_balance" -eq 0 ]; do + exec_graphql 'alice' 'wallets-for-account' + btc_balance=$(graphql_output | jq '.data.me.defaultAccount.wallets[] | select(.walletCurrency == "BTC") | .balance') + usd_balance=$(graphql_output | jq '.data.me.defaultAccount.wallets[] | select(.walletCurrency == "USD") | .balance') + + echo "BTC Balance: $btc_balance" + echo "USD Balance: $usd_balance" + sleep 1 +done + +exec_graphql 'alice' 'user-update-username' "{\"input\": {\"username\": \"$ALICE_USERNAME\"}}" +echo "alice funded & set up" + +BOB_SUFFIX=$(LC_ALL=C cat /dev/urandom | od -An -tu1 | tr -d ' ' | tr -cd '0-9' | head -c 6) +BOB_PHONE="+919836$BOB_SUFFIX" +BOB_USERNAME="bob_$BOB_SUFFIX" + +login_user "bob" "$BOB_PHONE" "000000" +echo "bob logged in" +BOB_TOKEN=$(read_value "bob") + +fund_wallet_from_onchain "bob" "bob.btc_wallet_id" "1" +fund_wallet_from_onchain "bob" "bob.usd_wallet_id" "1" + +# Bob account should report non zero balances +btc_balance=0 +usd_balance=0 +while [ "$btc_balance" -eq 0 ] || [ "$usd_balance" -eq 0 ]; do + exec_graphql 'bob' 'wallets-for-account' + btc_balance=$(graphql_output | jq '.data.me.defaultAccount.wallets[] | select(.walletCurrency == "BTC") | .balance') + usd_balance=$(graphql_output | jq '.data.me.defaultAccount.wallets[] | select(.walletCurrency == "USD") | .balance') + + echo "BTC Balance: $btc_balance" + echo "USD Balance: $usd_balance" + sleep 1 +done + +exec_graphql 'bob' 'user-update-username' "{\"input\": {\"username\": \"$BOB_USERNAME\"}}" +echo "bob funded & set up" + +cat < ${REPO_ROOT}/dev/.env.tmp.ci +ALICE_PHONE="$ALICE_PHONE" +ALICE_TOKEN="$ALICE_TOKEN" +ALICE_USERNAME="$ALICE_USERNAME" +BOB_PHONE="$BOB_PHONE" +BOB_TOKEN="$BOB_TOKEN" +BOB_USERNAME="bob_$BOB_SUFFIX" +EOF diff --git a/dev/bin/blocks.sh b/dev/bin/one-block-every-5s.sh similarity index 94% rename from dev/bin/blocks.sh rename to dev/bin/one-block-every-5s.sh index 0369178c58..767670b81d 100755 --- a/dev/bin/blocks.sh +++ b/dev/bin/one-block-every-5s.sh @@ -8,5 +8,5 @@ while true; do bitcoin_cli -generate 1 - sleep 2 + sleep 5 done diff --git a/dev/bin/setup.sh b/dev/bin/setup.sh deleted file mode 100755 index 9dc2c54989..0000000000 --- a/dev/bin/setup.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - - set -e - - REPO_ROOT=$(git rev-parse --show-toplevel) - - source ${REPO_ROOT}/dev/vendor/galoy-quickstart/bin/helpers.sh - source ${REPO_ROOT}/dev/.env - - login_user "alice" "$ALICE_PHONE" "000000" - echo "alice logged in" - ALICE_TOKEN=$(read_value "alice") - receive_onchain - echo "alice funded" - - login_user "alice" "$BOB_PHONE" "000000" - echo "bob logged in" - BOB_TOKEN=$(read_value "alice") - receive_onchain - echo "bob funded" - - cat < .env.tmp.ci - ALICE_TOKEN="$ALICE_TOKEN" - BOB_TOKEN="$BOB_TOKEN" - EOF diff --git a/dev/vendir.lock.yml b/dev/vendir.lock.yml index e90fe9121e..031802c44a 100644 --- a/dev/vendir.lock.yml +++ b/dev/vendir.lock.yml @@ -2,10 +2,10 @@ apiVersion: vendir.k14s.io/v1alpha1 directories: - contents: - git: - commitTitle: 'feat: adding suggest and approve endpoint for the map (#3937)' - sha: d2f6f35ab4fb67397094684d4b7fc0cf79d47111 + commitTitle: 'chore(release): [ci skip] bump quickstart image to sha256@949763b555858318808b9bc7fee2e33fa2deb7252a2c0eed1e7a9ad65193f021' + sha: a685e23f4986b51175ddb093b6005f0246409a4d tags: - - 0.20.42 + - 0.20.126 path: galoy-quickstart path: vendor kind: LockConfig diff --git a/dev/vendir.yml b/dev/vendir.yml index 71b1187bb4..26f7d74751 100644 --- a/dev/vendir.yml +++ b/dev/vendir.yml @@ -7,7 +7,7 @@ directories: - path: galoy-quickstart git: url: https://github.com/GaloyMoney/galoy.git - ref: '0.20.42' + ref: '0.20.126' includePaths: - quickstart/bin/* - quickstart/dev/**/* diff --git a/dev/vendor/galoy-quickstart/dev/BUCK b/dev/vendor/galoy-quickstart/dev/BUCK index 00fca6d337..1618908dd8 100644 --- a/dev/vendor/galoy-quickstart/dev/BUCK +++ b/dev/vendor/galoy-quickstart/dev/BUCK @@ -13,6 +13,25 @@ tilt( subcmd = "down", ) +# This should eventually be removed by refactoring config setup in core/api +export_file( + name = "dummy_env", + src = "core-bundle/dummy-env.json", + visibility = ["PUBLIC"], +) + +export_file( + name = "serve_env", + src = "core-bundle/serve-env.json", + visibility = ["PUBLIC"], +) + +export_file( + name = "integration_env", + src = "core-bundle/integration-env.json", + visibility = ["PUBLIC"], +) + python_bootstrap_binary( name = "healthcheck", main = "healthcheck.py", @@ -52,17 +71,16 @@ dev_update_file( out = "config/apollo-federation/supergraph.graphql" ) -dev_update_file( - name = "update-core-supergraph", - generated = ":supergraph", - out = "../core/api/dev/apollo-federation/supergraph.graphql" -) - sh_binary( name = "update-schemas", main = "bin/update-schemas.sh", ) +sh_binary( + name = "codegen", + main = "bin/codegen.sh", +) + sh_binary( name = "init-onchain", main = "bin/init-onchain.sh", @@ -74,8 +92,8 @@ sh_binary( ) sh_binary( - name = "init-user", - main = "bin/init-user.sh", + name = "init-test-user", + main = "bin/init-test-user.sh", ) sh_binary( diff --git a/dev/vendor/galoy-quickstart/dev/Tiltfile b/dev/vendor/galoy-quickstart/dev/Tiltfile index 0e781310ea..e4ec53d449 100644 --- a/dev/vendor/galoy-quickstart/dev/Tiltfile +++ b/dev/vendor/galoy-quickstart/dev/Tiltfile @@ -7,13 +7,17 @@ CONSENT_TEST_LABEL = "consent" DASHBOARD_TEST_LABEL = "dashboard" PAY_TEST_LABEL = "pay" ADMIN_PANEL_TEST_LABEL = "admin-panel" +MAP_TEST_LABEL = "map" +MIGRATE_MONGO_TEST_LABEL = "mongodb-migrate" TEST_RESOURCES = { - CORE_TEST_LABEL: "api-test", - CONSENT_TEST_LABEL: "consent-test", - DASHBOARD_TEST_LABEL: "dashboard-test", - PAY_TEST_LABEL: "pay-test", - ADMIN_PANEL_TEST_LABEL: "admin-panel-test" + CORE_TEST_LABEL: "test-api", + CONSENT_TEST_LABEL: "test-consent", + DASHBOARD_TEST_LABEL: "test-dashboard", + PAY_TEST_LABEL: "test-pay", + ADMIN_PANEL_TEST_LABEL: "test-admin-panel", + MAP_TEST_LABEL: "test-map", + MIGRATE_MONGO_TEST_LABEL: "test-mongodb-migrate", } is_ci=("ci" in sys.argv) or cfg.get("bats", False) @@ -38,9 +42,12 @@ def _buck2_dep_inputs(target): "uquery", "\"inputs(deps('{}'))\"".format(target), ] - file_paths = str(local(" ".join(cmd))).splitlines() + abs_file_paths = str(local(" ".join(cmd))).splitlines() - return file_paths + repo_root = str(local("git rev-parse --show-toplevel")).strip() + rel_file_paths = ["{}/{}".format(repo_root, path) for path in abs_file_paths] + + return rel_file_paths dashboard_target = "//apps/dashboard:dev" if is_ci: @@ -55,6 +62,13 @@ local_resource( "NEXTAUTH_SECRET": "secret", "PORT": "3001", }, + readiness_probe = probe( + period_secs = 5, + http_get = http_get_action( + path = "/", + port = 3001, + ), + ), deps = _buck2_dep_inputs(dashboard_target), allow_parallel = True, auto_init = run_apps, @@ -81,18 +95,28 @@ pay_env = { "NEXT_PUBLIC_CORE_GQL_URL": "http://localhost:4455/graphql", "NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL": "ws://localhost:4455/graphqlws", "NEXT_PUBLIC_PAY_DOMAIN": "localhost:3002", + "NEXTAUTH_URL":"http://localhost:3002" } local_resource( "pay", labels = ["apps"], cmd = "buck2 build {}".format(pay_target), - serve_cmd = "buck2 run {}".format(pay_target), + serve_cmd = ". .envs/pay.env && buck2 run {}".format(pay_target), env = pay_env, serve_env = pay_env, + readiness_probe = probe( + period_secs = 5, + http_get = http_get_action( + path = "/", + port = 3002, + ), + ), deps = _buck2_dep_inputs(pay_target), allow_parallel = True, resource_deps = [ "api", + "api-ws-server", + "hydra-pay" ], links = [ link("http://localhost:3002", "pay"), @@ -115,6 +139,13 @@ local_resource( serve_cmd = "buck2 run {}".format(admin_panel_target), env = admin_panel_env, serve_env = admin_panel_env, + readiness_probe = probe( + period_secs = 5, + http_get = http_get_action( + path = "/", + port = 3004, + ), + ), deps = _buck2_dep_inputs(admin_panel_target), allow_parallel = True, resource_deps = [ @@ -126,6 +157,38 @@ local_resource( ], ) +map_target = "//apps/map:dev" +if is_ci: + map_target = '//apps/map:map' +map_env = { + "PORT": "3005", + "CORE_URL" : "http://localhost:4455/graphql", +} +local_resource( + "map", + labels = ["apps"], + cmd = "buck2 build {}".format(map_target), + serve_cmd = "buck2 run {}".format(map_target), + env = map_env, + serve_env = map_env, + readiness_probe = probe( + period_secs = 5, + http_get = http_get_action( + path = "/", + port = 3005, + ), + ), + deps = _buck2_dep_inputs(map_target), + allow_parallel = True, + resource_deps = [ + "api", + "apollo-router", + ], + links = [ + link("http://localhost:3005", "map"), + ], +) + local_resource( name='hydra-dashboard', labels = ['apps'], @@ -146,9 +209,28 @@ local_resource( ] ) +local_resource( + name='hydra-pay', + labels = ['apps'], + cmd=[ + 'buck2', + 'run', + '//dev:setup-hydra-client', + '--', + 'pay', + 'authorization_code,refresh_token', + 'http://localhost:3002/api/auth/callback/blink', + ], + allow_parallel = True, + resource_deps = [ + "hydra", + "api", + ] +) + consent_test_target = "//apps/consent:test-integration" local_resource( - "consent-test", + "test-consent", labels = ["test"], auto_init = is_ci and CONSENT_TEST_LABEL in cfg.get("test", []), cmd = "buck2 test {}".format(consent_test_target), @@ -161,7 +243,7 @@ local_resource( dashboard_test_target = "//apps/dashboard:test-integration" local_resource( - "dashboard-test", + "test-dashboard", labels = ["test"], auto_init = is_ci and DASHBOARD_TEST_LABEL in cfg.get("test", []), cmd = "buck2 test {}".format(dashboard_test_target), @@ -174,7 +256,7 @@ local_resource( pay_test_target = "//apps/pay:test-integration" local_resource( - "pay-test", + "test-pay", labels = ["test"], auto_init = is_ci and PAY_TEST_LABEL in cfg.get("test", []), cmd = "buck2 test {}".format(pay_test_target), @@ -182,12 +264,13 @@ local_resource( "api", "pay", "add-test-users-with-usernames", + "fund-user", ], ) admin_panel_test_target = "//apps/admin-panel:test-integration" local_resource( - "admin-panel-test", + "test-admin-panel", labels = ["test"], auto_init = is_ci and ADMIN_PANEL_TEST_LABEL in cfg.get("test", []), cmd = "buck2 test {}".format(admin_panel_test_target), @@ -196,10 +279,21 @@ local_resource( ], ) +map_test_target = "//apps/map:test-integration" +local_resource( + "test-map", + labels = ["test"], + auto_init = is_ci and MAP_TEST_LABEL in cfg.get("test", []), + cmd = "buck2 test {}".format(map_test_target), + resource_deps = [ + "map", + ], +) + local_resource( name='init-test-user', labels = ['test'], - cmd='buck2 run //dev:init-user', + cmd='buck2 run //dev:init-test-user', allow_parallel = True, resource_deps = [ "oathkeeper", @@ -246,6 +340,13 @@ local_resource( labels = ["auth"], cmd = "buck2 build {}".format(consent_target), serve_cmd = "buck2 run {}".format(consent_target), + readiness_probe = probe( + period_secs = 5, + http_get = http_get_action( + path = "/", + port = 3000, + ), + ), deps = _buck2_dep_inputs(consent_target), allow_parallel = True, auto_init = run_apps, @@ -280,53 +381,8 @@ local_resource( ] ) -core_serve_env = { - "HELMREVISION": "dev", - "NETWORK": "regtest", - "OATHKEEPER_DECISION_ENDPOINT": "http://localhost:4456", - "TWILIO_ACCOUNT_SID": "AC_twilio_id", - "TWILIO_AUTH_TOKEN": "AC_twilio_auth_token", - "TWILIO_VERIFY_SERVICE_ID": "VA_twilio_service", - "KRATOS_PG_CON": "postgres://dbuser:secret@localhost:5432/default?sslmode=disable", - "KRATOS_PUBLIC_API": "http://localhost:4433", - "KRATOS_ADMIN_API": "http://localhost:4434", - "KRATOS_MASTER_USER_PASSWORD": "passwordHardtoFindWithNumber123", - "PRICE_HOST": "localhost", - "PRICE_HISTORY_HOST": "localhost", - "BRIA_HOST": "localhost", - "BRIA_API_KEY": "bria_dev_000000000000000000000", - "NOTIFICATIONS_HOST": "localhost", - "MONGODB_CON": "mongodb://localhost:27017/galoy", - "REDIS_MASTER_NAME": "mymaster", - "REDIS_PASSWORD": "", - "REDIS_0_DNS": "localhost", - "REDIS_0_PORT": "6379", - "REDIS_TYPE": "standalone", - "UNSECURE_IP_FROM_REQUEST_OBJECT": "true", - "UNSECURE_DEFAULT_LOGIN_CODE": "000000", - "GEETEST_ID": "geetest_id", - "GEETEST_KEY": "geetest_key", - - "LND1_TLS": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", - "LND1_MACAROON": "AgEDbG5kAvgBAwoQB1FdhGa9xoewc1LEXmnURRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgqHDdwGCqx0aQL1/Z3uUfzCpeBhfapGf9s/AZPOVwf6s=", - "LND1_PUBKEY":"03ca1907342d5d37744cb7038375e1867c24a87564c293157c95b2a9d38dcfb4c2", - "LND1_DNS": "localhost", - "LND1_RPCPORT": "10009", - "LND1_NAME": "lnd1", - "LND1_TYPE": "offchain,onchain", - - "LND2_TLS": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", - "LND2_MACAROON": "AgEDbG5kAvgBAwoQX0BxfhQTxLTiqaceBnGnfBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgMAKlr1HehfBpn2R5RPE2IuY9r/18QBeLZxYgRidpos4=", - "LND2_PUBKEY": "039341ef13e776dc1611502cf510110d9ac5cdc252141f5997adcfd72cef34c3a7", - "LND2_DNS": "localhost", - "LND2_RPCPORT": "10010", - "LND2_NAME": "lnd2", - "LND2_TYPE": "offchain", - - "SVIX_SECRET": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTE2NzIwMTQsImV4cCI6MjAwNzAzMjAxNCwibmJmIjoxNjkxNjcyMDE0LCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.b9s0aWSisNdUNki4edabBEToLNSwjC9-AiJQr4J3y4E", - "SVIX_ENDPOINT": "http://localhost:8071", - "EXPORTER_PORT": "3003" -} +env_json = _buck2_dep_inputs("//dev:serve_env")[0] +core_serve_env = read_json(env_json) callback_target = "//bats/helpers/callback:run" local_resource( @@ -382,7 +438,7 @@ local_resource( period_secs = 5, http_get = http_get_action( path = "healthz", - port = 4012, + port = 8888, ), ), deps = _buck2_dep_inputs(api_trigger_target), @@ -411,7 +467,7 @@ local_resource( period_secs = 5, http_get = http_get_action( path = "healthz", - port = 4012, + port = 3003, ), ), deps = _buck2_dep_inputs(api_exporter_target), @@ -463,7 +519,7 @@ local_resource( serve_cmd = "buck2 run {}".format(notifications_target), serve_env = { "PG_CON": "postgres://user:password@localhost:5433/pg", - "NOTIFICATIONS_CONFIG": "../core/notifications/notifications.yml", + "NOTIFICATIONS_CONFIG": "./config/notifications/notifications.yml", "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317", }, deps = _buck2_dep_inputs(notifications_target), @@ -592,9 +648,20 @@ for service in docker_groups["integration"]: api_test_target = "//core/api:test-integration" local_resource( - "api-test", + "test-api", labels = ["test"], auto_init = is_ci and CORE_TEST_LABEL in cfg.get("test", []), cmd = "buck2 test {}".format(api_test_target), resource_deps = [res for sublist in docker_groups.values() for res in sublist] + ["notifications"] ) + +local_resource( + "test-mongodb-migrate", + labels = ["test"], + auto_init = is_ci and MIGRATE_MONGO_TEST_LABEL in cfg.get("test", []), + cmd='buck2 run //core/api:mongodb-migrate', + allow_parallel = True, + resource_deps = [ + "mongodb", + ] +) diff --git a/dev/vendor/galoy-quickstart/dev/config/apollo-federation/supergraph.graphql b/dev/vendor/galoy-quickstart/dev/config/apollo-federation/supergraph.graphql index a865d53942..9a33f6f3a0 100644 --- a/dev/vendor/galoy-quickstart/dev/config/apollo-federation/supergraph.graphql +++ b/dev/vendor/galoy-quickstart/dev/config/apollo-federation/supergraph.graphql @@ -182,6 +182,7 @@ type ApiKey lastUsedAt: Timestamp expiresAt: Timestamp readOnly: Boolean! + scopes: [Scope!]! } input ApiKeyCreateInput @@ -189,7 +190,7 @@ input ApiKeyCreateInput { name: String! expireInDays: Int - readOnly: Boolean! = false + scopes: [Scope!]! = [READ, WRITE] } type ApiKeyCreatePayload @@ -211,6 +212,12 @@ type ApiKeyRevokePayload apiKey: ApiKey! } +type Authorization + @join__type(graph: PUBLIC) +{ + scopes: [Scope!]! +} + """An Opaque Bearer token""" scalar AuthToken @join__type(graph: PUBLIC) @@ -292,6 +299,7 @@ type BTCWallet implements Wallet last: Int ): TransactionConnection transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! + transactionsByPaymentRequest(paymentRequest: LnPaymentRequest!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -749,6 +757,15 @@ type LnInvoice implements Invoice satoshis: SatAmount! } +input LnInvoiceCancelInput + @join__type(graph: PUBLIC) +{ + paymentHash: PaymentHash! + + """Wallet ID for a wallet associated with the current account.""" + walletId: WalletId! +} + input LnInvoiceCreateInput @join__type(graph: PUBLIC) { @@ -811,6 +828,26 @@ input LnInvoicePaymentInput walletId: WalletId! } +type LnInvoicePaymentStatus + @join__type(graph: PUBLIC) +{ + paymentHash: PaymentHash + paymentRequest: LnPaymentRequest + status: InvoicePaymentStatus +} + +input LnInvoicePaymentStatusByHashInput + @join__type(graph: PUBLIC) +{ + paymentHash: PaymentHash! +} + +input LnInvoicePaymentStatusByPaymentRequestInput + @join__type(graph: PUBLIC) +{ + paymentRequest: LnPaymentRequest! +} + input LnInvoicePaymentStatusInput @join__type(graph: PUBLIC) { @@ -821,6 +858,8 @@ type LnInvoicePaymentStatusPayload @join__type(graph: PUBLIC) { errors: [Error!]! + paymentHash: PaymentHash + paymentRequest: LnPaymentRequest status: InvoicePaymentStatus } @@ -1035,6 +1074,40 @@ type MapMarker scalar Memo @join__type(graph: PUBLIC) +type Merchant + @join__type(graph: PUBLIC) +{ + """ + GPS coordinates for the merchant that can be used to place the related business on a map + """ + coordinates: Coordinates! + createdAt: Timestamp! + id: ID! + title: String! + + """The username of the merchant""" + username: Username! + + """Whether the merchant has been validated""" + validated: Boolean! +} + +input MerchantMapSuggestInput + @join__type(graph: PUBLIC) +{ + latitude: Float! + longitude: Float! + title: String! + username: Username! +} + +type MerchantPayload + @join__type(graph: PUBLIC) +{ + errors: [Error!]! + merchant: Merchant +} + """(Positive) amount of minutes""" scalar Minutes @join__type(graph: PUBLIC) @@ -1089,6 +1162,9 @@ type Mutation """Sends a payment to a lightning address.""" lnAddressPaymentSend(input: LnAddressPaymentSendInput!): PaymentSendPayload! @join__field(graph: PUBLIC) + """Cancel an unpaid lightning invoice for an associated wallet.""" + lnInvoiceCancel(input: LnInvoiceCancelInput!): SuccessPayload! @join__field(graph: PUBLIC) + """ Returns a lightning invoice for an associated wallet. When invoice is paid the value will be credited to a BTC wallet. @@ -1168,6 +1244,7 @@ type Mutation """Sends a payment to a lightning address.""" lnurlPaymentSend(input: LnurlPaymentSendInput!): PaymentSendPayload! @join__field(graph: PUBLIC) + merchantMapSuggest(input: MerchantMapSuggestInput!): MerchantPayload! @join__field(graph: PUBLIC) onChainAddressCreate(input: OnChainAddressCreateInput!): OnChainAddressPayload! @join__field(graph: PUBLIC) onChainAddressCurrent(input: OnChainAddressCurrentInput!): OnChainAddressPayload! @join__field(graph: PUBLIC) onChainPaymentSend(input: OnChainPaymentSendInput!): PaymentSendPayload! @join__field(graph: PUBLIC) @@ -1175,7 +1252,6 @@ type Mutation onChainUsdPaymentSend(input: OnChainUsdPaymentSendInput!): PaymentSendPayload! @join__field(graph: PUBLIC) onChainUsdPaymentSendAsBtcDenominated(input: OnChainUsdPaymentSendAsBtcDenominatedInput!): PaymentSendPayload! @join__field(graph: PUBLIC) quizClaim(input: QuizClaimInput!): QuizClaimPayload! @join__field(graph: PUBLIC) - quizCompleted(input: QuizCompletedInput!): QuizCompletedPayload! @join__field(graph: PUBLIC) @deprecated(reason: "Use quizClaim instead") userContactUpdateAlias(input: UserContactUpdateAliasInput!): UserContactUpdateAliasPayload! @join__field(graph: PUBLIC) @deprecated(reason: "will be moved to AccountContact") userEmailDelete: UserEmailDeletePayload! @join__field(graph: PUBLIC) userEmailRegistrationInitiate(input: UserEmailRegistrationInitiateInput!): UserEmailRegistrationInitiatePayload! @join__field(graph: PUBLIC) @@ -1506,11 +1582,16 @@ type Query @join__type(graph: PUBLIC) { accountDefaultWallet(username: Username!, walletCurrency: WalletCurrency): PublicWallet! @join__field(graph: PUBLIC) + + """Retrieve the list of scopes permitted for the user's token or API key""" + authorization: Authorization! @join__field(graph: PUBLIC) btcPriceList(range: PriceGraphRange!): [PricePoint] @join__field(graph: PUBLIC) businessMapMarkers: [MapMarker!]! @join__field(graph: PUBLIC) currencyList: [Currency!]! @join__field(graph: PUBLIC) globals: Globals @join__field(graph: PUBLIC) - lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! @join__field(graph: PUBLIC) + lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! @join__field(graph: PUBLIC) @deprecated(reason: "Deprecated in favor of lnInvoicePaymentStatusByPaymentRequest") + lnInvoicePaymentStatusByHash(input: LnInvoicePaymentStatusByHashInput!): LnInvoicePaymentStatus! @join__field(graph: PUBLIC) + lnInvoicePaymentStatusByPaymentRequest(input: LnInvoicePaymentStatusByPaymentRequestInput!): LnInvoicePaymentStatus! @join__field(graph: PUBLIC) me: User @join__field(graph: PUBLIC) mobileVersions: [MobileVersions] @join__field(graph: PUBLIC) onChainTxFee(address: OnChainAddress!, amount: SatAmount!, speed: PayoutSpeed! = FAST, walletId: WalletId!): OnChainTxFee! @join__field(graph: PUBLIC) @@ -1546,19 +1627,6 @@ type QuizClaimPayload quizzes: [Quiz!]! } -input QuizCompletedInput - @join__type(graph: PUBLIC) -{ - id: ID! -} - -type QuizCompletedPayload - @join__type(graph: PUBLIC) -{ - errors: [Error!]! - quiz: Quiz -} - type RealtimePrice @join__type(graph: PUBLIC) { @@ -1603,6 +1671,15 @@ type SatAmountPayload errors: [Error!]! } +enum Scope + @join__type(graph: API_KEYS) + @join__type(graph: PUBLIC) +{ + READ @join__enumValue(graph: API_KEYS) @join__enumValue(graph: PUBLIC) + WRITE @join__enumValue(graph: API_KEYS) @join__enumValue(graph: PUBLIC) + RECEIVE @join__enumValue(graph: API_KEYS) @join__enumValue(graph: PUBLIC) +} + """(Positive) amount of seconds""" scalar Seconds @join__type(graph: PUBLIC) @@ -1653,7 +1730,9 @@ scalar SignedDisplayMajorAmount type Subscription @join__type(graph: PUBLIC) { - lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! + lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! @deprecated(reason: "Deprecated in favor of lnInvoicePaymentStatusByPaymentRequest") + lnInvoicePaymentStatusByHash(input: LnInvoicePaymentStatusByHashInput!): LnInvoicePaymentStatusPayload! + lnInvoicePaymentStatusByPaymentRequest(input: LnInvoicePaymentStatusByPaymentRequestInput!): LnInvoicePaymentStatusPayload! myUpdates: MyUpdatesPayload! price(input: PriceInput!): PricePayload! @@ -1844,6 +1923,7 @@ type UsdWallet implements Wallet last: Int ): TransactionConnection transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! + transactionsByPaymentRequest(paymentRequest: LnPaymentRequest!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -2025,6 +2105,7 @@ enum UserNotificationCategory PAYMENTS @join__enumValue(graph: NOTIFICATIONS) BALANCE @join__enumValue(graph: NOTIFICATIONS) ADMIN_NOTIFICATION @join__enumValue(graph: NOTIFICATIONS) + MARKETING @join__enumValue(graph: NOTIFICATIONS) } enum UserNotificationChannel @@ -2241,6 +2322,12 @@ interface Wallet """The payment hash of the lightning invoice paid in this transaction.""" paymentHash: PaymentHash! ): [Transaction!]! + + """Returns the transactions that include this paymentRequest.""" + transactionsByPaymentRequest( + """Lightning invoice paid in this transaction.""" + paymentRequest: LnPaymentRequest! + ): [Transaction!]! walletCurrency: WalletCurrency! } diff --git a/dev/vendor/galoy-quickstart/dev/config/notifications/fake_service_account.json b/dev/vendor/galoy-quickstart/dev/config/notifications/fake_service_account.json new file mode 100644 index 0000000000..572f4afbb6 --- /dev/null +++ b/dev/vendor/galoy-quickstart/dev/config/notifications/fake_service_account.json @@ -0,0 +1,10 @@ +{ + "type": "service_account", + "project_id": "abc_app", + "private_key_id": "abc", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDY3E8o1NEFcjMM\nHW/5ZfFJw29/8NEqpViNjQIx95Xx5KDtJ+nWn9+OW0uqsSqKlKGhAdAo+Q6bjx2c\nuXVsXTu7XrZUY5Kltvj94DvUa1wjNXs606r/RxWTJ58bfdC+gLLxBfGnB6CwK0YQ\nxnfpjNbkUfVVzO0MQD7UP0Hl5ZcY0Puvxd/yHuONQn/rIAieTHH1pqgW+zrH/y3c\n59IGThC9PPtugI9ea8RSnVj3PWz1bX2UkCDpy9IRh9LzJLaYYX9RUd7++dULUlat\nAaXBh1U6emUDzhrIsgApjDVtimOPbmQWmX1S60mqQikRpVYZ8u+NDD+LNw+/Eovn\nxCj2Y3z1AgMBAAECggEAWDBzoqO1IvVXjBA2lqId10T6hXmN3j1ifyH+aAqK+FVl\nGjyWjDj0xWQcJ9ync7bQ6fSeTeNGzP0M6kzDU1+w6FgyZqwdmXWI2VmEizRjwk+/\n/uLQUcL7I55Dxn7KUoZs/rZPmQDxmGLoue60Gg6z3yLzVcKiDc7cnhzhdBgDc8vd\nQorNAlqGPRnm3EqKQ6VQp6fyQmCAxrr45kspRXNLddat3AMsuqImDkqGKBmF3Q1y\nxWGe81LphUiRqvqbyUlh6cdSZ8pLBpc9m0c3qWPKs9paqBIvgUPlvOZMqec6x4S6\nChbdkkTRLnbsRr0Yg/nDeEPlkhRBhasXpxpMUBgPywKBgQDs2axNkFjbU94uXvd5\nznUhDVxPFBuxyUHtsJNqW4p/ujLNimGet5E/YthCnQeC2P3Ym7c3fiz68amM6hiA\nOnW7HYPZ+jKFnefpAtjyOOs46AkftEg07T9XjwWNPt8+8l0DYawPoJgbM5iE0L2O\nx8TU1Vs4mXc+ql9F90GzI0x3VwKBgQDqZOOqWw3hTnNT07Ixqnmd3dugV9S7eW6o\nU9OoUgJB4rYTpG+yFqNqbRT8bkx37iKBMEReppqonOqGm4wtuRR6LSLlgcIU9Iwx\nyfH12UWqVmFSHsgZFqM/cK3wGev38h1WBIOx3/djKn7BdlKVh8kWyx6uC8bmV+E6\nOoK0vJD6kwKBgHAySOnROBZlqzkiKW8c+uU2VATtzJSydrWm0J4wUPJifNBa/hVW\ndcqmAzXC9xznt5AVa3wxHBOfyKaE+ig8CSsjNyNZ3vbmr0X04FoV1m91k2TeXNod\njMTobkPThaNm4eLJMN2SQJuaHGTGERWC0l3T18t+/zrDMDCPiSLX1NAvAoGBAN1T\nVLJYdjvIMxf1bm59VYcepbK7HLHFkRq6xMJMZbtG0ryraZjUzYvB4q4VjHk2UDiC\nlhx13tXWDZH7MJtABzjyg+AI7XWSEQs2cBXACos0M4Myc6lU+eL+iA+OuoUOhmrh\nqmT8YYGu76/IBWUSqWuvcpHPpwl7871i4Ga/I3qnAoGBANNkKAcMoeAbJQK7a/Rn\nwPEJB+dPgNDIaboAsh1nZhVhN5cvdvCWuEYgOGCPQLYQF0zmTLcM+sVxOYgfy8mV\nfbNgPgsP5xmu6dw2COBKdtozw0HrWSRjACd1N4yGu75+wPCcX/gQarcjRcXXZeEa\nNtBLSfcqPULqD+h7br9lEJio\n-----END PRIVATE KEY-----\n", + "client_email": "123-abc@developer.gserviceaccount.com", + "client_id": "123-abc.apps.googleusercontent.com", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "http://localhost:8081/token" +} diff --git a/dev/vendor/galoy-quickstart/dev/config/notifications/notifications.yml b/dev/vendor/galoy-quickstart/dev/config/notifications/notifications.yml new file mode 100644 index 0000000000..1f1702c732 --- /dev/null +++ b/dev/vendor/galoy-quickstart/dev/config/notifications/notifications.yml @@ -0,0 +1,4 @@ +app: + push_executor: + fcm: + google_application_credentials_path: "./config/notifications/fake_service_account.json" diff --git a/dev/vendor/galoy-quickstart/dev/config/price.yml b/dev/vendor/galoy-quickstart/dev/config/price.yml new file mode 100644 index 0000000000..15b833b7bf --- /dev/null +++ b/dev/vendor/galoy-quickstart/dev/config/price.yml @@ -0,0 +1,13 @@ +exchanges: + - provider: "dev-mock" + base: "BTC" + quote: "*" + enabled: true + config: + devMockPrice: + BTC: + EUR: 17500 + USD: 20000 + # Following require dummy values + name: "" + cron: "*/5 * * * * *" diff --git a/dev/vendor/galoy-quickstart/dev/core-bundle/dummy-env.json b/dev/vendor/galoy-quickstart/dev/core-bundle/dummy-env.json new file mode 100644 index 0000000000..827dfc33ff --- /dev/null +++ b/dev/vendor/galoy-quickstart/dev/core-bundle/dummy-env.json @@ -0,0 +1,19 @@ +{ + "HELMREVISION": "dummy", + "KRATOS_PG_CON": "pg://dummy", + "OATHKEEPER_DECISION_ENDPOINT": "http://dummy", + "NETWORK": "regtest", + "TWILIO_ACCOUNT_SID": "dummy", + "TWILIO_AUTH_TOKEN": "dummy", + "TWILIO_VERIFY_SERVICE_ID": "dummy", + "KRATOS_PUBLIC_API": "http://dummy", + "KRATOS_ADMIN_API": "http://dummy", + "KRATOS_MASTER_USER_PASSWORD": "dummy", + "BRIA_HOST": "dummy", + "BRIA_API_KEY": "dummy", + "NOTIFICATIONS_HOST": "dummy", + "MONGODB_CON": "mongodb://dummy", + "REDIS_MASTER_NAME": "dummy", + "REDIS_PASSWORD": "dummy", + "REDIS_0_DNS": "dummy" +} diff --git a/dev/vendor/galoy-quickstart/dev/core-bundle/integration-env.json b/dev/vendor/galoy-quickstart/dev/core-bundle/integration-env.json new file mode 100644 index 0000000000..ac705cc3ff --- /dev/null +++ b/dev/vendor/galoy-quickstart/dev/core-bundle/integration-env.json @@ -0,0 +1,61 @@ +{ + "HELMREVISION": "dev", + "NETWORK": "regtest", + "OATHKEEPER_DECISION_ENDPOINT": "http://localhost:4456", + "TWILIO_ACCOUNT_SID": "AC_twilio_id", + "TWILIO_AUTH_TOKEN": "AC_twilio_auth_token", + "TWILIO_VERIFY_SERVICE_ID": "VA_twilio_service", + "KRATOS_PG_CON": "postgres://dbuser:secret@localhost:5432/default?sslmode=disable", + "KRATOS_PUBLIC_API": "http://localhost:4433", + "KRATOS_ADMIN_API": "http://localhost:4434", + "KRATOS_MASTER_USER_PASSWORD": "passwordHardtoFindWithNumber123", + "PRICE_HOST": "localhost", + "PRICE_HISTORY_HOST": "localhost", + "BRIA_HOST": "localhost", + "BRIA_API_KEY": "bria_dev_000000000000000000000", + "NOTIFICATIONS_HOST": "localhost", + "MONGODB_CON": "mongodb://localhost:27017/galoy", + "REDIS_MASTER_NAME": "mymaster", + "REDIS_PASSWORD": "", + "REDIS_0_DNS": "localhost", + "REDIS_0_PORT": "6379", + "REDIS_TYPE": "standalone", + "UNSECURE_IP_FROM_REQUEST_OBJECT": "true", + "UNSECURE_DEFAULT_LOGIN_CODE": "000000", + "GEETEST_ID": "geetest_id", + "GEETEST_KEY": "geetest_key", + + "LND1_TLS": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + "LND1_MACAROON": "AgEDbG5kAvgBAwoQB1FdhGa9xoewc1LEXmnURRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgqHDdwGCqx0aQL1/Z3uUfzCpeBhfapGf9s/AZPOVwf6s=", + "LND1_PUBKEY":"03ca1907342d5d37744cb7038375e1867c24a87564c293157c95b2a9d38dcfb4c2", + "LND1_DNS": "localhost", + "LND1_RPCPORT": "10009", + "LND1_NAME": "lnd1", + "LND1_TYPE": "offchain,onchain", + + "LND2_TLS": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + "LND2_MACAROON": "AgEDbG5kAvgBAwoQX0BxfhQTxLTiqaceBnGnfBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgMAKlr1HehfBpn2R5RPE2IuY9r/18QBeLZxYgRidpos4=", + "LND2_PUBKEY": "039341ef13e776dc1611502cf510110d9ac5cdc252141f5997adcfd72cef34c3a7", + "LND2_DNS": "localhost", + "LND2_RPCPORT": "10010", + "LND2_NAME": "lnd2", + "LND2_TYPE": "offchain", + + "SVIX_SECRET": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTE2NzIwMTQsImV4cCI6MjAwNzAzMjAxNCwibmJmIjoxNjkxNjcyMDE0LCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.b9s0aWSisNdUNki4edabBEToLNSwjC9-AiJQr4J3y4E", + "SVIX_ENDPOINT": "http://localhost:8071", + "EXPORTER_PORT": "3003", + + + "BITCOINDADDR": "localhost", + "BITCOINDPORT": "18443", + + "LNDOUTSIDE1ADDR": "localhost", + "LNDOUTSIDE1RPCPORT": "10012", + "MACAROONOUTSIDE1": "AgEDbG5kAvgBAwoQeE+5exgz7/0ExCn7H6AJlBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgL7pU+cKOt6zGyWTdWWmAJLP1L3cnbOPb4Rd3QtniyyM=", + "TLSOUTSIDE1": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + + "LNDOUTSIDE2ADDR": "localhost", + "LNDOUTSIDE2RPCPORT": "10013", + "MACAROONOUTSIDE2": "AgEDbG5kAvgBAwoQfKO82/iPT2zIwWYPrOXvABIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYg2XkV+4Z4inbfXGZivRoY+r7KHNZhgxkCEdKByxbeb/Q=", + "TLSOUTSIDE2": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" +} diff --git a/dev/vendor/galoy-quickstart/dev/core-bundle/serve-env.json b/dev/vendor/galoy-quickstart/dev/core-bundle/serve-env.json new file mode 100644 index 0000000000..7d22416af7 --- /dev/null +++ b/dev/vendor/galoy-quickstart/dev/core-bundle/serve-env.json @@ -0,0 +1,47 @@ +{ + "HELMREVISION": "dev", + "NETWORK": "regtest", + "OATHKEEPER_DECISION_ENDPOINT": "http://localhost:4456", + "TWILIO_ACCOUNT_SID": "AC_twilio_id", + "TWILIO_AUTH_TOKEN": "AC_twilio_auth_token", + "TWILIO_VERIFY_SERVICE_ID": "VA_twilio_service", + "KRATOS_PG_CON": "postgres://dbuser:secret@localhost:5432/default?sslmode=disable", + "KRATOS_PUBLIC_API": "http://localhost:4433", + "KRATOS_ADMIN_API": "http://localhost:4434", + "KRATOS_MASTER_USER_PASSWORD": "passwordHardtoFindWithNumber123", + "PRICE_HOST": "localhost", + "PRICE_HISTORY_HOST": "localhost", + "BRIA_HOST": "localhost", + "BRIA_API_KEY": "bria_dev_000000000000000000000", + "NOTIFICATIONS_HOST": "localhost", + "MONGODB_CON": "mongodb://localhost:27017/galoy", + "REDIS_MASTER_NAME": "mymaster", + "REDIS_PASSWORD": "", + "REDIS_0_DNS": "localhost", + "REDIS_0_PORT": "6379", + "REDIS_TYPE": "standalone", + "UNSECURE_IP_FROM_REQUEST_OBJECT": "true", + "UNSECURE_DEFAULT_LOGIN_CODE": "000000", + "GEETEST_ID": "geetest_id", + "GEETEST_KEY": "geetest_key", + + "LND1_TLS": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + "LND1_MACAROON": "AgEDbG5kAvgBAwoQB1FdhGa9xoewc1LEXmnURRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgqHDdwGCqx0aQL1/Z3uUfzCpeBhfapGf9s/AZPOVwf6s=", + "LND1_PUBKEY":"03ca1907342d5d37744cb7038375e1867c24a87564c293157c95b2a9d38dcfb4c2", + "LND1_DNS": "localhost", + "LND1_RPCPORT": "10009", + "LND1_NAME": "lnd1", + "LND1_TYPE": "offchain,onchain", + + "LND2_TLS": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", + "LND2_MACAROON": "AgEDbG5kAvgBAwoQX0BxfhQTxLTiqaceBnGnfBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgMAKlr1HehfBpn2R5RPE2IuY9r/18QBeLZxYgRidpos4=", + "LND2_PUBKEY": "039341ef13e776dc1611502cf510110d9ac5cdc252141f5997adcfd72cef34c3a7", + "LND2_DNS": "localhost", + "LND2_RPCPORT": "10010", + "LND2_NAME": "lnd2", + "LND2_TYPE": "offchain", + + "SVIX_SECRET": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTE2NzIwMTQsImV4cCI6MjAwNzAzMjAxNCwibmJmIjoxNjkxNjcyMDE0LCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.b9s0aWSisNdUNki4edabBEToLNSwjC9-AiJQr4J3y4E", + "SVIX_ENDPOINT": "http://localhost:8071", + "EXPORTER_PORT": "3003" +} diff --git a/dev/vendor/galoy-quickstart/dev/docker-compose.deps.yml b/dev/vendor/galoy-quickstart/dev/docker-compose.deps.yml index 6c6866fe82..27cd626f5d 100644 --- a/dev/vendor/galoy-quickstart/dev/docker-compose.deps.yml +++ b/dev/vendor/galoy-quickstart/dev/docker-compose.deps.yml @@ -7,6 +7,8 @@ services: ports: - 50051:50051 - 9464:9464 + volumes: + - ${HOST_PROJECT_PATH:-.}/config/price.yml:/var/yaml/custom.yaml price-history: image: us.gcr.io/galoy-org/price-history:edge ports: @@ -44,6 +46,11 @@ services: - POSTGRES_USER=galoy-price-usr - POSTGRES_PASSWORD=galoy-price-pwd - POSTGRES_DB=galoy-price-history + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 5s + timeout: 30s + retries: 5 api-keys-pg: image: postgres:14.1 environment: @@ -157,13 +164,13 @@ services: - ALLOW_EMPTY_PASSWORD=yes - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL mongodb: - image: mongo:7.0.2 + image: mongo:7.0.6 ports: - "27017:27017" environment: - MONGO_INITDB_DATABASE=galoy lnd1: - image: lightninglabs/lnd:v0.17.3-beta + image: lightninglabs/lnd:v0.17.4-beta ports: - "10009:10009" volumes: @@ -183,7 +190,7 @@ services: cp /root/.lnd/admin.macaroon /root/.lnd/data/chain/bitcoin/regtest/admin.macaroon /bin/lnd lnd2: - image: lightninglabs/lnd:v0.17.3-beta + image: lightninglabs/lnd:v0.17.4-beta ports: - "10010:10009" volumes: @@ -203,7 +210,7 @@ services: cp /root/.lnd/admin.macaroon /root/.lnd/data/chain/bitcoin/regtest/admin.macaroon /bin/lnd lnd-outside-1: - image: lightninglabs/lnd:v0.17.3-beta + image: lightninglabs/lnd:v0.17.4-beta ports: - "10012:10009" volumes: @@ -223,7 +230,7 @@ services: /bin/lnd depends_on: [bitcoind] lnd-outside-2: - image: lightninglabs/lnd:v0.17.3-beta + image: lightninglabs/lnd:v0.17.4-beta ports: - "10013:10009" volumes: diff --git a/dev/vendor/galoy-quickstart/docker-compose.yml b/dev/vendor/galoy-quickstart/docker-compose.yml index f3a488b360..71c97dcd2d 100644 --- a/dev/vendor/galoy-quickstart/docker-compose.yml +++ b/dev/vendor/galoy-quickstart/docker-compose.yml @@ -98,7 +98,7 @@ services: - POSTGRES_PASSWORD=secret - POSTGRES_DB=hydra galoy: - image: us.gcr.io/galoy-org/galoy-api@sha256:c707ddebb0e6e8e79bd1c7dd58b7483064c77609e9eb30fd0a5c59f1d6206af3 + image: us.gcr.io/galoy-org/galoy-api@sha256:949763b555858318808b9bc7fee2e33fa2deb7252a2c0eed1e7a9ad65193f021 environment: - HELMREVISION=dev - NETWORK=regtest @@ -161,7 +161,7 @@ services: aliases: - bats-tests trigger: - image: us.gcr.io/galoy-org/galoy-api-trigger@sha256:8c6224c7478ec509bdda00ec61cdcd68c964bffd6c8aebfbbc66ea396579f6a3 + image: us.gcr.io/galoy-org/galoy-api-trigger@sha256:8cd1c54070a2b27086391f372f0fb87fee9c35b7bc4a4ed6775f09b82b27c99c environment: - HELMREVISION=dev - NETWORK=regtest @@ -213,14 +213,18 @@ services: - bria - mongodb notifications: - image: us.gcr.io/galoy-org/galoy-notifications@sha256:a580eceec5260d6c06221b03f5e1e45c7545b6eb380961c9285206011d1b96bd + image: us.gcr.io/galoy-org/galoy-notifications@sha256:d86095c8470a4ce551fbe65d507d333983c5f201ddfa27a733eb4d3758c4682d environment: - PG_CON=postgres://user:password@notifications-pg:5432/pg - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-agent:4318 + - NOTIFICATIONS_CONFIG=/config/notifications/notifications.yml ports: - 6685:6685 - links: - - notifications-pg:notifications-pg + volumes: + - ${HOST_PROJECT_PATH:-.}/${GALOY_QUICKSTART_PATH:-vendor/galoy-quickstart}/dev/config/notifications/notifications.yml:/config/notifications/notifications.yml + - ${HOST_PROJECT_PATH:-.}/${GALOY_QUICKSTART_PATH:-vendor/galoy-quickstart}/dev/config/notifications/fake_service_account.json:/app/notifications/config/notifications/fake_service_account.json + depends_on: + - notifications-pg notifications-pg: image: postgres:14.1 environment: @@ -244,13 +248,13 @@ services: - ALLOW_EMPTY_PASSWORD=yes - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL mongodb: - image: mongo:7.0.2 + image: mongo:7.0.6 ports: - 27017:27017 environment: - MONGO_INITDB_DATABASE=galoy mongodb-migrate: - image: us.gcr.io/galoy-org/galoy-api-migrate@sha256:a0862f9b08eebab72e17fadf8ad9b7292b984428e982eb8fea21a81edadcefd1 + image: us.gcr.io/galoy-org/galoy-api-migrate@sha256:63fbc734fbd6a7c545a81375736614bf751d8052da50391a979448d64f555718 depends_on: - mongodb environment: @@ -260,6 +264,8 @@ services: ports: - 50051:50051 - 9464:9464 + volumes: + - ${HOST_PROJECT_PATH:-.}/${GALOY_QUICKSTART_PATH:-vendor/galoy-quickstart}/dev/config/price.yml:/var/yaml/custom.yaml price-history: image: us.gcr.io/galoy-org/price-history:edge ports: @@ -297,6 +303,13 @@ services: - POSTGRES_USER=galoy-price-usr - POSTGRES_PASSWORD=galoy-price-pwd - POSTGRES_DB=galoy-price-history + healthcheck: + test: + - CMD-SHELL + - pg_isready + interval: 5s + timeout: 30s + retries: 5 bitcoind: image: lncm/bitcoind:v25.1 ports: @@ -317,7 +330,7 @@ services: - | bitcoind -connect=bitcoind:18444 lnd1: - image: lightninglabs/lnd:v0.17.3-beta + image: lightninglabs/lnd:v0.17.4-beta ports: - 10009:10009 volumes: @@ -340,7 +353,7 @@ services: cp /root/.lnd/admin.macaroon /root/.lnd/data/chain/bitcoin/regtest/admin.macaroon /bin/lnd lnd-outside-1: - image: lightninglabs/lnd:v0.17.3-beta + image: lightninglabs/lnd:v0.17.4-beta ports: - 10012:10009 volumes: diff --git a/dev/vendor/galoy-quickstart/graphql/gql/api-key-create.gql b/dev/vendor/galoy-quickstart/graphql/gql/api-key-create.gql index aaeb1e7cd0..f4d55566dd 100644 --- a/dev/vendor/galoy-quickstart/graphql/gql/api-key-create.gql +++ b/dev/vendor/galoy-quickstart/graphql/gql/api-key-create.gql @@ -6,6 +6,7 @@ mutation apiKeyCreate($input: ApiKeyCreateInput!) { createdAt expiresAt readOnly + scopes } apiKeySecret } diff --git a/dev/vendor/galoy-quickstart/graphql/gql/api-keys.gql b/dev/vendor/galoy-quickstart/graphql/gql/api-keys.gql index 2537653784..a14c2e4b67 100644 --- a/dev/vendor/galoy-quickstart/graphql/gql/api-keys.gql +++ b/dev/vendor/galoy-quickstart/graphql/gql/api-keys.gql @@ -10,6 +10,7 @@ query apiKeys { createdAt expiresAt readOnly + scopes } } } diff --git a/dev/vendor/galoy-quickstart/graphql/gql/authorization.gql b/dev/vendor/galoy-quickstart/graphql/gql/authorization.gql new file mode 100644 index 0000000000..63d82cfd6e --- /dev/null +++ b/dev/vendor/galoy-quickstart/graphql/gql/authorization.gql @@ -0,0 +1,5 @@ +query Authorization { + authorization { + scopes + } +} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-cancel.gql b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-cancel.gql new file mode 100644 index 0000000000..0dfdf54cbb --- /dev/null +++ b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-cancel.gql @@ -0,0 +1,9 @@ +mutation LnInvoiceCancel($input: LnInvoiceCancelInput!) { + lnInvoiceCancel(input: $input) { + errors { + code + message + } + success + } +} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-hash-sub.gql b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-hash-sub.gql new file mode 100644 index 0000000000..5393eebf3f --- /dev/null +++ b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-hash-sub.gql @@ -0,0 +1,10 @@ +subscription lnInvoicePaymentStatusByHashSubscription($input: LnInvoicePaymentStatusByHashInput!) { + lnInvoicePaymentStatusByHash(input: $input) { + errors { + message + } + status + paymentHash + paymentRequest + } +} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-hash.gql b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-hash.gql new file mode 100644 index 0000000000..2de705e071 --- /dev/null +++ b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-hash.gql @@ -0,0 +1,7 @@ +query LnInvoicePaymentStatusByHashQuery($input: LnInvoicePaymentStatusByHashInput!) { + lnInvoicePaymentStatusByHash(input: $input) { + status + paymentHash + paymentRequest + } +} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-payment-request-sub.gql b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-payment-request-sub.gql new file mode 100644 index 0000000000..82c215611d --- /dev/null +++ b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-payment-request-sub.gql @@ -0,0 +1,10 @@ +subscription lnInvoicePaymentStatusByPaymentRequestSubscription($input: LnInvoicePaymentStatusByPaymentRequestInput!) { + lnInvoicePaymentStatusByPaymentRequest(input: $input) { + errors { + message + } + status + paymentHash + paymentRequest + } +} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-payment-request.gql b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-payment-request.gql new file mode 100644 index 0000000000..815750f16d --- /dev/null +++ b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-by-payment-request.gql @@ -0,0 +1,7 @@ +query LnInvoicePaymentStatusByPaymentRequestQuery($input: LnInvoicePaymentStatusByPaymentRequestInput!) { + lnInvoicePaymentStatusByPaymentRequest(input: $input) { + status + paymentHash + paymentRequest + } +} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-sub.gql b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-sub.gql deleted file mode 100644 index 6e26bd0732..0000000000 --- a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status-sub.gql +++ /dev/null @@ -1,8 +0,0 @@ -subscription lnInvoicePaymentStatusSubscription($input: LnInvoicePaymentStatusInput!) { - lnInvoicePaymentStatus(input: $input) { - errors { - message - } - status - } -} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status.gql b/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status.gql deleted file mode 100644 index af9e729f35..0000000000 --- a/dev/vendor/galoy-quickstart/graphql/gql/ln-invoice-payment-status.gql +++ /dev/null @@ -1,5 +0,0 @@ -query LnInvoicePaymentStatusQuery($input: LnInvoicePaymentStatusInput!) { - lnInvoicePaymentStatus(input: $input) { - status - } -} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/merchant-map-suggest.gql b/dev/vendor/galoy-quickstart/graphql/gql/merchant-map-suggest.gql new file mode 100644 index 0000000000..799b506743 --- /dev/null +++ b/dev/vendor/galoy-quickstart/graphql/gql/merchant-map-suggest.gql @@ -0,0 +1,17 @@ +mutation merchantMapSuggest($input: MerchantMapSuggestInput!) { + merchantMapSuggest(input: $input) { + errors { + message + } + merchant { + id + validated + title + coordinates { + latitude + longitude + } + username + } + } +} diff --git a/dev/vendor/galoy-quickstart/graphql/gql/transactions-for-wallet-by-payment-request.gql b/dev/vendor/galoy-quickstart/graphql/gql/transactions-for-wallet-by-payment-request.gql new file mode 100644 index 0000000000..d50a37618c --- /dev/null +++ b/dev/vendor/galoy-quickstart/graphql/gql/transactions-for-wallet-by-payment-request.gql @@ -0,0 +1,58 @@ +query transactionsForWalletByPaymentRequest( + $walletId: WalletId! + $paymentRequest: LnPaymentRequest! +) { + me { + defaultAccount { + displayCurrency + walletById(walletId: $walletId) { + id + transactionsByPaymentRequest(paymentRequest: $paymentRequest) { + __typename + id + status + direction + memo + createdAt + settlementAmount + settlementFee + settlementDisplayAmount + settlementDisplayFee + settlementDisplayCurrency + settlementCurrency + settlementPrice { + base + offset + } + initiationVia { + __typename + ... on InitiationViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on InitiationViaLn { + paymentHash + paymentRequest + } + ... on InitiationViaOnChain { + address + } + } + settlementVia { + __typename + ... on SettlementViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on SettlementViaLn { + preImage + } + ... on SettlementViaOnChain { + transactionHash + } + } + } + } + } + } +} diff --git a/dev/vendor/galoy-quickstart/graphql/schemas/admin/schema.graphql b/dev/vendor/galoy-quickstart/graphql/schemas/admin/schema.graphql index 98d9247965..95040c7b7c 100644 --- a/dev/vendor/galoy-quickstart/graphql/schemas/admin/schema.graphql +++ b/dev/vendor/galoy-quickstart/graphql/schemas/admin/schema.graphql @@ -31,19 +31,6 @@ input AccountUpdateStatusInput { status: AccountStatus! } -input AdminPushNotificationSendInput { - accountId: String! - body: String! - data: Object - notificationCategory: NotificationCategory - title: String! -} - -type AdminPushNotificationSendPayload { - errors: [Error!]! - success: Boolean -} - """ Accounts are core to the Galoy architecture. they have users, and own wallets """ @@ -51,28 +38,13 @@ type AuditedAccount { createdAt: Timestamp! id: ID! level: AccountLevel! + merchants: [Merchant!]! owner: AuditedUser! status: AccountStatus! username: Username wallets: [Wallet!]! } -type AuditedMerchant { - """ - GPS coordinates for the merchant that can be used to place the related business on a map - """ - coordinates: Coordinates - createdAt: Timestamp! - id: ID! - title: String! - - """The username of the merchant""" - username: Username! - - """Whether the merchant has been validated""" - validated: Boolean! -} - type AuditedUser { createdAt: Timestamp! @@ -149,23 +121,24 @@ type BTCWallet implements Wallet { last: Int ): TransactionConnection transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! + transactionsByPaymentRequest(paymentRequest: LnPaymentRequest!): [Transaction!]! walletCurrency: WalletCurrency! } -input BusinessDeleteMapInfoInput { - username: Username! -} - -input BusinessUpdateMapInfoInput { +type Coordinates { latitude: Float! longitude: Float! - title: String! - username: Username! } -type Coordinates { - latitude: Float! - longitude: Float! +"""A CCA2 country code (ex US, FR, etc)""" +scalar CountryCode + +enum DeepLink { + CIRCLES + EARN + MAP + PEOPLE + PRICE } """Display currency of an account""" @@ -291,27 +264,60 @@ enum LnPaymentStatus { scalar LnPubkey +input LocalizedPushContentInput { + body: String! + language: Language! + title: String! +} + +input MarketingNotificationTriggerInput { + deepLink: DeepLink + localizedPushContents: [LocalizedPushContentInput!]! + phoneCountryCodesFilter: [CountryCode!] + userIdsFilter: [ID!] +} + """Text field in a lightning payment transaction""" scalar Memo +type Merchant { + """ + GPS coordinates for the merchant that can be used to place the related business on a map + """ + coordinates: Coordinates! + createdAt: Timestamp! + id: ID! + title: String! + + """The username of the merchant""" + username: Username! + + """Whether the merchant has been validated""" + validated: Boolean! +} + +input MerchantMapDeleteInput { + id: ID! +} + +input MerchantMapValidateInput { + id: ID! +} + type MerchantPayload { errors: [Error!]! - merchant: AuditedMerchant + merchant: Merchant } type Mutation { accountUpdateLevel(input: AccountUpdateLevelInput!): AccountDetailPayload! accountUpdateStatus(input: AccountUpdateStatusInput!): AccountDetailPayload! - adminPushNotificationSend(input: AdminPushNotificationSendInput!): AdminPushNotificationSendPayload! - businessDeleteMapInfo(input: BusinessDeleteMapInfoInput!): AccountDetailPayload! - businessUpdateMapInfo(input: BusinessUpdateMapInfoInput!): MerchantPayload! + marketingNotificationTrigger(input: MarketingNotificationTriggerInput!): SuccessPayload! + merchantMapDelete(input: MerchantMapDeleteInput!): MerchantPayload! + merchantMapValidate(input: MerchantMapValidateInput!): MerchantPayload! userUpdatePhone(input: UserUpdatePhoneInput!): AccountDetailPayload! } -scalar NotificationCategory - -scalar Object - """An address for an on-chain bitcoin destination""" scalar OnChainAddress @@ -360,11 +366,14 @@ type Query { accountDetailsByUserPhone(phone: Phone!): AuditedAccount! accountDetailsByUsername(username: Username!): AuditedAccount! allLevels: [AccountLevel!]! + filteredUserCount(phoneCountryCodesFilter: [CountryCode!], userIdsFilter: [ID!]): Int! lightningInvoice(hash: PaymentHash!): LightningInvoice! lightningPayment(hash: PaymentHash!): LightningPayment! listWalletIds(walletCurrency: WalletCurrency!): [WalletId!]! + merchantsPendingApproval: [Merchant!]! transactionById(id: ID!): Transaction transactionsByHash(hash: PaymentHash!): [Transaction] + transactionsByPaymentRequest(paymentRequest: LnPaymentRequest!): [Transaction] wallet(walletId: WalletId!): Wallet! } @@ -406,6 +415,11 @@ A string amount (of a currency) that can be negative (e.g. in a transaction) """ scalar SignedDisplayMajorAmount +type SuccessPayload { + errors: [Error!]! + success: Boolean +} + """ Timestamp field, serialized as Unix time (the number of seconds since the Unix epoch) """ @@ -536,6 +550,7 @@ type UsdWallet implements Wallet { last: Int ): TransactionConnection transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! + transactionsByPaymentRequest(paymentRequest: LnPaymentRequest!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -641,6 +656,12 @@ interface Wallet { """The payment hash of the lightning invoice paid in this transaction.""" paymentHash: PaymentHash! ): [Transaction!]! + + """Returns the transactions that include this paymentRequest.""" + transactionsByPaymentRequest( + """Lightning invoice paid in this transaction.""" + paymentRequest: LnPaymentRequest! + ): [Transaction!]! walletCurrency: WalletCurrency! } diff --git a/dev/vendor/galoy-quickstart/graphql/schemas/public/schema.graphql b/dev/vendor/galoy-quickstart/graphql/schemas/public/schema.graphql index a11ad983eb..441c959c61 100644 --- a/dev/vendor/galoy-quickstart/graphql/schemas/public/schema.graphql +++ b/dev/vendor/galoy-quickstart/graphql/schemas/public/schema.graphql @@ -129,6 +129,10 @@ type AuthTokenPayload { totpRequired: Boolean } +type Authorization { + scopes: [Scope!]! +} + """ A wallet belonging to an account which contains a BTC balance and a list of transactions. """ @@ -195,6 +199,7 @@ type BTCWallet implements Wallet { last: Int ): TransactionConnection transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! + transactionsByPaymentRequest(paymentRequest: LnPaymentRequest!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -543,6 +548,13 @@ type LnInvoice implements Invoice { satoshis: SatAmount! } +input LnInvoiceCancelInput { + paymentHash: PaymentHash! + + """Wallet ID for a wallet associated with the current account.""" + walletId: WalletId! +} + input LnInvoiceCreateInput { """Amount in satoshis.""" amount: SatAmount! @@ -595,12 +607,28 @@ input LnInvoicePaymentInput { walletId: WalletId! } +type LnInvoicePaymentStatus { + paymentHash: PaymentHash + paymentRequest: LnPaymentRequest + status: InvoicePaymentStatus +} + +input LnInvoicePaymentStatusByHashInput { + paymentHash: PaymentHash! +} + +input LnInvoicePaymentStatusByPaymentRequestInput { + paymentRequest: LnPaymentRequest! +} + input LnInvoicePaymentStatusInput { paymentRequest: LnPaymentRequest! } type LnInvoicePaymentStatusPayload { errors: [Error!]! + paymentHash: PaymentHash + paymentRequest: LnPaymentRequest status: InvoicePaymentStatus } @@ -778,6 +806,34 @@ type MapMarker { """Text field in a lightning payment transaction""" scalar Memo +type Merchant { + """ + GPS coordinates for the merchant that can be used to place the related business on a map + """ + coordinates: Coordinates! + createdAt: Timestamp! + id: ID! + title: String! + + """The username of the merchant""" + username: Username! + + """Whether the merchant has been validated""" + validated: Boolean! +} + +input MerchantMapSuggestInput { + latitude: Float! + longitude: Float! + title: String! + username: Username! +} + +type MerchantPayload { + errors: [Error!]! + merchant: Merchant +} + """(Positive) amount of minutes""" scalar Minutes @@ -819,6 +875,9 @@ type Mutation { """Sends a payment to a lightning address.""" lnAddressPaymentSend(input: LnAddressPaymentSendInput!): PaymentSendPayload! + """Cancel an unpaid lightning invoice for an associated wallet.""" + lnInvoiceCancel(input: LnInvoiceCancelInput!): SuccessPayload! + """ Returns a lightning invoice for an associated wallet. When invoice is paid the value will be credited to a BTC wallet. @@ -898,6 +957,7 @@ type Mutation { """Sends a payment to a lightning address.""" lnurlPaymentSend(input: LnurlPaymentSendInput!): PaymentSendPayload! + merchantMapSuggest(input: MerchantMapSuggestInput!): MerchantPayload! onChainAddressCreate(input: OnChainAddressCreateInput!): OnChainAddressPayload! onChainAddressCurrent(input: OnChainAddressCurrentInput!): OnChainAddressPayload! onChainPaymentSend(input: OnChainPaymentSendInput!): PaymentSendPayload! @@ -905,7 +965,6 @@ type Mutation { onChainUsdPaymentSend(input: OnChainUsdPaymentSendInput!): PaymentSendPayload! onChainUsdPaymentSendAsBtcDenominated(input: OnChainUsdPaymentSendAsBtcDenominatedInput!): PaymentSendPayload! quizClaim(input: QuizClaimInput!): QuizClaimPayload! - quizCompleted(input: QuizCompletedInput!): QuizCompletedPayload! @deprecated(reason: "Use quizClaim instead") userContactUpdateAlias(input: UserContactUpdateAliasInput!): UserContactUpdateAliasPayload! @deprecated(reason: "will be moved to AccountContact") userEmailDelete: UserEmailDeletePayload! userEmailRegistrationInitiate(input: UserEmailRegistrationInitiateInput!): UserEmailRegistrationInitiatePayload! @@ -1160,11 +1219,16 @@ type PublicWallet { type Query { accountDefaultWallet(username: Username!, walletCurrency: WalletCurrency): PublicWallet! + + """Retrieve the list of scopes permitted for the user's token or API key""" + authorization: Authorization! btcPriceList(range: PriceGraphRange!): [PricePoint] businessMapMarkers: [MapMarker!]! currencyList: [Currency!]! globals: Globals - lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! + lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! @deprecated(reason: "Deprecated in favor of lnInvoicePaymentStatusByPaymentRequest") + lnInvoicePaymentStatusByHash(input: LnInvoicePaymentStatusByHashInput!): LnInvoicePaymentStatus! + lnInvoicePaymentStatusByPaymentRequest(input: LnInvoicePaymentStatusByPaymentRequestInput!): LnInvoicePaymentStatus! me: User mobileVersions: [MobileVersions] onChainTxFee(address: OnChainAddress!, amount: SatAmount!, speed: PayoutSpeed! = FAST, walletId: WalletId!): OnChainTxFee! @@ -1194,15 +1258,6 @@ type QuizClaimPayload { quizzes: [Quiz!]! } -input QuizCompletedInput { - id: ID! -} - -type QuizCompletedPayload { - errors: [Error!]! - quiz: Quiz -} - type RealtimePrice { btcSatPrice: PriceOfOneSatInMinorUnit! denominatorCurrency: DisplayCurrency! @@ -1237,6 +1292,12 @@ type SatAmountPayload { errors: [Error!]! } +enum Scope { + READ + RECEIVE + WRITE +} + """(Positive) amount of seconds""" scalar Seconds @@ -1271,7 +1332,9 @@ A string amount (of a currency) that can be negative (e.g. in a transaction) scalar SignedDisplayMajorAmount type Subscription { - lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! + lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! @deprecated(reason: "Deprecated in favor of lnInvoicePaymentStatusByPaymentRequest") + lnInvoicePaymentStatusByHash(input: LnInvoicePaymentStatusByHashInput!): LnInvoicePaymentStatusPayload! + lnInvoicePaymentStatusByPaymentRequest(input: LnInvoicePaymentStatusByPaymentRequestInput!): LnInvoicePaymentStatusPayload! myUpdates: MyUpdatesPayload! price(input: PriceInput!): PricePayload! @@ -1438,6 +1501,7 @@ type UsdWallet implements Wallet { last: Int ): TransactionConnection transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! + transactionsByPaymentRequest(paymentRequest: LnPaymentRequest!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -1712,6 +1776,12 @@ interface Wallet { """The payment hash of the lightning invoice paid in this transaction.""" paymentHash: PaymentHash! ): [Transaction!]! + + """Returns the transactions that include this paymentRequest.""" + transactionsByPaymentRequest( + """Lightning invoice paid in this transaction.""" + paymentRequest: LnPaymentRequest! + ): [Transaction!]! walletCurrency: WalletCurrency! } diff --git a/e2e/detox/01-auth.test.ts b/e2e/detox/01-auth.test.ts new file mode 100644 index 0000000000..27d0b0cfb1 --- /dev/null +++ b/e2e/detox/01-auth.test.ts @@ -0,0 +1,138 @@ +import "detox" + +import { TranslationFunctions } from "../../app/i18n/i18n-types" +import { i18nObject } from "../../app/i18n/i18n-util" +import { loadLocale } from "../../app/i18n/i18n-util.sync" +import { getKratosCode } from "./utils/commandline" +import { waitForAccountScreen } from "./utils/common-flows" +import { timeout, ALICE_PHONE, ALICE_EMAIL, otp } from "./utils/config" +import { tap } from "./utils/controls" + +export const setLocalEnvironment = async () => { + const buildBtn = element(by.id("logo-button")) + await waitFor(buildBtn) + .toBeVisible() + // Wait for 5 mins because metro bundler might not finish sync + .withTimeout(5 * 600000) + await buildBtn.multiTap(5) + + const logoutBtn = element(by.id("logout button")) + await waitFor(logoutBtn).toBeVisible().withTimeout(timeout) + + const envBtn = element(by.id("Local Button")) + const developerScreenSV = by.id("developer-screen-scroll-view") + + await waitFor(envBtn) + .toBeVisible() + .whileElement(developerScreenSV) + .scroll(400, "down", NaN, 0.85) + await envBtn.tap() + + const saveChangesBtn = element(by.id("Save Changes")) + await saveChangesBtn.tap() + + const stagingInstanceText = element(by.text(`Galoy Instance: Local`)) + await waitFor(stagingInstanceText).toBeVisible().withTimeout(10000) + + await tap(by.id("Back")) +} + +export const loginAs = (phone: string, LL: TranslationFunctions) => async () => { + await tap(by.id(LL.GetStartedScreen.createAccount())) + + const telephoneInput = element(by.id("telephoneNumber")) + await waitFor(telephoneInput).toBeVisible().withTimeout(timeout) + await telephoneInput.clearText() + await telephoneInput.typeText(phone) + await tap(by.id(LL.PhoneLoginInitiateScreen.sms())) + + const otpInput = element(by.id("oneTimeCode")) + try { + await waitFor(otpInput).toBeVisible().withTimeout(timeout) + await otpInput.clearText() + await otpInput.typeText(otp) + } catch { + /* empty because sometimes the page just moves to the next page coz 000000 is default */ + } + + await waitFor(element(by.text(LL.HomeScreen.myAccounts()))) + .toBeVisible() + .withTimeout(timeout) +} + +describe("Login/Register Flow", () => { + loadLocale("en") + const LL = i18nObject("en") + + beforeAll(async () => { + await device.launchApp({ newInstance: true }) + }) + + it("set environment", setLocalEnvironment) + + it("login as an user", loginAs(ALICE_PHONE, LL)) + + it("add an email", async () => { + await tap(by.id("menu")) + await tap(by.text(LL.common.account())) + await tap(by.text(LL.AccountScreen.emailAuthentication())) + + const emailInput = element(by.id(LL.EmailRegistrationInitiateScreen.placeholder())) + await waitFor(emailInput).toBeVisible().withTimeout(timeout) + await emailInput.clearText() + await emailInput.typeText(ALICE_EMAIL) + await tap(by.id(LL.EmailRegistrationInitiateScreen.send())) + + const codeInput = element(by.id("code-input")) + await waitFor(codeInput).toBeVisible().withTimeout(timeout) + + const code = await getKratosCode(ALICE_EMAIL) + await codeInput.clearText() + await codeInput.typeText(code) + + await tap(by.text(LL.common.ok())) + }) + + it("logout", async () => { + await waitForAccountScreen(LL) + + const logoutBtn = element(by.id(LL.AccountScreen.logOutAndDeleteLocalData())) + const accountScreenSV = by.id("account-screen-scroll-view") + + await waitFor(logoutBtn) + .toBeVisible() + .whileElement(accountScreenSV) + .scroll(400, "down", NaN, 0.85) + await logoutBtn.tap() + + await tap(by.text(LL.AccountScreen.IUnderstand())) + await tap(by.text(LL.common.ok())) + }) + + it("reset to local environment", setLocalEnvironment) + + it("log back in, with the new email", async () => { + await tap(by.id("email-button")) + + const emailInput = element(by.id(LL.EmailRegistrationInitiateScreen.placeholder())) + await waitFor(emailInput).toBeVisible().withTimeout(timeout) + await emailInput.clearText() + await emailInput.typeText(ALICE_EMAIL) + await tap(by.id(LL.EmailRegistrationInitiateScreen.send())) + + const codeInput = element(by.id("code-input")) + await waitFor(codeInput).toBeVisible().withTimeout(timeout) + + const code = await getKratosCode(ALICE_EMAIL) + await codeInput.clearText() + await codeInput.typeText(code) + }) + + it("verify we are in the same account as we started with", async () => { + await tap(by.id("menu")) + await tap(by.text(LL.common.account())) + + const phoneNumber = element(by.text(ALICE_PHONE)) + await waitFor(phoneNumber).toBeVisible().withTimeout(timeout) + }) +}) diff --git a/e2e/detox/01-phone-flow.test.ts b/e2e/detox/01-phone-flow.test.ts deleted file mode 100644 index d75c8ed5ad..0000000000 --- a/e2e/detox/01-phone-flow.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { expect } from "detox" - -import { i18nObject } from "../../app/i18n/i18n-util" -import { loadLocale } from "../../app/i18n/i18n-util.sync" -import { timeout, otp, ALICE_PHONE } from "./utils/config" - -const tap = async (match: Detox.NativeMatcher) => { - const el = element(match) - await waitFor(el).toBeVisible().withTimeout(timeout) - await el.tap() -} - -describe("Login with Phone Flow", () => { - loadLocale("en") - const LL = i18nObject("en") - - beforeAll(async () => { - await device.launchApp() - }) - - it("set environment", async () => { - const buildBtn = element(by.id("logo-button")) - await waitFor(buildBtn) - .toBeVisible() - // Wait for 5 mins because metro bundler might not finish sync - .withTimeout(5 * 600000) - await buildBtn.multiTap(3) - - const logoutBtn = element(by.id("logout button")) - await expect(logoutBtn).toBeVisible() - - const envBtn = element(by.id("Local Button")) - const developerScreenSV = by.id("developer-screen-scroll-view") - - await waitFor(envBtn) - .toBeVisible() - .whileElement(developerScreenSV) - .scroll(400, "down", NaN, 0.85) - await envBtn.tap() - - const saveChangesBtn = element(by.id("Save Changes")) - await saveChangesBtn.tap() - - const stagingInstanceText = element(by.text(`Galoy Instance: Local`)) - await waitFor(stagingInstanceText).toBeVisible().withTimeout(10000) - - await tap(by.id("Back")) - }) - - it("login as an user", async () => { - await tap(by.id(LL.GetStartedScreen.createAccount())) - - const telephoneInput = element(by.id("telephoneNumber")) - await waitFor(telephoneInput).toBeVisible().withTimeout(timeout) - await telephoneInput.clearText() - await telephoneInput.typeText(ALICE_PHONE) - await tap(by.id(LL.PhoneLoginInitiateScreen.sms())) - - const otpInput = element(by.id("oneTimeCode")) - try { - await waitFor(otpInput).toBeVisible().withTimeout(timeout) - await otpInput.clearText() - await otpInput.typeText(otp) - } catch { - /* empty because sometimes the page just moves to the next page coz 000000 is default */ - } - - await waitFor(element(by.text(LL.HomeScreen.myAccounts()))) - .toBeVisible() - .withTimeout(timeout) - }) -}) diff --git a/e2e/detox/02-conversion.test.ts b/e2e/detox/02-conversion.test.ts new file mode 100644 index 0000000000..4c8c0ae4c5 --- /dev/null +++ b/e2e/detox/02-conversion.test.ts @@ -0,0 +1,60 @@ +import "detox" + +import { i18nObject } from "../../app/i18n/i18n-util" +import { loadLocale } from "../../app/i18n/i18n-util.sync" +import { waitForHomeScreen, setLocalAndLoginWithAccessToken } from "./utils/common-flows" +import { timeout, ALICE_USERNAME, ALICE_TOKEN } from "./utils/config" +import { tap, verifyTextPresent } from "./utils/controls" + +describe("Intraledger Flow", () => { + loadLocale("en") + const LL = i18nObject("en") + + beforeAll(async () => { + await device.launchApp({ newInstance: true }) + await setLocalAndLoginWithAccessToken(ALICE_TOKEN, LL) + }) + + it("go to conversion screen", async () => { + if (device.getPlatform() === "android") { + await tap(by.id(LL.ConversionDetailsScreen.title())) + } else { + await tap(by.id(LL.HomeScreen.send())) + + const usernameInput = element(by.id(LL.SendBitcoinScreen.placeholder())) + await waitFor(usernameInput).toBeVisible().withTimeout(timeout) + await usernameInput.clearText() + await usernameInput.typeText(ALICE_USERNAME) + await tap(by.id(LL.common.next())) + } + }) + + it("convert", async () => { + await tap(by.id("convert-25%")) + await tap(by.id(LL.common.next())) + await tap(by.id(LL.common.convert())) + await waitForHomeScreen(LL) + }) + + it("check if last two transactions indicate conversion", async () => { + // The home screen displays the top two transactions + const tx1 = element(by.id("transaction-by-index-0")) + await waitFor(tx1) + .toBeVisible() + .withTimeout(timeout * 10) + await tx1.tap() + + await verifyTextPresent("From Local User") + await tap(by.id("close")) + + const tx2 = element(by.id("transaction-by-index-1")) + await waitFor(tx2) + .toBeVisible() + .whileElement(by.id("home-screen")) + .scroll(400, "down", NaN, 0.85) + await tx2.tap() + + await verifyTextPresent("To Local User") + await tap(by.id("close")) + }) +}) diff --git a/e2e/detox/03-payment-send.test.ts b/e2e/detox/03-payment-send.test.ts new file mode 100644 index 0000000000..32c86d8f2d --- /dev/null +++ b/e2e/detox/03-payment-send.test.ts @@ -0,0 +1,228 @@ +import "detox" + +import { i18nObject } from "../../app/i18n/i18n-util" +import { loadLocale } from "../../app/i18n/i18n-util.sync" +import { + getExternalLNNoAmountInvoice, + getLnInvoiceForBob, + getOnchainAddress, +} from "./utils/commandline" +import { setLocalAndLoginWithAccessToken, waitForHomeScreen } from "./utils/common-flows" +import { timeout, BOB_USERNAME, ALICE_TOKEN } from "./utils/config" +import { addAmount, tap, verifyTextPresent, sleep, slideSlider } from "./utils/controls" + +loadLocale("en") +const LL = i18nObject("en") + +// Make sure all tests in this file reset back to home screen because tests reuse same session in this file +beforeAll(async () => { + await device.launchApp({ newInstance: true }) + await setLocalAndLoginWithAccessToken(ALICE_TOKEN, LL) +}) + +describe("Send: Intraledger using Username - BTC Amount", () => { + it("send btc to bob using his username", async () => { + await tap(by.id(LL.HomeScreen.send())) + + const usernameInput = element(by.id(LL.SendBitcoinScreen.placeholder())) + await waitFor(usernameInput).toBeVisible().withTimeout(timeout) + await usernameInput.clearText() + await usernameInput.typeText(BOB_USERNAME) + await tap(by.id(LL.common.next())) + + try { + await tap(by.id("address-is-right")) + await tap( + by.id(LL.SendBitcoinDestinationScreen.confirmUsernameModal.confirmButton()), + ) + } catch { + /* If this was not the first time that the tests were run, + then the address is right popup modal won't appear */ + } + + await addAmount("0.02", LL) + await tap(by.id(LL.common.next())) + + await slideSlider() + await sleep(3000) + + await tap(by.id(LL.common.back())) + await waitForHomeScreen(LL) + }) + + it("check if latest transaction has been updated", async () => { + const tx = element(by.id(`transaction-by-index-0`)) + await waitFor(tx) + .toBeVisible() + .withTimeout(timeout * 10) + await tx.tap() + + await verifyTextPresent(LL.TransactionDetailScreen.spent()) + await verifyTextPresent("-$0.02") + + await tap(by.id("close")) + await waitForHomeScreen(LL) + }) +}) + +describe("Send: Intraledger using Username - USD Amount", () => { + it("send btc to bob using his username", async () => { + await tap(by.id(LL.HomeScreen.send())) + + const usernameInput = element(by.id(LL.SendBitcoinScreen.placeholder())) + await waitFor(usernameInput).toBeVisible().withTimeout(timeout) + await usernameInput.clearText() + await usernameInput.typeText(BOB_USERNAME) + await tap(by.id(LL.common.next())) + + try { + await tap(by.id("address-is-right")) + await tap( + by.id(LL.SendBitcoinDestinationScreen.confirmUsernameModal.confirmButton()), + ) + } catch { + /* If this was not the first time that the tests were run, + then the address is right popup modal won't appear */ + } + + await tap(by.id("choose-wallet-to-send-from")) + await tap(by.id("USD")) + + await addAmount("0.02", LL) + await tap(by.id(LL.common.next())) + + await slideSlider() + await sleep(3000) + + await tap(by.id(LL.common.back())) + await waitForHomeScreen(LL) + }) + + it("check if latest transaction has been updated", async () => { + const tx = element(by.id(`transaction-by-index-0`)) + await waitFor(tx) + .toBeVisible() + .withTimeout(timeout * 10) + await tx.tap() + + await verifyTextPresent(LL.TransactionDetailScreen.spent()) + await verifyTextPresent("-$0.02") + + await tap(by.id("close")) + await waitForHomeScreen(LL) + }) +}) + +describe("Send: Intraledger using LN Invoice", () => { + it("send btc to bob using his ln invoice", async () => { + await tap(by.id(LL.HomeScreen.send())) + + const invoice = await getLnInvoiceForBob() + + const invoiceInput = element(by.id(LL.SendBitcoinScreen.placeholder())) + await waitFor(invoiceInput).toBeVisible().withTimeout(timeout) + await invoiceInput.clearText() + await invoiceInput.typeText(invoice) + await tap(by.id(LL.common.next())) + + await addAmount("0.02", LL) + + // some bug + await device.disableSynchronization() + await tap(by.id(LL.common.next())) + await device.enableSynchronization() + + await slideSlider() + await sleep(3000) + + await tap(by.id(LL.common.back())) + await waitForHomeScreen(LL) + }) + + it("check if latest transaction has been updated", async () => { + const tx = element(by.id(`transaction-by-index-0`)) + await waitFor(tx) + .toBeVisible() + .withTimeout(timeout * 10) + await tx.tap() + + await verifyTextPresent(LL.TransactionDetailScreen.spent()) + await verifyTextPresent("-$0.02") + + await tap(by.id("close")) + await waitForHomeScreen(LL) + }) +}) + +describe("Send: to External LN Invoice", () => { + it("send btc to an external invoice taken from lnd-outside-1", async () => { + await tap(by.id(LL.HomeScreen.send())) + + const invoice = await getExternalLNNoAmountInvoice() + + const invoiceInput = element(by.id(LL.SendBitcoinScreen.placeholder())) + await waitFor(invoiceInput).toBeVisible().withTimeout(timeout) + await invoiceInput.clearText() + await invoiceInput.typeText(invoice) + await tap(by.id(LL.common.next())) + + await addAmount("0.02", LL) + await tap(by.id(LL.common.next())) + + await slideSlider() + await sleep(3000) + + await tap(by.id(LL.common.back())) + await waitForHomeScreen(LL) + }) + + it("check if latest transaction has been updated", async () => { + const tx = element(by.id(`transaction-by-index-0`)) + await waitFor(tx) + .toBeVisible() + .withTimeout(timeout * 10) + await tx.tap() + + await verifyTextPresent(LL.TransactionDetailScreen.spent()) + + await tap(by.id("close")) + await waitForHomeScreen(LL) + }) +}) + +describe("Send: to Onchain Address", () => { + it("send btc to an onchain address from bitcoind", async () => { + await tap(by.id(LL.HomeScreen.send())) + + const address = await getOnchainAddress() + + const addressInput = element(by.id(LL.SendBitcoinScreen.placeholder())) + await waitFor(addressInput).toBeVisible().withTimeout(timeout) + await addressInput.clearText() + await addressInput.typeText(address) + await tap(by.id(LL.common.next())) + + await addAmount("25.0", LL) + await tap(by.id(LL.common.next())) + + await slideSlider() + await sleep(3000) + + await tap(by.id(LL.common.back())) + await waitForHomeScreen(LL) + }) + + it("check if latest transaction has been updated", async () => { + const tx = element(by.id(`transaction-by-index-0`)) + await waitFor(tx) + .toBeVisible() + .withTimeout(timeout * 10) + await tx.tap() + + // can take a bit of time for tx to be confirmed + await verifyTextPresent(LL.TransactionDetailScreen.spent(), timeout * 30) + + await tap(by.id("close")) + await waitForHomeScreen(LL) + }) +}) diff --git a/e2e/detox/04-payment-receive.test.ts b/e2e/detox/04-payment-receive.test.ts new file mode 100644 index 0000000000..09bd3384cb --- /dev/null +++ b/e2e/detox/04-payment-receive.test.ts @@ -0,0 +1,198 @@ +import "detox" +import jimp from "jimp" +import jsQR from "jsqr" + +import { i18nObject } from "../../app/i18n/i18n-util" +import { loadLocale } from "../../app/i18n/i18n-util.sync" +import { sendBtcTo, sendLnPaymentFromBob } from "./utils/commandline" +import { setLocalAndLoginWithAccessToken, waitForHomeScreen } from "./utils/common-flows" +import { timeout, ALICE_TOKEN } from "./utils/config" +import { tap, sleep, addAmount } from "./utils/controls" + +const decodeQRCode = async (imgPath: string): Promise => { + const image = await jimp.read(imgPath) + const { data, width, height } = image.bitmap + + const clampedArray = new Uint8ClampedArray( + data.buffer, + data.byteOffset, + data.byteLength / Uint8ClampedArray.BYTES_PER_ELEMENT, + ) + + const qrCode = jsQR(clampedArray, width, height) + + if (qrCode) { + return qrCode.data + } + throw new Error("QR code could not be decoded.") +} + +loadLocale("en") +const LL = i18nObject("en") + +// TODO: +// Transaction list doesn't get updated in the local setup because somehow websocket +// is broken. Thereby, the received animation is not happening. This needs debugging. + +// Make sure all tests in this file reset back to home screen because tests reuse same session in this file +beforeAll(async () => { + await device.launchApp({ newInstance: true, permissions: { notifications: "YES" } }) + await setLocalAndLoginWithAccessToken(ALICE_TOKEN, LL) +}) + +describe("Receive: LN BTC Amountless", () => { + it("receive", async () => { + await tap(by.id(LL.HomeScreen.receive())) + + const amountInput = element(by.id("Amount Input Button")) + await waitFor(amountInput).toBeVisible().withTimeout(timeout) + + const readablePaymentRequest = element(by.id("readable-payment-request")) + await waitFor(readablePaymentRequest) + .toBeVisible() + .withTimeout(timeout * 60) + + const qrCode = element(by.id("QR-Code")) + await waitFor(qrCode).toBeVisible().withTimeout(timeout) + const imgPath = await qrCode.takeScreenshot("qr-code") + const paymentRequest = await decodeQRCode(imgPath) + + await sendLnPaymentFromBob({ paymentRequest, amount: 2 }) + + await tap(by.id("Back")) + await waitForHomeScreen(LL) + }) +}) + +describe("Receive: LN BTC $0.02 Amount", () => { + it("receive", async () => { + await tap(by.id(LL.HomeScreen.receive())) + + const amountInput = element(by.id("Amount Input Button")) + await waitFor(amountInput).toBeVisible().withTimeout(timeout) + + await addAmount("0.02", LL) + + const readablePaymentRequest = element(by.id("readable-payment-request")) + await waitFor(readablePaymentRequest) + .toBeVisible() + .withTimeout(timeout * 60) + + const qrCode = element(by.id("QR-Code")) + await waitFor(qrCode).toBeVisible().withTimeout(timeout) + const imgPath = await qrCode.takeScreenshot("qr-code") + const paymentRequest = await decodeQRCode(imgPath) + + await sendLnPaymentFromBob({ paymentRequest }) + + await tap(by.id("Back")) + await waitForHomeScreen(LL) + }) +}) + +describe("Receive: LN Stablesats Amountless", () => { + it("receive", async () => { + await tap(by.id(LL.HomeScreen.receive())) + + const amountInput = element(by.id("Amount Input Button")) + await waitFor(amountInput).toBeVisible().withTimeout(timeout) + + await tap(by.id("Stablesats")) + await sleep(1000) + + const readablePaymentRequest = element(by.id("readable-payment-request")) + await waitFor(readablePaymentRequest) + .toBeVisible() + .withTimeout(timeout * 60) + + const qrCode = element(by.id("QR-Code")) + await waitFor(qrCode).toBeVisible().withTimeout(timeout) + const imgPath = await qrCode.takeScreenshot("qr-code") + const paymentRequest = await decodeQRCode(imgPath) + + await sendLnPaymentFromBob({ paymentRequest, amount: 2 }) + + await tap(by.id("Back")) + await waitForHomeScreen(LL) + }) +}) + +describe("Receive: LN Stablesats $0.02 Amount", () => { + it("receive", async () => { + await tap(by.id(LL.HomeScreen.receive())) + + const amountInput = element(by.id("Amount Input Button")) + await waitFor(amountInput).toBeVisible().withTimeout(timeout) + + await tap(by.id("Stablesats")) + await sleep(1000) + + // await element(by.id("receive-screen")).scroll(400, "down", NaN, 0.85) + await addAmount("0.02", LL) + + const readablePaymentRequest = element(by.id("readable-payment-request")) + await waitFor(readablePaymentRequest) + .toBeVisible() + .withTimeout(timeout * 60) + + const qrCode = element(by.id("QR-Code")) + await waitFor(qrCode).toBeVisible().withTimeout(timeout) + const imgPath = await qrCode.takeScreenshot("qr-code") + const paymentRequest = await decodeQRCode(imgPath) + + await sendLnPaymentFromBob({ paymentRequest }) + + await tap(by.id("Back")) + await waitForHomeScreen(LL) + }) +}) + +describe("Receive: Onchain BTC", () => { + it("receive", async () => { + await tap(by.id(LL.HomeScreen.receive())) + + const amountInput = element(by.id("Amount Input Button")) + await waitFor(amountInput).toBeVisible().withTimeout(timeout) + + await tap(by.id("Onchain"), 30) + await sleep(500) + await tap(by.id("Bitcoin")) + await sleep(500) + + const qrCode = element(by.id("QR-Code")) + await waitFor(qrCode).toBeVisible().withTimeout(timeout) + const imgPath = await qrCode.takeScreenshot("qr-code") + const addressQR = await decodeQRCode(imgPath) + const address = addressQR.split(":")[1].split("?")[0] + + await sendBtcTo({ address }) + + await tap(by.id("Back")) + await waitForHomeScreen(LL) + }) +}) + +describe("Receive: Onchain Stablesats", () => { + it("receive", async () => { + await tap(by.id(LL.HomeScreen.receive())) + + const amountInput = element(by.id("Amount Input Button")) + await waitFor(amountInput).toBeVisible().withTimeout(timeout) + + await tap(by.id("Onchain"), 30) + await sleep(500) + await tap(by.id("Stablesats")) + await sleep(500) + + const qrCode = element(by.id("QR-Code")) + await waitFor(qrCode).toBeVisible().withTimeout(timeout) + const imgPath = await qrCode.takeScreenshot("qr-code") + const addressQR = await decodeQRCode(imgPath) + const address = addressQR.split(":")[1].split("?")[0] + + await sendBtcTo({ address }) + + await tap(by.id("Back")) + await waitForHomeScreen(LL) + }) +}) diff --git a/e2e/detox/jest.config.js b/e2e/detox/jest.config.js index 029bd8dd6e..73a216bf5a 100644 --- a/e2e/detox/jest.config.js +++ b/e2e/detox/jest.config.js @@ -2,7 +2,12 @@ module.exports = { preset: "ts-jest", rootDir: "..", - testMatch: ["/detox/**/*.test.ts"], + testMatch: [ + "/detox/01-auth.test.ts", + "/detox/02-conversion.test.ts", + "/detox/03-payment-send.test.ts", + "/detox/04-payment-receive.test.ts", + ], testTimeout: 120000, maxWorkers: 1, globalSetup: "detox/runners/jest/globalSetup", @@ -10,4 +15,5 @@ module.exports = { reporters: ["detox/runners/jest/reporter"], testEnvironment: "detox/runners/jest/testEnvironment", verbose: true, + bail: true, } diff --git a/e2e/detox/utils/commandline.ts b/e2e/detox/utils/commandline.ts new file mode 100644 index 0000000000..3f218121c5 --- /dev/null +++ b/e2e/detox/utils/commandline.ts @@ -0,0 +1,140 @@ +import { exec } from "child_process" +import path from "path" + +const REPO_ROOT = path.join(__dirname, "../../..") + +export const getKratosCode = async (email: string): Promise => + new Promise((resolve) => { + exec( + `source "${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh" && + kratos_pg -c "SELECT body FROM courier_messages WHERE recipient='${email}' ORDER BY created_at DESC LIMIT 1;"`, + { encoding: "utf-8" }, + (_, emailBody, __) => { + const code = emailBody.match(/\b\d{6}\b/)?.[0] + resolve(code || "") + }, + ) + }) + +export const getExternalLNNoAmountInvoice = async (): Promise => + new Promise((resolve) => { + exec( + `source "${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh" && + lnd_outside_cli addinvoice`, + { encoding: "utf-8" }, + (_, invoiceResponse, __) => { + resolve(JSON.parse(invoiceResponse).payment_request as string) + }, + ) + }) + +export const getOnchainAddress = async (): Promise => + new Promise((resolve) => { + exec( + `source "${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh" && + bitcoin_cli getnewaddress`, + { encoding: "utf-8" }, + (_, address, __) => { + resolve(address) + }, + ) + }) + +export const getLnInvoiceForBob = async (): Promise => + new Promise((resolve) => { + exec( + `source ${REPO_ROOT}/dev/vendor/galoy-quickstart/bin/helpers.sh + source ${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh + + cd ${REPO_ROOT}/dev + + variables=$( + jq -n \ + --arg wallet_id "$(read_value 'bob.btc_wallet_id')" \ + '{input: {walletId: $wallet_id}}' + ) + exec_graphql "bob" 'ln-no-amount-invoice-create' "$variables" + invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" + payment_request="$(echo $invoice | jq -r '.paymentRequest')" + + echo $payment_request + `, + { encoding: "utf-8" }, + (_, output, __) => { + resolve(output) + }, + ) + }) + +export const sendLnPaymentFromBob = async ({ + paymentRequest, + amount, +}: { + paymentRequest: string + amount?: number +}): Promise => + new Promise((resolve, reject) => { + if (amount) { + exec( + `source ${REPO_ROOT}/dev/vendor/galoy-quickstart/bin/helpers.sh + source ${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh + + cd ${REPO_ROOT}/dev + + variables=$( + jq -n \ + --arg wallet_id "$(read_value 'bob.usd_wallet_id')" \ + --arg payment_request "${paymentRequest}" \ + --arg amount "${amount}" \ + '{input: {walletId: $wallet_id, paymentRequest: $payment_request, amount: $amount}}' + ) + exec_graphql "bob" 'ln-no-amount-usd-invoice-payment-send' "$variables" + graphql_output + `, + { encoding: "utf-8" }, + (_, output, __) => { + const jsonOutput = JSON.parse(output) + if (jsonOutput.data.lnNoAmountUsdInvoicePaymentSend.status === "SUCCESS") + return resolve(jsonOutput) + reject(new Error("LN Payment from Bob was not successful")) + }, + ) + } else { + exec( + `source ${REPO_ROOT}/dev/vendor/galoy-quickstart/bin/helpers.sh + source ${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh + + cd ${REPO_ROOT}/dev + + variables=$( + jq -n \ + --arg wallet_id "$(read_value 'bob.btc_wallet_id')" \ + --arg payment_request "${paymentRequest}" \ + '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' + ) + exec_graphql "bob" 'ln-invoice-payment-send' "$variables" + graphql_output + `, + { encoding: "utf-8" }, + (_, output, __) => { + const jsonOutput = JSON.parse(output) + if (jsonOutput.data.lnInvoicePaymentSend.status === "SUCCESS") + return resolve(jsonOutput) + reject(new Error("LN Payment from Bob was not successful")) + }, + ) + } + }) + +export const sendBtcTo = async ({ address }: { address: string }): Promise => + new Promise((resolve) => { + exec( + `source "${REPO_ROOT}/dev/vendor/galoy-quickstart/dev/helpers/cli.sh" && + bitcoin_cli sendtoaddress "${address}" 0.01 && + bitcoin_cli -generate 2`, + { encoding: "utf-8" }, + (_, output, __) => { + resolve(output) + }, + ) + }) diff --git a/e2e/detox/utils/common-flows.ts b/e2e/detox/utils/common-flows.ts new file mode 100644 index 0000000000..c7a591801c --- /dev/null +++ b/e2e/detox/utils/common-flows.ts @@ -0,0 +1,70 @@ +import { TranslationFunctions } from "../../../app/i18n/i18n-types" +import { timeout } from "./config" +import { tap } from "./controls" + +export const waitForAccountScreen = async (LL: TranslationFunctions) => { + const el = element(by.id(LL.AccountScreen.yourAccountId())) + await waitFor(el) + .toBeVisible() + .withTimeout(timeout * 3) +} + +export const waitForHomeScreen = async (LL: TranslationFunctions) => { + const el = element(by.id(LL.HomeScreen.myAccounts())) + await waitFor(el) + .toBeVisible() + .withTimeout(timeout * 3) +} + +export const setLocalAndLoginWithAccessToken = async ( + accessToken: string, + LL: TranslationFunctions, +) => { + const buildBtn = element(by.id("logo-button")) + await waitFor(buildBtn) + .toBeVisible() + // Wait for 5 mins because metro bundler might not finish sync + .withTimeout(5 * 600000) + await buildBtn.multiTap(5) + + const logoutBtn = element(by.id("logout button")) + await waitFor(logoutBtn).toBeVisible().withTimeout(timeout) + + const accessTokenInput = element(by.id("Input access token")) + const developerScreenSV = by.id("developer-screen-scroll-view") + + await waitFor(accessTokenInput) + .toBeVisible() + .whileElement(developerScreenSV) + .scroll(400, "down", NaN, 0.85) + + const envBtn = element(by.id("Local Button")) + await envBtn.tap() + + await accessTokenInput.clearText() + await accessTokenInput.typeText(accessToken + "\n") + + const saveChangesBtn = element(by.id("Save Changes")) + await saveChangesBtn.tap() + + const localInstanceText = element(by.text(`Galoy Instance: Local`)) + await waitFor(localInstanceText) + .toBeVisible() + .whileElement(developerScreenSV) + .scroll(100, "up", NaN, 0.85) + + await tap(by.id("Back")) + + // Sometimes prompt to save password to keychain appears which need to be dismissed + await device.sendToHome() + await device.launchApp({ newInstance: false }) + + await tap(by.id(LL.GetStartedScreen.exploreWallet())) + + const balanceHeader = element(by.id("balance-header")) + await waitFor(balanceHeader) + .toBeVisible() + .withTimeout(timeout * 3) + + await device.setURLBlacklist([".*127.0.0.1.*", ".*localhost.*"]) +} diff --git a/e2e/detox/utils/config.ts b/e2e/detox/utils/config.ts index dc390f4e9e..730b3ed08c 100644 --- a/e2e/detox/utils/config.ts +++ b/e2e/detox/utils/config.ts @@ -1,14 +1,21 @@ import { config } from "dotenv" import path from "path" -config({ path: path.join(__dirname, "../../../dev/.env") }) +config({ path: path.join(__dirname, "../../../dev/.env.tmp.ci") }) if (!process.env.ALICE_PHONE || !process.env.BOB_PHONE) { throw new Error("Development environment environment configuration is incorrect") } -export const timeout = 3000 +export const timeout = 10000 export const otp = process.env.GALOY_STAGING_GLOBAL_OTP || "000000" export const ALICE_PHONE = process.env.ALICE_PHONE +export const ALICE_TOKEN = process.env.ALICE_TOKEN || "" +export const ALICE_USERNAME = process.env.ALICE_USERNAME || "" +export const ALICE_EMAIL = process.env.ALICE_USERNAME + "@galoy.io" + export const BOB_PHONE = process.env.BOB_PHONE +export const BOB_TOKEN = process.env.BOB_TOKEN || "" +export const BOB_USERNAME = process.env.BOB_USERNAME || "" +export const BOB_EMAIL = process.env.BOB_USERNAME + "@galoy.io" diff --git a/e2e/detox/utils/controls.ts b/e2e/detox/utils/controls.ts new file mode 100644 index 0000000000..bc6f059d33 --- /dev/null +++ b/e2e/detox/utils/controls.ts @@ -0,0 +1,39 @@ +import { TranslationFunctions } from "../../../app/i18n/i18n-types" +import { timeout } from "./config" + +export const tap = async (match: Detox.NativeMatcher, timeoutMultiplier = 1) => { + const el = element(match) + await waitFor(el) + .toBeVisible() + .withTimeout(timeout * timeoutMultiplier) + await el.tap() +} + +export const addAmount = async (amount: string, LL: TranslationFunctions) => { + await tap(by.id("Amount Input Button")) + for (const char of amount) { + await tap(by.id(`Key ${char}`)) + } + await tap(by.id(LL.AmountInputScreen.setAmount())) + await waitFor(element(by.id("Amount Input Button"))) + .toBeVisible() + .withTimeout(timeout) +} + +export const slideSlider = async () => { + const slider = element(by.id("slider")) + await waitFor(slider).toBeVisible().withTimeout(timeout) + await slider.swipe("right", "fast", 0.9, 0.5, 0.5) +} + +export const verifyTextPresent = async (text: string, waitTime?: number) => { + const el = element(by.text(text)) + await waitFor(el) + .toBeVisible() + .withTimeout(waitTime || timeout) +} + +export const sleep = (ms: number): Promise => + new Promise((resolve) => { + setTimeout(resolve, ms) + }) diff --git a/flake.nix b/flake.nix index d7531d9f5c..ac95883f3f 100644 --- a/flake.nix +++ b/flake.nix @@ -66,7 +66,6 @@ vendir jq ytt - llvm fastlane # Overlays @@ -77,8 +76,8 @@ ++ lib.optionals stdenv.isDarwin [ pkgsStable.cocoapods watchman - darwin.apple_sdk.frameworks.SystemConfiguration xcodes + darwin.apple_sdk.frameworks.SystemConfiguration ]; in { packages = { @@ -129,7 +128,7 @@ XCODE_VERSION="15.3" XCODE_BUILD="15E204a" # When updating xcode version, get it by running xcodes installed - if [[ $(uname) == "Darwin" ]]; then + if [[ $(uname) == "Darwin" ]] && [ -z "$CI" ]; then if ! xcodes installed | grep "$XCODE_VERSION ($XCODE_BUILD) (Selected)" -q; then echo -e "\e[1;33m================================================\e[0m" echo -e "\e[1;33mXCode $XCODE_VERSION was not found or is not selected\e[0m" From da3a6cade781226276ea6ff600658dcccc3324ba Mon Sep 17 00:00:00 2001 From: nicolasburtey Date: Sun, 24 Mar 2024 16:24:24 +0000 Subject: [PATCH 09/44] chore: updating react native vision library to 3.9.1 (#3130) * chore: updating react native vision library to 3.9.1 * chore: fix codegen --------- Co-authored-by: Nicolas Burtey --- app/graphql/generated.ts | 5 ++++- ios/Podfile.lock | 4 ++-- package.json | 2 +- supergraph.graphql | 18 ++++++++++++++++-- yarn.lock | 8 ++++---- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/app/graphql/generated.ts b/app/graphql/generated.ts index 31c53fab8a..bacf4787cc 100644 --- a/app/graphql/generated.ts +++ b/app/graphql/generated.ts @@ -1600,7 +1600,7 @@ export type Query = { readonly onChainUsdTxFee: OnChainUsdTxFee; readonly onChainUsdTxFeeAsBtcDenominated: OnChainUsdTxFee; readonly price?: Maybe; - /** Returns 1 Sat and 1 Usd Cent price for the given currency */ + /** Returns 1 Sat and 1 Usd Cent price for the given currency in minor unit */ readonly realtimePrice: RealtimePrice; readonly region?: Maybe; /** @deprecated will be migrated to AccountDefaultWalletId */ @@ -1701,7 +1701,9 @@ export type QuizClaimPayload = { export type RealtimePrice = { readonly __typename: 'RealtimePrice'; readonly btcSatPrice: PriceOfOneSatInMinorUnit; + /** @deprecated Deprecated in favor of denominatorCurrencyDetails */ readonly denominatorCurrency: Scalars['DisplayCurrency']['output']; + readonly denominatorCurrencyDetails: Currency; readonly id: Scalars['ID']['output']; /** Unix timestamp (number of seconds elapsed since January 1, 1970 00:00:00 UTC) */ readonly timestamp: Scalars['Timestamp']['output']; @@ -8399,6 +8401,7 @@ export type QuizClaimPayloadResolvers = { btcSatPrice?: Resolver; denominatorCurrency?: Resolver; + denominatorCurrencyDetails?: Resolver; id?: Resolver; timestamp?: Resolver; usdCentPrice?: Resolver; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 861877a397..c98f9e21e7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -671,7 +671,7 @@ PODS: - RCT-Folly (= 2021.07.22.00) - React-Core - SocketRocket (0.6.1) - - VisionCamera (3.8.2): + - VisionCamera (3.9.1): - React - React-callinvoker - React-Core @@ -1052,7 +1052,7 @@ SPEC CHECKSUMS: RNSVG: ba3e7232f45e34b7b47e74472386cf4e1a676d0a RNVectorIcons: 23b6e11af4aaf104d169b1b0afa7e5cf96c676ce SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 - VisionCamera: 4b98b273902ac18491bb68481b6601f1f0da0f2d + VisionCamera: 52f98e7628f9deea3e311c3b6d4870bb44445e05 Yoga: 4c3aa327e4a6a23eeacd71f61c81df1bcdf677d5 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 diff --git a/package.json b/package.json index 1e6f844fd4..1bb6c923ed 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "react-native-url-polyfill": "^2.0.0", "react-native-vector-icons": "^10.0.2", "react-native-view-shot": "^3.7.0", - "react-native-vision-camera": "^3.8.2", + "react-native-vision-camera": "^3.9.1", "react-native-walkthrough-tooltip": "^1.4.0", "react-native-webln": "^0.1.11", "react-native-webview": "^13.6.0", diff --git a/supergraph.graphql b/supergraph.graphql index 003894e18a..5ff862d20e 100644 --- a/supergraph.graphql +++ b/supergraph.graphql @@ -212,6 +212,12 @@ type ApiKeyRevokePayload apiKey: ApiKey! } +type Authorization + @join__type(graph: GALOY) +{ + scopes: [Scope!]! +} + """An Opaque Bearer token""" scalar AuthToken @join__type(graph: GALOY) @@ -1630,6 +1636,9 @@ type Query { welcomeLeaderboard(input: WelcomeLeaderboardInput!): Leaderboard! @join__field(graph: CIRCLES) accountDefaultWallet(username: Username!, walletCurrency: WalletCurrency): PublicWallet! @join__field(graph: GALOY) + + """Retrieve the list of scopes permitted for the user's token or API key""" + authorization: Authorization! @join__field(graph: GALOY) btcPriceList(range: PriceGraphRange!): [PricePoint] @join__field(graph: GALOY) businessMapMarkers: [MapMarker!]! @join__field(graph: GALOY) currencyList: [Currency!]! @join__field(graph: GALOY) @@ -1643,7 +1652,9 @@ type Query onChainUsdTxFee(address: OnChainAddress!, amount: CentAmount!, speed: PayoutSpeed! = FAST, walletId: WalletId!): OnChainUsdTxFee! @join__field(graph: GALOY) onChainUsdTxFeeAsBtcDenominated(address: OnChainAddress!, amount: SatAmount!, speed: PayoutSpeed! = FAST, walletId: WalletId!): OnChainUsdTxFee! @join__field(graph: GALOY) - """Returns 1 Sat and 1 Usd Cent price for the given currency""" + """ + Returns 1 Sat and 1 Usd Cent price for the given currency in minor unit + """ realtimePrice(currency: DisplayCurrency = "USD"): RealtimePrice! @join__field(graph: GALOY) userDefaultWalletId(username: Username!): WalletId! @join__field(graph: GALOY) @deprecated(reason: "will be migrated to AccountDefaultWalletId") usernameAvailable(username: Username!): Boolean @join__field(graph: GALOY) @@ -1676,7 +1687,8 @@ type RealtimePrice @join__type(graph: GALOY) { btcSatPrice: PriceOfOneSatInMinorUnit! - denominatorCurrency: DisplayCurrency! + denominatorCurrency: DisplayCurrency! @deprecated(reason: "Deprecated in favor of denominatorCurrencyDetails") + denominatorCurrencyDetails: Currency! id: ID! """ @@ -2147,6 +2159,8 @@ enum UserNotificationCategory BALANCE @join__enumValue(graph: GALOY) ADMIN_NOTIFICATION @join__enumValue(graph: GALOY) MARKETING @join__enumValue(graph: GALOY) + PRICE @join__enumValue(graph: GALOY) + SECURITY @join__enumValue(graph: GALOY) } enum UserNotificationChannel diff --git a/yarn.lock b/yarn.lock index 831eb9b04d..76cb56320d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20033,10 +20033,10 @@ react-native-view-shot@^3.7.0: dependencies: html2canvas "^1.4.1" -react-native-vision-camera@^3.8.2: - version "3.8.2" - resolved "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-3.8.2.tgz#f4f75f84c6a19e1c3474ddc0f7f785b5a526739b" - integrity sha512-MY39l2e3hNRPUefn2JPShOFExcw0PblbAcUGvJrIfS9pMzdIyceo0umRAx8lOGXzDUAdb+xy/tFWb8zGxKimCQ== +react-native-vision-camera@^3.9.1: + version "3.9.1" + resolved "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-3.9.1.tgz#78eb9da9fcd1cf2ee2562ec18ab8966e0215ab1b" + integrity sha512-Pi9ikguJlN1ydVZOyRaMfUij1raUY93rVuPM92BsGnXEfxSLbvRYXW4ll1DRtVtjS0kZq4IW7Oavg8syRPc/xQ== react-native-walkthrough-tooltip@^1.4.0: version "1.5.0" From 957f7d624344670d9234fdb46011d97e0947524c Mon Sep 17 00:00:00 2001 From: nicolasburtey Date: Mon, 25 Mar 2024 15:12:14 +0000 Subject: [PATCH 10/44] chore: forceExit to build the mobile app (#3132) Co-authored-by: Nicolas Burtey --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1bb6c923ed..b79311150a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "check-code": "yarn tsc:check && yarn eslint:check && yarn check:translations && yarn check:codegen && yarn graphql-check", "check:translations": "yarn update-translations && if git diff --name-only | grep -q 'app/i18n/i18n-types.ts'; then echo 'Error: app/i18n/i18n-types.ts has changes, run `yarn update-translations` and re-recommit' >&2; exit 1; fi", "check:codegen": "yarn dev:codegen && if git diff --name-only | grep -q 'app/graphql/generated.ts'; then echo 'Error: app/graphql/generated.ts has changes, run `yarn dev:codegen` and re-recommit' >&2; exit 1; fi", - "test": "LOGLEVEL=warn jest --runInBand", + "test": "LOGLEVEL=warn jest --runInBand --forceExit", "graphql-check": "npx -f @graphql-inspector/cli validate app/graphql/generated.gql $(grep 'schema:' codegen.yml | cut -d' ' -f2 | tr -d '\"') --apollo --noStrictFragments --method=GET --header=apollo-require-preflight", "coverage": "jest --runInBand --coverage", "fastlane-update": "(cd ios && bundle update fastlane && cd ../android && bundle update fastlane)", From c09c117482f1693d7272736ad5efea87f674b0b6 Mon Sep 17 00:00:00 2001 From: nicolasburtey Date: Mon, 25 Mar 2024 15:17:01 +0000 Subject: [PATCH 11/44] chore: update firebase / fix crash on ios (#3131) Co-authored-by: Nicolas Burtey --- ios/GaloyApp.xcodeproj/project.pbxproj | 30 +++- ios/Podfile.lock | 204 +++++++++++++------------ package.json | 12 +- yarn.lock | 48 +++--- 4 files changed, 167 insertions(+), 127 deletions(-) diff --git a/ios/GaloyApp.xcodeproj/project.pbxproj b/ios/GaloyApp.xcodeproj/project.pbxproj index 361cac4fe9..1b7b1ce5b3 100644 --- a/ios/GaloyApp.xcodeproj/project.pbxproj +++ b/ios/GaloyApp.xcodeproj/project.pbxproj @@ -262,7 +262,7 @@ name = "[CP-User] [RNFB] Core Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; }; 6508FD55DBDE6A7F235BA203 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -329,7 +329,19 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-GaloyApp/Pods-GaloyApp-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", "${PODS_ROOT}/GT3Captcha-iOS/SDK/GT3Captcha.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", @@ -350,10 +362,23 @@ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GT3Captcha.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", @@ -374,6 +399,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -387,7 +413,7 @@ ); inputPaths = ( "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", - "$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); name = "[CP-User] [RNFB] Crashlytics Configuration"; runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c98f9e21e7..c0c2895360 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - AppCheckCore (10.18.0): + - AppCheckCore (10.18.1): - GoogleUtilities/Environment (~> 7.11) - PromisesObjC (~> 2.3) - boost (1.76.0) @@ -14,131 +14,143 @@ PODS: - React-Core (= 0.72.7) - React-jsi (= 0.72.7) - ReactCommon/turbomodule/core (= 0.72.7) - - Firebase/AnalyticsWithoutAdIdSupport (10.18.0): + - Firebase/AnalyticsWithoutAdIdSupport (10.23.0): - Firebase/CoreOnly - - FirebaseAnalytics/WithoutAdIdSupport (~> 10.18.0) - - Firebase/AppCheck (10.18.0): + - FirebaseAnalytics/WithoutAdIdSupport (~> 10.23.0) + - Firebase/AppCheck (10.23.0): - Firebase/CoreOnly - - FirebaseAppCheck (~> 10.18.0) - - Firebase/CoreOnly (10.18.0): - - FirebaseCore (= 10.18.0) - - Firebase/Crashlytics (10.18.0): + - FirebaseAppCheck (~> 10.23.0) + - Firebase/CoreOnly (10.23.0): + - FirebaseCore (= 10.23.0) + - Firebase/Crashlytics (10.23.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 10.18.0) - - Firebase/Messaging (10.18.0): + - FirebaseCrashlytics (~> 10.23.0) + - Firebase/Messaging (10.23.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 10.18.0) - - Firebase/RemoteConfig (10.18.0): + - FirebaseMessaging (~> 10.23.0) + - Firebase/RemoteConfig (10.23.0): - Firebase/CoreOnly - - FirebaseRemoteConfig (~> 10.18.0) - - FirebaseABTesting (10.19.0): + - FirebaseRemoteConfig (~> 10.23.0) + - FirebaseABTesting (10.23.0): - FirebaseCore (~> 10.0) - - FirebaseAnalytics/WithoutAdIdSupport (10.18.0): + - FirebaseAnalytics/WithoutAdIdSupport (10.23.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.18.0) + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.23.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseAppCheck (10.18.0): + - nanopb (< 2.30911.0, >= 2.30908.0) + - FirebaseAppCheck (10.23.0): - AppCheckCore (~> 10.18) - FirebaseAppCheckInterop (~> 10.17) - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - PromisesObjC (~> 2.1) - - FirebaseAppCheckInterop (10.19.0) - - FirebaseCore (10.18.0): + - FirebaseAppCheckInterop (10.23.0) + - FirebaseCore (10.23.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreExtension (10.18.0): + - FirebaseCoreExtension (10.23.0): - FirebaseCore (~> 10.0) - - FirebaseCoreInternal (10.19.0): + - FirebaseCoreInternal (10.23.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseCrashlytics (10.18.0): + - FirebaseCrashlytics (10.23.0): - FirebaseCore (~> 10.5) - FirebaseInstallations (~> 10.0) + - FirebaseRemoteConfigInterop (~> 10.23) - FirebaseSessions (~> 10.5) - GoogleDataTransport (~> 9.2) - GoogleUtilities/Environment (~> 7.8) - - nanopb (< 2.30910.0, >= 2.30908.0) + - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (~> 2.1) - - FirebaseInstallations (10.19.0): + - FirebaseInstallations (10.23.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) - - FirebaseMessaging (10.18.0): + - FirebaseMessaging (10.23.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleDataTransport (~> 9.2) + - GoogleDataTransport (~> 9.3) - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/Reachability (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseRemoteConfig (10.18.0): + - nanopb (< 2.30911.0, >= 2.30908.0) + - FirebaseRemoteConfig (10.23.0): - FirebaseABTesting (~> 10.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) + - FirebaseRemoteConfigInterop (~> 10.23) - FirebaseSharedSwift (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseSessions (10.19.0): + - FirebaseRemoteConfigInterop (10.23.0) + - FirebaseSessions (10.23.0): - FirebaseCore (~> 10.5) - FirebaseCoreExtension (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleDataTransport (~> 9.2) - GoogleUtilities/Environment (~> 7.10) - - nanopb (< 2.30910.0, >= 2.30908.0) + - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesSwift (~> 2.1) - - FirebaseSharedSwift (10.19.0) + - FirebaseSharedSwift (10.23.0) - fmt (6.2.1) - glog (0.3.5) - - GoogleAppMeasurement/WithoutAdIdSupport (10.18.0): + - GoogleAppMeasurement/WithoutAdIdSupport (10.23.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleDataTransport (9.3.0): + - nanopb (< 2.30911.0, >= 2.30908.0) + - GoogleDataTransport (9.4.1): - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30910.0, >= 2.30908.0) + - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/AppDelegateSwizzler (7.12.0): + - GoogleUtilities/AppDelegateSwizzler (7.13.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.12.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (7.13.0): + - GoogleUtilities/Privacy - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.12.0): + - GoogleUtilities/Logger (7.13.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.12.0): + - GoogleUtilities/Privacy + - GoogleUtilities/MethodSwizzler (7.13.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.12.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Network (7.13.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.12.0)" - - GoogleUtilities/Reachability (7.12.0): + - "GoogleUtilities/NSData+zlib (7.13.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (7.13.0) + - GoogleUtilities/Reachability (7.13.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.12.0): + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (7.13.0): - GoogleUtilities/Logger - - GT3Captcha-iOS (0.15.8.3) + - GoogleUtilities/Privacy + - GT3Captcha-iOS (0.15.8.4) - hermes-engine (0.72.7): - hermes-engine/Pre-built (= 0.72.7) - hermes-engine/Pre-built (0.72.7) - libevent (2.1.12) - - nanopb (2.30909.1): - - nanopb/decode (= 2.30909.1) - - nanopb/encode (= 2.30909.1) - - nanopb/decode (2.30909.1) - - nanopb/encode (2.30909.1) - - PromisesObjC (2.3.1) - - PromisesSwift (2.3.1): - - PromisesObjC (= 2.3.1) + - nanopb (2.30910.0): + - nanopb/decode (= 2.30910.0) + - nanopb/encode (= 2.30910.0) + - nanopb/decode (2.30910.0) + - nanopb/encode (2.30910.0) + - PromisesObjC (2.4.0) + - PromisesSwift (2.4.0): + - PromisesObjC (= 2.4.0) - RCT-Folly (2021.07.22.00): - boost - DoubleConversion @@ -588,29 +600,29 @@ PODS: - React-Core - RNDeviceInfo (10.13.1): - React-Core - - RNFBAnalytics (18.6.2): - - Firebase/AnalyticsWithoutAdIdSupport (= 10.18.0) + - RNFBAnalytics (19.1.0): + - Firebase/AnalyticsWithoutAdIdSupport (= 10.23.0) - React-Core - RNFBApp - - RNFBApp (18.6.2): - - Firebase/CoreOnly (= 10.18.0) + - RNFBApp (19.1.0): + - Firebase/CoreOnly (= 10.23.0) - React-Core - - RNFBAppCheck (18.6.2): - - Firebase/AppCheck (= 10.18.0) + - RNFBAppCheck (19.1.0): + - Firebase/AppCheck (= 10.23.0) - React-Core - RNFBApp - - RNFBCrashlytics (18.6.2): - - Firebase/Crashlytics (= 10.18.0) - - FirebaseCoreExtension (= 10.18.0) + - RNFBCrashlytics (19.1.0): + - Firebase/Crashlytics (= 10.23.0) + - FirebaseCoreExtension (= 10.23.0) - React-Core - RNFBApp - - RNFBMessaging (18.6.2): - - Firebase/Messaging (= 10.18.0) - - FirebaseCoreExtension (= 10.18.0) + - RNFBMessaging (19.1.0): + - Firebase/Messaging (= 10.23.0) + - FirebaseCoreExtension (= 10.23.0) - React-Core - RNFBApp - - RNFBRemoteConfig (18.6.2): - - Firebase/RemoteConfig (= 10.18.0) + - RNFBRemoteConfig (19.1.0): + - Firebase/RemoteConfig (= 10.23.0) - React-Core - RNFBApp - RNGestureHandler (2.14.0): @@ -777,6 +789,7 @@ SPEC REPOS: - FirebaseInstallations - FirebaseMessaging - FirebaseRemoteConfig + - FirebaseRemoteConfigInterop - FirebaseSessions - FirebaseSharedSwift - fmt @@ -951,37 +964,38 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - AppCheckCore: 4687c03428947d5b26a6bf9bb5566b39f5473bf1 + AppCheckCore: d0d4bcb6f90fd9f69958da5350467b79026b38c7 boost: 57d2868c099736d80fcd648bf211b4431e51a558 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 5fbbff1d7734827299274638deb8ba3024f6c597 FBReactNativeSpec: 638095fe8a01506634d77b260ef8a322019ac671 - Firebase: 414ad272f8d02dfbf12662a9d43f4bba9bec2a06 - FirebaseABTesting: bfa3b384b68cee10a89183649c64cd7998a37a12 - FirebaseAnalytics: 4d310b35c48eaa4a058ddc04bdca6bdb5dc0fe80 - FirebaseAppCheck: f34226df40cf6057dc54916ab5a5d461bb51ee68 - FirebaseAppCheckInterop: 37884781f3e16a1ba47e7ec80a1e805f987788e3 - FirebaseCore: 2322423314d92f946219c8791674d2f3345b598f - FirebaseCoreExtension: 62b201498aa10535801cdf3448c7f4db5e24ed80 - FirebaseCoreInternal: b444828ea7cfd594fca83046b95db98a2be4f290 - FirebaseCrashlytics: 86d5bce01f42fa1db265f87ff1d591f04db610ec - FirebaseInstallations: 033d199474164db20c8350736842a94fe717b960 - FirebaseMessaging: 9bc34a98d2e0237e1b121915120d4d48ddcf301e - FirebaseRemoteConfig: bbd42790a4e84fde6aab7eae810b608e7b5c0bf6 - FirebaseSessions: e5f4caa188dc8bc6142abc974355be75b042215e - FirebaseSharedSwift: f34eeb7d3ea87a34497629b6ca41657beadef76a + Firebase: 333ec7c6b12fa09c77b5162cda6b862102211d50 + FirebaseABTesting: aec61ed9a34d85a95e2013a3fdf051426a2419df + FirebaseAnalytics: 45f6e2e5ef8ccbb90be73ae983c3b20fa78837f7 + FirebaseAppCheck: 4bb8047366c2c975583c9eff94235f8f2c5b342d + FirebaseAppCheckInterop: a1955ce8c30f38f87e7d091630e871e91154d65d + FirebaseCore: 63efb128decaebb04c4033ed4e05fb0bb1cccef1 + FirebaseCoreExtension: cb88851781a24e031d1b58e0bd01eb1f46b044b5 + FirebaseCoreInternal: 6a292e6f0bece1243a737e81556e56e5e19282e3 + FirebaseCrashlytics: b7aca2d52dd2440257a13741d2909ad80745ac6c + FirebaseInstallations: 42d6ead4605d6eafb3b6683674e80e18eb6f2c35 + FirebaseMessaging: 1b2270e66c81bbf184f70184db1d6a736ad0def5 + FirebaseRemoteConfig: 70ebe9542cf5242d762d1c0b4d53bfc472e0a4ce + FirebaseRemoteConfigInterop: cbc87ffa4932719a7911a08e94510f18f026f5a7 + FirebaseSessions: f06853e30f99fe42aa511014d7ee6c8c319f08a3 + FirebaseSharedSwift: c92645b392db3c41a83a0aa967de16f8bad25568 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - GoogleAppMeasurement: 70ce9aa438cff1cfb31ea3e660bcc67734cb716e - GoogleDataTransport: 57c22343ab29bc686febbf7cbb13bad167c2d8fe - GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34 - GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6 + GoogleAppMeasurement: 453eb0de99fcf2bdec9403e9ac5d7871fdba3e3f + GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a + GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 + GT3Captcha-iOS: 3e7737ece3b2210ba19802be381b9aa88007f045 hermes-engine: 9180d43df05c1ed658a87cc733dc3044cf90c00a libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 - PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 - PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 + nanopb: 438bc412db1928dac798aa6fd75726007be04262 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: 83bca1c184feb4d2e51c72c8369b83d641443f95 RCTTypeSafety: 13c4a87a16d7db6cd66006ce9759f073402ef85b @@ -1032,12 +1046,12 @@ SPEC CHECKSUMS: RNCClipboard: d77213bfa269013bf4b857b7a9ca37ee062d8ef1 RNDateTimePicker: 65e1d202799460b286ff5e741d8baf54695e8abd RNDeviceInfo: 4f9c7cfd6b9db1b05eb919620a001cf35b536423 - RNFBAnalytics: 15ed5033d4f8e2e40496b29bd34b0db718be8102 - RNFBApp: 5a4df14a5094dab69fc29049be3cf54af07b0435 - RNFBAppCheck: abfc7878972acf31feb9636ad9f585c42086f3d2 - RNFBCrashlytics: 362bcaf00295243c10058eafe0bfaefe915770d9 - RNFBMessaging: ad43f856329b726580e8f3898335a2382e5294e1 - RNFBRemoteConfig: a1f962dd0a4ab1a1740f5a718be658fb1275c71f + RNFBAnalytics: 29df7312cb4e04b5d147c8081732d068bb814632 + RNFBApp: 909e1780a2c70abc89fa5a1375bbb31b2f6cd700 + RNFBAppCheck: 174957b043b0545add74305d5b0b123d701dcecc + RNFBCrashlytics: e7c27feba2e93c52dc614eadaa93d3db37eea450 + RNFBMessaging: 483682635ec294eb1fb58def54c847e0c52743fa + RNFBRemoteConfig: 537c291011ed379f2ffbbc59de9c20b87d933502 RNGestureHandler: 32a01c29ecc9bb0b5bf7bc0a33547f61b4dc2741 RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c diff --git a/package.json b/package.json index b79311150a..5f75a10ba2 100644 --- a/package.json +++ b/package.json @@ -67,12 +67,12 @@ "@react-native-async-storage/async-storage": "^1.19.3", "@react-native-clipboard/clipboard": "^1.12.1", "@react-native-community/geolocation": "^3.1.0", - "@react-native-firebase/analytics": "^18.6.0", - "@react-native-firebase/app": "^18.6.0", - "@react-native-firebase/app-check": "^18.6.0", - "@react-native-firebase/crashlytics": "^18.6.0", - "@react-native-firebase/messaging": "^18.6.0", - "@react-native-firebase/remote-config": "^18.6.0", + "@react-native-firebase/analytics": "^19.1.0", + "@react-native-firebase/app": "^19.1.0", + "@react-native-firebase/app-check": "^19.1.0", + "@react-native-firebase/crashlytics": "^19.1.0", + "@react-native-firebase/messaging": "^19.1.0", + "@react-native-firebase/remote-config": "^19.1.0", "@react-navigation/bottom-tabs": "^6.5.11", "@react-navigation/native": "^6.1.9", "@react-navigation/stack": "^6.3.20", diff --git a/yarn.lock b/yarn.lock index 76cb56320d..9450286f51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4163,40 +4163,40 @@ resolved "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.4.3.tgz#9b9dc639b88f5bfda72bd72a9dff55cbf9f777ed" integrity sha512-WdjvGtqJfqcCiLwtbzie53Z/H6w6dIfRHhlW832D89ySAdE5DxLAsqRhDOG0eacuAxxEB+T9sGCkVMD0fa3aBg== -"@react-native-firebase/analytics@^18.6.0": - version "18.6.2" - resolved "https://registry.npmjs.org/@react-native-firebase/analytics/-/analytics-18.6.2.tgz#4594cdd2d1b410b1dd6a86659b42d0c1b2f3e178" - integrity sha512-SDvQvAq7NZZixG/gpzgdy4++bjCHlSXytg4gWXxfVZQSquY3K/wiGFxelAvHjYQRvJ9CAuZw7jvq/RbJ3wT4zg== +"@react-native-firebase/analytics@^19.1.0": + version "19.1.0" + resolved "https://registry.npmjs.org/@react-native-firebase/analytics/-/analytics-19.1.0.tgz#2e6a76fcdac61b0b3fdc7a8d3721536621fc4810" + integrity sha512-p6ZwpbYJRN6yCd1i0gjICyUbtxaVGINMiL4OxTJxpWaKZFGn5+qLe+p948Zww7Wnw4IiuZoUhM7K9YXOjqFYqw== -"@react-native-firebase/app-check@^18.6.0": - version "18.6.2" - resolved "https://registry.npmjs.org/@react-native-firebase/app-check/-/app-check-18.6.2.tgz#17a27c8f77f7444d660c34a306e27416737ea9be" - integrity sha512-T5Et2bx6cwHkdiw7YA26yCCWcp9oUfhnKIYEhZO5HXDbngo9hAoO73/XQ+V2ADqwh7fr1pDn4pPn3ew0BFlb0Q== +"@react-native-firebase/app-check@^19.1.0": + version "19.1.0" + resolved "https://registry.npmjs.org/@react-native-firebase/app-check/-/app-check-19.1.0.tgz#558967f0a1ac78163ee3088ea7d86bb3e887ab98" + integrity sha512-izbIN9/skShGOBz1mikhMTYGoEso4onbsV9fFh2e6pFzeqsZZHmkBMytLfohiHquc5HbcZVBjGb6178AMbNQew== -"@react-native-firebase/app@^18.6.0": - version "18.6.2" - resolved "https://registry.npmjs.org/@react-native-firebase/app/-/app-18.6.2.tgz#c6ad83c86574e4e91fcef0ac2c1a10fa95dc6ae5" - integrity sha512-0hZ/4itfn5kxoUZsZfrpyTh4FDh4jFU/5svYaSAldPK3+xRmi1BSSx9PVzWt7vyw1Bi5ep/oLq/6sFaoqk5FFw== +"@react-native-firebase/app@^19.1.0": + version "19.1.0" + resolved "https://registry.npmjs.org/@react-native-firebase/app/-/app-19.1.0.tgz#46eaff1d7303da9fa305a641d4c8623ba7b80435" + integrity sha512-xR4Ui0p6Xe2OXAs/Q4XN79nSqtupKu1ZwlGbYiUrvXsYwtwNlU1Pk8WvIhpXUP5/jXsSNZr8NeGrfCbb9m9NLQ== dependencies: opencollective-postinstall "^2.0.3" superstruct "^0.6.2" -"@react-native-firebase/crashlytics@^18.6.0": - version "18.6.2" - resolved "https://registry.npmjs.org/@react-native-firebase/crashlytics/-/crashlytics-18.6.2.tgz#7bda5c6ab9e2531e6e09cca7347cc9443f0a832a" - integrity sha512-Wo7uuPKHx4eQg8MS/JnKZK7k90tIxmAyz8RonrqgQYBG2GO2NguNHZsZqo3VP9XlpHW0lImdq5J0BWBwL4+a3w== +"@react-native-firebase/crashlytics@^19.1.0": + version "19.1.0" + resolved "https://registry.npmjs.org/@react-native-firebase/crashlytics/-/crashlytics-19.1.0.tgz#44aeecb7a20a82e312d9e0c5eaf9946d2ad2782c" + integrity sha512-q+rNjYWvmlR53y5x8+qe0fxozjFb6RyhM3Jwcc/WB4ZaUbiiBdtWf2DJ3VhGVgBq3qosTYjEGMVfifqD5o3LhA== dependencies: stacktrace-js "^2.0.2" -"@react-native-firebase/messaging@^18.6.0": - version "18.6.2" - resolved "https://registry.npmjs.org/@react-native-firebase/messaging/-/messaging-18.6.2.tgz#1a52635293e1fbd413c64289be0cfa1c8a329073" - integrity sha512-xGkv9tBjRaSqN6iGRC2euDiZVlraXVva4s/UQNbz5JBehTiStRP2lsRfGKiWuSWMMQgeKR0abRBgJLor8/OfwA== +"@react-native-firebase/messaging@^19.1.0": + version "19.1.0" + resolved "https://registry.npmjs.org/@react-native-firebase/messaging/-/messaging-19.1.0.tgz#aee9489c894e48bb12bea8f076d9e3c485064af0" + integrity sha512-Jao9WdzNMdPQ7SmYAyFLnzFaU/ybbC3idrS4JGM0TByJzEKUv3jAG/lvXdGW6D8FfFWICiJ7oEFESdiBq1ugXw== -"@react-native-firebase/remote-config@^18.6.0": - version "18.6.2" - resolved "https://registry.npmjs.org/@react-native-firebase/remote-config/-/remote-config-18.6.2.tgz#b633e0d4ffb2a7f03aaa03b7677ad3f699f3512e" - integrity sha512-FiePyHHkK4Se9lAxtw03yR1oN4FiOSDo92BcH2SBBp+a+rx0+h2c7lSxFMzA0wyGj1VHHPH7gStF0iIsppr9hQ== +"@react-native-firebase/remote-config@^19.1.0": + version "19.1.0" + resolved "https://registry.npmjs.org/@react-native-firebase/remote-config/-/remote-config-19.1.0.tgz#8f9711c06cd5c1eb6b68fab3312d272d981d07e6" + integrity sha512-gB4kV7s4/8Xca3s0Hs0S0TiNlg6zQnSwdKF8hnQGudOAIi1EhiUrNGQ5ybtKkGIh6TX7LC8Ev9jUgkIaNvo8Zw== "@react-native/assets-registry@^0.72.0": version "0.72.0" From 7e5385d893a0a4b29b922e8bec75145ee0753097 Mon Sep 17 00:00:00 2001 From: UncleSamtoshi <88598461+UncleSamtoshi@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:07:34 -0500 Subject: [PATCH 12/44] feat: april circles challenge (#3133) --- .../card.tsx | 28 +++++++-------- .../index.tsx | 0 .../modal.tsx | 6 ++-- app/i18n/en/index.ts | 5 +++ app/i18n/i18n-types.ts | 36 +++++++++++++++++++ app/i18n/raw-i18n/source/en.json | 5 +++ .../circles/circles-dashboard-screen.tsx | 4 +-- app/utils/date.ts | 4 +++ 8 files changed, 69 insertions(+), 19 deletions(-) rename app/components/{february-challenge => april-challenge}/card.tsx (71%) rename app/components/{february-challenge => april-challenge}/index.tsx (100%) rename app/components/{february-challenge => april-challenge}/modal.tsx (94%) diff --git a/app/components/february-challenge/card.tsx b/app/components/april-challenge/card.tsx similarity index 71% rename from app/components/february-challenge/card.tsx rename to app/components/april-challenge/card.tsx index d8043c2956..9f2f90df62 100644 --- a/app/components/february-challenge/card.tsx +++ b/app/components/april-challenge/card.tsx @@ -3,17 +3,17 @@ import { View } from "react-native" import { useI18nContext } from "@app/i18n/i18n-react" import { - MAR_1_2024_12_AM_UTC_MINUS_6, - FEB_1_2024_12_AM_UTC_MINUS_6, + MAY_1_2024_12_AM_UTC_MINUS_6, + APR_1_2024_12_AM_UTC_MINUS_6, getTimeLeft, } from "@app/utils/date" import { Text, makeStyles, useTheme } from "@rneui/themed" import { GaloyIcon } from "../atomic/galoy-icon" import { PressableCard } from "../pressable-card" -import { FebruaryChallengeModal } from "./modal" +import { AprilChallengeModal } from "./modal" -export const FebruaryChallengeCard: React.FC = () => { +export const AprilChallengeCard: React.FC = () => { const [modalIsOpen, setModalIsOpen] = useState(false) const openModal = () => setModalIsOpen(true) @@ -25,20 +25,20 @@ export const FebruaryChallengeCard: React.FC = () => { const [countDown, setCountDown] = useState( getTimeLeft({ - after: FEB_1_2024_12_AM_UTC_MINUS_6, - until: MAR_1_2024_12_AM_UTC_MINUS_6, + after: APR_1_2024_12_AM_UTC_MINUS_6, + until: MAY_1_2024_12_AM_UTC_MINUS_6, }), ) useEffect(() => { const dateNow = Date.now() - if (dateNow > MAR_1_2024_12_AM_UTC_MINUS_6) return + if (dateNow > MAY_1_2024_12_AM_UTC_MINUS_6) return const t = setInterval(() => { setCountDown( getTimeLeft({ - after: FEB_1_2024_12_AM_UTC_MINUS_6, - until: MAR_1_2024_12_AM_UTC_MINUS_6, + after: APR_1_2024_12_AM_UTC_MINUS_6, + until: MAY_1_2024_12_AM_UTC_MINUS_6, }), ) }, 1000) @@ -48,23 +48,23 @@ export const FebruaryChallengeCard: React.FC = () => { const currentTime = Date.now() if ( - currentTime > MAR_1_2024_12_AM_UTC_MINUS_6 || - currentTime < FEB_1_2024_12_AM_UTC_MINUS_6 + currentTime > MAY_1_2024_12_AM_UTC_MINUS_6 || + currentTime < APR_1_2024_12_AM_UTC_MINUS_6 ) return <> return ( - + - {LL.Circles.februaryChallenge.title()} + {LL.Circles.aprilChallenge.title()} {countDown} - {LL.Circles.februaryChallenge.description()} + {LL.Circles.aprilChallenge.description()} diff --git a/app/components/february-challenge/index.tsx b/app/components/april-challenge/index.tsx similarity index 100% rename from app/components/february-challenge/index.tsx rename to app/components/april-challenge/index.tsx diff --git a/app/components/february-challenge/modal.tsx b/app/components/april-challenge/modal.tsx similarity index 94% rename from app/components/february-challenge/modal.tsx rename to app/components/april-challenge/modal.tsx index 471f6b6540..5ff0819d40 100644 --- a/app/components/february-challenge/modal.tsx +++ b/app/components/april-challenge/modal.tsx @@ -20,7 +20,7 @@ type Props = { setIsVisible: (isVisible: boolean) => void } -export const FebruaryChallengeModal: React.FC = ({ isVisible, setIsVisible }) => { +export const AprilChallengeModal: React.FC = ({ isVisible, setIsVisible }) => { const { LL } = useI18nContext() const { @@ -52,10 +52,10 @@ export const FebruaryChallengeModal: React.FC = ({ isVisible, setIsVisibl /> - {LL.Circles.februaryChallenge.title()} + {LL.Circles.aprilChallenge.title()} - {LL.Circles.februaryChallenge.details()} + {LL.Circles.aprilChallenge.details()} {ShareImg} diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index c30f331d2b..b5a28379e8 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -2820,6 +2820,11 @@ const en: BaseTranslation = { title: "March Challenge!", description: "+3 inner circle and +3 outer circle for a chance to win $100", details: "Grow your inner circle by 3 and your outer circle by 3 for a chance at winning $100!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." + }, + aprilChallenge: { + title: "April Challenge!", + description: "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", + details: "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." } }, FullOnboarding: { diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index de788faceb..f317b00e07 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -8842,6 +8842,24 @@ type RootTranslation = { */ details: string } + aprilChallenge: { + /** + * A​p​r​i​l​ ​C​h​a​l​l​e​n​g​e​! + */ + title: string + /** + * G​r​o​w​ ​y​o​u​r​ ​i​n​n​e​r​ ​c​i​r​c​l​e​ ​b​y​ ​1​2​ ​a​n​d​ ​y​o​u​r​ ​o​u​t​e​r​ ​c​i​r​c​l​e​ ​b​y​ ​3​ ​f​o​r​ ​a​ ​c​h​a​n​c​e​ ​t​o​ ​w​i​n​ ​a​ ​B​i​t​b​o​x​0​2​ ​h​a​r​d​w​a​r​e​ ​w​a​l​l​e​t​. + */ + description: string + /** + * D​u​r​i​n​g​ ​t​h​e​ ​h​a​l​v​i​n​g​ ​m​o​n​t​h​,​ ​e​x​p​a​n​d​ ​y​o​u​r​ ​i​n​n​e​r​ ​c​i​r​c​l​e​ ​b​y​ ​1​2​ ​a​n​d​ ​y​o​u​r​ ​o​u​t​e​r​ ​c​i​r​c​l​e​ ​b​y​ ​3​ ​f​o​r​ ​a​ ​c​h​a​n​c​e​ ​t​o​ ​w​i​n​ ​a​ ​B​i​t​b​o​x​0​2​ ​h​a​r​d​w​a​r​e​ ​w​a​l​l​e​t​!​ + ​ + ​R​e​m​i​n​d​e​r​:​ ​y​o​u​r​ ​c​i​r​c​l​e​s​ ​g​r​o​w​ ​w​h​e​n​ ​y​o​u​ ​s​e​n​d​ ​a​ ​n​e​w​ ​B​l​i​n​k​ ​u​s​e​r​ ​t​h​e​i​r​ ​f​i​r​s​t​ ​s​a​t​s​.​ + ​ + ​S​h​a​r​e​ ​y​o​u​r​ ​c​i​r​c​l​e​s​ ​o​n​ ​s​o​c​i​a​l​ ​w​i​t​h​ ​#​b​l​i​n​k​c​i​r​c​l​e​s​ ​t​o​ ​p​a​r​t​i​c​i​p​a​t​e​. + */ + details: string + } } FullOnboarding: { /** @@ -17636,6 +17654,24 @@ export type TranslationFunctions = { Reminder: your circles grow when you send a new Blink user their first sats. + Share your circles on social with #blinkcircles to participate. + */ + details: () => LocalizedString + } + aprilChallenge: { + /** + * April Challenge! + */ + title: () => LocalizedString + /** + * Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet. + */ + description: () => LocalizedString + /** + * During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet! + + Reminder: your circles grow when you send a new Blink user their first sats. + Share your circles on social with #blinkcircles to participate. */ details: () => LocalizedString diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index 72cbf3e107..9e5db6b1a0 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -2701,6 +2701,11 @@ "title": "March Challenge!", "description": "+3 inner circle and +3 outer circle for a chance to win $100", "details": "Grow your inner circle by 3 and your outer circle by 3 for a chance at winning $100!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." + }, + "aprilChallenge": { + "title": "April Challenge!", + "description": "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", + "details": "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." } }, "FullOnboarding": { diff --git a/app/screens/people-screen/circles/circles-dashboard-screen.tsx b/app/screens/people-screen/circles/circles-dashboard-screen.tsx index 507d7f8f8d..96b8a87631 100644 --- a/app/screens/people-screen/circles/circles-dashboard-screen.tsx +++ b/app/screens/people-screen/circles/circles-dashboard-screen.tsx @@ -5,8 +5,8 @@ import { ScrollView } from "react-native-gesture-handler" import { gql } from "@apollo/client" import LogoDarkMode from "@app/assets/logo/app-logo-dark.svg" import LogoLightMode from "@app/assets/logo/blink-logo-light.svg" +import { AprilChallengeCard } from "@app/components/april-challenge" import { Circle, CircleRef } from "@app/components/circle" -import { FebruaryChallengeCard } from "@app/components/february-challenge" import { IntroducingCirclesModal } from "@app/components/introducing-circles-modal" import { MarchChallengeCard } from "@app/components/march-challenge" import { useCirclesQuery } from "@app/graphql/generated" @@ -153,8 +153,8 @@ export const CirclesDashboardScreen: React.FC = () => { )} - + {isLonely ? : } diff --git a/app/utils/date.ts b/app/utils/date.ts index 0722f66d5f..42f218c91e 100644 --- a/app/utils/date.ts +++ b/app/utils/date.ts @@ -18,6 +18,10 @@ export const APR_1_2024_12_AM_UTC_MINUS_6 = new Date( Date.UTC(2024, 3, 1, 6, 0, 0), ).getTime() +export const MAY_1_2024_12_AM_UTC_MINUS_6 = new Date( + Date.UTC(2024, 4, 1, 6, 0, 0), +).getTime() + const secondsToDDMMSS = (totalSeconds: number) => { if (totalSeconds < 0) return "" From dfcfe32edba6b9134ddc4f83d62a4958cd45aad9 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:29:17 -0500 Subject: [PATCH 13/44] Updates for file app/i18n/raw-i18n/source/en.json (#3134) * chore: [skip CI] Translate en.json in el [Manual Sync] 99% of minimum 98% translated source file: 'en.json' on 'el'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format * chore: [skip CI] Translate en.json in hu [Manual Sync] 99% of minimum 98% translated source file: 'en.json' on 'hu'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format * chore: [skip CI] Translate en.json in es [Manual Sync] 99% of minimum 98% translated source file: 'en.json' on 'es'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format * chore: [skip CI] Translate en.json in tr [Manual Sync] 99% of minimum 98% translated source file: 'en.json' on 'tr'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format * chore: [skip CI] Translate en.json in sw [Manual Sync] 98% of minimum 98% translated source file: 'en.json' on 'sw'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format * chore: [skip CI] Translate en.json in pt [Manual Sync] 99% of minimum 98% translated source file: 'en.json' on 'pt'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format * chore: [skip CI] Translate en.json in ca [Manual Sync] 99% of minimum 98% translated source file: 'en.json' on 'ca'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- app/i18n/raw-i18n/translations/ca.json | 13 +++++++++++++ app/i18n/raw-i18n/translations/el.json | 13 +++++++++++++ app/i18n/raw-i18n/translations/es.json | 13 +++++++++++++ app/i18n/raw-i18n/translations/hu.json | 24 +++++++++++++++++++++--- app/i18n/raw-i18n/translations/pt.json | 13 +++++++++++++ app/i18n/raw-i18n/translations/sw.json | 22 ++++++++++++++++++++-- app/i18n/raw-i18n/translations/tr.json | 18 ++++++++++++++++++ 7 files changed, 111 insertions(+), 5 deletions(-) diff --git a/app/i18n/raw-i18n/translations/ca.json b/app/i18n/raw-i18n/translations/ca.json index df9f12f8d3..b0ffedfbf2 100644 --- a/app/i18n/raw-i18n/translations/ca.json +++ b/app/i18n/raw-i18n/translations/ca.json @@ -2262,6 +2262,14 @@ "Payments": { "title": "Pagaments", "description": "Notificacions relacionades amb enviar i rebre pagaments" + }, + "Marketing": { + "title": "Features and updates", + "description": "Notifications about new features and updates." + }, + "Price": { + "title": "Price changes", + "description": "Notifications about the price of Bitcoin." } } }, @@ -2693,6 +2701,11 @@ "title": "Desafiament de març!", "description": "+3 cercle interior i +3 cercle exterior per tenir l'oportunitat de guanyar 100 dòlars.", "details": "Fes créixer el teu cercle interior en 3 persones i el teu cercle exterior en 3 persones per tenir l'oportunitat de guanyar 100 $!\n\nRecordatori: els teus cercles creixen quan envies a un nou usuari de Blink els seus primers sats.\n\nComparteix els teus cercles a les xarxes socials amb #blinkcircles per participar." + }, + "aprilChallenge": { + "title": "April Challenge!", + "description": "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", + "details": "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." } }, "FullOnboarding": { diff --git a/app/i18n/raw-i18n/translations/el.json b/app/i18n/raw-i18n/translations/el.json index 170a617e24..9a142e13f7 100644 --- a/app/i18n/raw-i18n/translations/el.json +++ b/app/i18n/raw-i18n/translations/el.json @@ -2262,6 +2262,14 @@ "Payments": { "title": "Πληρωμές", "description": "Ειδοποιήσεις σχετικά με την αποστολή και τη λήψη πληρωμών." + }, + "Marketing": { + "title": "Features and updates", + "description": "Notifications about new features and updates." + }, + "Price": { + "title": "Price changes", + "description": "Notifications about the price of Bitcoin." } } }, @@ -2693,6 +2701,11 @@ "title": "Πρόκληση Μαρτίου!", "description": "+3 άτομα στον εσωτερικό και +3 άτομα στον εξωτερικό κύκλο επαφών σας για να έχετε μια ευκαιρία να κερδίσετε $100!", "details": "Μεγαλώστε τον εσωτερικό κύκλο επαφών σας κατά 3 άτομα στον εσωτερικό και 3 άτομα στον εξωτερικό κύκλο επαφών σας για να έχετε μια ευκαιρία να κερδίσετε $100\n\nΥπενθύμιση: οι κύκλοι σας αυξάνονται όταν στέλνετε σε έναν νέο χρήστη του Blink τα πρώτα του sats.\n\nΜοιραστείτε τους κύκλους σας στα κοινωνικά δίκτυα με το #blinkcircles για να συμμετάσχετε." + }, + "aprilChallenge": { + "title": "April Challenge!", + "description": "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", + "details": "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." } }, "FullOnboarding": { diff --git a/app/i18n/raw-i18n/translations/es.json b/app/i18n/raw-i18n/translations/es.json index ddae6d3f19..353bdad28d 100644 --- a/app/i18n/raw-i18n/translations/es.json +++ b/app/i18n/raw-i18n/translations/es.json @@ -2262,6 +2262,14 @@ "Payments": { "title": "Pagos", "description": "Notificaciones relacionadas con el envío y recepción de pagos." + }, + "Marketing": { + "title": "Features and updates", + "description": "Notifications about new features and updates." + }, + "Price": { + "title": "Price changes", + "description": "Notifications about the price of Bitcoin." } } }, @@ -2693,6 +2701,11 @@ "title": "¡Reto de marzo!", "description": "+3 círculo interior y +3 círculo exterior para tener la oportunidad de ganar $100", "details": "Amplía tu círculo interno en 3 personas y tu círculo externo en 3 personas para tener la oportunidad de ganar $100.\n\nRecordatorio: tus círculos crecen cuando envías a un nuevo usuario de Blink sus primeros sats.\n\nComparte tus círculos en redes sociales con #blinkcircles para participar." + }, + "aprilChallenge": { + "title": "Reto de abril!", + "description": "Amplia en 12 tu círculo interno y en 3 tu círculo externo para tener la oportunidad de ganar una Bitbox02 hardware wallet.", + "details": "En el mes del Halving, amplía tu Círculo Interior en 12 y aumenta tu Círculo Exterior en 3 para tener la oportunidad de ganar una Bitbox02 hardware wallet.\n\nRecordatorio: Tus círculos crecen cuando envías a un nuevo usuario de Blink sus primeros sats.\n\nComparte tus círculos en las redes sociales con el hashtag #blinkcircles para participar." } }, "FullOnboarding": { diff --git a/app/i18n/raw-i18n/translations/hu.json b/app/i18n/raw-i18n/translations/hu.json index e21b0bd57b..5401e17d99 100644 --- a/app/i18n/raw-i18n/translations/hu.json +++ b/app/i18n/raw-i18n/translations/hu.json @@ -557,7 +557,7 @@ "Sajnálom, ez nem jó válasz! Bár az arany lehet bőségesebb a gyémántoknál, ez nem az a fő ok, amiért jobban fungibilisnek számít. Legközelebb jobb szerencsét!" ], "question": "Mi az a fő ok, amiért az arany jobban fungibilis (helyettesíthető), mint a gyémánt", - "text": "\n\"Fungibilitás\" azt jelenti, hogy egy valuta egy egysége cserélhető egy másik ugyanolyan valutaegységgel. Ez egy fontos tulajdonsága egy jó értéktárolónak.\n\nAz arany egy nagyon jól cserélhető értéktároló, hiszen megolvasztva az arany uncia gyakorlatilag megkülönböztethetetlen bármely másikól. A fiat valuták viszont nem mindig teljesen cserélhetőek, mivel kibocsátó intézményeik különböző címleteket más módon kezelhetnek.\n\nMint az arany, úgy a bitcoin egységei is fungibilisek, de ezzel kapcsolatban van néhány részlet. Későbbi fejezetekben kifejtjük.", + "text": "\n\"Fungibilitás\" azt jelenti, hogy egy valuta egy egysége cserélhető egy másik ugyanolyan valutaegységgel. Ez egy fontos tulajdonsága egy jó értéktárolónak.\n\nAz arany egy nagyon jól cserélhető értéktároló, hiszen megolvasztva az arany uncia gyakorlatilag megkülönböztethetetlen bármely másikól. A fiat valuták viszont nem mindig teljesen cserélhetőek, mivel kibocsátó intézményeik különböző címleteket más módon kezelhetnek.\n\nMint az arany, úgy a bitcoin egységei is fungibilisek, de ezzel kapcsolatban van néhány részlet. Későbbi fejezetekben kifejtjük.\n", "title": "Egy példánynak cserélhetőnek kell lennie egy másik azonos mennyiságü példánnyra" }, "verifiability": { @@ -1992,8 +1992,8 @@ "youEarned": "Kerestél", "registerTitle": "A magasabb szintű számlára kell váltanod", "registerContent": "Regisztrálj a telefonszámoddal, hogy sat-okat fogadhass", - "notYet": "Még nem", - "availableTomorrow": "Az a rész holnaptól lesz elérhető!", + "oneSectionADay": "One section a day!", + "availableTomorrow": "Come back tomorrow to continue learning about Bitcoin!", "motivatingBadger": "Haladj végig ezeken a leckéken, és keress a tanulás során" }, "GetStartedScreen": { @@ -2262,6 +2262,14 @@ "Payments": { "title": "Fizetések", "description": "Fizetések küldésével és fogadásával kapcsolatos értesítések." + }, + "Marketing": { + "title": "Features and updates", + "description": "Notifications about new features and updates." + }, + "Price": { + "title": "Price changes", + "description": "Notifications about the price of Bitcoin." } } }, @@ -2688,6 +2696,16 @@ "title": "Februári kihívás!", "description": "Növeld 3-al a belső köröd, hogy Seedsigner hardver tárcát nyerhess!", "details": "Növeld belső körödet 3-mal februárban, hogy esélyed legyen nyerni egy Seedsigner hardver tárcát!\n\nEmlékeztető: a körök akkor növekednek, amikor egy új Blink felhasználónak küldöd az első satoshiját.\n\nOszd meg körödet a közösségi médiában a #blinkcircles hashtaggel a részvételhez." + }, + "marchChallenge": { + "title": "March Challenge!", + "description": "+3 inner circle and +3 outer circle for a chance to win $100", + "details": "Grow your inner circle by 3 and your outer circle by 3 for a chance at winning $100!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." + }, + "aprilChallenge": { + "title": "April Challenge!", + "description": "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", + "details": "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." } }, "FullOnboarding": { diff --git a/app/i18n/raw-i18n/translations/pt.json b/app/i18n/raw-i18n/translations/pt.json index 010edb3445..b2a4927dcf 100644 --- a/app/i18n/raw-i18n/translations/pt.json +++ b/app/i18n/raw-i18n/translations/pt.json @@ -2262,6 +2262,14 @@ "Payments": { "title": "Pagamentos", "description": "Notificações relacionadas ao envio e recebimento de pagamentos." + }, + "Marketing": { + "title": "Features and updates", + "description": "Notifications about new features and updates." + }, + "Price": { + "title": "Price changes", + "description": "Notifications about the price of Bitcoin." } } }, @@ -2693,6 +2701,11 @@ "title": "Desafio de março!", "description": "+3 círculo interno e +3 círculo externo para ter a chance de ganhar $100", "details": "Aumente seu círculo interno em 3 e seu círculo externo em 3 para ter a chance de ganhar $100!\n\nLembrete: seus círculos crescem quando você envia sats para um novo usuário do Blink.\n\nCompartilhe seus círculos nas redes sociais com #blinkcircles para participar." + }, + "aprilChallenge": { + "title": "April Challenge!", + "description": "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", + "details": "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." } }, "FullOnboarding": { diff --git a/app/i18n/raw-i18n/translations/sw.json b/app/i18n/raw-i18n/translations/sw.json index 385b999f7f..e67e4ccd53 100644 --- a/app/i18n/raw-i18n/translations/sw.json +++ b/app/i18n/raw-i18n/translations/sw.json @@ -1992,8 +1992,8 @@ "youEarned": "Umechuma", "registerTitle": "Unahitaji kuboresha akaunti yako", "registerContent": "Jiandikishe na namba yako ya simu ili upokee sats", - "notYet": "Not yet", - "availableTomorrow": "This section will be available tomorrow!", + "oneSectionADay": "One section a day!", + "availableTomorrow": "Come back tomorrow to continue learning about Bitcoin!", "motivatingBadger": "Dig your way through these lessons and earn as you learn" }, "GetStartedScreen": { @@ -2262,6 +2262,14 @@ "Payments": { "title": "Malipo", "description": "Arifa zinazohusiana na kutuma na kupokea malipo." + }, + "Marketing": { + "title": "Features and updates", + "description": "Notifications about new features and updates." + }, + "Price": { + "title": "Price changes", + "description": "Notifications about the price of Bitcoin." } } }, @@ -2688,6 +2696,16 @@ "title": "February Challenge!", "description": "+3 inner circle for a chance to win a Seedsigner hardware wallet!", "details": "Grow your inner circle by 3 in February for a chance to win a Seedsigner hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." + }, + "marchChallenge": { + "title": "March Challenge!", + "description": "+3 inner circle and +3 outer circle for a chance to win $100", + "details": "Grow your inner circle by 3 and your outer circle by 3 for a chance at winning $100!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." + }, + "aprilChallenge": { + "title": "April Challenge!", + "description": "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", + "details": "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." } }, "FullOnboarding": { diff --git a/app/i18n/raw-i18n/translations/tr.json b/app/i18n/raw-i18n/translations/tr.json index f1de2f6700..2a20cfe008 100644 --- a/app/i18n/raw-i18n/translations/tr.json +++ b/app/i18n/raw-i18n/translations/tr.json @@ -2262,6 +2262,14 @@ "Payments": { "title": "Ödemeler", "description": "Ödeme almak ve gördermekle alakalı bildirimler." + }, + "Marketing": { + "title": "Features and updates", + "description": "Notifications about new features and updates." + }, + "Price": { + "title": "Price changes", + "description": "Notifications about the price of Bitcoin." } } }, @@ -2688,6 +2696,16 @@ "title": "Şubat Meydan Okuması!", "description": "Seedsigner soğuk cüzdanı kazanmak için iç çevrenize 3 kişi katın!", "details": "Seedsigner soğuk cüzdanı kazanma şansı için Şubat ayında yakın çevrenizi 3 kişi büyütün!\n\nHatırlatma: Yeni bir Blink kullanıcısına ilk satlerini gönderdiğinizde çemberleriniz büyür.\n\nKatılmak için çevrelerinizi #blinkcircles etiketiyle sosyal medyada paylaşın." + }, + "marchChallenge": { + "title": "March Challenge!", + "description": "+3 inner circle and +3 outer circle for a chance to win $100", + "details": "Grow your inner circle by 3 and your outer circle by 3 for a chance at winning $100!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." + }, + "aprilChallenge": { + "title": "April Challenge!", + "description": "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", + "details": "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." } }, "FullOnboarding": { From e38808f8c37bbce2a1bcd269cf71a6456cc37fc9 Mon Sep 17 00:00:00 2001 From: nicolasburtey Date: Tue, 26 Mar 2024 19:53:07 +0000 Subject: [PATCH 14/44] chore: getting to prior firebase version (#3138) Co-authored-by: Nicolas Burtey --- ios/GaloyApp.xcodeproj/project.pbxproj | 12 --- ios/Podfile.lock | 125 ++++++++++++------------- package.json | 12 +-- yarn.lock | 48 +++++----- 4 files changed, 90 insertions(+), 107 deletions(-) diff --git a/ios/GaloyApp.xcodeproj/project.pbxproj b/ios/GaloyApp.xcodeproj/project.pbxproj index 1b7b1ce5b3..608d4fe5af 100644 --- a/ios/GaloyApp.xcodeproj/project.pbxproj +++ b/ios/GaloyApp.xcodeproj/project.pbxproj @@ -330,13 +330,8 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-GaloyApp/Pods-GaloyApp-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", "${PODS_ROOT}/GT3Captcha-iOS/SDK/GT3Captcha.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", @@ -362,18 +357,12 @@ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GT3Captcha.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", @@ -399,7 +388,6 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c0c2895360..635597e0c9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -14,64 +14,63 @@ PODS: - React-Core (= 0.72.7) - React-jsi (= 0.72.7) - ReactCommon/turbomodule/core (= 0.72.7) - - Firebase/AnalyticsWithoutAdIdSupport (10.23.0): + - Firebase/AnalyticsWithoutAdIdSupport (10.21.0): - Firebase/CoreOnly - - FirebaseAnalytics/WithoutAdIdSupport (~> 10.23.0) - - Firebase/AppCheck (10.23.0): + - FirebaseAnalytics/WithoutAdIdSupport (~> 10.21.0) + - Firebase/AppCheck (10.21.0): - Firebase/CoreOnly - - FirebaseAppCheck (~> 10.23.0) - - Firebase/CoreOnly (10.23.0): - - FirebaseCore (= 10.23.0) - - Firebase/Crashlytics (10.23.0): + - FirebaseAppCheck (~> 10.21.0) + - Firebase/CoreOnly (10.21.0): + - FirebaseCore (= 10.21.0) + - Firebase/Crashlytics (10.21.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 10.23.0) - - Firebase/Messaging (10.23.0): + - FirebaseCrashlytics (~> 10.21.0) + - Firebase/Messaging (10.21.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 10.23.0) - - Firebase/RemoteConfig (10.23.0): + - FirebaseMessaging (~> 10.21.0) + - Firebase/RemoteConfig (10.21.0): - Firebase/CoreOnly - - FirebaseRemoteConfig (~> 10.23.0) + - FirebaseRemoteConfig (~> 10.21.0) - FirebaseABTesting (10.23.0): - FirebaseCore (~> 10.0) - - FirebaseAnalytics/WithoutAdIdSupport (10.23.0): + - FirebaseAnalytics/WithoutAdIdSupport (10.21.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.23.0) + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.21.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseAppCheck (10.23.0): + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseAppCheck (10.21.0): - AppCheckCore (~> 10.18) - FirebaseAppCheckInterop (~> 10.17) - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - PromisesObjC (~> 2.1) - FirebaseAppCheckInterop (10.23.0) - - FirebaseCore (10.23.0): + - FirebaseCore (10.21.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreExtension (10.23.0): + - FirebaseCoreExtension (10.21.0): - FirebaseCore (~> 10.0) - FirebaseCoreInternal (10.23.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseCrashlytics (10.23.0): + - FirebaseCrashlytics (10.21.0): - FirebaseCore (~> 10.5) - FirebaseInstallations (~> 10.0) - - FirebaseRemoteConfigInterop (~> 10.23) - FirebaseSessions (~> 10.5) - GoogleDataTransport (~> 9.2) - GoogleUtilities/Environment (~> 7.8) - - nanopb (< 2.30911.0, >= 2.30908.0) + - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (~> 2.1) - FirebaseInstallations (10.23.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) - - FirebaseMessaging (10.23.0): + - FirebaseMessaging (10.21.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleDataTransport (~> 9.3) @@ -79,16 +78,14 @@ PODS: - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/Reachability (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseRemoteConfig (10.23.0): + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseRemoteConfig (10.21.0): - FirebaseABTesting (~> 10.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - FirebaseRemoteConfigInterop (~> 10.23) - FirebaseSharedSwift (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseRemoteConfigInterop (10.23.0) - FirebaseSessions (10.23.0): - FirebaseCore (~> 10.5) - FirebaseCoreExtension (~> 10.0) @@ -100,12 +97,12 @@ PODS: - FirebaseSharedSwift (10.23.0) - fmt (6.2.1) - glog (0.3.5) - - GoogleAppMeasurement/WithoutAdIdSupport (10.23.0): + - GoogleAppMeasurement/WithoutAdIdSupport (10.21.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30911.0, >= 2.30908.0) + - nanopb (< 2.30910.0, >= 2.30908.0) - GoogleDataTransport (9.4.1): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30911.0, >= 2.30908.0) @@ -143,11 +140,11 @@ PODS: - hermes-engine/Pre-built (= 0.72.7) - hermes-engine/Pre-built (0.72.7) - libevent (2.1.12) - - nanopb (2.30910.0): - - nanopb/decode (= 2.30910.0) - - nanopb/encode (= 2.30910.0) - - nanopb/decode (2.30910.0) - - nanopb/encode (2.30910.0) + - nanopb (2.30909.1): + - nanopb/decode (= 2.30909.1) + - nanopb/encode (= 2.30909.1) + - nanopb/decode (2.30909.1) + - nanopb/encode (2.30909.1) - PromisesObjC (2.4.0) - PromisesSwift (2.4.0): - PromisesObjC (= 2.4.0) @@ -600,29 +597,29 @@ PODS: - React-Core - RNDeviceInfo (10.13.1): - React-Core - - RNFBAnalytics (19.1.0): - - Firebase/AnalyticsWithoutAdIdSupport (= 10.23.0) + - RNFBAnalytics (19.0.0): + - Firebase/AnalyticsWithoutAdIdSupport (= 10.21.0) - React-Core - RNFBApp - - RNFBApp (19.1.0): - - Firebase/CoreOnly (= 10.23.0) + - RNFBApp (19.0.0): + - Firebase/CoreOnly (= 10.21.0) - React-Core - - RNFBAppCheck (19.1.0): - - Firebase/AppCheck (= 10.23.0) + - RNFBAppCheck (19.0.0): + - Firebase/AppCheck (= 10.21.0) - React-Core - RNFBApp - - RNFBCrashlytics (19.1.0): - - Firebase/Crashlytics (= 10.23.0) - - FirebaseCoreExtension (= 10.23.0) + - RNFBCrashlytics (19.0.0): + - Firebase/Crashlytics (= 10.21.0) + - FirebaseCoreExtension (= 10.21.0) - React-Core - RNFBApp - - RNFBMessaging (19.1.0): - - Firebase/Messaging (= 10.23.0) - - FirebaseCoreExtension (= 10.23.0) + - RNFBMessaging (19.0.0): + - Firebase/Messaging (= 10.21.0) + - FirebaseCoreExtension (= 10.21.0) - React-Core - RNFBApp - - RNFBRemoteConfig (19.1.0): - - Firebase/RemoteConfig (= 10.23.0) + - RNFBRemoteConfig (19.0.0): + - Firebase/RemoteConfig (= 10.21.0) - React-Core - RNFBApp - RNGestureHandler (2.14.0): @@ -789,7 +786,6 @@ SPEC REPOS: - FirebaseInstallations - FirebaseMessaging - FirebaseRemoteConfig - - FirebaseRemoteConfigInterop - FirebaseSessions - FirebaseSharedSwift - fmt @@ -970,30 +966,29 @@ SPEC CHECKSUMS: DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 5fbbff1d7734827299274638deb8ba3024f6c597 FBReactNativeSpec: 638095fe8a01506634d77b260ef8a322019ac671 - Firebase: 333ec7c6b12fa09c77b5162cda6b862102211d50 + Firebase: 4453b799f72f625384dc23f412d3be92b0e3b2a0 FirebaseABTesting: aec61ed9a34d85a95e2013a3fdf051426a2419df - FirebaseAnalytics: 45f6e2e5ef8ccbb90be73ae983c3b20fa78837f7 - FirebaseAppCheck: 4bb8047366c2c975583c9eff94235f8f2c5b342d + FirebaseAnalytics: d275f288881d4417f780115dd52c05fa9752d530 + FirebaseAppCheck: 7e0d2655c51a00410d41d2ed26905e351bd0ca95 FirebaseAppCheckInterop: a1955ce8c30f38f87e7d091630e871e91154d65d - FirebaseCore: 63efb128decaebb04c4033ed4e05fb0bb1cccef1 - FirebaseCoreExtension: cb88851781a24e031d1b58e0bd01eb1f46b044b5 + FirebaseCore: 74f647ad9739ea75112ce6c3b3b91f5488ce1122 + FirebaseCoreExtension: 1c044fd46e95036cccb29134757c499613f3f564 FirebaseCoreInternal: 6a292e6f0bece1243a737e81556e56e5e19282e3 - FirebaseCrashlytics: b7aca2d52dd2440257a13741d2909ad80745ac6c + FirebaseCrashlytics: 063b883186d7ccb1db1837c19113ca6294880c1b FirebaseInstallations: 42d6ead4605d6eafb3b6683674e80e18eb6f2c35 - FirebaseMessaging: 1b2270e66c81bbf184f70184db1d6a736ad0def5 - FirebaseRemoteConfig: 70ebe9542cf5242d762d1c0b4d53bfc472e0a4ce - FirebaseRemoteConfigInterop: cbc87ffa4932719a7911a08e94510f18f026f5a7 + FirebaseMessaging: b285329399cc6edd73b5b32864c68454177bcf3f + FirebaseRemoteConfig: 1dd5164b2183aff07c7726df6b0212ad5154c1fe FirebaseSessions: f06853e30f99fe42aa511014d7ee6c8c319f08a3 FirebaseSharedSwift: c92645b392db3c41a83a0aa967de16f8bad25568 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - GoogleAppMeasurement: 453eb0de99fcf2bdec9403e9ac5d7871fdba3e3f + GoogleAppMeasurement: a65314d316443969ed3d3709b3a187448ed6418f GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 GT3Captcha-iOS: 3e7737ece3b2210ba19802be381b9aa88007f045 hermes-engine: 9180d43df05c1ed658a87cc733dc3044cf90c00a libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - nanopb: 438bc412db1928dac798aa6fd75726007be04262 + nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 @@ -1046,12 +1041,12 @@ SPEC CHECKSUMS: RNCClipboard: d77213bfa269013bf4b857b7a9ca37ee062d8ef1 RNDateTimePicker: 65e1d202799460b286ff5e741d8baf54695e8abd RNDeviceInfo: 4f9c7cfd6b9db1b05eb919620a001cf35b536423 - RNFBAnalytics: 29df7312cb4e04b5d147c8081732d068bb814632 - RNFBApp: 909e1780a2c70abc89fa5a1375bbb31b2f6cd700 - RNFBAppCheck: 174957b043b0545add74305d5b0b123d701dcecc - RNFBCrashlytics: e7c27feba2e93c52dc614eadaa93d3db37eea450 - RNFBMessaging: 483682635ec294eb1fb58def54c847e0c52743fa - RNFBRemoteConfig: 537c291011ed379f2ffbbc59de9c20b87d933502 + RNFBAnalytics: daa0e9db0aec81a932c312c700d7f3acf59bc6d4 + RNFBApp: 495bf6662800c670fe8e9629d4de0cba72c78b0a + RNFBAppCheck: 3155bd968f834894113723f477da90921ea6ba29 + RNFBCrashlytics: 6646bc280ba6c8772e0f111a38fc815d8fd3d238 + RNFBMessaging: 6686d597a88f0054b87faba6299992a90cfe935b + RNFBRemoteConfig: 22d49f66a45712896e6a36078da6a0c4a7008beb RNGestureHandler: 32a01c29ecc9bb0b5bf7bc0a33547f61b4dc2741 RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c diff --git a/package.json b/package.json index 5f75a10ba2..e5821bbbdb 100644 --- a/package.json +++ b/package.json @@ -67,12 +67,12 @@ "@react-native-async-storage/async-storage": "^1.19.3", "@react-native-clipboard/clipboard": "^1.12.1", "@react-native-community/geolocation": "^3.1.0", - "@react-native-firebase/analytics": "^19.1.0", - "@react-native-firebase/app": "^19.1.0", - "@react-native-firebase/app-check": "^19.1.0", - "@react-native-firebase/crashlytics": "^19.1.0", - "@react-native-firebase/messaging": "^19.1.0", - "@react-native-firebase/remote-config": "^19.1.0", + "@react-native-firebase/analytics": "19.0.0", + "@react-native-firebase/app": "19.0.0", + "@react-native-firebase/app-check": "19.0.0", + "@react-native-firebase/crashlytics": "19.0.0", + "@react-native-firebase/messaging": "19.0.0", + "@react-native-firebase/remote-config": "19.0.0", "@react-navigation/bottom-tabs": "^6.5.11", "@react-navigation/native": "^6.1.9", "@react-navigation/stack": "^6.3.20", diff --git a/yarn.lock b/yarn.lock index 9450286f51..d08042b67f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4163,40 +4163,40 @@ resolved "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.4.3.tgz#9b9dc639b88f5bfda72bd72a9dff55cbf9f777ed" integrity sha512-WdjvGtqJfqcCiLwtbzie53Z/H6w6dIfRHhlW832D89ySAdE5DxLAsqRhDOG0eacuAxxEB+T9sGCkVMD0fa3aBg== -"@react-native-firebase/analytics@^19.1.0": - version "19.1.0" - resolved "https://registry.npmjs.org/@react-native-firebase/analytics/-/analytics-19.1.0.tgz#2e6a76fcdac61b0b3fdc7a8d3721536621fc4810" - integrity sha512-p6ZwpbYJRN6yCd1i0gjICyUbtxaVGINMiL4OxTJxpWaKZFGn5+qLe+p948Zww7Wnw4IiuZoUhM7K9YXOjqFYqw== +"@react-native-firebase/analytics@19.0.0": + version "19.0.0" + resolved "https://registry.npmjs.org/@react-native-firebase/analytics/-/analytics-19.0.0.tgz#0194ee9ada85745ea118b5d6d22071b43e1ffcce" + integrity sha512-kjQwFgtzMwntoNtbQ1Vxr8ca7csCJThIsyeyJFnSq5uxJpU9FaIYp1zMLAgIC+EagcmwfnOT76DW7vkvSREZ4A== -"@react-native-firebase/app-check@^19.1.0": - version "19.1.0" - resolved "https://registry.npmjs.org/@react-native-firebase/app-check/-/app-check-19.1.0.tgz#558967f0a1ac78163ee3088ea7d86bb3e887ab98" - integrity sha512-izbIN9/skShGOBz1mikhMTYGoEso4onbsV9fFh2e6pFzeqsZZHmkBMytLfohiHquc5HbcZVBjGb6178AMbNQew== +"@react-native-firebase/app-check@19.0.0": + version "19.0.0" + resolved "https://registry.npmjs.org/@react-native-firebase/app-check/-/app-check-19.0.0.tgz#05d20d2bed79a186830f2cbbeb674f97b73f0a42" + integrity sha512-83yH70pEhqBALCKnyX+8GUHqc5lgQNF89Pz6KBUMOXIxFiQhLutJieHEWJJBaAzY1Sb1HFNZEHnPQmb/5BCVcA== -"@react-native-firebase/app@^19.1.0": - version "19.1.0" - resolved "https://registry.npmjs.org/@react-native-firebase/app/-/app-19.1.0.tgz#46eaff1d7303da9fa305a641d4c8623ba7b80435" - integrity sha512-xR4Ui0p6Xe2OXAs/Q4XN79nSqtupKu1ZwlGbYiUrvXsYwtwNlU1Pk8WvIhpXUP5/jXsSNZr8NeGrfCbb9m9NLQ== +"@react-native-firebase/app@19.0.0": + version "19.0.0" + resolved "https://registry.npmjs.org/@react-native-firebase/app/-/app-19.0.0.tgz#a0368a46d7a142512a47bb645f0e7e4a604c5724" + integrity sha512-QcT4fJQjoEmvmRKDiEsLf5MBAgejlBGCAuAgXT/TBxE8/lrLubhlHVokodQvP0trjcVoA8L+HSEQFX2eIc3BPA== dependencies: opencollective-postinstall "^2.0.3" superstruct "^0.6.2" -"@react-native-firebase/crashlytics@^19.1.0": - version "19.1.0" - resolved "https://registry.npmjs.org/@react-native-firebase/crashlytics/-/crashlytics-19.1.0.tgz#44aeecb7a20a82e312d9e0c5eaf9946d2ad2782c" - integrity sha512-q+rNjYWvmlR53y5x8+qe0fxozjFb6RyhM3Jwcc/WB4ZaUbiiBdtWf2DJ3VhGVgBq3qosTYjEGMVfifqD5o3LhA== +"@react-native-firebase/crashlytics@19.0.0": + version "19.0.0" + resolved "https://registry.npmjs.org/@react-native-firebase/crashlytics/-/crashlytics-19.0.0.tgz#bf04aab9af26bda1d34c70f44008ecacd1db12d4" + integrity sha512-A+kF4h9y0rdwWPeAgMAAwQDJq4WErUTusmQAGiduBP95bdqscJum3c6cB/FMf+8EibVowt66LQBZr7ncVxSeOw== dependencies: stacktrace-js "^2.0.2" -"@react-native-firebase/messaging@^19.1.0": - version "19.1.0" - resolved "https://registry.npmjs.org/@react-native-firebase/messaging/-/messaging-19.1.0.tgz#aee9489c894e48bb12bea8f076d9e3c485064af0" - integrity sha512-Jao9WdzNMdPQ7SmYAyFLnzFaU/ybbC3idrS4JGM0TByJzEKUv3jAG/lvXdGW6D8FfFWICiJ7oEFESdiBq1ugXw== +"@react-native-firebase/messaging@19.0.0": + version "19.0.0" + resolved "https://registry.npmjs.org/@react-native-firebase/messaging/-/messaging-19.0.0.tgz#f949dd100e1c391bc0949729e18a83ef03a94276" + integrity sha512-KeGQHLhnDThCZZqWm67Qf4NI7qMsUF9eNbrJ39OFeqbinJK2TSQcTZcRPNwfwreCZfkkBdeV3sk0YNPoddlx/w== -"@react-native-firebase/remote-config@^19.1.0": - version "19.1.0" - resolved "https://registry.npmjs.org/@react-native-firebase/remote-config/-/remote-config-19.1.0.tgz#8f9711c06cd5c1eb6b68fab3312d272d981d07e6" - integrity sha512-gB4kV7s4/8Xca3s0Hs0S0TiNlg6zQnSwdKF8hnQGudOAIi1EhiUrNGQ5ybtKkGIh6TX7LC8Ev9jUgkIaNvo8Zw== +"@react-native-firebase/remote-config@19.0.0": + version "19.0.0" + resolved "https://registry.npmjs.org/@react-native-firebase/remote-config/-/remote-config-19.0.0.tgz#1f2c8f129642768fe89b2ca84cdd4d45d289d09a" + integrity sha512-FQ+kTVp2e+JFejvDbORZZC/yFm2RSffXWb/wBaInPygrDv0BhlOHG2iRg1aBMbLw9LEHnUZqfbgKffhLqmGJKQ== "@react-native/assets-registry@^0.72.0": version "0.72.0" From 9097cc8bef06da3a9d5c0860f4b2a057460e236b Mon Sep 17 00:00:00 2001 From: nicolasburtey Date: Thu, 28 Mar 2024 12:38:28 +0000 Subject: [PATCH 15/44] feat: conversation screen (#2995) * chore: misc update * feat: conversation screen POC * chore: renaming conversation to chatbot --------- Co-authored-by: Nicolas Burtey --- .storybook/storybook.requires.js | 28 +- .storybook/storybook.tsx | 4 +- .../contact-modal/contact-modal.tsx | 15 + app/graphql/generated.gql | 31 ++ app/graphql/generated.ts | 149 +++++++ app/i18n/en/index.ts | 1 + app/i18n/i18n-types.ts | 8 + app/i18n/raw-i18n/source/en.json | 1 + app/navigation/root-navigator.tsx | 8 + app/navigation/stack-param-lists.ts | 1 + .../chatbot-screen/chatbot.stories.tsx | 83 ++++ app/screens/chatbot-screen/chatbot.tsx | 388 ++++++++++++++++++ .../conversion-success-screen.tsx | 28 +- .../settings-screen/settings-screen.tsx | 13 +- package.json | 4 +- supergraph.graphql | 32 +- yarn.lock | 71 +++- 17 files changed, 823 insertions(+), 42 deletions(-) create mode 100644 app/screens/chatbot-screen/chatbot.stories.tsx create mode 100644 app/screens/chatbot-screen/chatbot.tsx diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js index eca88496f5..fcec5fbc18 100644 --- a/.storybook/storybook.requires.js +++ b/.storybook/storybook.requires.js @@ -1,12 +1,17 @@ /* 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 = [ { @@ -16,18 +21,10 @@ 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 = () => { @@ -67,6 +64,7 @@ const getStories = () => { "./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/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"), @@ -103,7 +101,7 @@ const getStories = () => { "./app/screens/settings-screen/settings-screen.stories.tsx": require("../app/screens/settings-screen/settings-screen.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 f04db77a4c..937c40ea8b 100644 --- a/.storybook/storybook.tsx +++ b/.storybook/storybook.tsx @@ -20,9 +20,9 @@ import { NotificationsProvider } from "../app/components/notifications" RNBootSplash.hide({ fade: true }) const StorybookUI = getStorybookUI({ - enableWebsockets: true, // for @storybook/react-native-server + enableWebsockets: true, onDeviceUI: true, - initialSelection: { kind: "Notification Card UI", name: "Default" }, + initialSelection: { kind: "ChatBot Screen", name: "Default" }, shouldPersistSelection: false, }) diff --git a/app/components/contact-modal/contact-modal.tsx b/app/components/contact-modal/contact-modal.tsx index fe99b37791..14826c7396 100644 --- a/app/components/contact-modal/contact-modal.tsx +++ b/app/components/contact-modal/contact-modal.tsx @@ -4,7 +4,10 @@ import ReactNativeModal from "react-native-modal" import { CONTACT_EMAIL_ADDRESS, WHATSAPP_CONTACT_NUMBER } from "@app/config" import { useI18nContext } from "@app/i18n/i18n-react" +import { RootStackParamList } from "@app/navigation/stack-param-lists" import { openWhatsApp } from "@app/utils/external" +import { useNavigation } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" import { Icon, ListItem, makeStyles, useTheme } from "@rneui/themed" import TelegramOutline from "./telegram.svg" @@ -16,6 +19,7 @@ export const SupportChannels = { StatusPage: "statusPage", Mattermost: "mattermost", Faq: "faq", + Chatbot: "chatbot", } as const export type SupportChannels = (typeof SupportChannels)[keyof typeof SupportChannels] @@ -44,7 +48,18 @@ const ContactModal: React.FC = ({ theme: { colors }, } = useTheme() + const { navigate } = useNavigation>() + const contactOptionList = [ + { + id: SupportChannels.Chatbot, + name: LL.support.chatbot(), + icon: , + action: () => { + navigate("chatbot") + toggleModal() + }, + }, { id: SupportChannels.StatusPage, name: LL.support.statusPage(), diff --git a/app/graphql/generated.gql b/app/graphql/generated.gql index ac65f17080..3e56172354 100644 --- a/app/graphql/generated.gql +++ b/app/graphql/generated.gql @@ -496,6 +496,23 @@ mutation quizClaim($input: QuizClaimInput!) { } } +mutation supportChatMessageAdd($input: SupportChatMessageAddInput!) { + supportChatMessageAdd(input: $input) { + errors { + message + __typename + } + supportMessage { + id + message + role + timestamp + __typename + } + __typename + } +} + mutation userContactUpdateAlias($input: UserContactUpdateAliasInput!) { userContactUpdateAlias(input: $input) { errors { @@ -1458,6 +1475,20 @@ query settingsScreen { } } +query supportChat { + me { + id + supportChat { + id + message + role + timestamp + __typename + } + __typename + } +} + query supportedCountries { globals { supportedCountries { diff --git a/app/graphql/generated.ts b/app/graphql/generated.ts index bacf4787cc..95961716b9 100644 --- a/app/graphql/generated.ts +++ b/app/graphql/generated.ts @@ -1034,6 +1034,7 @@ export type Mutation = { readonly onChainUsdPaymentSendAsBtcDenominated: PaymentSendPayload; readonly onboardingFlowStart: OnboardingFlowStartResult; readonly quizClaim: QuizClaimPayload; + readonly supportChatMessageAdd: SupportChatMessageAddPayload; /** @deprecated will be moved to AccountContact */ readonly userContactUpdateAlias: UserContactUpdateAliasPayload; readonly userEmailDelete: UserEmailDeletePayload; @@ -1259,6 +1260,11 @@ export type MutationQuizClaimArgs = { }; +export type MutationSupportChatMessageAddArgs = { + input: SupportChatMessageAddInput; +}; + + export type MutationUserContactUpdateAliasArgs = { input: UserContactUpdateAliasInput; }; @@ -1808,6 +1814,30 @@ export type SuccessPayload = { readonly success?: Maybe; }; +export type SupportChatMessageAddInput = { + readonly message: Scalars['String']['input']; +}; + +export type SupportChatMessageAddPayload = { + readonly __typename: 'SupportChatMessageAddPayload'; + readonly errors: ReadonlyArray; + readonly supportMessage?: Maybe>>; +}; + +export type SupportMessage = { + readonly __typename: 'SupportMessage'; + readonly id: Scalars['ID']['output']; + readonly message: Scalars['String']['output']; + readonly role: SupportRole; + readonly timestamp: Scalars['Timestamp']['output']; +}; + +export const SupportRole = { + Assistant: 'ASSISTANT', + User: 'USER' +} as const; + +export type SupportRole = typeof SupportRole[keyof typeof SupportRole]; /** * Give details about an individual transaction. * Galoy have a smart routing system which is automatically @@ -1992,6 +2022,7 @@ export type User = { readonly language: Scalars['Language']['output']; /** Phone number with international calling code. */ readonly phone?: Maybe; + readonly supportChat: ReadonlyArray; /** Whether TOTP is enabled for this user. */ readonly totpEnabled: Scalars['Boolean']['output']; /** @@ -2418,6 +2449,18 @@ export type UserLogoutMutationVariables = Exact<{ export type UserLogoutMutation = { readonly __typename: 'Mutation', readonly userLogout: { readonly __typename: 'SuccessPayload', readonly success?: boolean | null } }; +export type SupportChatQueryVariables = Exact<{ [key: string]: never; }>; + + +export type SupportChatQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly supportChat: ReadonlyArray<{ readonly __typename: 'SupportMessage', readonly id: string, readonly message: string, readonly role: SupportRole, readonly timestamp: number }> } | null }; + +export type SupportChatMessageAddMutationVariables = Exact<{ + input: SupportChatMessageAddInput; +}>; + + +export type SupportChatMessageAddMutation = { readonly __typename: 'Mutation', readonly supportChatMessageAdd: { readonly __typename: 'SupportChatMessageAddPayload', readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly message: string }>, readonly supportMessage?: ReadonlyArray<{ readonly __typename: 'SupportMessage', readonly id: string, readonly message: string, readonly role: SupportRole, readonly timestamp: number } | null> | null } }; + export type ConversionScreenQueryVariables = Exact<{ [key: string]: never; }>; @@ -3926,6 +3969,87 @@ export function useUserLogoutMutation(baseOptions?: Apollo.MutationHookOptions; export type UserLogoutMutationResult = Apollo.MutationResult; export type UserLogoutMutationOptions = Apollo.BaseMutationOptions; +export const SupportChatDocument = gql` + query supportChat { + me { + id + supportChat { + id + message + role + timestamp + } + } +} + `; + +/** + * __useSupportChatQuery__ + * + * To run a query within a React component, call `useSupportChatQuery` and pass it any options that fit your needs. + * When your component renders, `useSupportChatQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useSupportChatQuery({ + * variables: { + * }, + * }); + */ +export function useSupportChatQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(SupportChatDocument, options); + } +export function useSupportChatLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(SupportChatDocument, options); + } +export type SupportChatQueryHookResult = ReturnType; +export type SupportChatLazyQueryHookResult = ReturnType; +export type SupportChatQueryResult = Apollo.QueryResult; +export const SupportChatMessageAddDocument = gql` + mutation supportChatMessageAdd($input: SupportChatMessageAddInput!) { + supportChatMessageAdd(input: $input) { + errors { + message + } + supportMessage { + id + message + role + timestamp + } + } +} + `; +export type SupportChatMessageAddMutationFn = Apollo.MutationFunction; + +/** + * __useSupportChatMessageAddMutation__ + * + * To run a mutation, you first call `useSupportChatMessageAddMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useSupportChatMessageAddMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [supportChatMessageAddMutation, { data, loading, error }] = useSupportChatMessageAddMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useSupportChatMessageAddMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(SupportChatMessageAddDocument, options); + } +export type SupportChatMessageAddMutationHookResult = ReturnType; +export type SupportChatMessageAddMutationResult = Apollo.MutationResult; +export type SupportChatMessageAddMutationOptions = Apollo.BaseMutationOptions; export const ConversionScreenDocument = gql` query conversionScreen { me { @@ -7403,6 +7527,10 @@ export type ResolversTypes = { SignedDisplayMajorAmount: ResolverTypeWrapper; Subscription: ResolverTypeWrapper<{}>; SuccessPayload: ResolverTypeWrapper; + SupportChatMessageAddInput: SupportChatMessageAddInput; + SupportChatMessageAddPayload: ResolverTypeWrapper; + SupportMessage: ResolverTypeWrapper; + SupportRole: SupportRole; Timestamp: ResolverTypeWrapper; TotpCode: ResolverTypeWrapper; TotpRegistrationId: ResolverTypeWrapper; @@ -7616,6 +7744,9 @@ export type ResolversParentTypes = { SignedDisplayMajorAmount: Scalars['SignedDisplayMajorAmount']['output']; Subscription: {}; SuccessPayload: SuccessPayload; + SupportChatMessageAddInput: SupportChatMessageAddInput; + SupportChatMessageAddPayload: SupportChatMessageAddPayload; + SupportMessage: SupportMessage; Timestamp: Scalars['Timestamp']['output']; TotpCode: Scalars['TotpCode']['output']; TotpRegistrationId: Scalars['TotpRegistrationId']['output']; @@ -8180,6 +8311,7 @@ export type MutationResolvers>; onboardingFlowStart?: Resolver>; quizClaim?: Resolver>; + supportChatMessageAdd?: Resolver>; userContactUpdateAlias?: Resolver>; userEmailDelete?: Resolver; userEmailRegistrationInitiate?: Resolver>; @@ -8487,6 +8619,20 @@ export type SuccessPayloadResolvers; }; +export type SupportChatMessageAddPayloadResolvers = { + errors?: Resolver, ParentType, ContextType>; + supportMessage?: Resolver>>, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type SupportMessageResolvers = { + id?: Resolver; + message?: Resolver; + role?: Resolver; + timestamp?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export interface TimestampScalarConfig extends GraphQLScalarTypeConfig { name: 'Timestamp'; } @@ -8568,6 +8714,7 @@ export type UserResolvers; language?: Resolver; phone?: Resolver, ParentType, ContextType>; + supportChat?: Resolver, ParentType, ContextType>; totpEnabled?: Resolver; username?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -8808,6 +8955,8 @@ export type Resolvers = { SignedDisplayMajorAmount?: GraphQLScalarType; Subscription?: SubscriptionResolvers; SuccessPayload?: SuccessPayloadResolvers; + SupportChatMessageAddPayload?: SupportChatMessageAddPayloadResolvers; + SupportMessage?: SupportMessageResolvers; Timestamp?: GraphQLScalarType; TotpCode?: GraphQLScalarType; TotpRegistrationId?: GraphQLScalarType; diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index b5a28379e8..93cc83e425 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -2705,6 +2705,7 @@ const en: BaseTranslation = { faq: "FAQ", enjoyingApp: "Enjoying the app?", statusPage: "Status Page", + chatbot: "Chatbot", telegram: "Telegram", mattermost: "Mattermost", thankYouText: "Thank you for the feedback, would you like to suggest an improvement?", diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index f317b00e07..f28e981699 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -8459,6 +8459,10 @@ type RootTranslation = { * S​t​a​t​u​s​ ​P​a​g​e */ statusPage: string + /** + * C​h​a​t​b​o​t + */ + chatbot: string /** * T​e​l​e​g​r​a​m */ @@ -17292,6 +17296,10 @@ export type TranslationFunctions = { * Status Page */ statusPage: () => LocalizedString + /** + * Chatbot + */ + chatbot: () => LocalizedString /** * Telegram */ diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index 9e5db6b1a0..0b3fd9c43e 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -2599,6 +2599,7 @@ "faq": "FAQ", "enjoyingApp": "Enjoying the app?", "statusPage": "Status Page", + "chatbot": "Chatbot", "telegram": "Telegram", "mattermost": "Mattermost", "thankYouText": "Thank you for the feedback, would you like to suggest an improvement?", diff --git a/app/navigation/root-navigator.tsx b/app/navigation/root-navigator.tsx index 25ef6617b5..3ea775ea70 100644 --- a/app/navigation/root-navigator.tsx +++ b/app/navigation/root-navigator.tsx @@ -5,6 +5,7 @@ import LearnIcon from "@app/assets/icons/learn.svg" import MapIcon from "@app/assets/icons/map.svg" import { useIsAuthed } from "@app/graphql/is-authed-context" import { useI18nContext } from "@app/i18n/i18n-react" +import { ChatBotScreen } from "@app/screens/chatbot-screen/chatbot" import { ConversionConfirmationScreen, ConversionDetailsScreen, @@ -409,6 +410,13 @@ export const RootStack = () => { title: LL.FullOnboarding.title(), }} /> + ) } diff --git a/app/navigation/stack-param-lists.ts b/app/navigation/stack-param-lists.ts index cfc73f367a..c7e6bacb96 100644 --- a/app/navigation/stack-param-lists.ts +++ b/app/navigation/stack-param-lists.ts @@ -96,6 +96,7 @@ export type RootStackParamList = { totpLoginValidate: { authToken: string } webView: { url: string; initialTitle?: string } fullOnboardingFlow: undefined + chatbot: undefined } export type PeopleStackParamList = { diff --git a/app/screens/chatbot-screen/chatbot.stories.tsx b/app/screens/chatbot-screen/chatbot.stories.tsx new file mode 100644 index 0000000000..07378226a5 --- /dev/null +++ b/app/screens/chatbot-screen/chatbot.stories.tsx @@ -0,0 +1,83 @@ +import * as React from "react" + +import { MockedProvider } from "@apollo/client/testing" +import { Meta } from "@storybook/react" + +import { StoryScreen } from "../../../.storybook/views" +import { createCache } from "../../graphql/cache" +import { SupportChatDocument } from "../../graphql/generated" +import { IsAuthedContextProvider } from "../../graphql/is-authed-context" +import { ChatBotScreen } from "./chatbot" + +const mockEmpty = [ + { + request: { + query: SupportChatDocument, + }, + result: { + data: { + me: { + __typename: "User", + id: "70df9822-efe0-419c-b864-c9efa99872ea", + supportChat: [], + }, + __typename: "Query", + }, + }, + }, +] + +const mockShort = [ + { + request: { + query: SupportChatDocument, + }, + result: { + data: { + me: { + __typename: "User", + id: "70df9822-efe0-419c-b864-c9efa99872ea", + supportChat: [ + { + __typename: "SupportChat", + id: "1", + message: "Hello", + role: "user", + timestamp: 1677184189, + }, + { + __typename: "SupportChat", + id: "2", + message: "Hi", + role: "assistant", + timestamp: 1677184190, + }, + ], + }, + __typename: "Query", + }, + }, + }, +] + +export default { + title: "ChatBot Screen", + component: ChatBotScreen, + decorators: [(Story) => {Story()}], +} as Meta + +export const Empty = () => ( + + + + + +) + +export const Default = () => ( + + + + + +) diff --git a/app/screens/chatbot-screen/chatbot.tsx b/app/screens/chatbot-screen/chatbot.tsx new file mode 100644 index 0000000000..685ac26488 --- /dev/null +++ b/app/screens/chatbot-screen/chatbot.tsx @@ -0,0 +1,388 @@ +import { useState, useRef } from "react" +import { + ActivityIndicator, + Alert, + FlatList, + Keyboard, + TouchableHighlight, + View, +} from "react-native" +import { TextInput } from "react-native-gesture-handler" +import Icon from "react-native-vector-icons/Ionicons" + +import { gql } from "@apollo/client" +import { Screen } from "@app/components/screen" +import { + SupportChatDocument, + SupportChatQuery, + SupportRole, + useSupportChatMessageAddMutation, + useSupportChatQuery, +} from "@app/graphql/generated" +import { useActionSheet } from "@expo/react-native-action-sheet" +import Clipboard from "@react-native-clipboard/clipboard" +import { Text, makeStyles, useTheme } from "@rneui/themed" +import Markdown from "@ronradtke/react-native-markdown-display" + +type SupportChatMe = SupportChatQuery["me"] +type SupportChatArray = NonNullable["supportChat"] +type SupportChatMessage = NonNullable[number] + +gql` + query supportChat { + me { + id + supportChat { + id + message + role + timestamp + } + } + } + + mutation supportChatMessageAdd($input: SupportChatMessageAddInput!) { + supportChatMessageAdd(input: $input) { + errors { + message + } + supportMessage { + id + message + role + timestamp + } + } + } +` + +export const ChatBotScreen = () => { + const styles = useStyles() + const { theme } = useTheme() + + const supportChatQuery = useSupportChatQuery({ fetchPolicy: "network-only" }) + const supportChat = supportChatQuery.data?.me?.supportChat ?? [] + + const flatListRef = useRef>(null) + + const [supportChatMessageAdd, { loading }] = useSupportChatMessageAddMutation() + + const [input, setInput] = useState("") + const [pendingInput, setPendingInput] = useState("") + + // TODO: replace with cache: + // ie: adding the SupportRole messaage to the supportChat cache + const supportChatMaybeInput = pendingInput + ? [ + ...supportChat, + { + role: SupportRole.User, + message: pendingInput, + timestamp: new Date().getTime(), + id: "pending", + __typename: "SupportMessage" as const, + }, + ] + : supportChat + + const { showActionSheetWithOptions } = useActionSheet() + + const copyToClipboard = (text: string) => { + Clipboard.setString(text) + } + + async function addMessageToThread() { + try { + if (!input) return + Keyboard.dismiss() + setPendingInput(input) + setInput("") + setTimeout(() => { + flatListRef.current?.scrollToEnd() + }, 1000) + await supportChatMessageAdd({ + variables: { input: { message: input } }, + update: (cache, { data }) => { + if (!data || !data.supportChatMessageAdd.supportMessage) return + + const newMessages = data.supportChatMessageAdd.supportMessage + + cache.writeQuery({ + query: SupportChatDocument, + data: { + me: { + newMessages, + }, + }, + }) + }, + }) + } catch (err) { + // TODO: translation + Alert.alert("Error", "An error occurred while sending the message") + console.log("error: ", err) + } finally { + setPendingInput("") + setTimeout(() => { + // TODO: improve code clarity + // supportChatMaybeInput should be the "cache" version prior to the table update + // and length will increase by 1 after the update + // so I think supportChatMaybeInput.length - 1 + // is actually mapping to supportChatMaybeInput.length - 2 + // when consideing the new message + const indexBeforeLast = supportChatMaybeInput.length - 1 + if (indexBeforeLast >= 0) { + flatListRef.current?.scrollToIndex({ index: indexBeforeLast, animated: true }) + } + }, 500) + } + } + + function onChangeInputText(v: string) { + setInput(v) + } + + // TODO: make it work + async function clearChat() { + if (loading) return + setInput("") + } + + // FIXME: this is not working + async function showClipboardActionsheet(text: string) { + console.log("showClipboardActionsheet", text) + + const cancelButtonIndex = 2 + showActionSheetWithOptions( + { + options: ["Copy to clipboard", "Clear chat", "cancel"], + cancelButtonIndex, + }, + (selectedIndex) => { + if (selectedIndex === Number(0)) { + copyToClipboard(text) + } + if (selectedIndex === 1) { + clearChat() + } + }, + ) + } + + function renderItem({ item, index }: { item: SupportChatMessage; index: number }) { + return ( + + {item.role === SupportRole.User && ( + + + {item.message} + + + )} + {item.role === SupportRole.Assistant && ( + + {item.message} + showClipboardActionsheet(item.message)} + underlayColor={"transparent"} + > + + + + + + )} + + ) + } + + return ( + + + {loading && ( + + + + )} + + + + + + + + + + ) +} + +const useStyles = makeStyles(({ colors }) => ({ + optionsIconWrapper: { + padding: 10, + paddingTop: 9, + alignItems: "flex-end", + }, + promptResponse: { + marginTop: 10, + }, + textStyleContainer: { + borderWidth: 1, + marginRight: 25, + borderColor: colors.grey3, + padding: 15, + paddingBottom: 6, + paddingTop: 5, + margin: 10, + marginTop: 0, + borderRadius: 13, + }, + textStyle: { + body: { + color: colors.grey0, + }, + paragraph: { + color: colors.grey0, + fontSize: 16, + }, + heading1: { + color: colors.grey0, + marginVertical: 5, + }, + heading2: { + marginTop: 20, + color: colors.grey0, + marginBottom: 5, + }, + heading3: { + marginTop: 20, + color: colors.grey0, + marginBottom: 5, + }, + heading4: { + marginTop: 10, + color: colors.grey0, + marginBottom: 5, + }, + heading5: { + marginTop: 10, + color: colors.grey0, + marginBottom: 5, + }, + heading6: { + color: colors.grey0, + marginVertical: 5, + }, + /* eslint-disable camelcase */ + list_item: { + marginTop: 7, + fontSize: 16, + }, + ordered_list_icon: { + color: colors.grey0, + fontSize: 16, + }, + bullet_list: { + marginTop: 10, + }, + ordered_list: { + marginTop: 7, + }, + bullet_list_icon: { + color: colors.grey0, + fontSize: 16, + }, + code_inline: { + color: colors.grey1, + backgroundColor: colors.grey4, + borderWidth: 1, + borderColor: "rgba(255, 255, 255, .1)", + }, + /* eslint-enable camelcase */ + hr: { + backgroundColor: "rgba(255, 255, 255, .1)", + height: 1, + }, + fence: { + marginVertical: 5, + padding: 10, + color: colors.grey1, + backgroundColor: colors.grey4, + borderColor: "rgba(255, 255, 255, .1)", + }, + tr: { + borderBottomWidth: 1, + borderColor: "rgba(255, 255, 255, .2)", + flexDirection: "row", + }, + table: { + marginTop: 7, + borderWidth: 1, + borderColor: "rgba(255, 255, 255, .2)", + borderRadius: 3, + }, + blockquote: { + backgroundColor: "#312e2e", + borderColor: "#CCC", + borderLeftWidth: 4, + marginLeft: 5, + paddingHorizontal: 5, + marginVertical: 5, + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + promptTextContainer: { + flex: 1, + alignItems: "flex-end", + marginRight: 15, + marginLeft: 24, + }, + promptTextWrapper: { + borderRadius: 8, + borderTopRightRadius: 0, + backgroundColor: colors._lightBlue, + }, + promptText: { + color: colors._white, + paddingVertical: 5, + paddingHorizontal: 9, + fontSize: 16, + }, + chatInputContainer: { + paddingTop: 5, + borderColor: colors.grey3, + width: "100%", + flexDirection: "row", + alignItems: "center", + paddingBottom: 5, + }, + input: { + flex: 1, + borderWidth: 1, + borderRadius: 99, + color: colors.grey0, + marginHorizontal: 10, + paddingVertical: 10, + paddingHorizontal: 21, + paddingRight: 39, + borderColor: colors.grey4, + }, + chatButton: { + marginRight: 14, + padding: 5, + borderRadius: 99, + backgroundColor: colors._lightBlue, + }, + activityIndicator: { padding: 10 }, +})) diff --git a/app/screens/conversion-flow/conversion-success-screen.tsx b/app/screens/conversion-flow/conversion-success-screen.tsx index c78d664e33..5accb73a9f 100644 --- a/app/screens/conversion-flow/conversion-success-screen.tsx +++ b/app/screens/conversion-flow/conversion-success-screen.tsx @@ -13,20 +13,6 @@ import { useNavigation } from "@react-navigation/native" import { StackNavigationProp } from "@react-navigation/stack" import { Text, makeStyles } from "@rneui/themed" -const useStyles = makeStyles(() => ({ - successText: { - marginTop: 20, - }, - container: { - flex: 1, - justifyContent: "center", - alignItems: "center", - }, - screen: { - flexGrow: 1, - }, -})) - export const ConversionSuccessScreen = () => { const styles = useStyles() @@ -55,3 +41,17 @@ export const ConversionSuccessScreen = () => { ) } + +const useStyles = makeStyles(() => ({ + successText: { + marginTop: 20, + }, + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + screen: { + flexGrow: 1, + }, +})) diff --git a/app/screens/settings-screen/settings-screen.tsx b/app/screens/settings-screen/settings-screen.tsx index fae26d1b35..8ccda7d17d 100644 --- a/app/screens/settings-screen/settings-screen.tsx +++ b/app/screens/settings-screen/settings-screen.tsx @@ -10,6 +10,7 @@ import ContactModal, { } from "@app/components/contact-modal/contact-modal" import { SetLightningAddressModal } from "@app/components/set-lightning-address-modal" import { + useBetaQuery, useSettingsScreenQuery, useWalletCsvTransactionsLazyQuery, } from "@app/graphql/generated" @@ -87,6 +88,9 @@ export const SettingsScreen: React.FC = () => { skip: !isAtLeastLevelZero, }) + const betaQuery = useBetaQuery() + const beta = betaQuery.data?.beta ?? false + const { displayCurrency } = useDisplayCurrency() const username = data?.me?.username ?? undefined @@ -295,12 +299,17 @@ export const SettingsScreen: React.FC = () => { icon: "help-circle-outline", id: "contact-us", action: () => { - setContactMethods([ + const contactMethods: SupportChannels[] = [ SupportChannels.Faq, SupportChannels.StatusPage, SupportChannels.Email, SupportChannels.WhatsApp, - ]) + ] + if (beta) { + contactMethods.push(SupportChannels.Chatbot) + } + + setContactMethods(contactMethods) toggleIsContactModalVisible() }, enabled: true, diff --git a/package.json b/package.json index e5821bbbdb..c4e47a3520 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,9 @@ "e2e:test": "detox test --configuration" }, "dependencies": { - "@apollo/client": "^3.9.6", + "@apollo/client": "^3.9.9", "@bitcoinerlab/secp256k1": "^1.0.5", + "@expo/react-native-action-sheet": "^4.0.1", "@formatjs/intl-getcanonicallocales": "^2.3.0", "@formatjs/intl-locale": "^3.4.3", "@formatjs/intl-relativetimeformat": "^11.2.10", @@ -78,6 +79,7 @@ "@react-navigation/stack": "^6.3.20", "@rneui/base": "^4.0.0-rc.8", "@rneui/themed": "^4.0.0-rc.8", + "@ronradtke/react-native-markdown-display": "^8.0.0", "apollo3-cache-persist": "^0.14.1", "axios": "^1.6.2", "bech32": "^2.0.0", diff --git a/supergraph.graphql b/supergraph.graphql index 5ff862d20e..06ee76bc32 100644 --- a/supergraph.graphql +++ b/supergraph.graphql @@ -1275,6 +1275,7 @@ type Mutation onChainUsdPaymentSend(input: OnChainUsdPaymentSendInput!): PaymentSendPayload! @join__field(graph: GALOY) onChainUsdPaymentSendAsBtcDenominated(input: OnChainUsdPaymentSendAsBtcDenominatedInput!): PaymentSendPayload! @join__field(graph: GALOY) quizClaim(input: QuizClaimInput!): QuizClaimPayload! @join__field(graph: GALOY) + supportChatMessageAdd(input: SupportChatMessageAddInput!): SupportChatMessageAddPayload! @join__field(graph: GALOY) userContactUpdateAlias(input: UserContactUpdateAliasInput!): UserContactUpdateAliasPayload! @join__field(graph: GALOY) @deprecated(reason: "will be moved to AccountContact") userEmailDelete: UserEmailDeletePayload! @join__field(graph: GALOY) userEmailRegistrationInitiate(input: UserEmailRegistrationInitiateInput!): UserEmailRegistrationInitiatePayload! @join__field(graph: GALOY) @@ -1803,6 +1804,35 @@ type SuccessPayload success: Boolean } +input SupportChatMessageAddInput + @join__type(graph: GALOY) +{ + message: String! +} + +type SupportChatMessageAddPayload + @join__type(graph: GALOY) +{ + errors: [Error!]! + supportMessage: [SupportMessage] +} + +type SupportMessage + @join__type(graph: GALOY) +{ + id: ID! + message: String! + role: SupportRole! + timestamp: Timestamp! +} + +enum SupportRole + @join__type(graph: GALOY) +{ + ASSISTANT @join__enumValue(graph: GALOY) + USER @join__enumValue(graph: GALOY) +} + """ Timestamp field, serialized as Unix time (the number of seconds since the Unix epoch) """ @@ -2014,6 +2044,7 @@ type User """Phone number with international calling code.""" phone: Phone + supportChat: [SupportMessage!]! """Whether TOTP is enabled for this user.""" totpEnabled: Boolean! @@ -2156,7 +2187,6 @@ enum UserNotificationCategory { CIRCLES @join__enumValue(graph: GALOY) PAYMENTS @join__enumValue(graph: GALOY) - BALANCE @join__enumValue(graph: GALOY) ADMIN_NOTIFICATION @join__enumValue(graph: GALOY) MARKETING @join__enumValue(graph: GALOY) PRICE @join__enumValue(graph: GALOY) diff --git a/yarn.lock b/yarn.lock index d08042b67f..6292f49377 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,10 +15,10 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@apollo/client@^3.9.6": - version "3.9.6" - resolved "https://registry.npmjs.org/@apollo/client/-/client-3.9.6.tgz#4292448d9b0a48244a60307b74d2fea7e83dfe70" - integrity sha512-+zpddcnZ4G2VZ0xIEnvIHFsLqeopNOnWuE2ZVbRuetLLpj/biLPNN719B/iofdd1/iHRclKfv0XaAmX6PBhYKA== +"@apollo/client@^3.9.9": + version "3.9.9" + resolved "https://registry.npmjs.org/@apollo/client/-/client-3.9.9.tgz#38f983a1ad24e2687abfced0a9c1c3bef8d32616" + integrity sha512-/sMecU/M0WK9knrguts1lSLV8xFKzIgOMVb4mi6MOxgJXjliDB8PvOtmXhTqh2cVMMR4TzXgOnb+af/690zlQw== dependencies: "@graphql-typed-document-node/core" "^3.1.1" "@wry/caches" "^1.0.0" @@ -1926,6 +1926,14 @@ base64-js "^1.2.3" xmlbuilder "^14.0.0" +"@expo/react-native-action-sheet@^4.0.1": + version "4.0.1" + resolved "https://registry.npmjs.org/@expo/react-native-action-sheet/-/react-native-action-sheet-4.0.1.tgz#fa78e55a87a741f235be2c4ce0b0ea2b6afd06cf" + integrity sha512-FwCFpjpB6yzrK8CIWssLlh/i6zQFytFBiJfNdz0mJ2ckU4hWk8SrjB37P0Q4kF7w0bnIdYzPgRbdPR9hnfFqPw== + dependencies: + "@types/hoist-non-react-statics" "^3.3.1" + hoist-non-react-statics "^3.3.0" + "@expo/sdk-runtime-versions@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz#d7ebd21b19f1c6b0395e50d78da4416941c57f7c" @@ -4354,6 +4362,16 @@ resolved "https://registry.npmjs.org/@rneui/themed/-/themed-4.0.0-rc.8.tgz#5c0e1aaa3d190ead88936693c5cef50ec404cd05" integrity sha512-8L/XOrL9OK/r+/iBLvx63TbIdZOXF8SIjN9eArMYm6kRbMr8m4BitXllDN8nBhBsSPNYvL6EAgjk+i2MfY4sBA== +"@ronradtke/react-native-markdown-display@^8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@ronradtke/react-native-markdown-display/-/react-native-markdown-display-8.0.0.tgz#ac2763290e19efed5d054fdb59c595af7b5edeea" + integrity sha512-i56CYXGXWDGN+dxF72dGiEn4Kld0L6c/JvcOrO4azX9YzVVl02F5EDgdb6fWUaiOl8gPqyUI7YIEU2OVGnIg6Q== + dependencies: + css-to-react-native "^3.2.0" + markdown-it "^13.0.1" + prop-types "^15.7.2" + react-native-fit-image "^1.5.5" + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.1" resolved "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301" @@ -9820,6 +9838,15 @@ css-to-react-native@^2.2.1: css-color-keywords "^1.0.0" postcss-value-parser "^3.3.0" +css-to-react-native@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + css-tree@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" @@ -10821,7 +10848,7 @@ entities@^2.0.0: resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^3.0.1: +entities@^3.0.1, entities@~3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== @@ -15949,6 +15976,13 @@ lines-and-columns@^2.0.3: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz#d00318855905d2660d8c0822e3f5a4715855fc42" integrity sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A== +linkify-it@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec" + integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw== + dependencies: + uc.micro "^1.0.1" + listenercount@~1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" @@ -16525,6 +16559,17 @@ markdown-extensions@^1.1.0: resolved "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3" integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q== +markdown-it@^13.0.1: + version "13.0.2" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz#1bc22e23379a6952e5d56217fbed881e0c94d536" + integrity sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w== + dependencies: + argparse "^2.0.1" + entities "~3.0.1" + linkify-it "^4.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + marky@^1.2.2: version "1.2.5" resolved "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" @@ -16634,7 +16679,7 @@ mdn-data@2.0.30: resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== -mdurl@^1.0.0: +mdurl@^1.0.0, mdurl@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== @@ -19071,7 +19116,7 @@ postcss-value-parser@^3.3.0: resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.2.0" resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== @@ -19797,6 +19842,13 @@ react-native-error-boundary@^1.2.3: version "6.0.0" resolved "git+https://github.com/hieuvp/react-native-fingerprint-scanner.git#9cecc0db326471c571553ea85f7c016fee2f803d" +react-native-fit-image@^1.5.5: + version "1.5.5" + resolved "https://registry.npmjs.org/react-native-fit-image/-/react-native-fit-image-1.5.5.tgz#c660d1ad74b9dcaa1cba27a0d9c23837e000226c" + integrity sha512-Wl3Vq2DQzxgsWKuW4USfck9zS7YzhvLNPpkwUUCF90bL32e1a0zOVQ3WsJILJOwzmPdHfzZmWasiiAUNBkhNkg== + dependencies: + prop-types "^15.5.10" + react-native-gesture-handler@^2.14.0: version "2.14.0" resolved "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.0.tgz#d6aec0d8b2e55c67557fd6107e828c0a1a248be8" @@ -23124,6 +23176,11 @@ ua-parser-js@^1.0.35: resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f" integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + uglify-es@^3.1.9: version "3.3.9" resolved "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" From 5fa5a6fc111972a0a6523efca394c0772efe9a8d Mon Sep 17 00:00:00 2001 From: "galoybot-app[bot]" <139244493+galoybot-app[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 12:39:02 +0000 Subject: [PATCH 16/44] chore: update build numbers for mobile v2.2.239 (#3141) Co-authored-by: CI Bot --- android/app/build.gradle | 4 ++-- ios/GaloyApp.xcodeproj/project.pbxproj | 8 ++++---- ios/GaloyApp/Info.plist | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 3f235ce462..9367c9a0b8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -88,8 +88,8 @@ android { applicationId "com.galoyapp" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 691 - versionName "2.2.235" + versionCode 696 + versionName "2.2.239" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } diff --git a/ios/GaloyApp.xcodeproj/project.pbxproj b/ios/GaloyApp.xcodeproj/project.pbxproj index 608d4fe5af..176beb82f2 100644 --- a/ios/GaloyApp.xcodeproj/project.pbxproj +++ b/ios/GaloyApp.xcodeproj/project.pbxproj @@ -453,7 +453,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 679; + CURRENT_PROJECT_VERSION = 683; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = AYPCPV46WW; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = AYPCPV46WW; @@ -463,7 +463,7 @@ INFOPLIST_KEY_CFBundleDisplayName = Blink; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = 2.2.235; + MARKETING_VERSION = 2.2.239; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -489,7 +489,7 @@ CODE_SIGN_ENTITLEMENTS = GaloyApp/GaloyApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 679; + CURRENT_PROJECT_VERSION = 683; DEVELOPMENT_TEAM = AYPCPV46WW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -497,7 +497,7 @@ INFOPLIST_KEY_CFBundleDisplayName = Blink; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = 2.2.235; + MARKETING_VERSION = 2.2.239; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/ios/GaloyApp/Info.plist b/ios/GaloyApp/Info.plist index 830e942f22..e488a4023c 100644 --- a/ios/GaloyApp/Info.plist +++ b/ios/GaloyApp/Info.plist @@ -66,7 +66,7 @@ CFBundleVersion - 679 + 683 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS From 4cc76b8cdb83c90e7245181b922b88e30cff7291 Mon Sep 17 00:00:00 2001 From: Prakhar Agarwal Date: Fri, 29 Mar 2024 03:48:42 +0530 Subject: [PATCH 17/44] fix: confirm payment slider bug in rtl layouts (#3143) Signed-off-by: Prakhar Agarwal --- app/assets/icons-redesign/arrow-left.svg | 4 ++ .../atomic/galoy-icon/galoy-icon.tsx | 2 + .../galoy-slider-button.tsx | 42 +++++++++++-------- 3 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 app/assets/icons-redesign/arrow-left.svg diff --git a/app/assets/icons-redesign/arrow-left.svg b/app/assets/icons-redesign/arrow-left.svg new file mode 100644 index 0000000000..1735aa607a --- /dev/null +++ b/app/assets/icons-redesign/arrow-left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/components/atomic/galoy-icon/galoy-icon.tsx b/app/components/atomic/galoy-icon/galoy-icon.tsx index ce34a87217..da4468bf3d 100644 --- a/app/components/atomic/galoy-icon/galoy-icon.tsx +++ b/app/components/atomic/galoy-icon/galoy-icon.tsx @@ -1,6 +1,7 @@ import React from "react" import { StyleProp, View, ViewStyle } from "react-native" +import ArrowLeft from "@app/assets/icons-redesign/arrow-left.svg" import ArrowRight from "@app/assets/icons-redesign/arrow-right.svg" import BackSpace from "@app/assets/icons-redesign/back-space.svg" import Bank from "@app/assets/icons-redesign/bank.svg" @@ -54,6 +55,7 @@ import { makeStyles, useTheme } from "@rneui/themed" export const icons = { "arrow-right": ArrowRight, + "arrow-left": ArrowLeft, "back-space": BackSpace, "bank": Bank, "bitcoin": Bitcoin, diff --git a/app/components/atomic/galoy-slider-button/galoy-slider-button.tsx b/app/components/atomic/galoy-slider-button/galoy-slider-button.tsx index 66f1dc97e6..545dffb356 100644 --- a/app/components/atomic/galoy-slider-button/galoy-slider-button.tsx +++ b/app/components/atomic/galoy-slider-button/galoy-slider-button.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from "react" -import { ActivityIndicator, Dimensions, View } from "react-native" +import { ActivityIndicator, Dimensions, View, I18nManager } from "react-native" import { PanGestureHandler } from "react-native-gesture-handler" import Animated, { Extrapolate, @@ -21,6 +21,7 @@ import { GaloyIcon } from "../galoy-icon" const BUTTON_WIDTH = Dimensions.get("screen").width - 40 const SWIPE_RANGE = BUTTON_WIDTH - 50 +const isRTL = I18nManager.isRTL type SwipeButtonPropsType = { onSwipe: () => void @@ -53,7 +54,7 @@ const GaloySliderButton = ({ const animatedGestureHandler = useAnimatedGestureHandler({ onActive: (e) => { - const newValue = e.translationX + const newValue = Math.abs(e.translationX) if (newValue >= 0 && newValue <= SWIPE_RANGE) { X.value = newValue @@ -70,34 +71,37 @@ const GaloySliderButton = ({ const AnimatedStyles = { swipeButton: useAnimatedStyle(() => { + const translateX = interpolate( + X.value, + [20, BUTTON_WIDTH], + [0, BUTTON_WIDTH], + Extrapolation.CLAMP, + ) + return { transform: [ { - translateX: interpolate( - X.value, - [20, BUTTON_WIDTH], - [0, BUTTON_WIDTH], - Extrapolation.CLAMP, - ), + translateX: isRTL ? -translateX : translateX, }, ], } - }, [X]), + }, [X, isRTL]), swipeText: useAnimatedStyle(() => { + const translateX = interpolate( + X.value, + [20, SWIPE_RANGE], + [0, BUTTON_WIDTH / 3], + Extrapolate.CLAMP, + ) return { opacity: interpolate(X.value, [0, BUTTON_WIDTH / 4], [1, 0], Extrapolate.CLAMP), transform: [ { - translateX: interpolate( - X.value, - [20, SWIPE_RANGE], - [0, BUTTON_WIDTH / 3], - Extrapolate.CLAMP, - ), + translateX: isRTL ? -translateX : translateX, }, ], } - }, [X]), + }, [X, isRTL]), } return ( @@ -116,7 +120,11 @@ const GaloySliderButton = ({ ]} exiting={FadeOut.duration(400)} > - + {isRTL ? ( + + ) : ( + + )} )} From dfbd075056e77bf1ce7b90a4c554139263ac858b Mon Sep 17 00:00:00 2001 From: Prakhar Agarwal Date: Sat, 30 Mar 2024 02:15:13 +0530 Subject: [PATCH 18/44] feat: Add link opening functionality in ScanningQRCodeScreen (#3145) * feat: Add link opening functionality in ScanningQRCodeScreen Signed-off-by: Prakhar Agarwal * fix: add translations Signed-off-by: Prakhar Agarwal * fix: handle different types of destinations Signed-off-by: Prakhar Agarwal --------- Signed-off-by: Prakhar Agarwal --- app/i18n/en/index.ts | 2 + app/i18n/i18n-types.ts | 16 ++++ app/i18n/raw-i18n/source/en.json | 2 + .../scanning-qrcode-screen.tsx | 91 ++++++++++++++++--- 4 files changed, 97 insertions(+), 14 deletions(-) diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index 93cc83e425..b4c8e44bc4 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -2173,6 +2173,8 @@ const en: BaseTranslation = { "We found:\n\n{found: string}\n\nThis is not a valid Bitcoin address or Lightning invoice", expiredContent: "We found:\n\n{found: string}\n\nThis invoice has expired", invalidTitle: "Invalid QR Code", + openLinkTitle: "Open Link", + confirmOpenLink: "Are you sure you want to open this link?", noQrCode: "We could not find a QR code in the image", title: "Scan QR", permissionCamera: "We need permission to use your camera", diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index f28e981699..e7cfb28398 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -6744,6 +6744,14 @@ type RootTranslation = { * I​n​v​a​l​i​d​ ​Q​R​ ​C​o​d​e */ invalidTitle: string + /** + * O​p​e​n​ ​L​i​n​k + */ + openLinkTitle: string + /** + * A​r​e​ ​y​o​u​ ​s​u​r​e​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​o​p​e​n​ ​t​h​i​s​ ​l​i​n​k​? + */ + confirmOpenLink: string /** * W​e​ ​c​o​u​l​d​ ​n​o​t​ ​f​i​n​d​ ​a​ ​Q​R​ ​c​o​d​e​ ​i​n​ ​t​h​e​ ​i​m​a​g​e */ @@ -15624,6 +15632,14 @@ export type TranslationFunctions = { * Invalid QR Code */ invalidTitle: () => LocalizedString + /** + * Open Link + */ + openLinkTitle: () => LocalizedString + /** + * Are you sure you want to open this link? + */ + confirmOpenLink: () => LocalizedString /** * We could not find a QR code in the image */ diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index 0b3fd9c43e..286d62f1bd 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -2123,6 +2123,8 @@ "invalidContent": "We found:\n\n{found: string}\n\nThis is not a valid Bitcoin address or Lightning invoice", "expiredContent": "We found:\n\n{found: string}\n\nThis invoice has expired", "invalidTitle": "Invalid QR Code", + "openLinkTitle": "Open Link", + "confirmOpenLink": "Are you sure you want to open this link?", "noQrCode": "We could not find a QR code in the image", "title": "Scan QR", "permissionCamera": "We need permission to use your camera", diff --git a/app/screens/send-bitcoin-screen/scanning-qrcode-screen.tsx b/app/screens/send-bitcoin-screen/scanning-qrcode-screen.tsx index 7d1e97a4a2..374b0d8689 100644 --- a/app/screens/send-bitcoin-screen/scanning-qrcode-screen.tsx +++ b/app/screens/send-bitcoin-screen/scanning-qrcode-screen.tsx @@ -103,6 +103,22 @@ export const ScanningQRCodeScreen: React.FC = () => { } }, [hasPermission, requestPermission]) + const loadInBrowser = (url: string) => { + Linking.openURL(url).catch((err) => Alert.alert(err.toString())) + } + + function isValidHttpUrl(input: string) { + let url + + try { + url = new URL(input) + } catch (_) { + return false + } + + return url.protocol === "http:" || url.protocol === "https:" + } + const processInvoice = React.useMemo(() => { return async (data: string | undefined) => { if (pending || !wallets || !bitcoinNetwork || !data) { @@ -143,23 +159,70 @@ export const ScanningQRCodeScreen: React.FC = () => { }) return } - - Alert.alert( - LL.ScanningQRCodeScreen.invalidTitle(), - destination.invalidReason === "InvoiceExpired" - ? LL.ScanningQRCodeScreen.expiredContent({ + switch (destination.invalidReason) { + case "InvoiceExpired": + Alert.alert( + LL.ScanningQRCodeScreen.invalidTitle(), + LL.ScanningQRCodeScreen.expiredContent({ found: data.toString(), - }) - : LL.ScanningQRCodeScreen.invalidContent({ + }), + [ + { + text: LL.common.ok(), + onPress: () => setPending(false), + }, + ], + ) + break + case "UnknownDestination": + if (isValidHttpUrl(data.toString())) { + Alert.alert( + LL.ScanningQRCodeScreen.openLinkTitle(), + `${data.toString()}\n\n${LL.ScanningQRCodeScreen.confirmOpenLink()}`, + [ + { + text: LL.common.No(), + onPress: () => setPending(false), + }, + { + text: LL.common.yes(), + onPress: () => { + setPending(false) + loadInBrowser(data.toString()) + }, + }, + ], + ) + } else { + Alert.alert( + LL.ScanningQRCodeScreen.invalidTitle(), + LL.ScanningQRCodeScreen.invalidContent({ + found: data.toString(), + }), + [ + { + text: LL.common.ok(), + onPress: () => setPending(false), + }, + ], + ) + } + break + default: + Alert.alert( + LL.ScanningQRCodeScreen.invalidTitle(), + LL.ScanningQRCodeScreen.invalidContent({ found: data.toString(), }), - [ - { - text: LL.common.ok(), - onPress: () => setPending(false), - }, - ], - ) + [ + { + text: LL.common.ok(), + onPress: () => setPending(false), + }, + ], + ) + break + } } catch (err: unknown) { if (err instanceof Error) { crashlytics().recordError(err) From bffc32aab88be1649c16901a20d2fd47fa3b888b Mon Sep 17 00:00:00 2001 From: nicolasburtey Date: Sat, 30 Mar 2024 17:19:55 +0000 Subject: [PATCH 19/44] chore: putting ErrorBoundary within NavigationContainer (#3147) --- app/app.tsx | 8 ++++---- app/components/contact-modal/contact-modal.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/app.tsx b/app/app.tsx index 83c21da768..fa0198a9dd 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -52,8 +52,8 @@ export const App = () => ( - - + + @@ -63,8 +63,8 @@ export const App = () => ( - - + + diff --git a/app/components/contact-modal/contact-modal.tsx b/app/components/contact-modal/contact-modal.tsx index 14826c7396..e2ce0a5977 100644 --- a/app/components/contact-modal/contact-modal.tsx +++ b/app/components/contact-modal/contact-modal.tsx @@ -48,7 +48,7 @@ const ContactModal: React.FC = ({ theme: { colors }, } = useTheme() - const { navigate } = useNavigation>() + const navigation = useNavigation>() const contactOptionList = [ { @@ -56,7 +56,7 @@ const ContactModal: React.FC = ({ name: LL.support.chatbot(), icon: , action: () => { - navigate("chatbot") + navigation.navigate("chatbot") toggleModal() }, }, From 9fef0575e5f3e4b65b3cb7dbaa98c22603d420bb Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:52:48 +0100 Subject: [PATCH 20/44] Updates for file app/i18n/raw-i18n/source/en.json (#3152) * chore: [skip CI] Translate en.json in ca 100% translated source file: 'en.json' on 'ca'. * chore: [skip CI] Translate en.json in es 100% translated source file: 'en.json' on 'es'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- app/i18n/raw-i18n/translations/ca.json | 17 ++++++++++------- app/i18n/raw-i18n/translations/es.json | 11 +++++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/i18n/raw-i18n/translations/ca.json b/app/i18n/raw-i18n/translations/ca.json index b0ffedfbf2..add9943446 100644 --- a/app/i18n/raw-i18n/translations/ca.json +++ b/app/i18n/raw-i18n/translations/ca.json @@ -2123,6 +2123,8 @@ "invalidContent": "Hem trobat:\n\n{found}\n\nAquesta no és una adreça Bitcoin vàlida o una factura Lightning", "expiredContent": "Hem trobat:\n\n{found}\n\nAquesta factura ha caducat", "invalidTitle": "Codi QR no vàlid", + "openLinkTitle": "Obrir l'enllaç", + "confirmOpenLink": "Estàs segur que vols obrir aquest enllaç?", "noQrCode": "No hem trobat cap codi QR a la imatge", "title": "Escanejar el codi QR", "permissionCamera": "Necessitem permis per utilitzar la càmera", @@ -2264,12 +2266,12 @@ "description": "Notificacions relacionades amb enviar i rebre pagaments" }, "Marketing": { - "title": "Features and updates", - "description": "Notifications about new features and updates." + "title": "Característiques i actualitzacions", + "description": "Notificacions de les noves característiques i actualitzacions." }, "Price": { - "title": "Price changes", - "description": "Notifications about the price of Bitcoin." + "title": "Canvis de preu", + "description": "Notificacions sobre el preu de Bitcoin." } } }, @@ -2599,6 +2601,7 @@ "faq": "FAQ", "enjoyingApp": "Gaudint de l'aplicació?", "statusPage": "Pàgina d'estat", + "chatbot": "Bot de xat", "telegram": "Telegram", "mattermost": "Mattermost", "thankYouText": "Gràcies pels comentaris, vols suggerir una millora?", @@ -2703,9 +2706,9 @@ "details": "Fes créixer el teu cercle interior en 3 persones i el teu cercle exterior en 3 persones per tenir l'oportunitat de guanyar 100 $!\n\nRecordatori: els teus cercles creixen quan envies a un nou usuari de Blink els seus primers sats.\n\nComparteix els teus cercles a les xarxes socials amb #blinkcircles per participar." }, "aprilChallenge": { - "title": "April Challenge!", - "description": "Grow your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet.", - "details": "During the halving month, expand your inner circle by 12 and your outer circle by 3 for a chance to win a Bitbox02 hardware wallet!\n\nReminder: your circles grow when you send a new Blink user their first sats.\n\nShare your circles on social with #blinkcircles to participate." + "title": "Repte d'abril!", + "description": "Augmenta el teu cercle interior en 12 persones i el teu cercle exterior en 3 persones per a poder guanyar una cartera Bitbox02.", + "details": "Durant el mes del \"halving\", amplia el teu cercle interior en 12 persones i el cercle exterior en 3 persones per poder guanyar una cartera de Bitbox02!\n\nRecorda: els teus circles creixen quan envies a un usuari nou de Blink els seus primers sats.\n\nComparteix els teus cercles a les xarxes socials amb l'etiqueta #blinkcircles per poder participar." } }, "FullOnboarding": { diff --git a/app/i18n/raw-i18n/translations/es.json b/app/i18n/raw-i18n/translations/es.json index 353bdad28d..815f6b6374 100644 --- a/app/i18n/raw-i18n/translations/es.json +++ b/app/i18n/raw-i18n/translations/es.json @@ -2123,6 +2123,8 @@ "invalidContent": "Encontramos lo siguiente:\n\n{found: string}\n\nEsto no es una dirección Bitcoin o factura Lightning válida", "expiredContent": "Hemos encontrado:\n\n{found}\n\nEsta factura ha caducado", "invalidTitle": "Código QR inválido", + "openLinkTitle": "Abrir enlace", + "confirmOpenLink": "Estás seguro de que quieres abrir este enlace?", "noQrCode": "No pudimos encontrar un código QR en la imagen", "title": "Escanear QR", "permissionCamera": "Necesitamos permiso para usar su cámara.", @@ -2264,12 +2266,12 @@ "description": "Notificaciones relacionadas con el envío y recepción de pagos." }, "Marketing": { - "title": "Features and updates", - "description": "Notifications about new features and updates." + "title": "Funciones y actualizaciones", + "description": "Notificaciones sobre nuevas funciones y actualizaciones." }, "Price": { - "title": "Price changes", - "description": "Notifications about the price of Bitcoin." + "title": "Cambios de precio", + "description": "Notificaciones sobre el precio de Bitcoin." } } }, @@ -2599,6 +2601,7 @@ "faq": "Preguntas Frecuentes - FAQ", "enjoyingApp": "¿Disfrutas de la aplicación?", "statusPage": "Página de Estado", + "chatbot": "Chatbot", "telegram": "Telegram", "mattermost": "Mattermost", "thankYouText": "Gracias por tus comentarios, ¿te gustaría sugerir una mejora?", From c5528289d768f5d5cc0990769590f52029b23e01 Mon Sep 17 00:00:00 2001 From: Sandipan Date: Fri, 5 Apr 2024 11:57:49 +0530 Subject: [PATCH 21/44] feat: settings screen fresh look (#2893) * feat: settings screen refactor feat: account banner on settings screen feat: blink address feat: account section ported chore: stuff feat: preferences chore: security and privacy feat: export tx feat: login from banner chore: empty right feat: need help fix: color profile for contact modal feat: community fix: tsx issue chore: small stuff feat: acc screen, auth email feat: auth flow working feat: tx limits feat: perfected delete flow chore: some more quirks feat: upgrade trial acc chore: upgrade for l1 feat: port complete fix: check-code fix: email issue feat: ln addr chore: addressing comments feat: totp feat: refetch totp and dont use caching fix: check-code fix: totp stuff Update app/components/modal-tooltip/modal-tooltip.tsx Co-authored-by: nicolasburtey Update app/i18n/en/index.ts Co-authored-by: nicolasburtey Update app/i18n/en/index.ts Co-authored-by: nicolasburtey Update app/screens/settings-screen/account/settings/phone.tsx Co-authored-by: nicolasburtey Update app/screens/settings-screen/settings/account-ln-address.tsx Co-authored-by: nicolasburtey fix: comments test test(e2e): updated settings screen (#2922) chore: adding storybook test chore: addressing comments fix: typo fix: typo fix: typo test: e2e test: e2e test: e2e test: e2e test: e2e fix: flickering issue save test: e2e chore: login method refresh in case test: group suffix removed * chore: LL utils - finding unused keys and removing Blink Address ones * chore: comments * chore: addressing most comments * chore: security screen * fix: rebased --- .github/workflows/e2e.yml | 2 - .storybook/storybook.requires.js | 1 + .../use-show-warning-secure-account.spec.tsx | 2 +- android/fastlane/Fastfile | 2 +- android/fastlane/README.md | 44 +- app/assets/icons/refresh.svg | 1 + .../galoy-icon-button/galoy-icon-button.tsx | 24 +- .../atomic/galoy-icon/galoy-icon.tsx | 2 + .../contact-modal/contact-modal.tsx | 6 +- app/components/custom-modal/custom-modal.tsx | 7 +- .../set-default-account-modal.tsx | 20 +- .../set-lightning-address-modal.tsx | 8 +- app/components/version/version.tsx | 1 - app/graphql/generated.gql | 97 ++- app/graphql/generated.ts | 249 +++---- app/graphql/network-error-component.tsx | 2 +- app/hooks/use-logout.ts | 7 +- app/i18n/en/index.ts | 34 +- app/i18n/i18n-types.ts | 259 +++++++- app/i18n/raw-i18n/source/en.json | 36 +- app/navigation/root-navigator.tsx | 2 +- .../address-component.tsx | 7 +- .../galoy-address-screen/address-screen.tsx | 3 +- app/screens/home-screen/home-screen.tsx | 11 +- app/screens/people-screen/tab-icon.tsx | 2 +- .../phone-registration-validation.tsx | 2 - .../account-screen.stories.tsx | 11 +- .../settings-screen/account-screen.tsx | 628 ------------------ .../account/account-delete-context.tsx | 54 ++ .../account/account-screen.tsx | 59 ++ .../settings-screen/account/banner.tsx | 70 ++ .../{account-id.tsx => account/id.tsx} | 64 +- app/screens/settings-screen/account/index.ts | 1 + .../account/login-methods-hook.ts | 22 + .../account/settings/danger-zone.tsx | 35 + .../account/settings/delete.tsx | 231 +++++++ .../account/settings/email.tsx | 185 ++++++ .../account/settings/logout.tsx | 68 ++ .../account/settings/phone.tsx | 94 +++ .../settings/upgrade-trial-account.tsx | 74 +++ .../account/settings/upgrade.tsx | 24 + .../show-warning-secure-account-hook.ts} | 4 +- app/screens/settings-screen/button.tsx | 41 ++ app/screens/settings-screen/group.tsx | 48 ++ .../settings-screen/{index.ts => index.tsx} | 0 app/screens/settings-screen/row.tsx | 117 ++++ .../settings-screen/security-screen.tsx | 73 +- app/screens/settings-screen/settings-row.tsx | 78 --- .../settings-screen/settings-screen.tsx | 398 ++--------- .../settings-screen/settings.stories.tsx | 36 + .../settings/account-default-wallet.tsx | 32 + .../settings/account-level.tsx | 23 + .../settings/account-ln-address.tsx | 59 ++ .../settings-screen/settings/account-pos.tsx | 45 ++ .../settings/account-tx-limits.tsx | 20 + .../settings/advanced-export-csv.tsx | 75 +++ .../settings/community-join.tsx | 49 ++ .../settings/community-need-help.tsx | 54 ++ .../settings/preferences-currency.tsx | 23 + .../settings/preferences-language.tsx | 31 + .../settings/preferences-theme.tsx | 33 + .../settings/sp-notifications.tsx | 19 + .../settings-screen/settings/sp-security.tsx | 30 + app/screens/settings-screen/totp.tsx | 103 +++ app/screens/settings-screen/types.d.ts | 17 - .../totp-registration-validate.tsx | 13 +- e2e/01-phone-flow-and-resets.e2e.spec.ts | 2 + e2e/02-email-flow.e2e.spec.ts | 12 +- e2e/03-intraledger-flow.e2e.spec.ts | 21 +- e2e/05-payments-receive-flow.e2e.spec.ts | 5 + e2e/06-other-tests.e2e.spec.ts | 3 +- e2e/detox/01-auth.test.ts | 6 +- e2e/detox/utils/common-flows.ts | 2 +- e2e/helpers.ts | 10 +- e2e/utils/controls.ts | 2 +- e2e/utils/use-cases.ts | 3 +- ios/Podfile.lock | 4 +- package.json | 7 +- utils/find-unused-ll-keys.js | 97 +++ yarn.lock | 8 +- 80 files changed, 2599 insertions(+), 1455 deletions(-) create mode 100644 app/assets/icons/refresh.svg delete mode 100644 app/screens/settings-screen/account-screen.tsx create mode 100644 app/screens/settings-screen/account/account-delete-context.tsx create mode 100644 app/screens/settings-screen/account/account-screen.tsx create mode 100644 app/screens/settings-screen/account/banner.tsx rename app/screens/settings-screen/{account-id.tsx => account/id.tsx} (60%) create mode 100644 app/screens/settings-screen/account/index.ts create mode 100644 app/screens/settings-screen/account/login-methods-hook.ts create mode 100644 app/screens/settings-screen/account/settings/danger-zone.tsx create mode 100644 app/screens/settings-screen/account/settings/delete.tsx create mode 100644 app/screens/settings-screen/account/settings/email.tsx create mode 100644 app/screens/settings-screen/account/settings/logout.tsx create mode 100644 app/screens/settings-screen/account/settings/phone.tsx create mode 100644 app/screens/settings-screen/account/settings/upgrade-trial-account.tsx create mode 100644 app/screens/settings-screen/account/settings/upgrade.tsx rename app/screens/settings-screen/{show-warning-secure-account.tsx => account/show-warning-secure-account-hook.ts} (93%) create mode 100644 app/screens/settings-screen/button.tsx create mode 100644 app/screens/settings-screen/group.tsx rename app/screens/settings-screen/{index.ts => index.tsx} (100%) create mode 100644 app/screens/settings-screen/row.tsx delete mode 100644 app/screens/settings-screen/settings-row.tsx create mode 100644 app/screens/settings-screen/settings.stories.tsx create mode 100644 app/screens/settings-screen/settings/account-default-wallet.tsx create mode 100644 app/screens/settings-screen/settings/account-level.tsx create mode 100644 app/screens/settings-screen/settings/account-ln-address.tsx create mode 100644 app/screens/settings-screen/settings/account-pos.tsx create mode 100644 app/screens/settings-screen/settings/account-tx-limits.tsx create mode 100644 app/screens/settings-screen/settings/advanced-export-csv.tsx create mode 100644 app/screens/settings-screen/settings/community-join.tsx create mode 100644 app/screens/settings-screen/settings/community-need-help.tsx create mode 100644 app/screens/settings-screen/settings/preferences-currency.tsx create mode 100644 app/screens/settings-screen/settings/preferences-language.tsx create mode 100644 app/screens/settings-screen/settings/preferences-theme.tsx create mode 100644 app/screens/settings-screen/settings/sp-notifications.tsx create mode 100644 app/screens/settings-screen/settings/sp-security.tsx create mode 100644 app/screens/settings-screen/totp.tsx delete mode 100644 app/screens/settings-screen/types.d.ts create mode 100644 utils/find-unused-ll-keys.js diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index c1eb042892..5d74847052 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -12,7 +12,6 @@ jobs: e2e-android: name: Android runs-on: self-hosted - timeout-minutes: 45 steps: - uses: actions/checkout@v2 @@ -108,7 +107,6 @@ jobs: e2e-ios: name: iOS runs-on: self-hosted - timeout-minutes: 45 steps: - uses: actions/checkout@v2 diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js index fcec5fbc18..4d69f5b734 100644 --- a/.storybook/storybook.requires.js +++ b/.storybook/storybook.requires.js @@ -99,6 +99,7 @@ const getStories = () => { "./app/screens/settings-screen/display-currency-screen.stories.tsx": require("../app/screens/settings-screen/display-currency-screen.stories.tsx"), "./app/screens/settings-screen/language-screen.stories.tsx": require("../app/screens/settings-screen/language-screen.stories.tsx"), "./app/screens/settings-screen/settings-screen.stories.tsx": require("../app/screens/settings-screen/settings-screen.stories.tsx"), + "./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"), } diff --git a/__tests__/hooks/use-show-warning-secure-account.spec.tsx b/__tests__/hooks/use-show-warning-secure-account.spec.tsx index 405975dc79..721a385d60 100644 --- a/__tests__/hooks/use-show-warning-secure-account.spec.tsx +++ b/__tests__/hooks/use-show-warning-secure-account.spec.tsx @@ -9,7 +9,7 @@ import { WarningSecureAccountDocument, } from "@app/graphql/generated" import { IsAuthedContextProvider } from "@app/graphql/is-authed-context" -import { useShowWarningSecureAccount } from "@app/screens/settings-screen/show-warning-secure-account" +import { useShowWarningSecureAccount } from "@app/screens/settings-screen/account/show-warning-secure-account-hook" import { renderHook } from "@testing-library/react-hooks" // FIXME: the mockPrice doesn't work as expect. diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index 5df48337c4..08adf22557 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -109,7 +109,7 @@ platform :android do file_path: ENV["GRADLE_APK_OUTPUT_PATH"] ) - max_retries = 3 + max_retries = 10 retries = 0 begin diff --git a/android/fastlane/README.md b/android/fastlane/README.md index 876fdb3e82..a7649666d0 100644 --- a/android/fastlane/README.md +++ b/android/fastlane/README.md @@ -15,53 +15,53 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do ## Android -### android test +### android build ```sh -[bundle exec] fastlane android test +[bundle exec] fastlane android build ``` -Runs all the tests +Build Releasable APK -### android build +### android play_store_upload ```sh -[bundle exec] fastlane android build +[bundle exec] fastlane android play_store_upload ``` -Build a new version of the app +Deploy a new version to the Google Play -### android beta +### android huawei_store_upload ```sh -[bundle exec] fastlane android beta +[bundle exec] fastlane android huawei_store_upload ``` -Deploy a new version to both Play Store and Huawei Store +Deploy the new version to Huawei App Gallery -### android play_store_release +### android promote_to_beta ```sh -[bundle exec] fastlane android play_store_release +[bundle exec] fastlane android promote_to_beta ``` -Deploy a new version to the Google Play +Promote Internal Testing build to Beta -### android huawei_release +### android promote_to_public ```sh -[bundle exec] fastlane android huawei_release +[bundle exec] fastlane android promote_to_public ``` -Deploy a new version to Huawei App Gallery +Promote Internal Testing build to Public -### android browserstack +### android public_phased_percent ```sh -[bundle exec] fastlane android browserstack +[bundle exec] fastlane android public_phased_percent ``` -End to end testing on browserstack +Phased Public Rollout ### android build_e2e @@ -71,6 +71,14 @@ End to end testing on browserstack Build for end to end testing +### android browserstack + +```sh +[bundle exec] fastlane android browserstack +``` + +End to end testing on browserstack + ---- This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. diff --git a/app/assets/icons/refresh.svg b/app/assets/icons/refresh.svg new file mode 100644 index 0000000000..b485ae0921 --- /dev/null +++ b/app/assets/icons/refresh.svg @@ -0,0 +1 @@ + diff --git a/app/components/atomic/galoy-icon-button/galoy-icon-button.tsx b/app/components/atomic/galoy-icon-button/galoy-icon-button.tsx index 5e89fec5fb..7a22cb7e50 100644 --- a/app/components/atomic/galoy-icon-button/galoy-icon-button.tsx +++ b/app/components/atomic/galoy-icon-button/galoy-icon-button.tsx @@ -15,6 +15,8 @@ export type GaloyIconButtonProps = { size: "small" | "medium" | "large" text?: string iconOnly?: boolean + color?: string + backgroundColor?: string } const sizeMapping = { @@ -29,6 +31,8 @@ export const GaloyIconButton = ({ text, iconOnly, disabled, + color, + backgroundColor, ...remainingProps }: GaloyIconButtonProps & PressableProps) => { const { @@ -55,36 +59,36 @@ export const GaloyIconButton = ({ case iconOnly && disabled: return { opacity: 0.7, - color: colors.primary, + color: color || colors.primary, backgroundColor: colors.transparent, } case iconOnly && pressed: return { opacity: 0.7, - color: colors.primary, - backgroundColor: colors.grey4, + color: color || colors.primary, + backgroundColor: backgroundColor || colors.grey4, } case iconOnly && !pressed: return { - color: colors.primary, + color: color || colors.primary, backgroundColor: colors.transparent, } case !iconOnly && disabled: return { opacity: 0.7, - color: colors.primary, - backgroundColor: colors.grey4, + color: color || colors.primary, + backgroundColor: backgroundColor || colors.grey4, } case !iconOnly && pressed: return { opacity: 0.7, - color: colors.primary, - backgroundColor: colors.grey4, + color: color || colors.primary, + backgroundColor: backgroundColor || colors.grey4, } case !iconOnly && !pressed: return { - color: colors.primary, - backgroundColor: colors.grey4, + color: color || colors.primary, + backgroundColor: backgroundColor || colors.grey4, } default: return {} diff --git a/app/components/atomic/galoy-icon/galoy-icon.tsx b/app/components/atomic/galoy-icon/galoy-icon.tsx index da4468bf3d..679baec772 100644 --- a/app/components/atomic/galoy-icon/galoy-icon.tsx +++ b/app/components/atomic/galoy-icon/galoy-icon.tsx @@ -51,6 +51,7 @@ import Warning from "@app/assets/icons-redesign/warning.svg" import Note from "@app/assets/icons/note.svg" import People from "@app/assets/icons/people.svg" import Rank from "@app/assets/icons/rank.svg" +import Refresh from "@app/assets/icons/refresh.svg" import { makeStyles, useTheme } from "@rneui/themed" export const icons = { @@ -104,6 +105,7 @@ export const icons = { "payment-pending": PaymentPending, "payment-error": PaymentError, "bell": Bell, + "refresh": Refresh, } as const export type IconNamesType = keyof typeof icons diff --git a/app/components/contact-modal/contact-modal.tsx b/app/components/contact-modal/contact-modal.tsx index e2ce0a5977..0f5c5c3dcc 100644 --- a/app/components/contact-modal/contact-modal.tsx +++ b/app/components/contact-modal/contact-modal.tsx @@ -123,8 +123,8 @@ const ContactModal: React.FC = ({ return ( @@ -163,7 +163,7 @@ const useStyles = makeStyles(({ colors }) => ({ marginHorizontal: 0, }, listItemContainer: { - backgroundColor: colors.white, + backgroundColor: colors.grey5, }, listItemTitle: { color: colors.black, diff --git a/app/components/custom-modal/custom-modal.tsx b/app/components/custom-modal/custom-modal.tsx index db54263058..fd3e9627ec 100644 --- a/app/components/custom-modal/custom-modal.tsx +++ b/app/components/custom-modal/custom-modal.tsx @@ -71,10 +71,11 @@ const CustomModal: React.FC = ({ return ( {showCloseIconButton && ( @@ -135,7 +136,7 @@ type UseStylesProps = { const useStyles = makeStyles(({ colors }, props: UseStylesProps) => ({ container: { - backgroundColor: colors.white, + backgroundColor: colors.grey5, maxHeight: "95%", minHeight: props.minHeight || "auto", borderRadius: 16, diff --git a/app/components/set-default-account-modal/set-default-account-modal.tsx b/app/components/set-default-account-modal/set-default-account-modal.tsx index c13d089c26..e1bbb09ebf 100644 --- a/app/components/set-default-account-modal/set-default-account-modal.tsx +++ b/app/components/set-default-account-modal/set-default-account-modal.tsx @@ -19,7 +19,7 @@ import { StackNavigationProp } from "@react-navigation/stack" import { makeStyles, Text, useTheme } from "@rneui/themed" import { GaloyCurrencyBubble } from "../atomic/galoy-currency-bubble" -import { GaloyIcon } from "../atomic/galoy-icon" +import { GaloyIconButton } from "../atomic/galoy-icon-button" gql` query setDefaultAccountModal { @@ -148,15 +148,21 @@ export const SetDefaultAccountModalUI: React.FC = return ( - - - + ({ flexDirection: "column", }, container: { - backgroundColor: colors.white, + backgroundColor: colors.grey5, maxHeight: "80%", minHeight: "auto", borderRadius: 16, diff --git a/app/components/set-lightning-address-modal/set-lightning-address-modal.tsx b/app/components/set-lightning-address-modal/set-lightning-address-modal.tsx index 82c40fa94b..634ebc2f99 100644 --- a/app/components/set-lightning-address-modal/set-lightning-address-modal.tsx +++ b/app/components/set-lightning-address-modal/set-lightning-address-modal.tsx @@ -175,11 +175,11 @@ export const SetLightningAddressModalUI = ({ return ( ({ paddingHorizontal: 12, borderRadius: 8, minHeight: 60, - backgroundColor: colors.grey5, + backgroundColor: colors.grey4, alignItems: "center", justifyContent: "space-between", }, diff --git a/app/components/version/version.tsx b/app/components/version/version.tsx index 577d4338fb..e6ef286977 100644 --- a/app/components/version/version.tsx +++ b/app/components/version/version.tsx @@ -13,7 +13,6 @@ import { testProps } from "../../utils/testProps" const useStyles = makeStyles(({ colors }) => ({ version: { color: colors.grey0, - fontSize: 18, marginTop: 18, textAlign: "center", }, diff --git a/app/graphql/generated.gql b/app/graphql/generated.gql index 3e56172354..c3fdede831 100644 --- a/app/graphql/generated.gql +++ b/app/graphql/generated.gql @@ -798,6 +798,45 @@ query ContactsCard { } } +query ExportCsvSetting($walletIds: [WalletId!]!) { + me { + id + defaultAccount { + id + csvTransactions(walletIds: $walletIds) + __typename + } + __typename + } +} + +query SettingsScreen { + me { + id + username + language + defaultAccount { + id + defaultWalletId + wallets { + id + balance + walletCurrency + __typename + } + __typename + } + totpEnabled + phone + email { + address + verified + __typename + } + __typename + } +} + query accountDefaultWallet($walletCurrency: WalletCurrency, $username: Username!) { accountDefaultWallet(walletCurrency: $walletCurrency, username: $username) { id @@ -837,31 +876,6 @@ query accountLimits { } } -query accountScreen { - me { - id - phone - totpEnabled - email { - address - verified - __typename - } - defaultAccount { - id - level - wallets { - id - balance - walletCurrency - __typename - } - __typename - } - __typename - } -} - query addressScreen { me { id @@ -1454,27 +1468,6 @@ query setDefaultWalletScreen { } } -query settingsScreen { - me { - id - phone - username - language - defaultAccount { - id - defaultWalletId - wallets { - id - balance - walletCurrency - __typename - } - __typename - } - __typename - } -} - query supportChat { me { id @@ -1541,18 +1534,6 @@ query transactionListForDefaultAccount($first: Int, $after: String, $last: Int, } } -query walletCSVTransactions($walletIds: [WalletId!]!) { - me { - id - defaultAccount { - id - csvTransactions(walletIds: $walletIds) - __typename - } - __typename - } -} - query walletOverviewScreen { me { id diff --git a/app/graphql/generated.ts b/app/graphql/generated.ts index 95961716b9..75d0863796 100644 --- a/app/graphql/generated.ts +++ b/app/graphql/generated.ts @@ -2808,11 +2808,6 @@ export type OnChainUsdPaymentSendAsBtcDenominatedMutationVariables = Exact<{ export type OnChainUsdPaymentSendAsBtcDenominatedMutation = { readonly __typename: 'Mutation', readonly onChainUsdPaymentSendAsBtcDenominated: { readonly __typename: 'PaymentSendPayload', readonly status?: PaymentSendResult | null, readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly message: string }> } }; -export type AccountScreenQueryVariables = Exact<{ [key: string]: never; }>; - - -export type AccountScreenQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly phone?: string | null, readonly totpEnabled: boolean, readonly email?: { readonly __typename: 'Email', readonly address?: string | null, readonly verified?: boolean | null } | null, readonly defaultAccount: { readonly __typename: 'ConsumerAccount', readonly id: string, readonly level: AccountLevel, readonly wallets: ReadonlyArray<{ readonly __typename: 'BTCWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency } | { readonly __typename: 'UsdWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency }> } } | null }; - export type AccountDeleteMutationVariables = Exact<{ [key: string]: never; }>; @@ -2828,10 +2823,10 @@ export type UserPhoneDeleteMutationVariables = Exact<{ [key: string]: never; }>; export type UserPhoneDeleteMutation = { readonly __typename: 'Mutation', readonly userPhoneDelete: { readonly __typename: 'UserPhoneDeletePayload', readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly message: string }>, readonly me?: { readonly __typename: 'User', readonly id: string, readonly phone?: string | null, readonly totpEnabled: boolean, readonly email?: { readonly __typename: 'Email', readonly address?: string | null, readonly verified?: boolean | null } | null } | null } }; -export type UserTotpDeleteMutationVariables = Exact<{ [key: string]: never; }>; +export type WarningSecureAccountQueryVariables = Exact<{ [key: string]: never; }>; -export type UserTotpDeleteMutation = { readonly __typename: 'Mutation', readonly userTotpDelete: { readonly __typename: 'UserTotpDeletePayload', readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly message: string }>, readonly me?: { readonly __typename: 'User', readonly id: string, readonly phone?: string | null, readonly totpEnabled: boolean, readonly email?: { readonly __typename: 'Email', readonly address?: string | null, readonly verified?: boolean | null } | null } | null } }; +export type WarningSecureAccountQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly defaultAccount: { readonly __typename: 'ConsumerAccount', readonly level: AccountLevel, readonly id: string, readonly wallets: ReadonlyArray<{ readonly __typename: 'BTCWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency } | { readonly __typename: 'UsdWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency }> } } | null }; export type AccountUpdateDefaultWalletIdMutationVariables = Exact<{ input: AccountUpdateDefaultWalletIdInput; @@ -2897,22 +2892,22 @@ export type AccountDisableNotificationCategoryMutationVariables = Exact<{ export type AccountDisableNotificationCategoryMutation = { readonly __typename: 'Mutation', readonly accountDisableNotificationCategory: { readonly __typename: 'AccountUpdateNotificationSettingsPayload', readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly message: string }>, readonly account?: { readonly __typename: 'ConsumerAccount', readonly id: string, readonly notificationSettings: { readonly __typename: 'NotificationSettings', readonly push: { readonly __typename: 'NotificationChannelSettings', readonly enabled: boolean, readonly disabledCategories: ReadonlyArray } } } | null } }; -export type WalletCsvTransactionsQueryVariables = Exact<{ - walletIds: ReadonlyArray | Scalars['WalletId']['input']; -}>; +export type SettingsScreenQueryVariables = Exact<{ [key: string]: never; }>; -export type WalletCsvTransactionsQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly defaultAccount: { readonly __typename: 'ConsumerAccount', readonly id: string, readonly csvTransactions: string } } | null }; +export type SettingsScreenQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly username?: string | null, readonly language: string, readonly totpEnabled: boolean, readonly phone?: string | null, readonly defaultAccount: { readonly __typename: 'ConsumerAccount', readonly id: string, readonly defaultWalletId: string, readonly wallets: ReadonlyArray<{ readonly __typename: 'BTCWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency } | { readonly __typename: 'UsdWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency }> }, readonly email?: { readonly __typename: 'Email', readonly address?: string | null, readonly verified?: boolean | null } | null } | null }; -export type SettingsScreenQueryVariables = Exact<{ [key: string]: never; }>; +export type ExportCsvSettingQueryVariables = Exact<{ + walletIds: ReadonlyArray | Scalars['WalletId']['input']; +}>; -export type SettingsScreenQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly phone?: string | null, readonly username?: string | null, readonly language: string, readonly defaultAccount: { readonly __typename: 'ConsumerAccount', readonly id: string, readonly defaultWalletId: string, readonly wallets: ReadonlyArray<{ readonly __typename: 'BTCWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency } | { readonly __typename: 'UsdWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency }> } } | null }; +export type ExportCsvSettingQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly defaultAccount: { readonly __typename: 'ConsumerAccount', readonly id: string, readonly csvTransactions: string } } | null }; -export type WarningSecureAccountQueryVariables = Exact<{ [key: string]: never; }>; +export type UserTotpDeleteMutationVariables = Exact<{ [key: string]: never; }>; -export type WarningSecureAccountQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly defaultAccount: { readonly __typename: 'ConsumerAccount', readonly level: AccountLevel, readonly id: string, readonly wallets: ReadonlyArray<{ readonly __typename: 'BTCWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency } | { readonly __typename: 'UsdWallet', readonly id: string, readonly balance: number, readonly walletCurrency: WalletCurrency }> } } | null }; +export type UserTotpDeleteMutation = { readonly __typename: 'Mutation', readonly userTotpDelete: { readonly __typename: 'UserTotpDeletePayload', readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly message: string }>, readonly me?: { readonly __typename: 'User', readonly id: string, readonly phone?: string | null, readonly totpEnabled: boolean, readonly email?: { readonly __typename: 'Email', readonly address?: string | null, readonly verified?: boolean | null } | null } | null } }; export type AccountLimitsQueryVariables = Exact<{ [key: string]: never; }>; @@ -6216,55 +6211,6 @@ export function useOnChainUsdPaymentSendAsBtcDenominatedMutation(baseOptions?: A export type OnChainUsdPaymentSendAsBtcDenominatedMutationHookResult = ReturnType; export type OnChainUsdPaymentSendAsBtcDenominatedMutationResult = Apollo.MutationResult; export type OnChainUsdPaymentSendAsBtcDenominatedMutationOptions = Apollo.BaseMutationOptions; -export const AccountScreenDocument = gql` - query accountScreen { - me { - id - phone - totpEnabled - email { - address - verified - } - defaultAccount { - id - level - wallets { - id - balance - walletCurrency - } - } - } -} - `; - -/** - * __useAccountScreenQuery__ - * - * To run a query within a React component, call `useAccountScreenQuery` and pass it any options that fit your needs. - * When your component renders, `useAccountScreenQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useAccountScreenQuery({ - * variables: { - * }, - * }); - */ -export function useAccountScreenQuery(baseOptions?: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(AccountScreenDocument, options); - } -export function useAccountScreenLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(AccountScreenDocument, options); - } -export type AccountScreenQueryHookResult = ReturnType; -export type AccountScreenLazyQueryHookResult = ReturnType; -export type AccountScreenQueryResult = Apollo.QueryResult; export const AccountDeleteDocument = gql` mutation accountDelete { accountDelete { @@ -6386,49 +6332,49 @@ export function useUserPhoneDeleteMutation(baseOptions?: Apollo.MutationHookOpti export type UserPhoneDeleteMutationHookResult = ReturnType; export type UserPhoneDeleteMutationResult = Apollo.MutationResult; export type UserPhoneDeleteMutationOptions = Apollo.BaseMutationOptions; -export const UserTotpDeleteDocument = gql` - mutation userTotpDelete { - userTotpDelete { - errors { - message - } - me { +export const WarningSecureAccountDocument = gql` + query warningSecureAccount { + me { + id + defaultAccount { + level id - phone - totpEnabled - email { - address - verified + wallets { + id + balance + walletCurrency } } } } `; -export type UserTotpDeleteMutationFn = Apollo.MutationFunction; /** - * __useUserTotpDeleteMutation__ + * __useWarningSecureAccountQuery__ * - * To run a mutation, you first call `useUserTotpDeleteMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUserTotpDeleteMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution + * To run a query within a React component, call `useWarningSecureAccountQuery` and pass it any options that fit your needs. + * When your component renders, `useWarningSecureAccountQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const [userTotpDeleteMutation, { data, loading, error }] = useUserTotpDeleteMutation({ + * const { data, loading, error } = useWarningSecureAccountQuery({ * variables: { * }, * }); */ -export function useUserTotpDeleteMutation(baseOptions?: Apollo.MutationHookOptions) { +export function useWarningSecureAccountQuery(baseOptions?: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(UserTotpDeleteDocument, options); + return Apollo.useQuery(WarningSecureAccountDocument, options); } -export type UserTotpDeleteMutationHookResult = ReturnType; -export type UserTotpDeleteMutationResult = Apollo.MutationResult; -export type UserTotpDeleteMutationOptions = Apollo.BaseMutationOptions; +export function useWarningSecureAccountLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(WarningSecureAccountDocument, options); + } +export type WarningSecureAccountQueryHookResult = ReturnType; +export type WarningSecureAccountLazyQueryHookResult = ReturnType; +export type WarningSecureAccountQueryResult = Apollo.QueryResult; export const AccountUpdateDefaultWalletIdDocument = gql` mutation accountUpdateDefaultWalletId($input: AccountUpdateDefaultWalletIdInput!) { accountUpdateDefaultWalletId(input: $input) { @@ -6843,50 +6789,10 @@ export function useAccountDisableNotificationCategoryMutation(baseOptions?: Apol export type AccountDisableNotificationCategoryMutationHookResult = ReturnType; export type AccountDisableNotificationCategoryMutationResult = Apollo.MutationResult; export type AccountDisableNotificationCategoryMutationOptions = Apollo.BaseMutationOptions; -export const WalletCsvTransactionsDocument = gql` - query walletCSVTransactions($walletIds: [WalletId!]!) { - me { - id - defaultAccount { - id - csvTransactions(walletIds: $walletIds) - } - } -} - `; - -/** - * __useWalletCsvTransactionsQuery__ - * - * To run a query within a React component, call `useWalletCsvTransactionsQuery` and pass it any options that fit your needs. - * When your component renders, `useWalletCsvTransactionsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useWalletCsvTransactionsQuery({ - * variables: { - * walletIds: // value for 'walletIds' - * }, - * }); - */ -export function useWalletCsvTransactionsQuery(baseOptions: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(WalletCsvTransactionsDocument, options); - } -export function useWalletCsvTransactionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(WalletCsvTransactionsDocument, options); - } -export type WalletCsvTransactionsQueryHookResult = ReturnType; -export type WalletCsvTransactionsLazyQueryHookResult = ReturnType; -export type WalletCsvTransactionsQueryResult = Apollo.QueryResult; export const SettingsScreenDocument = gql` - query settingsScreen { + query SettingsScreen { me { id - phone username language defaultAccount { @@ -6898,6 +6804,12 @@ export const SettingsScreenDocument = gql` walletCurrency } } + totpEnabled + phone + email { + address + verified + } } } `; @@ -6928,49 +6840,88 @@ export function useSettingsScreenLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt export type SettingsScreenQueryHookResult = ReturnType; export type SettingsScreenLazyQueryHookResult = ReturnType; export type SettingsScreenQueryResult = Apollo.QueryResult; -export const WarningSecureAccountDocument = gql` - query warningSecureAccount { +export const ExportCsvSettingDocument = gql` + query ExportCsvSetting($walletIds: [WalletId!]!) { me { id defaultAccount { - level id - wallets { - id - balance - walletCurrency - } + csvTransactions(walletIds: $walletIds) } } } `; /** - * __useWarningSecureAccountQuery__ + * __useExportCsvSettingQuery__ * - * To run a query within a React component, call `useWarningSecureAccountQuery` and pass it any options that fit your needs. - * When your component renders, `useWarningSecureAccountQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useExportCsvSettingQuery` and pass it any options that fit your needs. + * When your component renders, `useExportCsvSettingQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const { data, loading, error } = useWarningSecureAccountQuery({ + * const { data, loading, error } = useExportCsvSettingQuery({ * variables: { + * walletIds: // value for 'walletIds' * }, * }); */ -export function useWarningSecureAccountQuery(baseOptions?: Apollo.QueryHookOptions) { +export function useExportCsvSettingQuery(baseOptions: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(WarningSecureAccountDocument, options); + return Apollo.useQuery(ExportCsvSettingDocument, options); } -export function useWarningSecureAccountLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useExportCsvSettingLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(WarningSecureAccountDocument, options); + return Apollo.useLazyQuery(ExportCsvSettingDocument, options); } -export type WarningSecureAccountQueryHookResult = ReturnType; -export type WarningSecureAccountLazyQueryHookResult = ReturnType; -export type WarningSecureAccountQueryResult = Apollo.QueryResult; +export type ExportCsvSettingQueryHookResult = ReturnType; +export type ExportCsvSettingLazyQueryHookResult = ReturnType; +export type ExportCsvSettingQueryResult = Apollo.QueryResult; +export const UserTotpDeleteDocument = gql` + mutation userTotpDelete { + userTotpDelete { + errors { + message + } + me { + id + phone + totpEnabled + email { + address + verified + } + } + } +} + `; +export type UserTotpDeleteMutationFn = Apollo.MutationFunction; + +/** + * __useUserTotpDeleteMutation__ + * + * To run a mutation, you first call `useUserTotpDeleteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUserTotpDeleteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [userTotpDeleteMutation, { data, loading, error }] = useUserTotpDeleteMutation({ + * variables: { + * }, + * }); + */ +export function useUserTotpDeleteMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UserTotpDeleteDocument, options); + } +export type UserTotpDeleteMutationHookResult = ReturnType; +export type UserTotpDeleteMutationResult = Apollo.MutationResult; +export type UserTotpDeleteMutationOptions = Apollo.BaseMutationOptions; export const AccountLimitsDocument = gql` query accountLimits { me { diff --git a/app/graphql/network-error-component.tsx b/app/graphql/network-error-component.tsx index ca4aeaa8b4..511c549939 100644 --- a/app/graphql/network-error-component.tsx +++ b/app/graphql/network-error-component.tsx @@ -60,7 +60,7 @@ export const NetworkErrorComponent: React.FC = () => { onPress: () => { setShowedAlert(false) navigation.dispatch(() => { - const routes = [{ name: "Primary" }] + const routes = [{ name: "getStarted" }] return CommonActions.reset({ routes, index: routes.length - 1, diff --git a/app/hooks/use-logout.ts b/app/hooks/use-logout.ts index 76f548a41b..a3a9c683ef 100644 --- a/app/hooks/use-logout.ts +++ b/app/hooks/use-logout.ts @@ -37,9 +37,6 @@ const useLogout = () => { await KeyStoreWrapper.removeSecurePersistentState() logLogout() - if (stateToDefault) { - resetState() - } await Promise.race([ userLogoutMutation({ variables: { input: { deviceToken } } }), @@ -56,6 +53,10 @@ const useLogout = () => { crashlytics().recordError(err) console.debug({ err }, `error logout`) } + } finally { + if (stateToDefault) { + resetState() + } } }, [resetState, userLogoutMutation], diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index b4c8e44bc4..72a52a520d 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -6,7 +6,7 @@ const en: BaseTranslation = { GaloyAddressScreen: { title: "Receive payment by using:", buttonTitle: "Set your address", - yourAddress: "Your {bankName: string} address", + yourLightningAddress: "Your Lightning address", notAbleToChange: "You won't be able to change your {bankName: string} address after it's set.", addressNotAvailable: "This {bankName: string} address is already taken.", @@ -14,7 +14,7 @@ const en: BaseTranslation = { merchantTitle: "For merchants", yourCashRegister: "Your Lightning Cash Register", yourPaycode: "Your Paycode", - copiedAddressToClipboard: "Copied {bankName: string} address to clipboard", + copiedLightningAddressToClipboard: "Copied Lightning address to clipboard", copiedPaycodeToClipboard: "Copied Paycode to clipboard", copiedCashRegisterLinkToClipboard: "Copied Cash Register Link to clipboard", howToUseIt: "How to use it?", @@ -2300,6 +2300,10 @@ const en: BaseTranslation = { pendingPayment: "The payment has been sent, but hasn't confirmed yet.\n\nIt's possible the payment will not confirm, in which case the funds will be returned to your account.", }, SettingsScreen: { + setByOs: "Set by OS", + pos: "Point of Sale", + posCopied: "Your point of sale link has been copied", + setYourLightningAddress: "Set Your Lightning Address", activated: "Activated", addressScreen: "Ways to get paid", tapUserName: "Tap to set username", @@ -2350,6 +2354,15 @@ const en: BaseTranslation = { } }, AccountScreen: { + fundsMoreThan5Dollars: "Your account has more than $5", + itsATrialAccount: "Trial accounts have reduced transaction limits and no recovery method. If you lose your phone or uninstall the app, your funds will be unrecoverable.", + accountBeingDeleted: "Your account is being deleted, please wait...", + dangerZone: "Danger Zone", + phoneDeletedSuccessfully: "Phone deleted successfully", + phoneNumber: "Phone Number", + tapToAddPhoneNumber: "Tap to add phone number", + loginMethods: "Login Methods", + level: "Level {level: string}", accountLevel: "Account Level", upgrade: "Upgrade your account", logOutAndDeleteLocalData: "Log out and clear all local data", @@ -2365,6 +2378,11 @@ const en: BaseTranslation = { btcBalanceWarning: "You have a bitcoin balance of {balance: string}.", secureYourAccount: "Register to secure your account", tapToAdd: "Tap to add", + tapToAddEmail: "Tap to add email", + unverifiedEmail: "Email (Unverified)", + email: "Email", + emailDeletedSuccessfully: "Email deleted successfully", + unverifiedEmailAdvice: "Unverified emails can't be used to login. You should re-verify your email address.", deleteEmailPromptTitle: "Delete email", deleteEmailPromptContent: "Are you sure you want to delete your email address? you will only be able to log back in with your phone number.", @@ -2390,6 +2408,7 @@ const en: BaseTranslation = { "Are you sure you want to delete your two-factor authentication?", copiedAccountId: "Copied your account ID to clipboard", yourAccountId: "Your Account ID", + accountId: "Account ID", copy: "Copy" }, TotpRegistrationInitiateScreen: { @@ -2425,6 +2444,8 @@ const en: BaseTranslation = { system: "Use System setting", light: "Use Light Mode", dark: "Use Dark Mode", + setToDark: "Dark Mode", + setToLight: "Light Mode", }, Languages: { DEFAULT: "Default (OS)", @@ -2485,6 +2506,7 @@ const en: BaseTranslation = { }, SetAddressModal: { title: "Set {bankName: string} address", + setLightningAddress: "Set Lightning address", Errors: { tooShort: "Address must be at least 3 characters long", tooLong: "Address must be at most 50 characters long", @@ -2579,7 +2601,15 @@ const en: BaseTranslation = { success: "Email {email: string} confirmed successfully", }, common: { + enabled: "Enabled", + notifications: "Notifications", + preferences: "Preferences", + securityAndPrivacy: "Security and Privacy", + advanced: "Advanced", + community: "Community", account: "Account", + trialAccount: "Trial Account", + blinkUser: "Blink User", transactionLimits: "Transaction Limits", activateWallet: "Activate Wallet", amountRequired: "Amount is required", diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index e7cfb28398..a34546883d 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -46,10 +46,9 @@ type RootTranslation = { */ buttonTitle: string /** - * Y​o​u​r​ ​{​b​a​n​k​N​a​m​e​}​ ​a​d​d​r​e​s​s - * @param {string} bankName + * Y​o​u​r​ ​L​i​g​h​t​n​i​n​g​ ​a​d​d​r​e​s​s */ - yourAddress: RequiredParams<'bankName'> + yourLightningAddress: string /** * Y​o​u​ ​w​o​n​'​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​c​h​a​n​g​e​ ​y​o​u​r​ ​{​b​a​n​k​N​a​m​e​}​ ​a​d​d​r​e​s​s​ ​a​f​t​e​r​ ​i​t​'​s​ ​s​e​t​. * @param {string} bankName @@ -77,10 +76,9 @@ type RootTranslation = { */ yourPaycode: string /** - * C​o​p​i​e​d​ ​{​b​a​n​k​N​a​m​e​}​ ​a​d​d​r​e​s​s​ ​t​o​ ​c​l​i​p​b​o​a​r​d - * @param {string} bankName + * C​o​p​i​e​d​ ​L​i​g​h​t​n​i​n​g​ ​a​d​d​r​e​s​s​ ​t​o​ ​c​l​i​p​b​o​a​r​d */ - copiedAddressToClipboard: RequiredParams<'bankName'> + copiedLightningAddressToClipboard: string /** * C​o​p​i​e​d​ ​P​a​y​c​o​d​e​ ​t​o​ ​c​l​i​p​b​o​a​r​d */ @@ -7166,6 +7164,22 @@ type RootTranslation = { pendingPayment: string } SettingsScreen: { + /** + * S​e​t​ ​b​y​ ​O​S + */ + setByOs: string + /** + * P​o​i​n​t​ ​o​f​ ​S​a​l​e + */ + pos: string + /** + * Y​o​u​r​ ​p​o​i​n​t​ ​o​f​ ​s​a​l​e​ ​l​i​n​k​ ​h​a​s​ ​b​e​e​n​ ​c​o​p​i​e​d + */ + posCopied: string + /** + * S​e​t​ ​Y​o​u​r​ ​L​i​g​h​t​n​i​n​g​ ​A​d​d​r​e​s​s + */ + setYourLightningAddress: string /** * A​c​t​i​v​a​t​e​d */ @@ -7313,6 +7327,43 @@ type RootTranslation = { } } AccountScreen: { + /** + * Y​o​u​r​ ​a​c​c​o​u​n​t​ ​h​a​s​ ​m​o​r​e​ ​t​h​a​n​ ​$​5 + */ + fundsMoreThan5Dollars: string + /** + * T​r​i​a​l​ ​a​c​c​o​u​n​t​s​ ​h​a​v​e​ ​r​e​d​u​c​e​d​ ​t​r​a​n​s​a​c​t​i​o​n​ ​l​i​m​i​t​s​ ​a​n​d​ ​n​o​ ​r​e​c​o​v​e​r​y​ ​m​e​t​h​o​d​.​ ​I​f​ ​y​o​u​ ​l​o​s​e​ ​y​o​u​r​ ​p​h​o​n​e​ ​o​r​ ​u​n​i​n​s​t​a​l​l​ ​t​h​e​ ​a​p​p​,​ ​y​o​u​r​ ​f​u​n​d​s​ ​w​i​l​l​ ​b​e​ ​u​n​r​e​c​o​v​e​r​a​b​l​e​. + */ + itsATrialAccount: string + /** + * Y​o​u​r​ ​a​c​c​o​u​n​t​ ​i​s​ ​b​e​i​n​g​ ​d​e​l​e​t​e​d​,​ ​p​l​e​a​s​e​ ​w​a​i​t​.​.​. + */ + accountBeingDeleted: string + /** + * D​a​n​g​e​r​ ​Z​o​n​e + */ + dangerZone: string + /** + * P​h​o​n​e​ ​d​e​l​e​t​e​d​ ​s​u​c​c​e​s​s​f​u​l​l​y + */ + phoneDeletedSuccessfully: string + /** + * P​h​o​n​e​ ​N​u​m​b​e​r + */ + phoneNumber: string + /** + * T​a​p​ ​t​o​ ​a​d​d​ ​p​h​o​n​e​ ​n​u​m​b​e​r + */ + tapToAddPhoneNumber: string + /** + * L​o​g​i​n​ ​M​e​t​h​o​d​s + */ + loginMethods: string + /** + * L​e​v​e​l​ ​{​l​e​v​e​l​} + * @param {string} level + */ + level: RequiredParams<'level'> /** * A​c​c​o​u​n​t​ ​L​e​v​e​l */ @@ -7370,6 +7421,26 @@ type RootTranslation = { * T​a​p​ ​t​o​ ​a​d​d */ tapToAdd: string + /** + * T​a​p​ ​t​o​ ​a​d​d​ ​e​m​a​i​l + */ + tapToAddEmail: string + /** + * E​m​a​i​l​ ​(​U​n​v​e​r​i​f​i​e​d​) + */ + unverifiedEmail: string + /** + * E​m​a​i​l + */ + email: string + /** + * E​m​a​i​l​ ​d​e​l​e​t​e​d​ ​s​u​c​c​e​s​s​f​u​l​l​y + */ + emailDeletedSuccessfully: string + /** + * U​n​v​e​r​i​f​i​e​d​ ​e​m​a​i​l​s​ ​c​a​n​'​t​ ​b​e​ ​u​s​e​d​ ​t​o​ ​l​o​g​i​n​.​ ​Y​o​u​ ​s​h​o​u​l​d​ ​r​e​-​v​e​r​i​f​y​ ​y​o​u​r​ ​e​m​a​i​l​ ​a​d​d​r​e​s​s​. + */ + unverifiedEmailAdvice: string /** * D​e​l​e​t​e​ ​e​m​a​i​l */ @@ -7454,6 +7525,10 @@ type RootTranslation = { * Y​o​u​r​ ​A​c​c​o​u​n​t​ ​I​D */ yourAccountId: string + /** + * A​c​c​o​u​n​t​ ​I​D + */ + accountId: string /** * C​o​p​y */ @@ -7542,6 +7617,14 @@ type RootTranslation = { * U​s​e​ ​D​a​r​k​ ​M​o​d​e */ dark: string + /** + * D​a​r​k​ ​M​o​d​e + */ + setToDark: string + /** + * L​i​g​h​t​ ​M​o​d​e + */ + setToLight: string } Languages: { /** @@ -7722,6 +7805,10 @@ type RootTranslation = { * @param {string} bankName */ title: RequiredParams<'bankName'> + /** + * S​e​t​ ​L​i​g​h​t​n​i​n​g​ ​a​d​d​r​e​s​s + */ + setLightningAddress: string Errors: { /** * A​d​d​r​e​s​s​ ​m​u​s​t​ ​b​e​ ​a​t​ ​l​e​a​s​t​ ​3​ ​c​h​a​r​a​c​t​e​r​s​ ​l​o​n​g @@ -7991,10 +8078,42 @@ type RootTranslation = { success: RequiredParams<'email'> } common: { + /** + * E​n​a​b​l​e​d + */ + enabled: string + /** + * N​o​t​i​f​i​c​a​t​i​o​n​s + */ + notifications: string + /** + * P​r​e​f​e​r​e​n​c​e​s + */ + preferences: string + /** + * S​e​c​u​r​i​t​y​ ​a​n​d​ ​P​r​i​v​a​c​y + */ + securityAndPrivacy: string + /** + * A​d​v​a​n​c​e​d + */ + advanced: string + /** + * C​o​m​m​u​n​i​t​y + */ + community: string /** * A​c​c​o​u​n​t */ account: string + /** + * T​r​i​a​l​ ​A​c​c​o​u​n​t + */ + trialAccount: string + /** + * B​l​i​n​k​ ​U​s​e​r + */ + blinkUser: string /** * T​r​a​n​s​a​c​t​i​o​n​ ​L​i​m​i​t​s */ @@ -8956,9 +9075,9 @@ export type TranslationFunctions = { */ buttonTitle: () => LocalizedString /** - * Your {bankName} address + * Your Lightning address */ - yourAddress: (arg: { bankName: string }) => LocalizedString + yourLightningAddress: () => LocalizedString /** * You won't be able to change your {bankName} address after it's set. */ @@ -8984,9 +9103,9 @@ export type TranslationFunctions = { */ yourPaycode: () => LocalizedString /** - * Copied {bankName} address to clipboard + * Copied Lightning address to clipboard */ - copiedAddressToClipboard: (arg: { bankName: string }) => LocalizedString + copiedLightningAddressToClipboard: () => LocalizedString /** * Copied Paycode to clipboard */ @@ -16034,6 +16153,22 @@ export type TranslationFunctions = { pendingPayment: () => LocalizedString } SettingsScreen: { + /** + * Set by OS + */ + setByOs: () => LocalizedString + /** + * Point of Sale + */ + pos: () => LocalizedString + /** + * Your point of sale link has been copied + */ + posCopied: () => LocalizedString + /** + * Set Your Lightning Address + */ + setYourLightningAddress: () => LocalizedString /** * Activated */ @@ -16180,6 +16315,42 @@ export type TranslationFunctions = { } } AccountScreen: { + /** + * Your account has more than $5 + */ + fundsMoreThan5Dollars: () => LocalizedString + /** + * Trial accounts have reduced transaction limits and no recovery method. If you lose your phone or uninstall the app, your funds will be unrecoverable. + */ + itsATrialAccount: () => LocalizedString + /** + * Your account is being deleted, please wait... + */ + accountBeingDeleted: () => LocalizedString + /** + * Danger Zone + */ + dangerZone: () => LocalizedString + /** + * Phone deleted successfully + */ + phoneDeletedSuccessfully: () => LocalizedString + /** + * Phone Number + */ + phoneNumber: () => LocalizedString + /** + * Tap to add phone number + */ + tapToAddPhoneNumber: () => LocalizedString + /** + * Login Methods + */ + loginMethods: () => LocalizedString + /** + * Level {level} + */ + level: (arg: { level: string }) => LocalizedString /** * Account Level */ @@ -16231,6 +16402,26 @@ export type TranslationFunctions = { * Tap to add */ tapToAdd: () => LocalizedString + /** + * Tap to add email + */ + tapToAddEmail: () => LocalizedString + /** + * Email (Unverified) + */ + unverifiedEmail: () => LocalizedString + /** + * Email + */ + email: () => LocalizedString + /** + * Email deleted successfully + */ + emailDeletedSuccessfully: () => LocalizedString + /** + * Unverified emails can't be used to login. You should re-verify your email address. + */ + unverifiedEmailAdvice: () => LocalizedString /** * Delete email */ @@ -16315,6 +16506,10 @@ export type TranslationFunctions = { * Your Account ID */ yourAccountId: () => LocalizedString + /** + * Account ID + */ + accountId: () => LocalizedString /** * Copy */ @@ -16403,6 +16598,14 @@ export type TranslationFunctions = { * Use Dark Mode */ dark: () => LocalizedString + /** + * Dark Mode + */ + setToDark: () => LocalizedString + /** + * Light Mode + */ + setToLight: () => LocalizedString } Languages: { /** @@ -16579,6 +16782,10 @@ export type TranslationFunctions = { * Set {bankName} address */ title: (arg: { bankName: string }) => LocalizedString + /** + * Set Lightning address + */ + setLightningAddress: () => LocalizedString Errors: { /** * Address must be at least 3 characters long @@ -16839,10 +17046,42 @@ export type TranslationFunctions = { success: (arg: { email: string }) => LocalizedString } common: { + /** + * Enabled + */ + enabled: () => LocalizedString + /** + * Notifications + */ + notifications: () => LocalizedString + /** + * Preferences + */ + preferences: () => LocalizedString + /** + * Security and Privacy + */ + securityAndPrivacy: () => LocalizedString + /** + * Advanced + */ + advanced: () => LocalizedString + /** + * Community + */ + community: () => LocalizedString /** * Account */ account: () => LocalizedString + /** + * Trial Account + */ + trialAccount: () => LocalizedString + /** + * Blink User + */ + blinkUser: () => LocalizedString /** * Transaction Limits */ diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index 286d62f1bd..2051d665ca 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -2,14 +2,14 @@ "GaloyAddressScreen": { "title": "Receive payment by using:", "buttonTitle": "Set your address", - "yourAddress": "Your {bankName: string} address", + "yourLightningAddress": "Your Lightning address", "notAbleToChange": "You won't be able to change your {bankName: string} address after it's set.", "addressNotAvailable": "This {bankName: string} address is already taken.", "somethingWentWrong": "Something went wrong. Please try again later.", "merchantTitle": "For merchants", "yourCashRegister": "Your Lightning Cash Register", "yourPaycode": "Your Paycode", - "copiedAddressToClipboard": "Copied {bankName: string} address to clipboard", + "copiedLightningAddressToClipboard": "Copied Lightning address to clipboard", "copiedPaycodeToClipboard": "Copied Paycode to clipboard", "copiedCashRegisterLinkToClipboard": "Copied Cash Register Link to clipboard", "howToUseIt": "How to use it?", @@ -2229,6 +2229,10 @@ "pendingPayment": "The payment has been sent, but hasn't confirmed yet.\n\nIt's possible the payment will not confirm, in which case the funds will be returned to your account." }, "SettingsScreen": { + "setByOs": "Set by OS", + "pos": "Point of Sale", + "posCopied": "Your point of sale link has been copied", + "setYourLightningAddress": "Set Your Lightning Address", "activated": "Activated", "addressScreen": "Ways to get paid", "tapUserName": "Tap to set username", @@ -2276,6 +2280,15 @@ } }, "AccountScreen": { + "fundsMoreThan5Dollars": "Your account has more than $5", + "itsATrialAccount": "Trial accounts have reduced transaction limits and no recovery method. If you lose your phone or uninstall the app, your funds will be unrecoverable.", + "accountBeingDeleted": "Your account is being deleted, please wait...", + "dangerZone": "Danger Zone", + "phoneDeletedSuccessfully": "Phone deleted successfully", + "phoneNumber": "Phone Number", + "tapToAddPhoneNumber": "Tap to add phone number", + "loginMethods": "Login Methods", + "level": "Level {level: string}", "accountLevel": "Account Level", "upgrade": "Upgrade your account", "logOutAndDeleteLocalData": "Log out and clear all local data", @@ -2288,6 +2301,11 @@ "btcBalanceWarning": "You have a bitcoin balance of {balance: string}.", "secureYourAccount": "Register to secure your account", "tapToAdd": "Tap to add", + "tapToAddEmail": "Tap to add email", + "unverifiedEmail": "Email (Unverified)", + "email": "Email", + "emailDeletedSuccessfully": "Email deleted successfully", + "unverifiedEmailAdvice": "Unverified emails can't be used to login. You should re-verify your email address.", "deleteEmailPromptTitle": "Delete email", "deleteEmailPromptContent": "Are you sure you want to delete your email address? you will only be able to log back in with your phone number.", "deletePhonePromptTitle": "Delete phone", @@ -2309,6 +2327,7 @@ "totpDeleteAlertContent": "Are you sure you want to delete your two-factor authentication?", "copiedAccountId": "Copied your account ID to clipboard", "yourAccountId": "Your Account ID", + "accountId": "Account ID", "copy": "Copy" }, "TotpRegistrationInitiateScreen": { @@ -2337,7 +2356,9 @@ "info": "Pick your preferred theme for using Blink, or choose to keep it synced with your system settings.", "system": "Use System setting", "light": "Use Light Mode", - "dark": "Use Dark Mode" + "dark": "Use Dark Mode", + "setToDark": "Dark Mode", + "setToLight": "Light Mode" }, "Languages": { "DEFAULT": "Default (OS)" @@ -2395,6 +2416,7 @@ }, "SetAddressModal": { "title": "Set {bankName: string} address", + "setLightningAddress": "Set Lightning address", "Errors": { "tooShort": "Address must be at least 3 characters long", "tooLong": "Address must be at most 50 characters long", @@ -2476,7 +2498,15 @@ "success": "Email {email: string} confirmed successfully" }, "common": { + "enabled": "Enabled", + "notifications": "Notifications", + "preferences": "Preferences", + "securityAndPrivacy": "Security and Privacy", + "advanced": "Advanced", + "community": "Community", "account": "Account", + "trialAccount": "Trial Account", + "blinkUser": "Blink User", "transactionLimits": "Transaction Limits", "activateWallet": "Activate Wallet", "amountRequired": "Amount is required", diff --git a/app/navigation/root-navigator.tsx b/app/navigation/root-navigator.tsx index 3ea775ea70..d871638104 100644 --- a/app/navigation/root-navigator.tsx +++ b/app/navigation/root-navigator.tsx @@ -38,7 +38,7 @@ import SendBitcoinCompletedScreen from "@app/screens/send-bitcoin-screen/send-bi import SendBitcoinConfirmationScreen from "@app/screens/send-bitcoin-screen/send-bitcoin-confirmation-screen" import SendBitcoinDestinationScreen from "@app/screens/send-bitcoin-screen/send-bitcoin-destination-screen" import SendBitcoinDetailsScreen from "@app/screens/send-bitcoin-screen/send-bitcoin-details-screen" -import { AccountScreen } from "@app/screens/settings-screen/account-screen" +import { AccountScreen } from "@app/screens/settings-screen/account" import { DefaultWalletScreen } from "@app/screens/settings-screen/default-wallet" import { DisplayCurrencyScreen } from "@app/screens/settings-screen/display-currency-screen" import { NotificationSettingsScreen } from "@app/screens/settings-screen/notifications-screen" diff --git a/app/screens/galoy-address-screen/address-component.tsx b/app/screens/galoy-address-screen/address-component.tsx index 30f821dc7a..46b1933881 100644 --- a/app/screens/galoy-address-screen/address-component.tsx +++ b/app/screens/galoy-address-screen/address-component.tsx @@ -1,7 +1,6 @@ import { Linking, Pressable, Share, View } from "react-native" import { GaloyIcon } from "@app/components/atomic/galoy-icon" -import { useAppConfig } from "@app/hooks" import { useI18nContext } from "@app/i18n/i18n-react" import { toastShow } from "@app/utils/toast" import Clipboard from "@react-native-clipboard/clipboard" @@ -33,8 +32,6 @@ const AddressComponent: React.FC = ({ theme: { colors }, } = useTheme() const styles = useStyles() - const { appConfig } = useAppConfig() - const { name: bankName } = appConfig.galoyInstance const trimmedUrl = address.includes("https://") || address.includes("http://") ? address.replace("https://", "") @@ -46,9 +43,7 @@ const AddressComponent: React.FC = ({ message: (translations) => { switch (addressType) { case addressTypes.lightning: - return translations.GaloyAddressScreen.copiedAddressToClipboard({ - bankName, - }) + return translations.GaloyAddressScreen.copiedLightningAddressToClipboard() case addressTypes.pos: return translations.GaloyAddressScreen.copiedCashRegisterLinkToClipboard() case addressTypes.paycode: diff --git a/app/screens/galoy-address-screen/address-screen.tsx b/app/screens/galoy-address-screen/address-screen.tsx index abf37be13b..9ebd09aae0 100644 --- a/app/screens/galoy-address-screen/address-screen.tsx +++ b/app/screens/galoy-address-screen/address-screen.tsx @@ -54,7 +54,6 @@ export const GaloyAddressScreen = () => { }) const { appConfig } = useAppConfig() - const { name: bankName } = appConfig.galoyInstance const [explainerModalVisible, setExplainerModalVisible] = React.useState(false) const [isPosExplainerModalOpen, setIsPosExplainerModalOpen] = React.useState(false) @@ -94,7 +93,7 @@ export const GaloyAddressScreen = () => { { nextFetchPolicy: "cache-and-network", }) - const loading = loadingAuthed || loadingPrice || loadingUnauthed + // keep settings info cached and ignore network call if it's already cached + const { loading: loadingSettings } = useSettingsScreenQuery({ + skip: !isAuthed, + fetchPolicy: "cache-first", + // this enables offline mode use-case + nextFetchPolicy: "cache-and-network", + }) + + const loading = loadingAuthed || loadingPrice || loadingUnauthed || loadingSettings const refetch = React.useCallback(() => { if (isAuthed) { diff --git a/app/screens/people-screen/tab-icon.tsx b/app/screens/people-screen/tab-icon.tsx index 2a9a105716..29c0ce7d99 100644 --- a/app/screens/people-screen/tab-icon.tsx +++ b/app/screens/people-screen/tab-icon.tsx @@ -30,7 +30,7 @@ export const PeopleTabIcon: React.FC = ({ color, focused }) => { useEffect(() => { const innerCircleCachedValue = cachedData?.innerCircleValue || -1 const innerCircleRealValue = - networkData?.me?.defaultAccount.welcomeProfile?.innerCircleAllTimeCount || -1 + networkData?.me?.defaultAccount?.welcomeProfile?.innerCircleAllTimeCount || -1 if (innerCircleCachedValue === -1 && innerCircleRealValue === -1) { setHidden(false) diff --git a/app/screens/phone-auth-screen/phone-registration-validation.tsx b/app/screens/phone-auth-screen/phone-registration-validation.tsx index 0a37f6fff1..eac227eb9c 100644 --- a/app/screens/phone-auth-screen/phone-registration-validation.tsx +++ b/app/screens/phone-auth-screen/phone-registration-validation.tsx @@ -7,7 +7,6 @@ import { GaloyErrorBox } from "@app/components/atomic/galoy-error-box" import { GaloyInfo } from "@app/components/atomic/galoy-info" import { GaloySecondaryButton } from "@app/components/atomic/galoy-secondary-button" import { - AccountScreenDocument, PhoneCodeChannelType, useUserPhoneRegistrationValidateMutation, } from "@app/graphql/generated" @@ -191,7 +190,6 @@ export const PhoneRegistrationValidateScreen: React.FC< logAddPhoneAttempt() const { data } = await phoneValidate({ variables: { input: { phone, code } }, - refetchQueries: [AccountScreenDocument], }) const errors = data?.userPhoneRegistrationValidate?.errors || [] diff --git a/app/screens/settings-screen/account-screen.stories.tsx b/app/screens/settings-screen/account-screen.stories.tsx index 7445b082f6..53acef1449 100644 --- a/app/screens/settings-screen/account-screen.stories.tsx +++ b/app/screens/settings-screen/account-screen.stories.tsx @@ -5,16 +5,16 @@ import { Meta } from "@storybook/react" import { StoryScreen } from "../../../.storybook/views" import { createCache } from "../../graphql/cache" -import { AccountScreenDocument } from "../../graphql/generated" +import { SettingsScreenDocument } from "../../graphql/generated" import { AccountLevel, LevelContextProvider } from "../../graphql/level-context" import mocks from "../../graphql/mocks" -import { AccountScreen } from "./account-screen" +import { AccountScreen } from "../settings-screen/account/account-screen" const mocksLevelOne = [ ...mocks, { request: { - query: AccountScreenDocument, + query: SettingsScreenDocument, }, result: { data: { @@ -55,7 +55,7 @@ const mocksNoEmail = [ ...mocks, { request: { - query: AccountScreenDocument, + query: SettingsScreenDocument, }, result: { data: { @@ -103,6 +103,7 @@ export const Unauthed = () => ( value={{ isAtLeastLevelZero: false, isAtLeastLevelOne: false, + isAtLeastLevelTwo: true, currentLevel: AccountLevel.NonAuth, }} > @@ -117,6 +118,7 @@ export const AuthedEmailNotSet = () => ( value={{ isAtLeastLevelZero: true, isAtLeastLevelOne: true, + isAtLeastLevelTwo: true, currentLevel: AccountLevel.One, }} > @@ -131,6 +133,7 @@ export const AuthedEmailSet = () => ( value={{ isAtLeastLevelZero: true, isAtLeastLevelOne: true, + isAtLeastLevelTwo: true, currentLevel: AccountLevel.One, }} > diff --git a/app/screens/settings-screen/account-screen.tsx b/app/screens/settings-screen/account-screen.tsx deleted file mode 100644 index 74a1eaa5a4..0000000000 --- a/app/screens/settings-screen/account-screen.tsx +++ /dev/null @@ -1,628 +0,0 @@ -import React from "react" -import { Alert, TextInput, View } from "react-native" -import Modal from "react-native-modal" - -import { gql } from "@apollo/client" -import { GaloyPrimaryButton } from "@app/components/atomic/galoy-primary-button" -import { GaloySecondaryButton } from "@app/components/atomic/galoy-secondary-button" -import { Screen } from "@app/components/screen" -import { UpgradeAccountModal } from "@app/components/upgrade-account-modal" -import { CONTACT_EMAIL_ADDRESS } from "@app/config" -import { - useAccountDeleteMutation, - useAccountScreenQuery, - useBetaQuery, - useUserEmailDeleteMutation, - useUserEmailRegistrationInitiateMutation, - useUserPhoneDeleteMutation, - useUserTotpDeleteMutation, -} from "@app/graphql/generated" -import { AccountLevel, useLevel } from "@app/graphql/level-context" -import { getBtcWallet, getUsdWallet } from "@app/graphql/wallets-utils" -import { useDisplayCurrency } from "@app/hooks/use-display-currency" -import useLogout from "@app/hooks/use-logout" -import { useI18nContext } from "@app/i18n/i18n-react" -import { RootStackParamList } from "@app/navigation/stack-param-lists" -import { toBtcMoneyAmount, toUsdMoneyAmount } from "@app/types/amounts" -import { testProps } from "@app/utils/testProps" -import { useNavigation } from "@react-navigation/native" -import { StackNavigationProp } from "@react-navigation/stack" -import { Text, makeStyles, useTheme } from "@rneui/themed" - -import { AccountId } from "./account-id" -import { SettingsRow } from "./settings-row" -import { useShowWarningSecureAccount } from "./show-warning-secure-account" - -gql` - query accountScreen { - me { - id - phone - totpEnabled - email { - address - verified - } - defaultAccount { - id - level - wallets { - id - balance - walletCurrency - } - } - } - } - - mutation accountDelete { - accountDelete { - errors { - message - } - success - } - } - - mutation userEmailDelete { - userEmailDelete { - errors { - message - } - me { - id - phone - totpEnabled - email { - address - verified - } - } - } - } - - mutation userPhoneDelete { - userPhoneDelete { - errors { - message - } - me { - id - phone - totpEnabled - email { - address - verified - } - } - } - } - - mutation userTotpDelete { - userTotpDelete { - errors { - message - } - me { - id - phone - totpEnabled - email { - address - verified - } - } - } - } -` - -export const AccountScreen = () => { - const navigation = - useNavigation>() - - const { logout } = useLogout() - const { LL } = useI18nContext() - const styles = useStyles() - - const { - theme: { colors }, - } = useTheme() - - const { isAtLeastLevelZero, currentLevel, isAtLeastLevelOne } = useLevel() - - const [deleteAccount] = useAccountDeleteMutation() - const [emailDeleteMutation] = useUserEmailDeleteMutation() - const [phoneDeleteMutation] = useUserPhoneDeleteMutation() - const [totpDeleteMutation] = useUserTotpDeleteMutation() - - const [text, setText] = React.useState("") - const [modalVisible, setModalVisible] = React.useState(false) - const [upgradeAccountModalVisible, setUpgradeAccountModalVisible] = - React.useState(false) - const closeUpgradeAccountModal = () => setUpgradeAccountModalVisible(false) - const openUpgradeAccountModal = () => setUpgradeAccountModalVisible(true) - - const { data } = useAccountScreenQuery({ - fetchPolicy: "cache-and-network", - skip: !isAtLeastLevelZero, - }) - - const email = data?.me?.email?.address - const emailVerified = Boolean(email) && Boolean(data?.me?.email?.verified) - const emailUnverified = Boolean(email) && !data?.me?.email?.verified - const phoneVerified = Boolean(data?.me?.phone) - const phoneAndEmailVerified = phoneVerified && emailVerified - const emailString = String(email) - const totpEnabled = Boolean(data?.me?.totpEnabled) - - const showWarningSecureAccount = useShowWarningSecureAccount() - - const [setEmailMutation] = useUserEmailRegistrationInitiateMutation() - - const btcWallet = getBtcWallet(data?.me?.defaultAccount?.wallets) - const usdWallet = getUsdWallet(data?.me?.defaultAccount?.wallets) - - const usdWalletBalance = toUsdMoneyAmount(usdWallet?.balance) - const btcWalletBalance = toBtcMoneyAmount(btcWallet?.balance) - - const { formatMoneyAmount } = useDisplayCurrency() - - let usdBalanceWarning = "" - let btcBalanceWarning = "" - let balancePositive = false - if (usdWalletBalance.amount > 0) { - const balance = - formatMoneyAmount && formatMoneyAmount({ moneyAmount: usdWalletBalance }) - usdBalanceWarning = LL.AccountScreen.usdBalanceWarning({ balance }) - balancePositive = true - } - - if (btcWalletBalance.amount > 0) { - const balance = - formatMoneyAmount && formatMoneyAmount({ moneyAmount: btcWalletBalance }) - btcBalanceWarning = LL.AccountScreen.btcBalanceWarning({ balance }) - balancePositive = true - } - - const dataBeta = useBetaQuery() - const beta = dataBeta.data?.beta ?? false - beta - - const deletePhonePrompt = async () => { - Alert.alert( - LL.AccountScreen.deletePhonePromptTitle(), - LL.AccountScreen.deletePhonePromptContent(), - [ - { text: LL.common.cancel(), onPress: () => {} }, - { - text: LL.common.yes(), - onPress: async () => { - deletePhone() - }, - }, - ], - ) - } - - const deleteEmailPrompt = async () => { - Alert.alert( - LL.AccountScreen.deleteEmailPromptTitle(), - LL.AccountScreen.deleteEmailPromptContent(), - [ - { text: LL.common.cancel(), onPress: () => {} }, - { - text: LL.common.yes(), - onPress: async () => { - deleteEmail() - }, - }, - ], - ) - } - - const deletePhone = async () => { - try { - await phoneDeleteMutation() - } catch (err) { - let message = "" - if (err instanceof Error) { - message = err?.message - } - Alert.alert(LL.common.error(), message) - } - } - - const deleteEmail = async () => { - try { - await emailDeleteMutation() - } catch (err) { - let message = "" - if (err instanceof Error) { - message = err?.message - } - Alert.alert(LL.common.error(), message) - } - } - - const logoutAlert = () => { - const logAlertContent = () => { - const phoneNumber = String(data?.me?.phone) - if (phoneAndEmailVerified) { - return LL.AccountScreen.logoutAlertContentPhoneEmail({ - phoneNumber, - email: emailString, - }) - } else if (emailVerified) { - return LL.AccountScreen.logoutAlertContentEmail({ email: emailString }) - } - // phone verified - return LL.AccountScreen.logoutAlertContentPhone({ phoneNumber }) - } - - Alert.alert(LL.AccountScreen.logoutAlertTitle(), logAlertContent(), [ - { - text: LL.common.cancel(), - onPress: () => console.log("Cancel Pressed"), - style: "cancel", - }, - { - text: LL.AccountScreen.IUnderstand(), - onPress: logoutAction, - }, - ]) - } - - const logoutAction = async () => { - try { - await logout() - Alert.alert(LL.common.loggedOut(), "", [ - { - text: LL.common.ok(), - onPress: () => - navigation.reset({ - index: 0, - routes: [{ name: "getStarted" }], - }), - }, - ]) - } catch (err) { - // TODO: figure out why ListItem onPress is swallowing errors - console.error(err) - } - } - - const deleteAccountAction = async () => { - if (balancePositive) { - const fullMessage = - usdBalanceWarning + - "\n" + - btcBalanceWarning + - "\n" + - LL.support.deleteAccountBalanceWarning() - - Alert.alert(LL.common.warning(), fullMessage, [ - { text: LL.common.cancel(), onPress: () => {} }, - { - text: LL.common.yes(), - onPress: async () => setModalVisible(true), - }, - ]) - } else { - setModalVisible(true) - } - } - - const deleteUserAccount = async () => { - try { - const res = await deleteAccount() - - if (res.data?.accountDelete?.success) { - await logout() - Alert.alert(LL.support.bye(), LL.support.deleteAccountConfirmation(), [ - { - text: LL.common.ok(), - onPress: () => - navigation.reset({ - index: 0, - routes: [{ name: "getStarted" }], - }), - }, - ]) - } else { - Alert.alert( - LL.common.error(), - LL.support.deleteAccountError({ email: CONTACT_EMAIL_ADDRESS }) + - "\n\n" + - res.data?.accountDelete?.errors[0].message, - ) - } - } catch (err) { - console.error(err) - Alert.alert( - LL.common.error(), - LL.support.deleteAccountError({ email: CONTACT_EMAIL_ADDRESS }), - ) - } - } - - const tryConfirmEmailAgain = async (email: string) => { - try { - await emailDeleteMutation({ - // to avoid flacky behavior - // this could lead to inconsistent state if delete works but set fails - fetchPolicy: "no-cache", - }) - - const { data } = await setEmailMutation({ - variables: { input: { email } }, - }) - - const errors = data?.userEmailRegistrationInitiate.errors - if (errors && errors.length > 0) { - Alert.alert(errors[0].message) - } - - const emailRegistrationId = data?.userEmailRegistrationInitiate.emailRegistrationId - - if (emailRegistrationId) { - navigation.navigate("emailRegistrationValidate", { - emailRegistrationId, - email, - }) - } else { - console.warn("no flow returned") - } - } catch (err) { - console.error(err, "error in setEmailMutation") - } finally { - // setLoading(false) - } - } - - const confirmEmailAgain = async () => { - if (email) { - Alert.alert( - LL.AccountScreen.emailUnverified(), - LL.AccountScreen.emailUnverifiedContent(), - [ - { text: LL.common.cancel(), onPress: () => {} }, - { - text: LL.common.ok(), - onPress: () => tryConfirmEmailAgain(email), - }, - ], - ) - } else { - console.error("email not set, wrong flow") - } - } - - const totpDelete = async () => { - Alert.alert( - LL.AccountScreen.totpDeleteAlertTitle(), - LL.AccountScreen.totpDeleteAlertContent(), - [ - { text: LL.common.cancel(), onPress: () => {} }, - { - text: LL.common.ok(), - onPress: async () => { - const res = await totpDeleteMutation() - if (res.data?.userTotpDelete?.me?.totpEnabled === false) { - Alert.alert(LL.AccountScreen.totpDeactivated()) - } else { - console.log(res.data?.userTotpDelete.errors) - Alert.alert(LL.common.error(), res.data?.userTotpDelete?.errors[0]?.message) - } - }, - }, - ], - ) - } - - const accountSettingsList: SettingRow[] = [ - { - category: LL.AccountScreen.accountLevel(), - id: "level", - icon: "flash-outline", - subTitleText: currentLevel, - enabled: false, - greyed: true, - }, - { - category: LL.common.transactionLimits(), - id: "limits", - icon: "custom-info-icon", - action: () => navigation.navigate("transactionLimitsScreen"), - enabled: isAtLeastLevelZero, - greyed: !isAtLeastLevelZero, - styleDivider: true, - }, - - { - category: LL.AccountScreen.upgrade(), - id: "upgrade-to-level-two", - icon: "person-outline", - action: () => navigation.navigate("fullOnboardingFlow"), - enabled: true, - hidden: currentLevel !== AccountLevel.One, - styleDivider: true, - }, - { - category: LL.common.backupAccount(), - id: "upgrade-to-level-one", - icon: "person-outline", - subTitleText: showWarningSecureAccount ? LL.AccountScreen.secureYourAccount() : "", - chevronLogo: showWarningSecureAccount ? "alert-circle-outline" : undefined, - chevronColor: showWarningSecureAccount ? colors.primary : undefined, - chevronSize: showWarningSecureAccount ? 24 : undefined, - action: openUpgradeAccountModal, - enabled: true, - hidden: currentLevel !== AccountLevel.Zero, - styleDivider: true, - }, - - { - category: LL.AccountScreen.phoneNumberAuthentication(), - id: "phone", - icon: "call-outline", - subTitleText: data?.me?.phone, - action: phoneVerified - ? deletePhonePrompt - : () => navigation.navigate("phoneRegistrationInitiate"), - enabled: phoneAndEmailVerified || !phoneVerified, - chevronLogo: phoneAndEmailVerified ? "close-circle-outline" : undefined, - chevronColor: phoneAndEmailVerified ? colors.red : undefined, - chevronSize: phoneAndEmailVerified ? 28 : undefined, - hidden: !isAtLeastLevelOne, - }, - - { - category: LL.AccountScreen.emailAuthentication(), - id: "email", - icon: "mail-outline", - subTitleText: email ?? LL.AccountScreen.tapToAdd(), - action: phoneAndEmailVerified - ? deleteEmailPrompt - : () => navigation.navigate("emailRegistrationInitiate"), - enabled: phoneAndEmailVerified || !emailUnverified, - chevronLogo: phoneAndEmailVerified ? "close-circle-outline" : undefined, - chevronColor: phoneAndEmailVerified ? colors.red : undefined, - chevronSize: phoneAndEmailVerified ? 28 : undefined, - styleDivider: !emailUnverified, - hidden: !isAtLeastLevelOne, - }, - { - category: LL.AccountScreen.unverified(), - id: "confirm-email", - icon: "checkmark-circle-outline", - subTitleText: LL.AccountScreen.unverifiedContent(), - action: confirmEmailAgain, - enabled: Boolean(emailUnverified), - chevron: false, - dangerous: true, - hidden: !emailUnverified, - }, - { - category: LL.AccountScreen.removeEmail(), - id: "remove-email", - icon: "trash-outline", - action: deleteEmailPrompt, - enabled: Boolean(emailUnverified), - chevron: false, - styleDivider: true, - hidden: !emailUnverified, - }, - { - category: LL.AccountScreen.totp(), - id: "totp", - icon: "lock-closed-outline", - action: totpEnabled - ? totpDelete - : () => navigation.navigate("totpRegistrationInitiate"), - enabled: true, - chevronLogo: totpEnabled ? "close-circle-outline" : undefined, - chevronColor: totpEnabled ? colors.red : undefined, - chevronSize: totpEnabled ? 28 : undefined, - styleDivider: true, - }, - ] - - if (isAtLeastLevelOne) { - accountSettingsList.push({ - category: LL.AccountScreen.logOutAndDeleteLocalData(), - id: "logout", - icon: "log-out-outline", - action: logoutAlert, - enabled: true, - greyed: false, - chevron: false, - styleDivider: true, - }) - } - - if (currentLevel !== AccountLevel.NonAuth) { - accountSettingsList.push({ - category: LL.support.deleteAccount(), - id: "deleteAccount", - icon: "trash-outline", - dangerous: true, - action: deleteAccountAction, - chevron: false, - enabled: true, - greyed: false, - styleDivider: true, - }) - } - - const AccountDeletionModal = ( - setModalVisible(false)} - backdropOpacity={0.3} - backdropColor={colors.grey3} - avoidKeyboard={true} - > - - {LL.support.typeDelete({ delete: LL.support.delete() })} - - { - setModalVisible(false) - Alert.alert( - LL.support.finalConfirmationAccountDeletionTitle(), - LL.support.finalConfirmationAccountDeletionMessage(), - [ - { text: LL.common.cancel(), onPress: () => {} }, - { text: LL.common.ok(), onPress: () => deleteUserAccount() }, - ], - ) - }} - containerStyle={styles.mainButton} - /> - setModalVisible(false)} /> - - - ) - - return ( - - - {accountSettingsList.map((setting) => ( - - ))} - {AccountDeletionModal} - - - ) -} - -const useStyles = makeStyles(({ colors }) => ({ - view: { - marginHorizontal: 20, - backgroundColor: colors.white, - padding: 20, - borderRadius: 20, - }, - - textInput: { - height: 40, - borderColor: colors.grey3, - borderWidth: 1, - paddingVertical: 12, - color: colors.black, - }, - - mainButton: { marginVertical: 20 }, -})) diff --git a/app/screens/settings-screen/account/account-delete-context.tsx b/app/screens/settings-screen/account/account-delete-context.tsx new file mode 100644 index 0000000000..9ac4c91013 --- /dev/null +++ b/app/screens/settings-screen/account/account-delete-context.tsx @@ -0,0 +1,54 @@ +import { PropsWithChildren, createContext, useContext, useState } from "react" +import { ActivityIndicator, View } from "react-native" + +import { useI18nContext } from "@app/i18n/i18n-react" +import { Text, makeStyles, useTheme } from "@rneui/themed" + +type AccountDeleteContextType = { + setAccountIsBeingDeleted: React.Dispatch> +} + +const AccountDeleteContext = createContext({ + setAccountIsBeingDeleted: () => {}, +}) + +export const AccountDeleteContextProvider: React.FC = ({ + children, +}) => { + const styles = useStyles() + const { + theme: { colors }, + } = useTheme() + + const { LL } = useI18nContext() + + const [accountIsBeingDeleted, setAccountIsBeingDeleted] = useState(false) + + const Loading = ( + + + + {LL.AccountScreen.accountBeingDeleted()} + + + ) + + return ( + + {accountIsBeingDeleted ? Loading : children} + + ) +} + +export const useAccountDeleteContext = () => useContext(AccountDeleteContext) + +const useStyles = makeStyles(() => ({ + center: { + height: "100%", + display: "flex", + flexDirection: "column", + rowGap: 10, + justifyContent: "center", + alignItems: "center", + }, +})) diff --git a/app/screens/settings-screen/account/account-screen.tsx b/app/screens/settings-screen/account/account-screen.tsx new file mode 100644 index 0000000000..35f1812abc --- /dev/null +++ b/app/screens/settings-screen/account/account-screen.tsx @@ -0,0 +1,59 @@ +import { ScrollView } from "react-native-gesture-handler" + +import { Screen } from "@app/components/screen" +import { useLevel } from "@app/graphql/level-context" +import { useI18nContext } from "@app/i18n/i18n-react" +import { testProps } from "@app/utils/testProps" +import { makeStyles } from "@rneui/themed" + +import { SettingsGroup } from "../group" +import { TotpSetting } from "../totp" +import { AccountDeleteContextProvider } from "./account-delete-context" +import { AccountBanner } from "./banner" +import { AccountId } from "./id" +import { DangerZoneSettings } from "./settings/danger-zone" +import { EmailSetting } from "./settings/email" +import { PhoneSetting } from "./settings/phone" +import { UpgradeAccountLevelOne } from "./settings/upgrade" +import { UpgradeTrialAccount } from "./settings/upgrade-trial-account" + +export const AccountScreen: React.FC = () => { + const styles = useStyles() + const { LL } = useI18nContext() + + const { isAtLeastLevelOne } = useLevel() + + return ( + + + + + + + {isAtLeastLevelOne && ( + + )} + + + + + + ) +} + +const useStyles = makeStyles(() => ({ + outer: { + marginTop: 4, + paddingHorizontal: 12, + paddingBottom: 20, + display: "flex", + flexDirection: "column", + rowGap: 12, + }, +})) diff --git a/app/screens/settings-screen/account/banner.tsx b/app/screens/settings-screen/account/banner.tsx new file mode 100644 index 0000000000..df41712bfb --- /dev/null +++ b/app/screens/settings-screen/account/banner.tsx @@ -0,0 +1,70 @@ +/** + * This component is the top banner on the settings screen + * It shows the user their own username with a people icon + * If the user isn't logged in, it shows Login or Create Account + * Later on, this will support switching between accounts + */ +import { View } from "react-native" +import { TouchableWithoutFeedback } from "react-native-gesture-handler" + +import { GaloyIcon } from "@app/components/atomic/galoy-icon" +import { useSettingsScreenQuery } from "@app/graphql/generated" +import { AccountLevel, useLevel } from "@app/graphql/level-context" +import { useI18nContext } from "@app/i18n/i18n-react" +import { RootStackParamList } from "@app/navigation/stack-param-lists" +import { useNavigation } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" +import { Text, makeStyles, useTheme, Skeleton } from "@rneui/themed" + +export const AccountBanner = () => { + const styles = useStyles() + const { LL } = useI18nContext() + + const navigation = useNavigation>() + + const { currentLevel } = useLevel() + const isUserLoggedIn = currentLevel !== AccountLevel.NonAuth + + const { data, loading } = useSettingsScreenQuery({ fetchPolicy: "cache-first" }) + + const usernameTitle = data?.me?.username || LL.common.blinkUser() + + if (loading) return + + return ( + + !isUserLoggedIn && + navigation.reset({ + index: 0, + routes: [{ name: "getStarted" }], + }) + } + > + + + + {isUserLoggedIn ? usernameTitle : LL.SettingsScreen.logInOrCreateAccount()} + + + + ) +} + +export const AccountIcon: React.FC<{ size: number }> = ({ size }) => { + const { + theme: { colors }, + } = useTheme() + return +} + +const useStyles = makeStyles(() => ({ + outer: { + height: 70, + padding: 4, + display: "flex", + flexDirection: "row", + alignItems: "center", + columnGap: 12, + }, +})) diff --git a/app/screens/settings-screen/account-id.tsx b/app/screens/settings-screen/account/id.tsx similarity index 60% rename from app/screens/settings-screen/account-id.tsx rename to app/screens/settings-screen/account/id.tsx index 0235c2686f..b12a2e1df9 100644 --- a/app/screens/settings-screen/account-id.tsx +++ b/app/screens/settings-screen/account/id.tsx @@ -2,15 +2,14 @@ import { useCallback } from "react" import { View } from "react-native" import { GaloyIconButton } from "@app/components/atomic/galoy-icon-button" -import { useAccountScreenQuery } from "@app/graphql/generated" +import { useSettingsScreenQuery } from "@app/graphql/generated" import { useI18nContext } from "@app/i18n/i18n-react" -import { testProps } from "@app/utils/testProps" import { toastShow } from "@app/utils/toast" import Clipboard from "@react-native-clipboard/clipboard" -import { Text, makeStyles } from "@rneui/themed" +import { Skeleton, Text, makeStyles } from "@rneui/themed" export const AccountId: React.FC = () => { - const { data } = useAccountScreenQuery() + const { data, loading } = useSettingsScreenQuery() const { LL } = useI18nContext() const styles = useStyles() @@ -30,44 +29,49 @@ export const AccountId: React.FC = () => { }, [LL, accountId]) return ( - - - {LL.AccountScreen.yourAccountId()} + + + {LL.AccountScreen.accountId()} - - - - {Array(20) - .fill(null) - .map((_, i) => ( - - ))} + {loading ? ( + + ) : ( + + + + {Array(20) + .fill(null) + .map((_, i) => ( + + ))} + + + {last6digitsOfAccountId} + - - {last6digitsOfAccountId} - + - - + )} ) } const useStyles = makeStyles(({ colors }) => ({ - accountId: { - margin: 20, - }, circle: { height: 4, width: 4, borderRadius: 2, backgroundColor: colors.black, }, + spacing: { + paddingHorizontal: 10, + paddingVertical: 6, + }, wrapper: { marginTop: 5, display: "flex", @@ -76,8 +80,8 @@ const useStyles = makeStyles(({ colors }) => ({ justifyContent: "space-between", backgroundColor: colors.grey5, borderRadius: 10, - paddingHorizontal: 10, - paddingVertical: 6, + marginBottom: 10, + height: 48, }, accIdWrapper: { display: "flex", diff --git a/app/screens/settings-screen/account/index.ts b/app/screens/settings-screen/account/index.ts new file mode 100644 index 0000000000..e0a5266ce9 --- /dev/null +++ b/app/screens/settings-screen/account/index.ts @@ -0,0 +1 @@ +export * from "./account-screen" diff --git a/app/screens/settings-screen/account/login-methods-hook.ts b/app/screens/settings-screen/account/login-methods-hook.ts new file mode 100644 index 0000000000..5627698b0f --- /dev/null +++ b/app/screens/settings-screen/account/login-methods-hook.ts @@ -0,0 +1,22 @@ +import { useSettingsScreenQuery } from "@app/graphql/generated" + +export const useLoginMethods = () => { + const { data } = useSettingsScreenQuery({ fetchPolicy: "cache-and-network" }) + + const email = data?.me?.email?.address || undefined + const emailVerified = Boolean(email && data?.me?.email?.verified) + + const phone = data?.me?.phone + const phoneVerified = Boolean(phone) + + const bothEmailAndPhoneVerified = phoneVerified && emailVerified + + return { + loading: !data, // Data would auto refresh after network call + email, + emailVerified, + phone, + phoneVerified, + bothEmailAndPhoneVerified, + } +} diff --git a/app/screens/settings-screen/account/settings/danger-zone.tsx b/app/screens/settings-screen/account/settings/danger-zone.tsx new file mode 100644 index 0000000000..92ed37617b --- /dev/null +++ b/app/screens/settings-screen/account/settings/danger-zone.tsx @@ -0,0 +1,35 @@ +import { View } from "react-native" + +import { AccountLevel, useLevel } from "@app/graphql/level-context" +import { useI18nContext } from "@app/i18n/i18n-react" +import { Text, makeStyles } from "@rneui/themed" + +import { Delete } from "./delete" +import { LogOut } from "./logout" + +export const DangerZoneSettings: React.FC = () => { + const { LL } = useI18nContext() + const styles = useStyles() + + const { currentLevel, isAtLeastLevelOne, isAtLeastLevelZero } = useLevel() + if (!isAtLeastLevelZero) return <> + + return ( + + + {LL.AccountScreen.dangerZone()} + + {isAtLeastLevelOne && } + {currentLevel !== AccountLevel.NonAuth && } + + ) +} + +const useStyles = makeStyles(() => ({ + verticalSpacing: { + marginTop: 10, + display: "flex", + flexDirection: "column", + rowGap: 10, + }, +})) diff --git a/app/screens/settings-screen/account/settings/delete.tsx b/app/screens/settings-screen/account/settings/delete.tsx new file mode 100644 index 0000000000..afd7dde522 --- /dev/null +++ b/app/screens/settings-screen/account/settings/delete.tsx @@ -0,0 +1,231 @@ +import { useState } from "react" +import { Alert, TextInput, View } from "react-native" +import Modal from "react-native-modal" + +import { gql } from "@apollo/client" +import { GaloyIconButton } from "@app/components/atomic/galoy-icon-button" +import { GaloyPrimaryButton } from "@app/components/atomic/galoy-primary-button" +import { GaloySecondaryButton } from "@app/components/atomic/galoy-secondary-button" +import { CONTACT_EMAIL_ADDRESS } from "@app/config" +import { useAccountDeleteMutation, useSettingsScreenQuery } from "@app/graphql/generated" +import { getBtcWallet, getUsdWallet } from "@app/graphql/wallets-utils" +import { useDisplayCurrency } from "@app/hooks/use-display-currency" +import useLogout from "@app/hooks/use-logout" +import { useI18nContext } from "@app/i18n/i18n-react" +import { RootStackParamList } from "@app/navigation/stack-param-lists" +import { toBtcMoneyAmount, toUsdMoneyAmount } from "@app/types/amounts" +import { useNavigation } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" +import { useTheme, Text, makeStyles } from "@rneui/themed" + +import { SettingsButton } from "../../button" +import { useAccountDeleteContext } from "../account-delete-context" + +gql` + mutation accountDelete { + accountDelete { + errors { + message + } + success + } + } +` + +export const Delete = () => { + const styles = useStyles() + + const navigation = useNavigation>() + const { logout } = useLogout() + + const { LL } = useI18nContext() + + const [text, setText] = useState("") + const [modalVisible, setModalVisible] = useState(false) + const closeModal = () => { + setModalVisible(false) + setText("") + } + + const { + theme: { colors }, + } = useTheme() + + const { setAccountIsBeingDeleted } = useAccountDeleteContext() + + const [deleteAccount] = useAccountDeleteMutation({ fetchPolicy: "no-cache" }) + + const { data, loading } = useSettingsScreenQuery() + const { formatMoneyAmount } = useDisplayCurrency() + + const btcWallet = getBtcWallet(data?.me?.defaultAccount?.wallets) + const usdWallet = getUsdWallet(data?.me?.defaultAccount?.wallets) + + const usdWalletBalance = toUsdMoneyAmount(usdWallet?.balance) + const btcWalletBalance = toBtcMoneyAmount(btcWallet?.balance) + + let usdBalanceWarning = "" + let btcBalanceWarning = "" + let balancePositive = false + if (usdWalletBalance.amount > 0) { + const balance = + formatMoneyAmount && formatMoneyAmount({ moneyAmount: usdWalletBalance }) + usdBalanceWarning = LL.AccountScreen.usdBalanceWarning({ balance }) + balancePositive = true + } + + if (btcWalletBalance.amount > 0) { + const balance = + formatMoneyAmount && formatMoneyAmount({ moneyAmount: btcWalletBalance }) + btcBalanceWarning = LL.AccountScreen.btcBalanceWarning({ balance }) + balancePositive = true + } + + const fullMessage = ( + usdBalanceWarning + + "\n" + + btcBalanceWarning + + "\n" + + LL.support.deleteAccountBalanceWarning() + ).trim() + + const deleteAccountAction = async () => { + if (balancePositive) { + Alert.alert(LL.common.warning(), fullMessage, [ + { text: LL.common.cancel(), onPress: () => {} }, + { + text: LL.common.yes(), + onPress: async () => setModalVisible(true), + }, + ]) + } else { + setModalVisible(true) + } + } + + const deleteUserAccount = async () => { + try { + navigation.setOptions({ + headerLeft: () => null, // Hides the default back button + gestureEnabled: false, // Disables swipe to go back gesture + }) + setAccountIsBeingDeleted(true) + + const res = await deleteAccount() + + if (res.data?.accountDelete?.success) { + await logout(true) + setAccountIsBeingDeleted(false) + navigation.reset({ + index: 0, + routes: [{ name: "getStarted" }], + }) + Alert.alert(LL.support.bye(), LL.support.deleteAccountConfirmation(), [ + { + text: LL.common.ok(), + onPress: () => {}, + }, + ]) + } else { + Alert.alert( + LL.common.error(), + LL.support.deleteAccountError({ email: CONTACT_EMAIL_ADDRESS }) + + "\n\n" + + res.data?.accountDelete?.errors[0].message, + ) + } + } catch (err) { + console.error(err) + Alert.alert( + LL.common.error(), + LL.support.deleteAccountError({ email: CONTACT_EMAIL_ADDRESS }), + ) + } + } + + const userWroteDelete = + text.toLowerCase().trim() === LL.support.delete().toLocaleLowerCase().trim() + + const AccountDeletionModal = ( + + + + + {LL.support.deleteAccount()} + + + + {LL.support.typeDelete({ delete: LL.support.delete() })} + + + { + closeModal() + Alert.alert( + LL.support.finalConfirmationAccountDeletionTitle(), + LL.support.finalConfirmationAccountDeletionMessage(), + [ + { text: LL.common.cancel(), onPress: () => {} }, + { text: LL.common.ok(), onPress: () => deleteUserAccount() }, + ], + ) + }} + /> + + + + + ) + + return ( + <> + + {AccountDeletionModal} + + ) +} + +const useStyles = makeStyles(({ colors }) => ({ + view: { + marginHorizontal: 20, + backgroundColor: colors.grey5, + padding: 20, + borderRadius: 20, + display: "flex", + flexDirection: "column", + rowGap: 20, + }, + textInput: { + fontSize: 16, + backgroundColor: colors.grey4, + padding: 12, + color: colors.black, + borderRadius: 8, + }, + actionButtons: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }, +})) diff --git a/app/screens/settings-screen/account/settings/email.tsx b/app/screens/settings-screen/account/settings/email.tsx new file mode 100644 index 0000000000..76861f44a5 --- /dev/null +++ b/app/screens/settings-screen/account/settings/email.tsx @@ -0,0 +1,185 @@ +import { Alert, View } from "react-native" + +import { gql } from "@apollo/client" +import { GaloyIconButton } from "@app/components/atomic/galoy-icon-button" +import { + useUserEmailDeleteMutation, + useUserEmailRegistrationInitiateMutation, +} from "@app/graphql/generated" +import { useI18nContext } from "@app/i18n/i18n-react" +import { TranslationFunctions } from "@app/i18n/i18n-types" +import { RootStackParamList } from "@app/navigation/stack-param-lists" +import { toastShow } from "@app/utils/toast" +import { useNavigation } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" +import { makeStyles } from "@rneui/themed" + +import { SettingsRow } from "../../row" +import { useLoginMethods } from "../login-methods-hook" + +gql` + mutation userEmailDelete { + userEmailDelete { + errors { + message + } + me { + id + phone + totpEnabled + email { + address + verified + } + } + } + } + + mutation userEmailRegistrationInitiate($input: UserEmailRegistrationInitiateInput!) { + userEmailRegistrationInitiate(input: $input) { + errors { + message + } + emailRegistrationId + me { + id + email { + address + verified + } + } + } + } +` + +const title = ( + email: string | undefined, + emailVerified: boolean, + LL: TranslationFunctions, +): string => { + if (email) { + if (emailVerified) return LL.AccountScreen.email() + return LL.AccountScreen.unverifiedEmail() + } + return LL.AccountScreen.tapToAddEmail() +} + +export const EmailSetting: React.FC = () => { + const styles = useStyles() + + const { LL } = useI18nContext() + const { navigate } = useNavigation>() + + const { loading, email, emailVerified, bothEmailAndPhoneVerified } = useLoginMethods() + + const [emailDeleteMutation, { loading: emDelLoading }] = useUserEmailDeleteMutation() + const [setEmailMutation, { loading: emRegLoading }] = + useUserEmailRegistrationInitiateMutation() + + const deleteEmail = async () => { + try { + await emailDeleteMutation() + toastShow({ + type: "success", + message: LL.AccountScreen.emailDeletedSuccessfully(), + LL, + }) + } catch (err) { + Alert.alert(LL.common.error(), err instanceof Error ? err.message : "") + } + } + + const deleteEmailPrompt = async () => { + Alert.alert( + LL.AccountScreen.deleteEmailPromptTitle(), + LL.AccountScreen.deleteEmailPromptContent(), + [ + { text: LL.common.cancel(), onPress: () => {} }, + { + text: LL.common.yes(), + onPress: async () => { + deleteEmail() + }, + }, + ], + ) + } + + const tryConfirmEmailAgain = async (email: string) => { + try { + await emailDeleteMutation({ + // to avoid flacky behavior + // this could lead to inconsistent state if delete works but set fails + fetchPolicy: "no-cache", + }) + + const { data } = await setEmailMutation({ + variables: { input: { email } }, + }) + + const errors = data?.userEmailRegistrationInitiate.errors + if (errors && errors.length > 0) { + Alert.alert(errors[0].message) + } + + const emailRegistrationId = data?.userEmailRegistrationInitiate.emailRegistrationId + + if (emailRegistrationId) { + navigate("emailRegistrationValidate", { + emailRegistrationId, + email, + }) + } else { + console.warn("no flow returned") + } + } catch (err) { + console.error(err, "error in setEmailMutation") + } + } + + const reVerifyEmailPrompt = () => { + if (!email) return + Alert.alert( + LL.AccountScreen.emailUnverified(), + LL.AccountScreen.emailUnverifiedContent(), + [ + { text: LL.common.cancel(), onPress: () => {} }, + { + text: LL.common.ok(), + onPress: () => tryConfirmEmailAgain(email), + }, + ], + ) + } + + const RightIcon = email ? ( + + {!emailVerified && ( + + )} + {(bothEmailAndPhoneVerified || (email && !emailVerified)) && ( + + )} + + ) : undefined + + return ( + navigate("emailRegistrationInitiate")} + rightIcon={RightIcon} + /> + ) +} + +const useStyles = makeStyles(() => ({ + sidetoside: { + display: "flex", + flexDirection: "row", + columnGap: 10, + }, +})) diff --git a/app/screens/settings-screen/account/settings/logout.tsx b/app/screens/settings-screen/account/settings/logout.tsx new file mode 100644 index 0000000000..a3564d2a11 --- /dev/null +++ b/app/screens/settings-screen/account/settings/logout.tsx @@ -0,0 +1,68 @@ +import { Alert } from "react-native" + +import useLogout from "@app/hooks/use-logout" +import { useI18nContext } from "@app/i18n/i18n-react" +import { RootStackParamList } from "@app/navigation/stack-param-lists" +import { useNavigation } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" + +import { SettingsButton } from "../../button" +import { useLoginMethods } from "../login-methods-hook" + +export const LogOut = () => { + const navigation = useNavigation>() + + const { phone, bothEmailAndPhoneVerified, email, emailVerified } = useLoginMethods() + const { LL } = useI18nContext() + + const { logout } = useLogout() + + const logoutAlert = () => { + const logAlertContent = () => { + if (phone && email && bothEmailAndPhoneVerified) { + return LL.AccountScreen.logoutAlertContentPhoneEmail({ + phoneNumber: phone, + email, + }) + } else if (email && emailVerified) { + return LL.AccountScreen.logoutAlertContentEmail({ email }) + } + // phone verified + if (phone) return LL.AccountScreen.logoutAlertContentPhone({ phoneNumber: phone }) + console.error("Phone and email both not verified - Impossible to reach") + } + + Alert.alert(LL.AccountScreen.logoutAlertTitle(), logAlertContent(), [ + { + text: LL.common.cancel(), + style: "cancel", + }, + { + text: LL.AccountScreen.IUnderstand(), + onPress: logoutAction, + }, + ]) + } + + const logoutAction = async () => { + await logout() + navigation.reset({ + index: 0, + routes: [{ name: "getStarted" }], + }) + Alert.alert(LL.common.loggedOut(), "", [ + { + text: LL.common.ok(), + onPress: () => {}, + }, + ]) + } + + return ( + + ) +} diff --git a/app/screens/settings-screen/account/settings/phone.tsx b/app/screens/settings-screen/account/settings/phone.tsx new file mode 100644 index 0000000000..706e5d098f --- /dev/null +++ b/app/screens/settings-screen/account/settings/phone.tsx @@ -0,0 +1,94 @@ +import { Alert } from "react-native" + +import { gql } from "@apollo/client" +import { GaloyIconButton } from "@app/components/atomic/galoy-icon-button" +import { useUserPhoneDeleteMutation } from "@app/graphql/generated" +import { useI18nContext } from "@app/i18n/i18n-react" +import { RootStackParamList } from "@app/navigation/stack-param-lists" +import { toastShow } from "@app/utils/toast" +import { useNavigation } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" + +import { SettingsRow } from "../../row" +import { useLoginMethods } from "../login-methods-hook" + +gql` + mutation userPhoneDelete { + userPhoneDelete { + errors { + message + } + me { + id + phone + totpEnabled + email { + address + verified + } + } + } + } +` + +export const PhoneSetting: React.FC = () => { + const { LL } = useI18nContext() + const { navigate } = useNavigation>() + + const { loading, phone, emailVerified, phoneVerified } = useLoginMethods() + + const [phoneDeleteMutation, { loading: phoneDeleteLoading }] = + useUserPhoneDeleteMutation() + + const deletePhone = async () => { + try { + await phoneDeleteMutation() + toastShow({ + message: LL.AccountScreen.phoneDeletedSuccessfully(), + LL, + type: "success", + }) + } catch (err) { + Alert.alert(LL.common.error(), err instanceof Error ? err.message : "") + } + } + const deletePhonePrompt = async () => { + Alert.alert( + LL.AccountScreen.deletePhonePromptTitle(), + LL.AccountScreen.deletePhonePromptContent(), + [ + { text: LL.common.cancel(), onPress: () => {} }, + { + text: LL.common.yes(), + onPress: async () => { + deletePhone() + }, + }, + ], + ) + } + + return ( + navigate("phoneRegistrationInitiate")} + spinner={phoneDeleteLoading} + rightIcon={ + phoneVerified ? ( + emailVerified ? ( + + ) : null + ) : ( + "chevron-forward" + ) + } + /> + ) +} diff --git a/app/screens/settings-screen/account/settings/upgrade-trial-account.tsx b/app/screens/settings-screen/account/settings/upgrade-trial-account.tsx new file mode 100644 index 0000000000..4335e1f823 --- /dev/null +++ b/app/screens/settings-screen/account/settings/upgrade-trial-account.tsx @@ -0,0 +1,74 @@ +import { useState } from "react" +import { View } from "react-native" + +import { GaloyIcon } from "@app/components/atomic/galoy-icon" +import { GaloySecondaryButton } from "@app/components/atomic/galoy-secondary-button" +import { UpgradeAccountModal } from "@app/components/upgrade-account-modal" +import { AccountLevel, useLevel } from "@app/graphql/level-context" +import { useI18nContext } from "@app/i18n/i18n-react" +import { makeStyles, Text } from "@rneui/themed" + +import { useShowWarningSecureAccount } from "../show-warning-secure-account-hook" + +export const UpgradeTrialAccount: React.FC = () => { + const styles = useStyles() + const { currentLevel } = useLevel() + const { LL } = useI18nContext() + const hasBalance = useShowWarningSecureAccount() + + const [upgradeAccountModalVisible, setUpgradeAccountModalVisible] = useState(false) + const closeUpgradeAccountModal = () => setUpgradeAccountModalVisible(false) + const openUpgradeAccountModal = () => setUpgradeAccountModalVisible(true) + + if (currentLevel !== AccountLevel.Zero) return <> + + return ( + <> + + + + + {LL.common.trialAccount()} + + + + {LL.AccountScreen.itsATrialAccount()} + {hasBalance && ( + ⚠️ {LL.AccountScreen.fundsMoreThan5Dollars()} + )} + + + + ) +} + +const useStyles = makeStyles(({ colors }) => ({ + container: { + borderRadius: 20, + backgroundColor: colors.grey5, + padding: 16, + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "flex-start", + rowGap: 10, + }, + selfCenter: { alignSelf: "center" }, + sideBySide: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + width: "100%", + marginBottom: 4, + }, +})) diff --git a/app/screens/settings-screen/account/settings/upgrade.tsx b/app/screens/settings-screen/account/settings/upgrade.tsx new file mode 100644 index 0000000000..5639de44a1 --- /dev/null +++ b/app/screens/settings-screen/account/settings/upgrade.tsx @@ -0,0 +1,24 @@ +import { AccountLevel, useLevel } from "@app/graphql/level-context" +import { useI18nContext } from "@app/i18n/i18n-react" +import { RootStackParamList } from "@app/navigation/stack-param-lists" +import { useNavigation } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" + +import { SettingsRow } from "../../row" + +export const UpgradeAccountLevelOne: React.FC = () => { + const { currentLevel } = useLevel() + const { LL } = useI18nContext() + + const { navigate } = useNavigation>() + + if (currentLevel !== AccountLevel.One) return <> + + return ( + navigate("fullOnboardingFlow")} + /> + ) +} diff --git a/app/screens/settings-screen/show-warning-secure-account.tsx b/app/screens/settings-screen/account/show-warning-secure-account-hook.ts similarity index 93% rename from app/screens/settings-screen/show-warning-secure-account.tsx rename to app/screens/settings-screen/account/show-warning-secure-account-hook.ts index 7dd9d05869..574954eb17 100644 --- a/app/screens/settings-screen/show-warning-secure-account.tsx +++ b/app/screens/settings-screen/account/show-warning-secure-account-hook.ts @@ -35,11 +35,11 @@ export const useShowWarningSecureAccount = () => { const isAuthed = useIsAuthed() const { data } = useWarningSecureAccountQuery({ - fetchPolicy: "cache-first", + fetchPolicy: "cache-and-network", skip: !isAuthed, }) - if (data?.me?.defaultAccount.level !== "ZERO") return false + if (data?.me?.defaultAccount?.level !== "ZERO") return false const btcWallet = getBtcWallet(data?.me?.defaultAccount?.wallets) const usdWallet = getUsdWallet(data?.me?.defaultAccount?.wallets) diff --git a/app/screens/settings-screen/button.tsx b/app/screens/settings-screen/button.tsx new file mode 100644 index 0000000000..8373709db8 --- /dev/null +++ b/app/screens/settings-screen/button.tsx @@ -0,0 +1,41 @@ +import { testProps } from "@app/utils/testProps" +import { Button, Skeleton, makeStyles } from "@rneui/themed" + +type Props = { + title: string + onPress: () => void + variant: "warning" | "danger" + loading?: boolean +} + +export const SettingsButton: React.FC = ({ title, onPress, variant, loading }) => { + const styles = useStyles(variant) + + if (loading) return + + return ( +