diff --git a/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Automation/index.md b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Automation/index.md index 89c65c4c757..219fd683465 100644 --- a/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Automation/index.md +++ b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Automation/index.md @@ -23,6 +23,7 @@ Follow instructions for FE [Development Setup Instructions](https://department-o ### Local setup for detox 1. Check for the [detox pre-reqs](https://wix.github.io/Detox/docs/introduction/getting-started#detox-prerequisites) on your local machine and install if needed using the `yarn` package manager. + - If needed find and delete the detoxrc.js file. This file is sometimes created when you initially install detox and is not needed because the VA Mobile App uses .detoxrc.json file. 2. Check that the emulators used by the script ([listed in .detoxrc.json](https://github.com/department-of-veterans-affairs/va-mobile-app/blob/develop/VAMobile/.detoxrc.json#L17)) are installed on your machine, and install them if not. [Helpful instructions from detox](https://wix.github.io/Detox/docs/introduction/project-setup#step-3-device-configs) - If you need to create a new Android emulator, make sure to bump up the internal storage (default is 800, bumping to 8000 definitely works). If you don't do this, you'll get an out of storage error. One path that works for this: Shift-Shift > search "Virtual Device Manager" > {create new device or edit existing device} > Show Advanced Settings > scroll down to the "Memory and Storage" section to find the "Internal Storage" field 3. Build and Run App diff --git a/VAMobile/documentation/docs/UX/Foundations/Information-Architecture.md b/VAMobile/documentation/docs/UX/Foundations/Information-Architecture.md index ee1952145be..46fbec3f811 100644 --- a/VAMobile/documentation/docs/UX/Foundations/Information-Architecture.md +++ b/VAMobile/documentation/docs/UX/Foundations/Information-Architecture.md @@ -16,10 +16,10 @@ The following guiding principles are used to help Veterans find information and * **Informed by Veterans:** IA decisions are based on user research conducted with Veterans, examining their mental models around the organization of common tasks and undertanding of labels, as well as getting feedback on proposed solutions. * **Keeps it simple:** Content is prioiritized and includes only what’s absolutely necessary—the fewer elements (number of levels in the hierarchy, number of screens, number of links on a screen) the better. -References: -[VA.gov Information Architecture (IA) team](https://github.com/department-of-veterans-affairs/va.gov-team/tree/69833737d9fe22b8990bb987e7c50de13205c5d5/platform/information-architecture) -[Best Practices for IA on VA.gov](https://github.com/department-of-veterans-affairs/va.gov-team/blob/master/platform/information-architecture/ia-best-practices.md) -[VA.gov Design principles](https://design.va.gov/about/principles) +### References: +* [VA.gov Information Architecture (IA) team](https://github.com/department-of-veterans-affairs/va.gov-team/tree/69833737d9fe22b8990bb987e7c50de13205c5d5/platform/information-architecture) +* [Best Practices for IA on VA.gov](https://github.com/department-of-veterans-affairs/va.gov-team/blob/69833737d9fe22b8990bb987e7c50de13205c5d5/platform/information-architecture/ia-best-practices.md) +* [VA.gov Design principles](https://design.va.gov/about/principles) @@ -68,6 +68,6 @@ The VA Health and Benefits app’s IA contains four top level categories: Home, ## Background The VA Health and Benefits app’s Information Architecture and navigation model are based on the findings and output from a multi-stage, collaborative and cross-functional design and research process: [Information Architecture and Navigation - High Level Project Summary](https://github.com/department-of-veterans-affairs/va.gov-team/blob/master/products/va-mobile-app/ux-design/information-architecture-navigation/High%20Level%20Project%20Summary.md) -* [**Phase I:** Two rounds of card sorting](https://github.com/department-of-veterans-affairs/va.gov-team/tree/master/products/va-mobile-app/ux-research/information-architecture) (open and closed) with Veterans +* [**Phase I:** Two rounds of card sorting](https://github.com/department-of-veterans-affairs/va.gov-team/tree/master/products/va-mobile-app/ux-design/information-architecture-navigation#phase-i---information-architecture-research) (open and closed) with Veterans * [**Phase II:** Navigation model design exploration](https://github.com/department-of-veterans-affairs/va.gov-team/tree/master/products/va-mobile-app/ux-design/information-architecture-navigation#phase-ii---navigation-model-exploration--implementation), audit and comparative analysis -* [**Phase III:** Evaluative testing](https://github.com/department-of-veterans-affairs/va.gov-team/tree/master/products/va-mobile-app/ux-research/usability-testing/new%20navigation%20usability) with Veterans, including a usability study of the proposed navigation model and sitemap reflected through a low-fidelity prototype \ No newline at end of file +* [**Phase III:** Evaluative testing](https://github.com/department-of-veterans-affairs/va.gov-team/tree/master/products/va-mobile-app/ux-design/information-architecture-navigation#phase-iii----evaluative-research) with Veterans, including a usability study of the proposed navigation model and sitemap reflected through a low-fidelity prototype \ No newline at end of file diff --git a/VAMobile/documentation/docs/UX/Foundations/Research/index.md b/VAMobile/documentation/docs/UX/Foundations/Research/index.md index 76e85e5dc29..738dd61966f 100644 --- a/VAMobile/documentation/docs/UX/Foundations/Research/index.md +++ b/VAMobile/documentation/docs/UX/Foundations/Research/index.md @@ -59,9 +59,9 @@ The research study lead uses the information provided from Perigean, the VA’s - [Sign Up Sheet Template](https://docs.google.com/spreadsheets/d/1N8pTY9LEujEzMWVuI5Bzf0_9yJ_2JpHt/edit?usp=sharing&ouid=118400044101943019619&rtpof=true&sd=true) ## VA Mobile app research -* [VA mobile app UX research on Github](https://github.com/department-of-veterans-affairs/va.gov-team/tree/master/products/va-mobile-app/ux-research) +* [VA mobile app UX research on Github](https://github.com/department-of-veterans-affairs/va.gov-team/tree/777d2cbccd0121a76011b7b43b74720c875f4f34/products/va-mobile-app/research/ux) Research plans and findings for VA mobile app features and research efforts, 2020 to present. -* [Conducting remote User Research with a pre-release production app build & user credentials](https://github.com/department-of-veterans-affairs/va.gov-team/blob/master/products/va-mobile-app/ux-research/research-operations/research-pre-release-prod-app-build-how-to.md) +* [Conducting remote User Research with a pre-release production app build & user credentials](https://github.com/department-of-veterans-affairs/va.gov-team/blob/cffaa1b43377935e24b90dd6b5b21d4135cf67d4/products/va-mobile-app/research/ux/research-operations/research-pre-release-prod-app-build-how-to.md) Planning tips and a template for conducting remote research with a pre-release production app build. ## VA Research resources diff --git a/VAMobile/documentation/docs/UX/How-We-Work/designing-ui.md b/VAMobile/documentation/docs/UX/How-We-Work/designing-ui.md index ef42b3d57ef..fab6f3ba9f4 100644 --- a/VAMobile/documentation/docs/UX/How-We-Work/designing-ui.md +++ b/VAMobile/documentation/docs/UX/How-We-Work/designing-ui.md @@ -102,7 +102,7 @@ Once designs are ready to be handed off to the engineering team, you can review - If you notice that QA engineering is not finding bugs that should have been caught in earlier tickets, let QA know, so they can improve their work. ### Launching -- For major features, team members should expect to work with the [Design Librarian](https://department-of-veterans-affairs.github.io/va-mobile-app/docs/UX/How-We-Work/design-librarian) and the Product team to prepare the [app store content](https://didactic-robot-p9wpqpp4wwh99w-3000.app.github.dev/va-mobile-app/docs/Operations/Updating%20the%20App%20Stores). +- For major features, team members should expect to work with the [Design Librarian](https://department-of-veterans-affairs.github.io/va-mobile-app/docs/UX/How-We-Work/design-librarian) and the Product team to prepare the [app store content](https://department-of-veterans-affairs.github.io/va-mobile-app/docs/Operations/Updating%20the%20App%20Stores). ## Moving/Publishing Work diff --git a/VAMobile/documentation/docs/UX/Resources.md b/VAMobile/documentation/docs/UX/Resources.md index 7a3a2b42c48..96f495628b5 100644 --- a/VAMobile/documentation/docs/UX/Resources.md +++ b/VAMobile/documentation/docs/UX/Resources.md @@ -8,18 +8,17 @@ VA Mobile app [UX resources on github](https://github.com/department-of-veterans ## Design ### Figma -#### [VA Mobile Team](https://www.figma.com/files/team/1114266503868297401) +#### [VA Mobile Team](https://www.figma.com/files/project/301450677) * Current features, resources, libraries, and design explorations. -#### [Design Library](https://www.figma.com/files/827597988283174959/project/60961499/%F0%9F%93%90-Design-Library?fuid=1114240858371616544) +#### [Design Library](https://www.figma.com/design/QVLPB3eOunmKrgQOuOt0SU/%F0%9F%93%90-Flagship-Library---Resource---VA-Mobile?m=auto) * Finalized components, text styles, and color styles. #### 🚢 Shipped files * Example screens that are in production. - * [Global](https://www.figma.com/file/PpHk9Yyw8dC9xj38AeR9pL/%F0%9F%9A%A2-Global-2.0---Shipped---VA-Mobile?t=i6c9U7y2iqNodqgF-1) - * [Home](https://www.figma.com/file/ddMWiCQCfmUKFhMcYG9fYv/%F0%9F%9A%A2-Home-2.0---Shipped---VA-Mobile?t=i6c9U7y2iqNodqgF-1) & [Profile](https://www.figma.com/file/O6sdr5N7xV6GOZhTPcgY5x/%F0%9F%9A%A2-Profile-2.0---Shipped---VA-Mobile) - * [Benefits](https://www.figma.com/file/p0vlRz38TKIOwWDfI2bGc7/%F0%9F%9A%A2-Benefits-2.0---Shipped---VA-Mobile?t=i6c9U7y2iqNodqgF-1) - * [Health](https://www.figma.com/file/JQAoUBxvSWCzKvu2ifRRE7/%F0%9F%9A%A2-Health-2.0---Shipped---VA-Mobile?t=i6c9U7y2iqNodqgF-1) - * [Payments](https://www.figma.com/file/yhGsaPc2px6eCVzXhuOwm4/%F0%9F%9A%A2-Payments-2.0---Shipped---VA-Mobile?t=i6c9U7y2iqNodqgF-1) + * [Home](https://www.figma.com/design/ddMWiCQCfmUKFhMcYG9fYv/Home-2.0---%F0%9F%9A%A2-Shipped---VA-Mobile?m=auto&t=2n0WqYtVYQ75swD8-6) & [Profile](https://www.figma.com/design/O6sdr5N7xV6GOZhTPcgY5x/Profile-2.0---%F0%9F%9A%A2-Shipped---VA-Mobile?m=auto&t=2n0WqYtVYQ75swD8-6) + * [Benefits](https://www.figma.com/design/p0vlRz38TKIOwWDfI2bGc7/Benefits-2.0---%F0%9F%9A%A2-Shipped---VA-Mobile?m=auto&t=2n0WqYtVYQ75swD8-6) + * [Health](https://www.figma.com/design/JQAoUBxvSWCzKvu2ifRRE7/Health-2.0---%F0%9F%9A%A2-Shipped---VA-Mobile?m=auto&t=2n0WqYtVYQ75swD8-6) + * [Payments](https://www.figma.com/design/yhGsaPc2px6eCVzXhuOwm4/Payments-2.0---%F0%9F%9A%A2-Shipped---VA-Mobile?m=auto&t=2n0WqYtVYQ75swD8-6) ### Balsamiq * [UX brainstorm & work](https://balsamiq.cloud/s4uw4la/pnnwuqv) @@ -32,7 +31,7 @@ VA Mobile app [UX resources on github](https://github.com/department-of-veterans ## Research -* [UX research on Github](https://github.com/department-of-veterans-affairs/va.gov-team/tree/master/products/va-mobile-app/ux-research) +* [UX research on Github](https://github.com/department-of-veterans-affairs/va.gov-team/tree/4b1fcf3124f72b518e91c0af83723bd270ff793f/products/va-mobile-app/research/ux) * Research folders for all VA Mobile app studies. ## VA Design System diff --git a/VAMobile/documentation/src/components/HomepageFeatures.tsx b/VAMobile/documentation/src/components/HomepageFeatures.tsx index 8dda3971190..966bc0d9bab 100644 --- a/VAMobile/documentation/src/components/HomepageFeatures.tsx +++ b/VAMobile/documentation/src/components/HomepageFeatures.tsx @@ -23,13 +23,14 @@ const baseUrl = '/va-mobile-app' const FeatureList: FeatureItem[] = [ { - title: 'VA: Health and Benefits App', + title: 'VA: Health and Benefits', image: `${baseUrl}/img/va-logo.png`, alt: `Department of Veteran Affairs logo`, useMobileImageCss: true, description: ( <> - The mobile app allows Veterans to more easily complete key transactions across VA health and benefits services. + With the VA: Health and Benefits app, Veterans can manage the VA health care and benefits tasks they do most + often—all in one simple app. ), }, diff --git a/VAMobile/e2e/tests/utils.ts b/VAMobile/e2e/tests/utils.ts index 564ed270373..7754cbcdd3d 100644 --- a/VAMobile/e2e/tests/utils.ts +++ b/VAMobile/e2e/tests/utils.ts @@ -147,9 +147,9 @@ export const CommonE2eIdConstants = { } /** Logs into demo mode. -* @param skipOnboarding: Boolean value that defaults to true. Set this to false if you want the detox test to view the onboarding carasoul on login -* @param pushNotifications: Boolean value that tells the detox tests whether to turn on/off push notifications -* */ + * @param skipOnboarding: Boolean value that defaults to true. Set this to false if you want the detox test to view the onboarding carasoul on login + * @param pushNotifications: Boolean value that tells the detox tests whether to turn on/off push notifications + * */ export async function loginToDemoMode(skipOnboarding = true, pushNotifications?: boolean) { try { await waitFor(element(by.id(CommonE2eIdConstants.VA_LOGO_ICON_ID))) @@ -460,10 +460,10 @@ export async function backButton(backButtonName: string) { } /** Enables the availibility banner. -* @param AFFeature: Name of the AF waygate. -* @param AFUseCase: Name of the AF type. -* @param AFAppUpdate: Boolean value that tells the script whether to enable the update now button or not -* */ + * @param AFFeature: Name of the AF waygate. + * @param AFUseCase: Name of the AF type. + * @param AFAppUpdate: Boolean value that tells the script whether to enable the update now button or not + * */ export async function enableAF(AFFeature, AFUseCase, AFAppUpdate = false) { await device.launchApp({ newInstance: true, permissions: { notifications: 'YES' } }) await loginToDemoMode() @@ -543,11 +543,11 @@ export async function enableAF(AFFeature, AFUseCase, AFAppUpdate = false) { } /** Disables the availibility banner. -* @param featureNavigationArray: Array that tells the AF script how to navigate to the feature -* @param AFFeature: Name of the AF waygate. -* @param AFUseCaseName: Name of the AF type. -* @param AFAppUpdate: Boolean value that tells the script whether to enable the update now button or not -* */ + * @param featureNavigationArray: Array that tells the AF script how to navigate to the feature + * @param AFFeature: Name of the AF waygate. + * @param AFUseCaseName: Name of the AF type. + * @param AFAppUpdate: Boolean value that tells the script whether to enable the update now button or not + * */ export async function disableAF(featureNavigationArray, AFFeature, AFFeatureName, AFUseCaseName) { if (AFUseCaseName === 'AllowFunction') { await element(by.id(CommonE2eIdConstants.HOME_TAB_BUTTON_ID)).tap() @@ -583,7 +583,7 @@ export async function disableAF(featureNavigationArray, AFFeature, AFFeatureName } /** Function that allows the AF script to navigate to a certain feature -* */ + * */ const navigateToFeature = async (featureNavigationArray) => { for (let j = 2; j < featureNavigationArray.length; j++) { if (featureNavigationArray[j] === 'Talk to the Veterans Crisis Line now') { @@ -652,10 +652,10 @@ const navigateToFeature = async (featureNavigationArray) => { } /** Verifies that the availibility banner is the correct type and is populated with the correct information. -* @param featureNavigationArray: Array that tells the AF script how to navigate to the feature -* @param AFUseCaseName: Name of the AF type. -* @param AFUseCaseUpgrade: Boolean value that tells the script whether to enable the update now button or not -* */ + * @param featureNavigationArray: Array that tells the AF script how to navigate to the feature + * @param AFUseCaseName: Name of the AF type. + * @param AFUseCaseUpgrade: Boolean value that tells the script whether to enable the update now button or not + * */ export async function verifyAF(featureNavigationArray, AFUseCase, AFUseCaseUpgrade = false) { let featureName if (AFUseCase !== 'AllowFunction') { @@ -718,7 +718,7 @@ export async function verifyAF(featureNavigationArray, AFUseCase, AFUseCaseUpgra /** Toggle the specified remote config feature flag * @param flagName - name of flag to toggle -* */ + * */ export async function toggleRemoteConfigFlag(flagName: string) { await loginToDemoMode() await openProfile() diff --git a/VAMobile/src/App.tsx b/VAMobile/src/App.tsx index ad4b79a1d32..21b0b0bc349 100644 --- a/VAMobile/src/App.tsx +++ b/VAMobile/src/App.tsx @@ -16,7 +16,11 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native' import { createStackNavigator } from '@react-navigation/stack' -import { SnackbarProvider, useIsScreenReaderEnabled, useSnackbar } from '@department-of-veterans-affairs/mobile-component-library' +import { + SnackbarProvider, + useIsScreenReaderEnabled, + useSnackbar, +} from '@department-of-veterans-affairs/mobile-component-library' import { ActionSheetProvider, connectActionSheet } from '@expo/react-native-action-sheet' import { QueryClientProvider } from '@tanstack/react-query' import { ThemeProvider } from 'styled-components' @@ -104,7 +108,7 @@ export type RootNavStackParamList = WebviewStackParams & { SubmitEvidenceSubtask: { claimID: string } - InAppFeedback: { task: string } + InAppFeedback: { screen: string } Tabs: undefined } diff --git a/VAMobile/src/api/appointments/cancelAppointment.tsx b/VAMobile/src/api/appointments/cancelAppointment.tsx index cdb8f223ef0..0c9a34c7c0a 100644 --- a/VAMobile/src/api/appointments/cancelAppointment.tsx +++ b/VAMobile/src/api/appointments/cancelAppointment.tsx @@ -6,7 +6,7 @@ import { DEFAULT_UPCOMING_DAYS_LIMIT, TimeFrameTypeConstants } from 'constants/a import { put } from 'store/api' import { logNonFatalErrorToFirebase } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { appointmentsKeys } from './queryKeys' @@ -30,7 +30,7 @@ const cancelAppointment = (cancelID: string) => { */ export const useCancelAppointment = () => { const queryClient = useQueryClient() - + const registerReviewEvent = useReviewEvent() return useMutation({ mutationFn: cancelAppointment, onSuccess(_, variables) { diff --git a/VAMobile/src/api/claimsAndAppeals/downloadEFolderDocument.tsx b/VAMobile/src/api/claimsAndAppeals/downloadEFolderDocument.tsx index 13a87ff846c..43acad02003 100644 --- a/VAMobile/src/api/claimsAndAppeals/downloadEFolderDocument.tsx +++ b/VAMobile/src/api/claimsAndAppeals/downloadEFolderDocument.tsx @@ -6,7 +6,7 @@ import store from 'store' import { DEMO_MODE_LETTER_ENDPOINT } from 'store/api/demo/letters' import getEnv from 'utils/env' import { downloadDemoFile, downloadFile } from 'utils/filesystem' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { claimsAndAppealsKeys } from './queryKeys' @@ -15,14 +15,18 @@ const { API_ROOT } = getEnv() /** * Fetch user E Folder Document */ -const downloadEFolderDocument = async (id: string, fileName: string): Promise => { +const downloadEFolderDocument = async ( + id: string, + fileName: string, + func: () => Promise, +): Promise => { const eFolderDocumentAPI = `${API_ROOT}/v0/efolder/documents/${id}/download?file_name=${fileName}}` const filePath = store.getState().demo.demoMode ? await downloadDemoFile(DEMO_MODE_LETTER_ENDPOINT, fileName) : await downloadFile('POST', eFolderDocumentAPI, fileName, undefined, 1) if (filePath) { - await FileViewer.open(filePath, { onDismiss: () => registerReviewEvent() }) + await FileViewer.open(filePath, { onDismiss: () => func() }) return true } } @@ -31,10 +35,11 @@ const downloadEFolderDocument = async (id: string, fileName: string): Promise { + const registerReviewEvent = useReviewEvent(false) return useQuery({ enabled: false, queryKey: [claimsAndAppealsKeys.eFolderDownloadDoc, id, fileName], - queryFn: () => downloadEFolderDocument(id, fileName), + queryFn: () => downloadEFolderDocument(id, fileName, registerReviewEvent), meta: { errorName: 'downloadEFolderDocument: Service error', }, diff --git a/VAMobile/src/api/contactInformation/deleteEmail.tsx b/VAMobile/src/api/contactInformation/deleteEmail.tsx index 64f235ccb1e..161bd464faa 100644 --- a/VAMobile/src/api/contactInformation/deleteEmail.tsx +++ b/VAMobile/src/api/contactInformation/deleteEmail.tsx @@ -5,7 +5,7 @@ import { Events, UserAnalytics } from 'constants/analytics' import { Params as APIParams, EditResponseData, del } from 'store/api' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { contactInformationKeys } from './queryKeys' @@ -20,6 +20,7 @@ const deleteEmail = (emailData: EmailData) => { * Returns a mutation for deleting an email */ export const useDeleteEmail = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() return useMutation({ diff --git a/VAMobile/src/api/contactInformation/saveAddress.tsx b/VAMobile/src/api/contactInformation/saveAddress.tsx index d46b837d376..c585833281a 100644 --- a/VAMobile/src/api/contactInformation/saveAddress.tsx +++ b/VAMobile/src/api/contactInformation/saveAddress.tsx @@ -5,7 +5,7 @@ import { Events, UserAnalytics } from 'constants/analytics' import { Params as APIParams, EditResponseData, post, put } from 'store/api' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { contactInformationKeys } from './queryKeys' import { validateAddress } from './validateAddress' @@ -32,6 +32,7 @@ export const saveAddress = async ({ addressData, revalidate }: SaveAddressParame * Returns a mutation for saving an address */ export const useSaveAddress = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() return useMutation({ diff --git a/VAMobile/src/api/contactInformation/saveEmail.tsx b/VAMobile/src/api/contactInformation/saveEmail.tsx index e6f26f6e4e2..fbd683c2ba4 100644 --- a/VAMobile/src/api/contactInformation/saveEmail.tsx +++ b/VAMobile/src/api/contactInformation/saveEmail.tsx @@ -5,7 +5,7 @@ import { Events, UserAnalytics } from 'constants/analytics' import { Params as APIParams, EditResponseData, post, put } from 'store/api' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { contactInformationKeys } from './queryKeys' @@ -26,6 +26,7 @@ const saveEmail = (emailData: SaveEmailData) => { * Returns a mutation for saving an email */ export const useSaveEmail = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() return useMutation({ diff --git a/VAMobile/src/api/contactInformation/savePhoneNumber.tsx b/VAMobile/src/api/contactInformation/savePhoneNumber.tsx index 0eb91a55b67..38ec6b93960 100644 --- a/VAMobile/src/api/contactInformation/savePhoneNumber.tsx +++ b/VAMobile/src/api/contactInformation/savePhoneNumber.tsx @@ -5,7 +5,7 @@ import { Events, UserAnalytics } from 'constants/analytics' import { Params as APIParams, EditResponseData, post, put } from 'store/api' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { contactInformationKeys } from './queryKeys' @@ -26,6 +26,7 @@ const savePhoneNumber = (phoneData: PhoneData) => { * Returns a mutation for saving a phone number */ export const useSavePhoneNumber = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() return useMutation({ diff --git a/VAMobile/src/api/decisionLetters/downloadDecisionLetter.tsx b/VAMobile/src/api/decisionLetters/downloadDecisionLetter.tsx index b707c33e2dc..11a75a69ebb 100644 --- a/VAMobile/src/api/decisionLetters/downloadDecisionLetter.tsx +++ b/VAMobile/src/api/decisionLetters/downloadDecisionLetter.tsx @@ -6,7 +6,7 @@ import store from 'store' import { DEMO_MODE_LETTER_ENDPOINT, DEMO_MODE_LETTER_NAME } from 'store/api/demo/letters' import getEnv from 'utils/env' import { downloadDemoFile, downloadFile } from 'utils/filesystem' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { decisionLettersKeys } from './queryKeys' @@ -15,14 +15,14 @@ const { API_ROOT } = getEnv() /** * Fetch user decision letter */ -const downloadDecisionLetter = async (id: string): Promise => { +const downloadDecisionLetter = async (id: string, func: () => Promise): Promise => { const escapedId = encodeURI(id) // escape chars like {} in document ID const decisionLettersEndpoint = `${API_ROOT}/v0/claims/decision-letters/${escapedId}/download` const filePath = store.getState().demo.demoMode ? await downloadDemoFile(DEMO_MODE_LETTER_ENDPOINT, DEMO_MODE_LETTER_NAME) : await downloadFile('GET', decisionLettersEndpoint, 'decision_letter.pdf', undefined, 3) if (filePath) { - await FileViewer.open(filePath, { onDismiss: () => registerReviewEvent() }) + await FileViewer.open(filePath, { onDismiss: () => func() }) return true } } @@ -31,10 +31,11 @@ const downloadDecisionLetter = async (id: string): Promise * Returns a query for a user decision letter */ export const useDownloadDecisionLetter = (id: string, options?: { enabled?: boolean }) => { + const registerReviewEvent = useReviewEvent(false) return useQuery({ ...options, queryKey: [decisionLettersKeys.downloadLetter, id], - queryFn: () => downloadDecisionLetter(id), + queryFn: () => downloadDecisionLetter(id, registerReviewEvent), meta: { errorName: 'downloadDecisionLetter: Service error', }, diff --git a/VAMobile/src/api/demographics/updateGenderIdentity.tsx b/VAMobile/src/api/demographics/updateGenderIdentity.tsx index 7dc00acb194..c4a573bc8fa 100644 --- a/VAMobile/src/api/demographics/updateGenderIdentity.tsx +++ b/VAMobile/src/api/demographics/updateGenderIdentity.tsx @@ -4,7 +4,7 @@ import { Events, UserAnalytics } from 'constants/analytics' import { put } from 'store/api' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { demographicsKeys } from './queryKeys' @@ -19,6 +19,7 @@ const updateGenderIdentity = (genderIdentity: string) => { * Returns a mutation for updating gender identity */ export const useUpdateGenderIdentity = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() return useMutation({ diff --git a/VAMobile/src/api/demographics/updatePreferredName.tsx b/VAMobile/src/api/demographics/updatePreferredName.tsx index ebf11905572..6ed209611a2 100644 --- a/VAMobile/src/api/demographics/updatePreferredName.tsx +++ b/VAMobile/src/api/demographics/updatePreferredName.tsx @@ -4,7 +4,7 @@ import { Events, UserAnalytics } from 'constants/analytics' import { put } from 'store/api' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { demographicsKeys } from './queryKeys' @@ -22,6 +22,7 @@ const updatePreferredName = (preferredName: string) => { * Returns a mutation for updating preferred name */ export const useUpdatePreferredName = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() return useMutation({ diff --git a/VAMobile/src/api/directDeposit/updateBankInfo.tsx b/VAMobile/src/api/directDeposit/updateBankInfo.tsx index 69eb5d8eaac..d0450509059 100644 --- a/VAMobile/src/api/directDeposit/updateBankInfo.tsx +++ b/VAMobile/src/api/directDeposit/updateBankInfo.tsx @@ -12,7 +12,7 @@ import { DemoState } from 'store/slices/demoSlice' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' import { getErrorKeys } from 'utils/errors' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { directDepositKeys } from './queryKeys' @@ -28,6 +28,7 @@ const updateBankInfo = (paymentAccountData: PaymentAccountData) => { * Returns a mutation for updating direct deposit information */ export const useUpdateBankInfo = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() const { demoMode } = useSelector((state) => state.demo) diff --git a/VAMobile/src/api/letters/downloadLetter.tsx b/VAMobile/src/api/letters/downloadLetter.tsx index be234faca9c..72a3dd2c71d 100644 --- a/VAMobile/src/api/letters/downloadLetter.tsx +++ b/VAMobile/src/api/letters/downloadLetter.tsx @@ -10,7 +10,7 @@ import { DEMO_MODE_LETTER_ENDPOINT, DEMO_MODE_LETTER_NAME } from 'store/api/demo import { logAnalyticsEvent, setAnalyticsUserProperty } from 'utils/analytics' import getEnv from 'utils/env' import { downloadDemoFile, downloadFile } from 'utils/filesystem' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { lettersKeys } from './queryKeys' @@ -22,6 +22,7 @@ const { API_ROOT } = getEnv() const downloadLetter = async ( letterType: LetterTypes, lettersOption: LettersDownloadParams, + func: () => Promise, ): Promise => { const lettersAPI = `${API_ROOT}/v0/letters/${letterType}/download` const filePath = store.getState().demo.demoMode @@ -30,7 +31,7 @@ const downloadLetter = async ( if (filePath) { logAnalyticsEvent(Events.vama_letter_download(letterType)) setAnalyticsUserProperty(UserAnalytics.vama_uses_letters()) - await FileViewer.open(filePath, { onDismiss: () => registerReviewEvent() }) + await FileViewer.open(filePath, { onDismiss: () => func() }) return true } } @@ -39,10 +40,11 @@ const downloadLetter = async ( * Returns a query for a user letter */ export const useDownloadLetter = (letterType: LetterTypes, lettersOption: LettersDownloadParams) => { + const registerReviewEvent = useReviewEvent(false) return useQuery({ enabled: false, queryKey: [lettersKeys.downloadLetter, letterType, lettersOption], - queryFn: () => downloadLetter(letterType, lettersOption), + queryFn: () => downloadLetter(letterType, lettersOption, registerReviewEvent), meta: { errorName: 'downloadLetter: Service error', }, diff --git a/VAMobile/src/api/prescriptions/requestRefills.tsx b/VAMobile/src/api/prescriptions/requestRefills.tsx index e959395f5ec..b1309d063a5 100644 --- a/VAMobile/src/api/prescriptions/requestRefills.tsx +++ b/VAMobile/src/api/prescriptions/requestRefills.tsx @@ -5,7 +5,7 @@ import { Events, UserAnalytics } from 'constants/analytics' import { put } from 'store/api' import { logAnalyticsEvent, logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { prescriptionKeys } from './queryKeys' @@ -30,6 +30,7 @@ const requestRefills = async (prescriptions: PrescriptionsList): Promise { + const registerReviewEvent = useReviewEvent(false, 'refillRequest') const queryClient = useQueryClient() return useMutation({ mutationFn: requestRefills, diff --git a/VAMobile/src/api/secureMessaging/saveDraft.tsx b/VAMobile/src/api/secureMessaging/saveDraft.tsx index 28fd2cb0da6..b99e8a8f7cc 100644 --- a/VAMobile/src/api/secureMessaging/saveDraft.tsx +++ b/VAMobile/src/api/secureMessaging/saveDraft.tsx @@ -5,7 +5,7 @@ import { UserAnalytics } from 'constants/analytics' import { Params, post, put } from 'store/api' import { logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { secureMessagingKeys } from './queryKeys' @@ -28,6 +28,7 @@ const saveDraft = ({ messageID, replyID, messageData }: SaveDraftParameters) => * Returns a mutation for saving a draft message */ export const useSaveDraft = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() return useMutation({ mutationFn: saveDraft, diff --git a/VAMobile/src/api/secureMessaging/sendMessage.tsx b/VAMobile/src/api/secureMessaging/sendMessage.tsx index 03c6bb3d3ad..4daaf22d7a7 100644 --- a/VAMobile/src/api/secureMessaging/sendMessage.tsx +++ b/VAMobile/src/api/secureMessaging/sendMessage.tsx @@ -5,7 +5,7 @@ import { UserAnalytics } from 'constants/analytics' import { Params, contentTypes, post } from 'store/api' import { logNonFatalErrorToFirebase, setAnalyticsUserProperty } from 'utils/analytics' import { isErrorObject } from 'utils/common' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { secureMessagingKeys } from './queryKeys' @@ -58,6 +58,7 @@ const sendMessage = ({ messageData, replyToID, uploads }: SendMessageParameters) * Returns a mutation for sending a message */ export const useSendMessage = () => { + const registerReviewEvent = useReviewEvent() const queryClient = useQueryClient() return useMutation({ mutationFn: sendMessage, diff --git a/VAMobile/src/components/Templates/FeatureLandingAndChildTemplate.tsx b/VAMobile/src/components/Templates/FeatureLandingAndChildTemplate.tsx index 5c735eccad9..018c30dc54e 100644 --- a/VAMobile/src/components/Templates/FeatureLandingAndChildTemplate.tsx +++ b/VAMobile/src/components/Templates/FeatureLandingAndChildTemplate.tsx @@ -1,4 +1,5 @@ import React, { FC, ReactNode, useState } from 'react' +import { useTranslation } from 'react-i18next' import { LayoutChangeEvent, StatusBar, View, ViewStyle, useWindowDimensions } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' @@ -6,6 +7,7 @@ import { useIsScreenReaderEnabled } from '@department-of-veterans-affairs/mobile import { HeaderButton, TextView, TextViewProps, WaygateWrapper } from 'components' import VAScrollView, { VAScrollViewProps } from 'components/VAScrollView' +import { NAMESPACE } from 'constants/namespaces' import { useTheme } from 'utils/hooks' import HeaderBanner, { HeaderBannerProps } from './HeaderBanner' @@ -59,6 +61,7 @@ export const ChildTemplate: FC = ({ const fontScale = useWindowDimensions().fontScale const theme = useTheme() const screenReaderEnabled = useIsScreenReaderEnabled() + const { t } = useTranslation(NAMESPACE.COMMON) const [scrollOffset, setScrollOffset] = useState(0) const [trackScrollOffset, setTrackScrollOffset] = useState(true) @@ -73,7 +76,9 @@ export const ChildTemplate: FC = ({ const headerProps: HeaderBannerProps = { leftButton: { text: backLabel, - a11yLabel: backLabelA11y, + a11yLabel: backLabelA11y + ? t('back.a11yLabel', { screenName: backLabelA11y }) + : t('back.a11yLabel', { screenName: backLabel }), testID: backLabelTestID, onPress: backLabelOnPress, descriptiveBack: true, diff --git a/VAMobile/src/components/Templates/HeaderBanner.tsx b/VAMobile/src/components/Templates/HeaderBanner.tsx index 05af9409a10..72ac97b8e9d 100644 --- a/VAMobile/src/components/Templates/HeaderBanner.tsx +++ b/VAMobile/src/components/Templates/HeaderBanner.tsx @@ -277,6 +277,7 @@ const HeaderBanner: FC = ({ {leftButton?.descriptiveBack ? ( { - // return { - // name: 'vama_feedback_page_entered', - // } - // }, - // vama_feedback_page_closed: (): Event => { - // return { - // name: 'vama_feedback_page_closed', - // } - // }, - // vama_feedback_submitted: (taskCompleted: string, satisfaction: string): Event => { - // return { - // name: 'vama_feedback_submitted', - // params: { - // taskCompleted, - // satisfaction, - // }, - // } - // }, + vama_feedback_ask: (screen: string, response: boolean): Event => { + return { + name: 'vama_feedback_ask', + params: { + screen, + response, + }, + } + }, + vama_feedback_closed: (screen: string): Event => { + return { + name: 'vama_feedback_closed', + params: { + screen, + }, + } + }, + vama_feedback_submitted: (screen: string, taskCompleted: string, satisfaction: string): Event => { + return { + name: 'vama_feedback_submitted', + params: { + screen, + taskCompleted, + satisfaction, + }, + } + }, vama_givefb_close: (screenName: string): Event => { return { name: 'vama_givefb_close', diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.tsx index d3cc73f0a15..478e1d58f66 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.tsx @@ -16,7 +16,7 @@ import { ScreenIDTypesConstants } from 'store/api/types/Screens' import { logAnalyticsEvent } from 'utils/analytics' import { formatDateMMMMDDYYYY, getFormattedTimeForTimeZone, getTranslation } from 'utils/formattingUtils' import { useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { screenContentAllowed } from 'utils/waygateConfig' import NeedHelpData from '../NeedHelpData/NeedHelpData' @@ -28,6 +28,7 @@ type AppealDetailsScreenProps = StackScreenProps { if (appeal && !loadingAppeal && !appealError) { - registerReviewEvent(true) + registerReviewEvent() } - }, [appeal, loadingAppeal, appealError]) + }, [appeal, loadingAppeal, appealError, registerReviewEvent]) const onTabChange = (tab: number) => { setSelectedTab(tab) diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.tsx index 167dca2a69f..d39a1d754dc 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.tsx @@ -37,7 +37,7 @@ import { logAnalyticsEvent } from 'utils/analytics' import { isDisabilityCompensationClaim, numberOfItemsNeedingAttentionFromVet } from 'utils/claims' import { formatDateMMMMDDYYYY } from 'utils/formattingUtils' import { useBeforeNavBackListener, useRouteNavigation, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { featureEnabled } from 'utils/remoteConfig' import { screenContentAllowed } from 'utils/waygateConfig' @@ -64,6 +64,7 @@ function ClaimDetailsScreen({ navigation, route }: ClaimDetailsScreenProps) { const [downloadFile, setDownloadFile] = useState(false) const { claimID, claimType } = route.params + const registerReviewEvent = useReviewEvent(true) const queryClient = useQueryClient() const { data: claim, @@ -117,7 +118,7 @@ function ClaimDetailsScreen({ navigation, route }: ClaimDetailsScreenProps) { useEffect(() => { if (claim && !loadingClaim && !claimError) { - registerReviewEvent(true) + registerReviewEvent() logAnalyticsEvent( Events.vama_claim_details_open( claimID, @@ -133,7 +134,7 @@ function ClaimDetailsScreen({ navigation, route }: ClaimDetailsScreenProps) { // Keep tab switching or panel opening from triggering autoscroll setScrollIsEnabled(false) } - }, [claim, loadingClaim, claimError, claimID, attributes]) + }, [claim, loadingClaim, claimError, claimID, attributes, registerReviewEvent]) useEffect(() => { if (claimType === ClaimTypeConstants.ACTIVE && claim) { diff --git a/VAMobile/src/screens/BenefitsScreen/DisabilityRatingsScreen/DisabilityRatingsScreen.tsx b/VAMobile/src/screens/BenefitsScreen/DisabilityRatingsScreen/DisabilityRatingsScreen.tsx index 7a7c053b73e..ffb56d81bda 100644 --- a/VAMobile/src/screens/BenefitsScreen/DisabilityRatingsScreen/DisabilityRatingsScreen.tsx +++ b/VAMobile/src/screens/BenefitsScreen/DisabilityRatingsScreen/DisabilityRatingsScreen.tsx @@ -25,7 +25,7 @@ import { a11yLabelVA } from 'utils/a11yLabel' import getEnv from 'utils/env' import { capitalizeFirstLetter, displayedTextPhoneNumber } from 'utils/formattingUtils' import { useDowntime, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { screenContentAllowed } from 'utils/waygateConfig' import NoDisabilityRatings from './NoDisabilityRatings/NoDisabilityRatings' @@ -34,6 +34,7 @@ function DisabilityRatingsScreen() { const theme = useTheme() const { t } = useTranslation(NAMESPACE.COMMON) const navigation = useNavigation() + const registerReviewEvent = useReviewEvent(true) const { LINK_URL_ABOUT_DISABILITY_RATINGS } = getEnv() const { condensedMarginBetween, contentMarginBottom, gutter } = theme.dimensions @@ -57,8 +58,8 @@ function DisabilityRatingsScreen() { useFocusEffect( React.useCallback(() => { - registerReviewEvent(true) - }, []), + registerReviewEvent() + }, [registerReviewEvent]), ) const individualRatingsList: Array = ratingData?.individualRatings || [] diff --git a/VAMobile/src/screens/HealthScreen/Appointments/PastAppointments/PastAppointmentDetails.tsx b/VAMobile/src/screens/HealthScreen/Appointments/PastAppointments/PastAppointmentDetails.tsx index 0e6f7100e6d..4873e2cc04f 100644 --- a/VAMobile/src/screens/HealthScreen/Appointments/PastAppointments/PastAppointmentDetails.tsx +++ b/VAMobile/src/screens/HealthScreen/Appointments/PastAppointments/PastAppointmentDetails.tsx @@ -8,7 +8,7 @@ import { FeatureLandingTemplate } from 'components' import { Events, UserAnalytics } from 'constants/analytics' import { NAMESPACE } from 'constants/namespaces' import { logAnalyticsEvent, setAnalyticsUserProperty } from 'utils/analytics' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { AppointmentDetailsSubTypeConstants, @@ -33,6 +33,7 @@ type PastAppointmentDetailsProps = StackScreenProps { if (!getApptError && appointmentNotFound) { diff --git a/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionDetails/PrescriptionDetails.tsx b/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionDetails/PrescriptionDetails.tsx index 917d3301007..58200f7be57 100644 --- a/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionDetails/PrescriptionDetails.tsx +++ b/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionDetails/PrescriptionDetails.tsx @@ -18,7 +18,7 @@ import { a11yLabelVA } from 'utils/a11yLabel' import { logAnalyticsEvent, setAnalyticsUserProperty } from 'utils/analytics' import getEnv from 'utils/env' import { useDestructiveActionSheet, useDowntime, useExternalLink, useRouteNavigation, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { RefillTag, getDateTextAndLabel, getRxNumberTextAndLabel } from '../PrescriptionCommon' import DetailsTextSections from './DetailsTextSections' @@ -34,6 +34,7 @@ function PrescriptionDetails({ route, navigation }: PrescriptionDetailsProps) { const launchExternalLink = useExternalLink() const submitRefillAlert = useDestructiveActionSheet() const navigateTo = useRouteNavigation() + const registerReviewEvent = useReviewEvent(true) const prescriptionInDowntime = useDowntime(DowntimeFeatureTypeConstants.rx) const { t } = useTranslation(NAMESPACE.COMMON) const noneNoted = t('noneNoted') @@ -60,8 +61,8 @@ function PrescriptionDetails({ route, navigation }: PrescriptionDetailsProps) { useFocusEffect( React.useCallback(() => { setAnalyticsUserProperty(UserAnalytics.vama_uses_rx()) - registerReviewEvent(true) - }, []), + registerReviewEvent() + }, [registerReviewEvent]), ) const redirectLink = (): void => { diff --git a/VAMobile/src/screens/HealthScreen/Pharmacy/RefillTrackingDetails/RefillTrackingDetails.tsx b/VAMobile/src/screens/HealthScreen/Pharmacy/RefillTrackingDetails/RefillTrackingDetails.tsx index 271aee9dc30..2738decf43d 100644 --- a/VAMobile/src/screens/HealthScreen/Pharmacy/RefillTrackingDetails/RefillTrackingDetails.tsx +++ b/VAMobile/src/screens/HealthScreen/Pharmacy/RefillTrackingDetails/RefillTrackingDetails.tsx @@ -27,7 +27,7 @@ import { a11yLabelID, a11yLabelVA } from 'utils/a11yLabel' import { logAnalyticsEvent } from 'utils/analytics' import getEnv from 'utils/env' import { useBeforeNavBackListener, useDowntime, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { screenContentAllowed } from 'utils/waygateConfig' import { HealthStackParamList } from '../../HealthStackScreens' @@ -57,6 +57,7 @@ const getTrackingLink = (deliveryService: string): string => { function RefillTrackingDetails({ route, navigation }: RefillTrackingDetailsProps) { const { prescription } = route.params const prescriptionInDowntime = useDowntime(DowntimeFeatureTypeConstants.rx) + const registerReviewEvent = useReviewEvent(true) const { data: trackingInfo, isFetching: loadingTrackingInfo, @@ -76,8 +77,8 @@ function RefillTrackingDetails({ route, navigation }: RefillTrackingDetailsProps useFocusEffect( React.useCallback(() => { - registerReviewEvent(true) - }, []), + registerReviewEvent() + }, [registerReviewEvent]), ) const renderOtherPrescription = (otherPrescriptions: Array) => { diff --git a/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.tsx b/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.tsx index 70bcd588b81..69134118fc9 100644 --- a/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.tsx +++ b/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.tsx @@ -51,7 +51,7 @@ import { DemoState } from 'store/slices/demoSlice' import { GenerateFolderMessage } from 'translations/en/functions' import { logAnalyticsEvent, setAnalyticsUserProperty } from 'utils/analytics' import { useDowntimeByScreenID, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { getfolderName } from 'utils/secureMessaging' import { screenContentAllowed } from 'utils/waygateConfig' @@ -107,6 +107,7 @@ function ViewMessageScreen({ route, navigation }: ViewMessageScreenProps) { const snackbar = useSnackbar() const { t } = useTranslation(NAMESPACE.COMMON) const theme = useTheme() + const registerReviewEvent = useReviewEvent(true) const queryClient = useQueryClient() const { demoMode } = useSelector((state) => state.demo) @@ -167,9 +168,9 @@ function ViewMessageScreen({ route, navigation }: ViewMessageScreenProps) { useEffect(() => { if (threadFetched) { setAnalyticsUserProperty(UserAnalytics.vama_uses_sm()) - registerReviewEvent(true) + registerReviewEvent() } - }, [threadFetched]) + }, [threadFetched, registerReviewEvent]) useEffect(() => { if (messageFetched && currentFolderIdParam === SecureMessagingSystemFolderIdConstants.INBOX && currentPage) { diff --git a/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressSummary/AddressSummary.test.tsx b/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressSummary/AddressSummary.test.tsx index c10fa3d4f48..88647e41a4c 100644 --- a/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressSummary/AddressSummary.test.tsx +++ b/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressSummary/AddressSummary.test.tsx @@ -1,6 +1,7 @@ import React from 'react' import { fireEvent, screen } from '@testing-library/react-native' +import { t } from 'i18next' import { contactInformationKeys } from 'api/contactInformation/queryKeys' import { AddressData, UserContactInformation } from 'api/types' @@ -76,7 +77,11 @@ describe('AddressSummary', () => { describe('when there is no mailing address', () => { it('displays the message for adding a mailing address', () => { - expect(screen.getByText('Add your mailing address')).toBeTruthy() + expect( + screen.getByText( + t('contactInformation.addYour', { field: t(`contactInformation.mailingAddress`).toLowerCase() }), + ), + ).toBeTruthy() }) }) @@ -91,7 +96,11 @@ describe('AddressSummary', () => { describe('when there is no residential address', () => { it('displays the message for adding a home address', () => { renderWithData() - expect(screen.getByText('Add your home address')).toBeTruthy() + expect( + screen.getByText( + t('contactInformation.addYour', { field: t(`contactInformation.residentialAddress`).toLowerCase() }), + ), + ).toBeTruthy() }) }) @@ -209,7 +218,11 @@ describe('AddressSummary', () => { } renderWithData({ mailingAddress: noCountryCodeAddress }) - expect(screen.getByText('Add your mailing address')).toBeTruthy() + expect( + screen.getByText( + t('contactInformation.addYour', { field: t(`contactInformation.mailingAddress`).toLowerCase() }), + ), + ).toBeTruthy() }) }) @@ -237,9 +250,9 @@ describe('AddressSummary', () => { describe('when the address summary is clicked', () => { it('calls onPress', () => { - fireEvent.press(screen.getByRole('link', { name: 'Mailing address' })) + fireEvent.press(screen.getByRole('link', { name: t('contactInformation.mailingAddress') })) expect(mailingAddressOnPressSpy).toBeCalled() - fireEvent.press(screen.getByRole('link', { name: 'Home address' })) + fireEvent.press(screen.getByRole('link', { name: t('contactInformation.residentialAddress') })) expect(residentialAddressOnPressSpy).toBeCalled() }) }) diff --git a/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressValidation/AddressValidation.test.tsx b/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressValidation/AddressValidation.test.tsx index e0c09eedf85..496a2bc80f6 100644 --- a/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressValidation/AddressValidation.test.tsx +++ b/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressValidation/AddressValidation.test.tsx @@ -1,6 +1,7 @@ import React from 'react' import { fireEvent, screen } from '@testing-library/react-native' +import { t } from 'i18next' import { AddressData, ValidateAddressData } from 'api/types' import { render } from 'testUtils' @@ -84,18 +85,18 @@ describe('AddressValidation', () => { describe('when the address validation scenario type is SHOW_SUGGESTIONS_OVERRIDE', () => { it('displays the alert texts', () => { - const collapsibleAlert = screen.getByText('Verify your address') + const collapsibleAlert = screen.getByText(t('editAddress.validation.verifyAddress.title')) expect(collapsibleAlert).toBeTruthy() fireEvent.press(collapsibleAlert) - expect(screen.getByText("We can't confirm the address you entered with the U.S. Postal Service.")).toBeTruthy() + expect(screen.getByText(t('editAddress.validation.verifyAddress.body.1'))).toBeTruthy() }) }) describe('when use this address button is pressed', () => { it('calls updateAddress', () => { fireEvent.press(screen.getByTestId('youEnteredTestID')) - fireEvent.press(screen.getByRole('button', { name: 'Use this address' })) + fireEvent.press(screen.getByRole('button', { name: t('editAddress.validation.useThisAddress') })) expect(saveAddressSpy).toBeCalled() }) }) diff --git a/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/ContactInformationScreen.tsx b/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/ContactInformationScreen.tsx index ceaa6a07e90..ce5d429750e 100644 --- a/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/ContactInformationScreen.tsx +++ b/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/ContactInformationScreen.tsx @@ -30,7 +30,7 @@ import { ScreenIDTypesConstants } from 'store/api/types' import { a11yLabelVA } from 'utils/a11yLabel' import { logAnalyticsEvent } from 'utils/analytics' import { useDowntimeByScreenID, useRouteNavigation, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { screenContentAllowed } from 'utils/waygateConfig' const getTextForPhoneData = ( @@ -134,6 +134,7 @@ function ContactInformationScreen({ navigation }: ContactInformationScreenProps) refetch: refetchContactInformation, failureCount, } = useContactInformation({ enabled: screenContentAllowed('WG_ContactInformation') }) + const registerReviewEvent = useReviewEvent(true) const contactInformationInDowntime = useDowntimeByScreenID(ScreenIDTypesConstants.CONTACT_INFORMATION_SCREEN_ID) const { contentMarginBottom, gutter, condensedMarginBetween } = theme.dimensions const [retried, setRetried] = useState(false) @@ -155,7 +156,7 @@ function ContactInformationScreen({ navigation }: ContactInformationScreenProps) const [reviewEventRegistered, setReviewEventRegistered] = useState(false) if (!reviewEventRegistered) { console.debug('REVIEW EVENT REGISTERED') - registerReviewEvent(true) + registerReviewEvent() setReviewEventRegistered(true) } diff --git a/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/EditAddressScreen/EditAddressScreen.test.tsx b/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/EditAddressScreen/EditAddressScreen.test.tsx index e11cea97a2a..328fff9f382 100644 --- a/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/EditAddressScreen/EditAddressScreen.test.tsx +++ b/VAMobile/src/screens/HomeScreen/ProfileScreen/ContactInformationScreen/EditAddressScreen/EditAddressScreen.test.tsx @@ -1,6 +1,7 @@ import React from 'react' import { fireEvent, screen, waitFor } from '@testing-library/react-native' +import { t } from 'i18next' import { contactInformationKeys } from 'api/contactInformation' import { AddressData, DeliveryPointValidationTypesConstants, UserContactInformation } from 'api/types' @@ -134,7 +135,7 @@ describe('EditAddressScreen', () => { fireEvent.press(countrySelector) fireEvent.press(screen.getByText('Algeria')) - fireEvent.press(screen.getByRole('button', { name: 'Done' })) + fireEvent.press(screen.getByRole('button', { name: t('done') })) expect(countrySelector.props.children).toEqual('Algeria') }) @@ -148,7 +149,7 @@ describe('EditAddressScreen', () => { fireEvent.press(screen.getByTestId('countryPickerTestID')) fireEvent.press(screen.getByText('Algeria')) - fireEvent.press(screen.getByRole('button', { name: 'Done' })) + fireEvent.press(screen.getByRole('button', { name: t('done') })) expect(screen.getByTestId('stateTestID').props.value).toEqual('') expect(zipCodeInput.props.value).toEqual('') @@ -180,7 +181,7 @@ describe('EditAddressScreen', () => { fireEvent.press(screen.getByTestId('countryPickerTestID')) fireEvent.press(screen.getByText('Algeria')) - fireEvent.press(screen.getByRole('button', { name: 'Done' })) + fireEvent.press(screen.getByRole('button', { name: t('done') })) expect(screen.getByTestId('stateTestID').props.value).toEqual('Northern Territory') expect(screen.getByTestId('zipCodeTestID').props.value).toEqual('5922') @@ -247,7 +248,7 @@ describe('EditAddressScreen', () => { fireEvent.press(militaryPostOfficeSelector) fireEvent.press(screen.getByText('APO')) - fireEvent.press(screen.getByRole('button', { name: 'Done' })) + fireEvent.press(screen.getByRole('button', { name: t('done') })) expect(militaryPostOfficeSelector.props.children).toEqual('APO') }) }) @@ -256,7 +257,7 @@ describe('EditAddressScreen', () => { it('updates the value of state', () => { fireEvent.press(screen.getByTestId('stateTestID')) fireEvent.press(screen.getByText('California')) - fireEvent.press(screen.getByRole('button', { name: 'Done' })) + fireEvent.press(screen.getByRole('button', { name: t('done') })) expect(screen.getByTestId('stateTestID').props.children).toEqual('California') }) }) @@ -425,7 +426,7 @@ describe('EditAddressScreen', () => { renderWithData(false, { mailingAddress: newAddress }) - fireEvent.press(screen.getByRole('button', { name: 'Save' })) + fireEvent.press(screen.getByRole('button', { name: t('save') })) await waitFor(() => expect(goBackSpy).toBeCalled()) }) }) @@ -486,32 +487,32 @@ describe('EditAddressScreen', () => { renderWithData(false, { mailingAddress: newAddress }) - fireEvent.press(screen.getByRole('button', { name: 'Save' })) + fireEvent.press(screen.getByRole('button', { name: t('save') })) await waitFor(() => expect(post as jest.Mock).toBeCalledWith('/v0/user/addresses/validate', newAddress, undefined, abortSignal), ) - await waitFor(() => expect(screen.getByText('Verify your address')).toBeTruthy()) + await waitFor(() => expect(screen.getByText(t('editAddress.validation.verifyAddress.title'))).toBeTruthy()) }) }) describe('when content is invalid for domestic address', () => { it('displays an AlertBox and a field error for each required field', () => { - fireEvent.press(screen.getByRole('button', { name: 'Save' })) - expect(screen.getByText('Please check your mailing address')).toBeTruthy() - expect(screen.getByText('Country is required')).toBeTruthy() - expect(screen.getByText('Street address is required')).toBeTruthy() - expect(screen.getByText('City is required')).toBeTruthy() - expect(screen.getByText('State is required')).toBeTruthy() - expect(screen.getByText('Postal code is required')).toBeTruthy() + fireEvent.press(screen.getByRole('button', { name: t('save') })) + expect(screen.getByText(t('editAddress.alertError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.countryFieldError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.streetAddressLine1FieldError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.cityFieldError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.stateFieldError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.zipCodeFieldError'))).toBeTruthy() }) }) describe('when content is invalid for military address', () => { it('displays an AlertBox and a field error for each required field', () => { - fireEvent.press(screen.getByRole('button', { name: 'Save' })) - expect(screen.getByText('Please check your mailing address')).toBeTruthy() - expect(screen.getByText('Street address is required')).toBeTruthy() - expect(screen.getByText('Postal code is required')).toBeTruthy() + fireEvent.press(screen.getByRole('button', { name: t('save') })) + expect(screen.getByText(t('editAddress.alertError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.streetAddressLine1FieldError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.zipCodeFieldError'))).toBeTruthy() }) }) @@ -519,10 +520,10 @@ describe('EditAddressScreen', () => { it('displays an AlertBox and a field error for each required field', () => { fireEvent.press(screen.getByTestId('countryPickerTestID')) fireEvent.press(screen.getByText('Algeria')) - fireEvent.press(screen.getByRole('button', { name: 'Save' })) - expect(screen.getByText('Street address is required')).toBeTruthy() - expect(screen.getByText('City is required')).toBeTruthy() - expect(screen.getByText('Postal code is required')).toBeTruthy() + fireEvent.press(screen.getByRole('button', { name: t('save') })) + expect(screen.getByText(t('editAddress.streetAddressLine1FieldError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.cityFieldError'))).toBeTruthy() + expect(screen.getByText(t('editAddress.internationalPostCodeFieldError'))).toBeTruthy() }) }) @@ -553,7 +554,7 @@ describe('EditAddressScreen', () => { renderWithData(false, { mailingAddress: newAddress }) - fireEvent.press(screen.getByRole('button', { name: 'Save' })) + fireEvent.press(screen.getByRole('button', { name: t('save') })) const abortController = new AbortController() const abortSignal = abortController.signal @@ -601,7 +602,7 @@ describe('EditAddressScreen', () => { } renderWithData(false, { mailingAddress: newAddress }) - fireEvent.press(screen.getByRole('button', { name: 'Save' })) + fireEvent.press(screen.getByRole('button', { name: t('save') })) const abortController = new AbortController() const abortSignal = abortController.signal @@ -648,7 +649,7 @@ describe('EditAddressScreen', () => { } renderWithData(false, { mailingAddress: newAddress }) - fireEvent.press(screen.getByRole('button', { name: 'Save' })) + fireEvent.press(screen.getByRole('button', { name: t('save') })) const abortController = new AbortController() const abortSignal = abortController.signal diff --git a/VAMobile/src/screens/HomeScreen/ProfileScreen/PersonalInformationScreen/PersonalInformationScreen.tsx b/VAMobile/src/screens/HomeScreen/ProfileScreen/PersonalInformationScreen/PersonalInformationScreen.tsx index e55df48754e..658d0f37314 100644 --- a/VAMobile/src/screens/HomeScreen/ProfileScreen/PersonalInformationScreen/PersonalInformationScreen.tsx +++ b/VAMobile/src/screens/HomeScreen/ProfileScreen/PersonalInformationScreen/PersonalInformationScreen.tsx @@ -25,7 +25,7 @@ import { ScreenIDTypesConstants } from 'store/api/types' import { a11yLabelVA } from 'utils/a11yLabel' import { stringToTitleCase } from 'utils/formattingUtils' import { useDowntimeByScreenID, useRouteNavigation, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { featureEnabled } from 'utils/remoteConfig' import { screenContentAllowed } from 'utils/waygateConfig' @@ -62,6 +62,7 @@ function PersonalInformationScreen({ navigation }: PersonalInformationScreenProp const { gutter, condensedMarginBetween, formMarginBetween } = theme.dimensions const personalInformationInDowntime = useDowntimeByScreenID(ScreenIDTypesConstants.PERSONAL_INFORMATION_SCREEN_ID) const isScreenContentAllowed = screenContentAllowed('WG_PersonalInformation') + const registerReviewEvent = useReviewEvent(true) const { data: personalInfo, isFetching: loadingPersonalInfo, @@ -86,7 +87,7 @@ function PersonalInformationScreen({ navigation }: PersonalInformationScreenProp /** IN-App review events need to be recorded once, so we use the setState hook to guard this **/ const [reviewEventRegistered, setReviewEventRegistered] = useState(false) if (!reviewEventRegistered) { - registerReviewEvent(true) + registerReviewEvent() setReviewEventRegistered(true) } diff --git a/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/DeveloperScreen/DeveloperScreen.tsx b/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/DeveloperScreen/DeveloperScreen.tsx index a23c2657664..50916d7196f 100644 --- a/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/DeveloperScreen/DeveloperScreen.tsx +++ b/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/DeveloperScreen/DeveloperScreen.tsx @@ -177,7 +177,6 @@ function DeveloperScreen({ navigation }: DeveloperScreenSettingsScreenProps) { ] const onFeedback = () => { - //logAnalyticsEvent(Events.vama_feedback_page_entered()) inAppFeedback('Developer') } diff --git a/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/InAppFeedbackScreen/InAppFeedbackScreen.tsx b/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/InAppFeedbackScreen/InAppFeedbackScreen.tsx index a0697269668..6e79068807b 100644 --- a/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/InAppFeedbackScreen/InAppFeedbackScreen.tsx +++ b/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/InAppFeedbackScreen/InAppFeedbackScreen.tsx @@ -8,29 +8,36 @@ import { Button } from '@department-of-veterans-affairs/mobile-component-library import { RootNavStackParamList } from 'App' import { BorderColorVariant, Box, LargePanel, RadioGroup, RadioGroupProps, TextView, VATextInput } from 'components' +import { Events } from 'constants/analytics' import { NAMESPACE } from 'constants/namespaces' -import { checkStringForPII } from 'utils/common' +import { logAnalyticsEvent } from 'utils/analytics' +import { checkStringForPII, showSnackBar } from 'utils/common' import getEnv from 'utils/env' -import { useExternalLink, useTheme } from 'utils/hooks' +import { useAppDispatch, useBeforeNavBackListener, useExternalLink, useTheme } from 'utils/hooks' const { LINK_URL_OMB_PAGE } = getEnv() type InAppFeedbackScreenProps = StackScreenProps -function InAppFeedbackScreen({ navigation }: InAppFeedbackScreenProps) { +function InAppFeedbackScreen({ navigation, route }: InAppFeedbackScreenProps) { const { t } = useTranslation(NAMESPACE.COMMON) const theme = useTheme() const [satisfaction, setSatisfaction] = useState('') const [task, setTaskOverride] = useState('') + const dispatch = useAppDispatch() + const { screen } = route.params + let submittedCheck = false const launchExternalLink = useExternalLink() - // useBeforeNavBackListener(navigation, () => { - // logAnalyticsEvent(Events.vama_feedback_page_closed()) - // }) + useBeforeNavBackListener(navigation, () => { + if (submittedCheck === true) { + return + } + logAnalyticsEvent(Events.vama_feedback_closed(screen)) + }) const onSubmit = (): void => { const { found, newText } = checkStringForPII(task) - // logAnalyticsEvent(Events.vama_feedback_submitted(taskCompleted, satisfaction)) if (found) { Alert.alert(t('inAppFeedback.personalInfo.title'), t('inAppFeedback.personalInfo.body'), [ { @@ -40,14 +47,19 @@ function InAppFeedbackScreen({ navigation }: InAppFeedbackScreenProps) { { text: t('inAppFeedback.personalInfo.submit'), onPress: () => { - setTaskOverride(newText) + logAnalyticsEvent(Events.vama_feedback_submitted(screen, newText, satisfaction)) + submittedCheck = true navigation.goBack() + showSnackBar(t('inAppFeedback.snackbar.success'), dispatch, undefined, true, false, false) }, style: 'default', }, ]) } else { + logAnalyticsEvent(Events.vama_feedback_submitted(screen, task, satisfaction)) + submittedCheck = true navigation.goBack() + showSnackBar(t('inAppFeedback.snackbar.success'), dispatch, undefined, true, false, false) } } diff --git a/VAMobile/src/screens/HomeScreen/VeteranStatusScreen/VeteranStatusScreen.tsx b/VAMobile/src/screens/HomeScreen/VeteranStatusScreen/VeteranStatusScreen.tsx index 2fcaacdae2c..496e2606237 100644 --- a/VAMobile/src/screens/HomeScreen/VeteranStatusScreen/VeteranStatusScreen.tsx +++ b/VAMobile/src/screens/HomeScreen/VeteranStatusScreen/VeteranStatusScreen.tsx @@ -24,7 +24,7 @@ import { import { NAMESPACE } from 'constants/namespaces' import { HomeStackParamList } from 'screens/HomeScreen/HomeStackScreens' import { useBeforeNavBackListener, useOrientation, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { displayedTextPhoneNumber } from '../../../utils/formattingUtils' @@ -39,6 +39,7 @@ function VeteranStatusScreen({ navigation }: VeteranStatusScreenProps) { const { data: ratingData } = useDisabilityRating() const { data: userAuthorizedServices } = useAuthorizedServices() const { data: personalInfo } = usePersonalInformation() + const registerReviewEvent = useReviewEvent(true) const accessToMilitaryInfo = userAuthorizedServices?.militaryServiceHistory && serviceHistory.length > 0 const theme = useTheme() const { t } = useTranslation(NAMESPACE.COMMON) @@ -50,7 +51,7 @@ function VeteranStatusScreen({ navigation }: VeteranStatusScreenProps) { : undefined useBeforeNavBackListener(navigation, () => { - registerReviewEvent(true) + registerReviewEvent() }) const getPeriodOfService: React.ReactNode = map(serviceHistory, (service: ServiceData) => { diff --git a/VAMobile/src/screens/PaymentsScreen/PaymentHistory/PaymentDetailsScreen/PaymentDetailsScreen.tsx b/VAMobile/src/screens/PaymentsScreen/PaymentHistory/PaymentDetailsScreen/PaymentDetailsScreen.tsx index e3febaf63e6..e5955b5b704 100644 --- a/VAMobile/src/screens/PaymentsScreen/PaymentHistory/PaymentDetailsScreen/PaymentDetailsScreen.tsx +++ b/VAMobile/src/screens/PaymentsScreen/PaymentHistory/PaymentDetailsScreen/PaymentDetailsScreen.tsx @@ -9,7 +9,7 @@ import { DIRECT_DEPOSIT } from 'constants/common' import { NAMESPACE } from 'constants/namespaces' import { formatDateUtc } from 'utils/formattingUtils' import { useRouteNavigation, useTheme } from 'utils/hooks' -import { registerReviewEvent } from 'utils/inAppReviews' +import { useReviewEvent } from 'utils/inAppReviews' import { PaymentsStackParamList } from '../../PaymentsStackScreens' @@ -20,11 +20,12 @@ function PaymentDetailsScreen({ navigation, route }: PaymentDetailsScreenProps) const { t } = useTranslation(NAMESPACE.COMMON) const theme = useTheme() const navigateTo = useRouteNavigation() + const registerReviewEvent = useReviewEvent(true) useFocusEffect( React.useCallback(() => { - registerReviewEvent(true) - }, []), + registerReviewEvent() + }, [registerReviewEvent]), ) const placeHolder = t('noneNoted') diff --git a/VAMobile/src/translations/en/common.json b/VAMobile/src/translations/en/common.json index 394dd218556..ae9863d5e06 100644 --- a/VAMobile/src/translations/en/common.json +++ b/VAMobile/src/translations/en/common.json @@ -348,6 +348,7 @@ "automatedPhoneSystem": "Automated phone system: ", "back": "Back", "back.a11yHint": "Navigates to the previous page", + "back.a11yLabel": "Back to {{screenName}}", "benefits.title": "Benefits", "benefits.activity.error": "We can't get some of your information. Benefits activity may not be accurate.", "benefits.activity.nonFatalError": "We can't get some of your information right now. Benefits activity may not be accurate. Check back later.", @@ -847,6 +848,7 @@ "inAppFeedback.personalInfo.body": "You can still submit your feedback. But we recommend deleting any personal information you included.", "inAppFeedback.personalInfo.edit": "Edit feedback", "inAppFeedback.personalInfo.submit": "Submit anyway", + "inAppFeedback.snackbar.success": "Feedback sent", "inAppRecruitment.contracts": "VA contracts: 36C10B22C0011 & 36C10X18C0061", "inAppRecruitment.goToQuestionnaire": "Go to questionnaire", "inAppRecruitment.goToQuestionnaire.loading": "Loading questionnaire...", diff --git a/VAMobile/src/utils/hooks/index.tsx b/VAMobile/src/utils/hooks/index.tsx index 52666d665f3..75ef40da0f2 100644 --- a/VAMobile/src/utils/hooks/index.tsx +++ b/VAMobile/src/utils/hooks/index.tsx @@ -223,10 +223,13 @@ export function useGiveFeedback(): (task: string) => void { return (task: string) => { const onOKPress = () => { + logAnalyticsEvent(Events.vama_feedback_ask(task, true)) navigateTo('InAppFeedback', { task }) } - const onCancelPress = () => {} + const onCancelPress = () => { + logAnalyticsEvent(Events.vama_feedback_ask(task, false)) + } Alert.alert(t('inAppFeedback.popup.title'), t('inAppFeedback.popup.body'), [ { diff --git a/VAMobile/src/utils/inAppReviews.tsx b/VAMobile/src/utils/inAppReviews.tsx index 1d7515a2b18..bcc852eba67 100644 --- a/VAMobile/src/utils/inAppReviews.tsx +++ b/VAMobile/src/utils/inAppReviews.tsx @@ -6,34 +6,70 @@ import { Events } from 'constants/analytics' import { logAnalyticsEvent } from './analytics' import { getVersionName } from './deviceData' +import { useGiveFeedback } from './hooks' import { featureEnabled } from './remoteConfig' import { requestReview } from './rnReviews' export const STORAGE_REVIEW_EVENT_KEY = '@review_event' +export const STORAGE_FEEDBACK_EVENT_KEY = '@feedback_event_' export const STORAGE_LAST_REVIEW_PROMPT_DATE_MILLIS = '@last_review_date' +export const STORAGE_LAST_FEEDBACK_PROMPT_DATE_MILLIS = '@last_feedback_date' export const STORAGE_LAST_REVIEW_VERSION = '@last_review_version' export const IN_APP_REVIEW_INTERVAL_DAYS = 122 +export const IN_APP_FEEDBACK_INTERVAL_DAYS = 30 export const IN_APP_REVIEW_ACTIONS_THRESHOLD = 7 +export const IN_APP_FEEDBACK_ACTIONS_THRESHOLD = 3 -export const registerReviewEvent = async (screenView?: boolean): Promise => { - if (!featureEnabled('inAppReview')) return - const prev = await AsyncStorage.getItem(STORAGE_REVIEW_EVENT_KEY) - const total = prev ? parseInt(prev, 10) + 1 : 1 - await AsyncStorage.setItem(STORAGE_REVIEW_EVENT_KEY, `${total}`) - const versionName = await getVersionName() - if (!screenView && total >= IN_APP_REVIEW_ACTIONS_THRESHOLD) { - const lastReview = await AsyncStorage.getItem(STORAGE_LAST_REVIEW_PROMPT_DATE_MILLIS) - if (!lastReview) { - await callReviewAPI(versionName) - } else { - const days = DateTime.fromMillis(parseInt(lastReview, 10)).diffNow('days').days - const lastReviewVersion = await AsyncStorage.getItem(STORAGE_LAST_REVIEW_VERSION) - - if (days > IN_APP_REVIEW_INTERVAL_DAYS && lastReviewVersion !== versionName) { +export const useReviewEvent = (screenView?: boolean, feedbackScreen?: string): (() => Promise) => { + const inAppFeedback = useGiveFeedback() + + const reviewEvent = async () => { + if (!featureEnabled('inAppReview') && !featureEnabled('inAppFeedback')) return + //Checked for feedbackScreen triggers first. + if (featureEnabled('inAppFeedback') && feedbackScreen) { + const feedbackKey = STORAGE_FEEDBACK_EVENT_KEY.concat(feedbackScreen) + const feedbackCount = await AsyncStorage.getItem(STORAGE_FEEDBACK_EVENT_KEY.concat(feedbackScreen)) + const totalFeedback = feedbackCount ? parseInt(feedbackCount, 10) + 1 : 1 + //always increment the count when a feedback screen is registered + await AsyncStorage.setItem(feedbackKey, `${totalFeedback}`) + //check if this register would prompt for feedback + if (totalFeedback >= IN_APP_FEEDBACK_ACTIONS_THRESHOLD) { + //this date does not need to be feedbackScreen specific based on AC's: + //'Do not display feedback if user has submitted feedback for any feature within the past 30 days' + const lastFeedback = await AsyncStorage.getItem(STORAGE_LAST_FEEDBACK_PROMPT_DATE_MILLIS) + //doesn't prompt for feedback if feedback has been given in past 30 days. If prompt for feedback, return and skip potential in app review + if ( + (lastFeedback && + DateTime.fromMillis(parseInt(lastFeedback, 10)).diffNow('days').days > IN_APP_FEEDBACK_INTERVAL_DAYS) || + !lastFeedback + ) { + inAppFeedback(feedbackScreen) + await AsyncStorage.setItem(feedbackKey, '0') + await AsyncStorage.setItem(STORAGE_LAST_FEEDBACK_PROMPT_DATE_MILLIS, `${DateTime.now().millisecond}`) + return + } + } + } + const prev = await AsyncStorage.getItem(STORAGE_REVIEW_EVENT_KEY) + const total = prev ? parseInt(prev, 10) + 1 : 1 + await AsyncStorage.setItem(STORAGE_REVIEW_EVENT_KEY, `${total}`) + const versionName = await getVersionName() + if (!screenView && total >= IN_APP_REVIEW_ACTIONS_THRESHOLD) { + const lastReview = await AsyncStorage.getItem(STORAGE_LAST_REVIEW_PROMPT_DATE_MILLIS) + if (!lastReview) { await callReviewAPI(versionName) + } else { + const days = DateTime.fromMillis(parseInt(lastReview, 10)).diffNow('days').days + const lastReviewVersion = await AsyncStorage.getItem(STORAGE_LAST_REVIEW_VERSION) + + if (days > IN_APP_REVIEW_INTERVAL_DAYS && lastReviewVersion !== versionName) { + await callReviewAPI(versionName) + } } } } + + return reviewEvent } const callReviewAPI = async (versionName: string): Promise => { diff --git a/VAMobile/src/utils/remoteConfig.test.ts b/VAMobile/src/utils/remoteConfig.test.ts index f80de88dc3c..0ebfc5c62b9 100644 --- a/VAMobile/src/utils/remoteConfig.test.ts +++ b/VAMobile/src/utils/remoteConfig.test.ts @@ -23,6 +23,7 @@ const mockOverrides = { decisionLettersWaygate: false, haptics: false, homeScreenPrefetch: false, + inAppFeedback: false, inAppRecruitment: false, inAppReview: true, inAppUpdates: false, diff --git a/VAMobile/src/utils/remoteConfig.ts b/VAMobile/src/utils/remoteConfig.ts index 98e6f7308b4..bf8127e2f6a 100644 --- a/VAMobile/src/utils/remoteConfig.ts +++ b/VAMobile/src/utils/remoteConfig.ts @@ -22,6 +22,7 @@ export type FeatureToggleType = | 'haptics' | 'homeScreenPrefetch' | 'inAppRecruitment' + | 'inAppFeedback' | 'inAppReview' | 'inAppUpdates' | 'patientCheckIn' @@ -41,6 +42,7 @@ type FeatureToggleValues = { haptics: boolean homeScreenPrefetch: boolean inAppRecruitment: boolean + inAppFeedback: boolean inAppReview: boolean inAppUpdates: boolean patientCheckIn: boolean @@ -61,6 +63,7 @@ export const defaults: FeatureToggleValues = { haptics: true, homeScreenPrefetch: true, inAppRecruitment: false, + inAppFeedback: false, inAppReview: true, inAppUpdates: true, patientCheckIn: false,