@bigbinary/neeto-commons-rn 1.8.5 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/eslint-config/index.js +84 -17
- package/eslint-config/rules/no-dangling-constants.js +1 -1
- package/lib/typescript/api/app_version.d.ts +10 -0
- package/lib/typescript/api/app_version.d.ts.map +1 -0
- package/lib/typescript/api/auth.d.ts +26 -0
- package/lib/typescript/api/auth.d.ts.map +1 -0
- package/lib/typescript/api/global_roles.d.ts +10 -0
- package/lib/typescript/api/global_roles.d.ts.map +1 -0
- package/lib/typescript/api/notifications.d.ts +38 -0
- package/lib/typescript/api/notifications.d.ts.map +1 -0
- package/lib/typescript/api/organization_roles.d.ts +11 -0
- package/lib/typescript/api/organization_roles.d.ts.map +1 -0
- package/lib/typescript/api/profile.d.ts +15 -0
- package/lib/typescript/api/profile.d.ts.map +1 -0
- package/lib/typescript/api/team_members.d.ts +34 -0
- package/lib/typescript/api/team_members.d.ts.map +1 -0
- package/lib/typescript/auth/index.d.ts +3 -0
- package/lib/typescript/auth/index.d.ts.map +1 -0
- package/lib/typescript/components/AppVersion.d.ts +2 -0
- package/lib/typescript/components/AppVersion.d.ts.map +1 -0
- package/lib/typescript/components/BottomSheet.d.ts +9 -0
- package/lib/typescript/components/BottomSheet.d.ts.map +1 -0
- package/lib/typescript/components/BottomSheetMenu.d.ts +7 -0
- package/lib/typescript/components/BottomSheetMenu.d.ts.map +1 -0
- package/lib/typescript/components/ConfirmSheet.d.ts +15 -0
- package/lib/typescript/components/ConfirmSheet.d.ts.map +1 -0
- package/lib/typescript/components/EmptyView.d.ts +19 -0
- package/lib/typescript/components/EmptyView.d.ts.map +1 -0
- package/lib/typescript/components/Header/BaseHeader.d.ts +7 -0
- package/lib/typescript/components/Header/BaseHeader.d.ts.map +1 -0
- package/lib/typescript/components/Header/ScreenHeader.d.ts +7 -0
- package/lib/typescript/components/Header/ScreenHeader.d.ts.map +1 -0
- package/lib/typescript/components/Header.d.ts +22 -0
- package/lib/typescript/components/Header.d.ts.map +1 -0
- package/lib/typescript/components/HyperlinkText.d.ts +17 -0
- package/lib/typescript/components/HyperlinkText.d.ts.map +1 -0
- package/lib/typescript/components/LineLoader.d.ts +13 -0
- package/lib/typescript/components/LineLoader.d.ts.map +1 -0
- package/lib/typescript/components/NotificationIcon.d.ts +6 -0
- package/lib/typescript/components/NotificationIcon.d.ts.map +1 -0
- package/lib/typescript/components/NotificationPreferenceList.d.ts +14 -0
- package/lib/typescript/components/NotificationPreferenceList.d.ts.map +1 -0
- package/lib/typescript/components/OfflineScreen.d.ts +7 -0
- package/lib/typescript/components/OfflineScreen.d.ts.map +1 -0
- package/lib/typescript/components/OrganizationItem.d.ts +8 -0
- package/lib/typescript/components/OrganizationItem.d.ts.map +1 -0
- package/lib/typescript/components/SocialButton.d.ts +11 -0
- package/lib/typescript/components/SocialButton.d.ts.map +1 -0
- package/lib/typescript/components/TextWithUrl.d.ts +7 -0
- package/lib/typescript/components/TextWithUrl.d.ts.map +1 -0
- package/lib/typescript/components/ToggleSwitch.d.ts +8 -0
- package/lib/typescript/components/ToggleSwitch.d.ts.map +1 -0
- package/lib/typescript/components/WorkspaceSwitcherPane.d.ts +9 -0
- package/lib/typescript/components/WorkspaceSwitcherPane.d.ts.map +1 -0
- package/lib/typescript/components/constants.d.ts +7 -0
- package/lib/typescript/components/constants.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +14 -0
- package/lib/typescript/components/index.d.ts.map +1 -0
- package/lib/typescript/config/axios/caseConversion.d.ts +4 -0
- package/lib/typescript/config/axios/caseConversion.d.ts.map +1 -0
- package/lib/typescript/config/axios/constants.d.ts +4 -0
- package/lib/typescript/config/axios/constants.d.ts.map +1 -0
- package/lib/typescript/config/axios/index.d.ts +13 -0
- package/lib/typescript/config/axios/index.d.ts.map +1 -0
- package/lib/typescript/config/axios/networkErrorHandler.d.ts +5 -0
- package/lib/typescript/config/axios/networkErrorHandler.d.ts.map +1 -0
- package/lib/typescript/config/axios/paramsSerializer.d.ts +3 -0
- package/lib/typescript/config/axios/paramsSerializer.d.ts.map +1 -0
- package/lib/typescript/config/axios/types.d.ts +10 -0
- package/lib/typescript/config/axios/types.d.ts.map +1 -0
- package/lib/typescript/config/bugsnag.d.ts +4 -0
- package/lib/typescript/config/bugsnag.d.ts.map +1 -0
- package/lib/typescript/config/dayjs.d.ts +23 -0
- package/lib/typescript/config/dayjs.d.ts.map +1 -0
- package/lib/typescript/config/googleSignin.d.ts +2 -0
- package/lib/typescript/config/googleSignin.d.ts.map +1 -0
- package/lib/typescript/config/index.d.ts +2 -0
- package/lib/typescript/config/index.d.ts.map +1 -0
- package/lib/typescript/config/validations.d.ts +2 -0
- package/lib/typescript/config/validations.d.ts.map +1 -0
- package/lib/typescript/constants/index.d.ts +8 -0
- package/lib/typescript/constants/index.d.ts.map +1 -0
- package/lib/typescript/contexts/index.d.ts +2 -0
- package/lib/typescript/contexts/index.d.ts.map +1 -0
- package/lib/typescript/contexts/localization.d.ts +26 -0
- package/lib/typescript/contexts/localization.d.ts.map +1 -0
- package/lib/typescript/helpers/asyncStore.d.ts +6 -0
- package/lib/typescript/helpers/asyncStore.d.ts.map +1 -0
- package/lib/typescript/helpers/index.d.ts +2 -0
- package/lib/typescript/helpers/index.d.ts.map +1 -0
- package/lib/typescript/helpers/keychain.d.ts +4 -0
- package/lib/typescript/helpers/keychain.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/apnsToken.d.ts +2 -0
- package/lib/typescript/helpers/notifications/apnsToken.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/constants.d.ts +5 -0
- package/lib/typescript/helpers/notifications/constants.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/getDeviceToken.d.ts +2 -0
- package/lib/typescript/helpers/notifications/getDeviceToken.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/index.d.ts +4 -0
- package/lib/typescript/helpers/notifications/index.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/registerBackgroundHandlers.d.ts +3 -0
- package/lib/typescript/helpers/notifications/registerBackgroundHandlers.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/types.d.ts +19 -0
- package/lib/typescript/helpers/notifications/types.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/usePushNotification.d.ts +7 -0
- package/lib/typescript/helpers/notifications/usePushNotification.d.ts.map +1 -0
- package/lib/typescript/helpers/scale.d.ts +2 -0
- package/lib/typescript/helpers/scale.d.ts.map +1 -0
- package/lib/typescript/helpers/utils.d.ts +5 -0
- package/lib/typescript/helpers/utils.d.ts.map +1 -0
- package/lib/typescript/hooks/index.d.ts +3 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -0
- package/lib/typescript/hooks/query/queryKeys.d.ts +9 -0
- package/lib/typescript/hooks/query/queryKeys.d.ts.map +1 -0
- package/lib/typescript/hooks/query/useAppVersionApi.d.ts +2 -0
- package/lib/typescript/hooks/query/useAppVersionApi.d.ts.map +1 -0
- package/lib/typescript/hooks/query/useAuth.d.ts +27 -0
- package/lib/typescript/hooks/query/useAuth.d.ts.map +1 -0
- package/lib/typescript/hooks/query/useNotification.d.ts +19 -0
- package/lib/typescript/hooks/query/useNotification.d.ts.map +1 -0
- package/lib/typescript/hooks/query/useProfile.d.ts +9 -0
- package/lib/typescript/hooks/query/useProfile.d.ts.map +1 -0
- package/lib/typescript/hooks/query/useRolesApi.d.ts +4 -0
- package/lib/typescript/hooks/query/useRolesApi.d.ts.map +1 -0
- package/lib/typescript/hooks/query/useTeamMembersApi.d.ts +23 -0
- package/lib/typescript/hooks/query/useTeamMembersApi.d.ts.map +1 -0
- package/lib/typescript/hooks/useAppState.d.ts +2 -0
- package/lib/typescript/hooks/useAppState.d.ts.map +1 -0
- package/lib/typescript/hooks/usePushNotificationPermissions.d.ts +2 -0
- package/lib/typescript/hooks/usePushNotificationPermissions.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +9 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/initializers/index.d.ts +3 -0
- package/lib/typescript/initializers/index.d.ts.map +1 -0
- package/lib/typescript/initializers/logRocket.d.ts +2 -0
- package/lib/typescript/initializers/logRocket.d.ts.map +1 -0
- package/lib/typescript/localization/i18n.d.ts +4 -0
- package/lib/typescript/localization/i18n.d.ts.map +1 -0
- package/lib/typescript/localization/index.d.ts +7 -0
- package/lib/typescript/localization/index.d.ts.map +1 -0
- package/lib/typescript/navigation/navigationService.d.ts +14 -0
- package/lib/typescript/navigation/navigationService.d.ts.map +1 -0
- package/lib/typescript/navigation/screenNames.d.ts +10 -0
- package/lib/typescript/navigation/screenNames.d.ts.map +1 -0
- package/lib/typescript/navigation/stacks.d.ts +3 -0
- package/lib/typescript/navigation/stacks.d.ts.map +1 -0
- package/lib/typescript/navigation/types.d.ts +24 -0
- package/lib/typescript/navigation/types.d.ts.map +1 -0
- package/lib/typescript/providers/NeetoCommonsRNProvider.d.ts +14 -0
- package/lib/typescript/providers/NeetoCommonsRNProvider.d.ts.map +1 -0
- package/lib/typescript/providers/index.d.ts +2 -0
- package/lib/typescript/providers/index.d.ts.map +1 -0
- package/lib/typescript/screens/NotificationsScreen.d.ts +11 -0
- package/lib/typescript/screens/NotificationsScreen.d.ts.map +1 -0
- package/lib/typescript/screens/authentication/AuthOptions.d.ts +7 -0
- package/lib/typescript/screens/authentication/AuthOptions.d.ts.map +1 -0
- package/lib/typescript/screens/authentication/IncompleteOnboardingInfo.d.ts +4 -0
- package/lib/typescript/screens/authentication/IncompleteOnboardingInfo.d.ts.map +1 -0
- package/lib/typescript/screens/authentication/LoginScreen.d.ts +3 -0
- package/lib/typescript/screens/authentication/LoginScreen.d.ts.map +1 -0
- package/lib/typescript/screens/authentication/OTPVerificationScreen.d.ts +4 -0
- package/lib/typescript/screens/authentication/OTPVerificationScreen.d.ts.map +1 -0
- package/lib/typescript/screens/authentication/Organization.d.ts +11 -0
- package/lib/typescript/screens/authentication/Organization.d.ts.map +1 -0
- package/lib/typescript/screens/authentication/SubDomainScreen.d.ts +4 -0
- package/lib/typescript/screens/authentication/SubDomainScreen.d.ts.map +1 -0
- package/lib/typescript/screens/authentication/constants.d.ts +17 -0
- package/lib/typescript/screens/authentication/constants.d.ts.map +1 -0
- package/lib/typescript/screens/index.d.ts +11 -0
- package/lib/typescript/screens/index.d.ts.map +1 -0
- package/lib/typescript/screens/profile/ChatWidget.d.ts +3 -0
- package/lib/typescript/screens/profile/ChatWidget.d.ts.map +1 -0
- package/lib/typescript/screens/profile/ProfileScreen.d.ts +12 -0
- package/lib/typescript/screens/profile/ProfileScreen.d.ts.map +1 -0
- package/lib/typescript/screens/profile/UserData.d.ts +21 -0
- package/lib/typescript/screens/profile/UserData.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/Add.d.ts +4 -0
- package/lib/typescript/screens/team_members/Add.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/Card.d.ts +9 -0
- package/lib/typescript/screens/team_members/Card.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/Checkbox.d.ts +11 -0
- package/lib/typescript/screens/team_members/Checkbox.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/Edit.d.ts +5 -0
- package/lib/typescript/screens/team_members/Edit.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/Email.d.ts +11 -0
- package/lib/typescript/screens/team_members/Email.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/LoadingCard.d.ts +6 -0
- package/lib/typescript/screens/team_members/LoadingCard.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/ProductSelect.d.ts +11 -0
- package/lib/typescript/screens/team_members/ProductSelect.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/RolesRadioGroup.d.ts +14 -0
- package/lib/typescript/screens/team_members/RolesRadioGroup.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/SaveButtons.d.ts +10 -0
- package/lib/typescript/screens/team_members/SaveButtons.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/constants.d.ts +16 -0
- package/lib/typescript/screens/team_members/constants.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/index.d.ts +16 -0
- package/lib/typescript/screens/team_members/index.d.ts.map +1 -0
- package/lib/typescript/screens/team_members/utils.d.ts +20 -0
- package/lib/typescript/screens/team_members/utils.d.ts.map +1 -0
- package/lib/typescript/slices/auth.d.ts +31 -0
- package/lib/typescript/slices/auth.d.ts.map +1 -0
- package/lib/typescript/slices/index.d.ts +6 -0
- package/lib/typescript/slices/index.d.ts.map +1 -0
- package/lib/typescript/store/hooks.d.ts +6 -0
- package/lib/typescript/store/hooks.d.ts.map +1 -0
- package/lib/typescript/store/index.d.ts +18 -0
- package/lib/typescript/store/index.d.ts.map +1 -0
- package/lib/typescript/store/utils.d.ts +8 -0
- package/lib/typescript/store/utils.d.ts.map +1 -0
- package/lib/typescript/utils/apiHelpers.d.ts +5 -0
- package/lib/typescript/utils/apiHelpers.d.ts.map +1 -0
- package/package.json +267 -70
- package/src/api/app_version.ts +24 -0
- package/src/api/auth.ts +68 -0
- package/src/api/global_roles.ts +15 -0
- package/src/api/notifications.ts +66 -0
- package/src/api/organization_roles.ts +17 -0
- package/src/api/profile.ts +17 -0
- package/src/api/team_members.ts +39 -0
- package/src/assets/images/apple-logo.svg +3 -0
- package/src/assets/images/google-logo.svg +6 -0
- package/src/auth/index.ts +2 -0
- package/src/components/AppVersion.ts +154 -0
- package/src/components/BottomSheet.ts +9 -0
- package/src/components/BottomSheetMenu.ts +6 -0
- package/src/components/ConfirmSheet.ts +15 -0
- package/src/components/EmptyView.tsx +40 -0
- package/src/components/Header/BaseHeader.ts +6 -0
- package/src/components/Header/ScreenHeader.ts +6 -0
- package/src/components/Header.tsx +139 -0
- package/src/components/HyperlinkText.tsx +34 -0
- package/src/components/LineLoader.tsx +23 -0
- package/src/components/NotificationIcon.tsx +53 -0
- package/src/components/NotificationPreferenceList.tsx +132 -0
- package/src/components/OfflineScreen.tsx +89 -0
- package/src/components/OrganizationItem.tsx +67 -0
- package/src/components/SocialButton.tsx +78 -0
- package/src/components/TextWithUrl.ts +6 -0
- package/src/components/ToggleSwitch.tsx +89 -0
- package/src/components/WorkspaceSwitcherPane.tsx +113 -0
- package/src/components/constants.ts +16 -0
- package/src/components/index.ts +13 -0
- package/src/config/axios/caseConversion.ts +60 -0
- package/src/config/axios/constants.ts +17 -0
- package/src/config/axios/index.ts +291 -0
- package/src/config/axios/networkErrorHandler.ts +47 -0
- package/src/config/axios/paramsSerializer.ts +9 -0
- package/src/config/axios/types.ts +13 -0
- package/src/config/bugsnag.ts +48 -0
- package/src/config/dayjs.ts +42 -0
- package/src/config/googleSignin.ts +36 -0
- package/src/config/index.ts +11 -0
- package/src/config/validations.ts +15 -0
- package/src/constants/index.ts +9 -0
- package/src/contexts/index.ts +4 -0
- package/src/contexts/localization.tsx +183 -0
- package/src/helpers/asyncStore.ts +39 -0
- package/src/helpers/index.ts +1 -0
- package/src/helpers/keychain.ts +46 -0
- package/src/helpers/notifications/apnsToken.ts +69 -0
- package/src/helpers/notifications/constants.ts +34 -0
- package/src/helpers/notifications/getDeviceToken.ts +17 -0
- package/src/helpers/notifications/index.ts +3 -0
- package/src/helpers/notifications/registerBackgroundHandlers.ts +62 -0
- package/src/helpers/notifications/types.ts +27 -0
- package/src/helpers/notifications/usePushNotification.ts +208 -0
- package/src/helpers/scale.ts +4 -0
- package/src/helpers/utils.ts +68 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/query/queryKeys.ts +8 -0
- package/src/hooks/query/useAppVersionApi.ts +12 -0
- package/src/hooks/query/useAuth.ts +253 -0
- package/src/hooks/query/useNotification.ts +130 -0
- package/src/hooks/query/useProfile.ts +21 -0
- package/src/hooks/query/useRolesApi.ts +25 -0
- package/src/hooks/query/useTeamMembersApi.ts +109 -0
- package/src/hooks/useAppState.ts +16 -0
- package/src/hooks/usePushNotificationPermissions.ts +111 -0
- package/src/index.ts +8 -0
- package/src/initializers/index.ts +3 -0
- package/src/initializers/logRocket.ts +25 -0
- package/src/localization/i18n.ts +19 -0
- package/src/localization/index.ts +14 -0
- package/src/navigation/navigationService.ts +37 -0
- package/src/navigation/screenNames.ts +11 -0
- package/src/navigation/stacks.tsx +30 -0
- package/src/navigation/types.ts +19 -0
- package/src/providers/NeetoCommonsRNProvider.tsx +77 -0
- package/src/providers/index.ts +1 -0
- package/src/screens/NotificationsScreen.tsx +290 -0
- package/src/screens/authentication/AuthOptions.tsx +254 -0
- package/src/screens/authentication/IncompleteOnboardingInfo.tsx +110 -0
- package/src/screens/authentication/LoginScreen.tsx +170 -0
- package/src/screens/authentication/OTPVerificationScreen.tsx +235 -0
- package/src/screens/authentication/Organization.tsx +102 -0
- package/src/screens/authentication/SubDomainScreen.tsx +176 -0
- package/src/screens/authentication/constants.ts +30 -0
- package/src/screens/index.ts +10 -0
- package/src/screens/profile/ChatWidget.tsx +8 -0
- package/src/screens/profile/ProfileScreen.tsx +251 -0
- package/src/screens/profile/UserData.tsx +92 -0
- package/src/screens/team_members/Add.tsx +248 -0
- package/src/screens/team_members/Card.tsx +124 -0
- package/src/screens/team_members/Checkbox.tsx +88 -0
- package/src/screens/team_members/Edit.tsx +171 -0
- package/src/screens/team_members/Email.tsx +59 -0
- package/src/screens/team_members/LoadingCard.tsx +75 -0
- package/src/screens/team_members/ProductSelect.tsx +218 -0
- package/src/screens/team_members/RolesRadioGroup.tsx +135 -0
- package/src/screens/team_members/SaveButtons.tsx +62 -0
- package/src/screens/team_members/constants.ts +33 -0
- package/src/screens/team_members/index.tsx +223 -0
- package/src/screens/team_members/utils.ts +84 -0
- package/src/slices/auth.ts +86 -0
- package/src/slices/index.ts +7 -0
- package/src/store/hooks.ts +8 -0
- package/src/store/index.ts +47 -0
- package/src/store/utils.ts +36 -0
- package/src/utils/apiHelpers.ts +7 -0
- package/types/assets.d.ts +38 -0
- package/types/neeto-icons-rn.d.ts +548 -0
- package/api/app_version.js +0 -1
- package/api/auth.js +0 -1
- package/api/global_roles.js +0 -1
- package/api/notifications.js +0 -1
- package/api/organization_roles.js +0 -1
- package/api/profile.js +0 -1
- package/api/team_members.js +0 -1
- package/auth/index.d.ts +0 -9
- package/auth/index.js +0 -1
- package/components/AppVersion.js +0 -1
- package/components/BottomSheet.js +0 -1
- package/components/BottomSheetMenu.js +0 -1
- package/components/ConfirmSheet.js +0 -1
- package/components/EmptyView.js +0 -1
- package/components/Header/BaseHeader.js +0 -1
- package/components/Header/ScreenHeader.js +0 -1
- package/components/Header.js +0 -1
- package/components/HyperlinkText.js +0 -1
- package/components/LineLoader.js +0 -1
- package/components/NotificationIcon.js +0 -1
- package/components/OfflineScreen.js +0 -1
- package/components/TextWithUrl.js +0 -1
- package/components/WorkspaceSwitcherPane.js +0 -1
- package/components/constants.js +0 -1
- package/components/index.js +0 -1
- package/config/axios/caseConversion.js +0 -1
- package/config/axios/constants.js +0 -1
- package/config/axios/index.js +0 -1
- package/config/axios/networkErrorHandler.js +0 -1
- package/config/axios/paramsSerializer.js +0 -1
- package/config/bugsnag.js +0 -1
- package/config/dayjs.js +0 -1
- package/config/googleSignin.js +0 -1
- package/config/index.js +0 -1
- package/config/validations.js +0 -1
- package/constants/index.d.ts +0 -5
- package/constants/index.js +0 -1
- package/contexts/index.js +0 -0
- package/contexts/localization.js +0 -1
- package/helpers/asyncStore.js +0 -1
- package/helpers/index.d.ts +0 -13
- package/helpers/index.js +0 -1
- package/helpers/keychain.d.ts +0 -4
- package/helpers/keychain.js +0 -1
- package/helpers/notifications/index.d.ts +0 -8
- package/helpers/notifications/index.js +0 -1
- package/helpers/notifications/usePushNotification.js +0 -1
- package/helpers/scale.js +0 -1
- package/helpers/utils.js +0 -1
- package/hooks/index.d.ts +0 -11
- package/hooks/index.js +0 -1
- package/hooks/query/constants.js +0 -1
- package/hooks/query/queryKeys.js +0 -1
- package/hooks/query/useAppVersionApi.js +0 -1
- package/hooks/query/useAuth.js +0 -1
- package/hooks/query/useNotification.js +0 -1
- package/hooks/query/useProfile.js +0 -1
- package/hooks/query/useRolesApi.js +0 -1
- package/hooks/query/useTeamMembersApi.js +0 -1
- package/hooks/useAppState.js +0 -1
- package/hooks/usePushNotificationPermissions.js +0 -1
- package/initializers/index.js +0 -1
- package/initializers/logRocket.js +0 -1
- package/localization/index.js +0 -1
- package/localization/translations/it.json +0 -13
- package/navigation/navigationService.js +0 -1
- package/navigation/screenNames.js +0 -1
- package/navigation/stacks.js +0 -1
- package/providers/NeetoCommonsRNProvider.js +0 -1
- package/providers/index.d.ts +0 -16
- package/providers/index.js +0 -1
- package/screens/NotificationsScreen.js +0 -1
- package/screens/authentication/AuthOptions.js +0 -1
- package/screens/authentication/IncompleteOnboardingInfo.js +0 -1
- package/screens/authentication/LoginScreen.js +0 -1
- package/screens/authentication/OTPVerificationScreen.js +0 -1
- package/screens/authentication/Organization.js +0 -1
- package/screens/authentication/SubDomainScreen.js +0 -1
- package/screens/authentication/constants.js +0 -1
- package/screens/index.js +0 -1
- package/screens/profile/ChatWidget.js +0 -1
- package/screens/profile/ProfileScreen.js +0 -1
- package/screens/profile/UserData.js +0 -1
- package/screens/team_members/Add.js +0 -1
- package/screens/team_members/Card.js +0 -1
- package/screens/team_members/Checkbox.js +0 -1
- package/screens/team_members/Edit.js +0 -1
- package/screens/team_members/Email.js +0 -1
- package/screens/team_members/LoadingCard.js +0 -1
- package/screens/team_members/ProductSelect.js +0 -1
- package/screens/team_members/RolesRadioGroup.js +0 -1
- package/screens/team_members/SaveButtons.js +0 -1
- package/screens/team_members/constants.js +0 -1
- package/screens/team_members/index.js +0 -1
- package/screens/team_members/utils.js +0 -1
- package/slices/auth.js +0 -1
- package/slices/index.js +0 -1
- package/store/index.d.ts +0 -7
- package/store/index.js +0 -1
- package/store/utils.js +0 -1
- package/utils/apiHelpers.js +0 -1
- /package/{assets → src/assets}/images/empty.svg +0 -0
- /package/{assets → src/assets}/images/wifi.png +0 -0
- /package/{localization → src/localization}/translations/en.json +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useContext,
|
|
3
|
+
createContext,
|
|
4
|
+
useState,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useCallback,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
import { mergeDeepRight } from "ramda";
|
|
12
|
+
|
|
13
|
+
import { getItem, setItem } from "../helpers/asyncStore";
|
|
14
|
+
import {
|
|
15
|
+
strings as commonsCatalog,
|
|
16
|
+
type CommonsCatalog,
|
|
17
|
+
} from "../localization";
|
|
18
|
+
import { i18n } from "../localization/i18n";
|
|
19
|
+
|
|
20
|
+
export type FormatStringArg = unknown;
|
|
21
|
+
|
|
22
|
+
export type FormatString = (
|
|
23
|
+
template: string,
|
|
24
|
+
...args: FormatStringArg[]
|
|
25
|
+
) => string | Array<string | ReactNode>;
|
|
26
|
+
|
|
27
|
+
// Host apps deep-merge their own catalogs in at the root, so the facade
|
|
28
|
+
// cannot enumerate its own key set.
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
type HostStringsRoot = Record<string, any>;
|
|
31
|
+
|
|
32
|
+
export type LocalizedStrings = HostStringsRoot & {
|
|
33
|
+
commons: CommonsCatalog;
|
|
34
|
+
formatString: FormatString;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export interface LocalizationValue {
|
|
38
|
+
strings: LocalizedStrings;
|
|
39
|
+
code: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ChangeLanguage = (languageCode: string) => void;
|
|
43
|
+
|
|
44
|
+
// Locale code -> catalog, as a host app passes it in.
|
|
45
|
+
export type HostStrings = Record<string, Record<string, unknown>>;
|
|
46
|
+
|
|
47
|
+
const LocalizationContext = createContext<LocalizationValue | undefined>(
|
|
48
|
+
undefined
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const LocalizationUpdateContext = createContext<ChangeLanguage | undefined>(
|
|
52
|
+
undefined
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Both hooks are only reachable under the provider, which always supplies a
|
|
56
|
+
// value; the assertions keep consumers free of null checks.
|
|
57
|
+
export const useLocalization = () =>
|
|
58
|
+
useContext(LocalizationContext) as LocalizationValue;
|
|
59
|
+
|
|
60
|
+
export const useLocalizationUpdate = () =>
|
|
61
|
+
useContext(LocalizationUpdateContext) as ChangeLanguage;
|
|
62
|
+
|
|
63
|
+
// Drop-in replacement for LocalizedStrings.formatString: supports positional
|
|
64
|
+
// {0} tokens, named {token} tokens (single object argument), and returns an
|
|
65
|
+
// array when any argument is a React element.
|
|
66
|
+
export const formatString: FormatString = (template, ...args) => {
|
|
67
|
+
const named =
|
|
68
|
+
args.length === 1 &&
|
|
69
|
+
typeof args[0] === "object" &&
|
|
70
|
+
args[0] !== null &&
|
|
71
|
+
!React.isValidElement(args[0])
|
|
72
|
+
? (args[0] as Record<string, unknown>)
|
|
73
|
+
: null;
|
|
74
|
+
|
|
75
|
+
const parts = String(template)
|
|
76
|
+
.split(/(\{[\w\d]+\})/g)
|
|
77
|
+
.filter(part => part !== "")
|
|
78
|
+
.map(part => {
|
|
79
|
+
const match = part.match(/^\{([\w\d]+)\}$/);
|
|
80
|
+
if (!match) return part;
|
|
81
|
+
|
|
82
|
+
const value = named ? named[match[1]] : args[Number(match[1])];
|
|
83
|
+
|
|
84
|
+
return value === undefined || value === null ? "" : value;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return parts.some(React.isValidElement)
|
|
88
|
+
? (parts as Array<string | ReactNode>)
|
|
89
|
+
: parts.join("");
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const buildStrings = (
|
|
93
|
+
language: string,
|
|
94
|
+
hostStrings: HostStrings = {}
|
|
95
|
+
): LocalizedStrings => {
|
|
96
|
+
const commons = mergeDeepRight(
|
|
97
|
+
commonsCatalog.en,
|
|
98
|
+
commonsCatalog[language] ?? {}
|
|
99
|
+
) as CommonsCatalog;
|
|
100
|
+
|
|
101
|
+
const host = mergeDeepRight(
|
|
102
|
+
hostStrings.en ?? {},
|
|
103
|
+
hostStrings[language] ?? {}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return { ...host, commons, formatString };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
interface LocalizationProviderProps {
|
|
110
|
+
children?: ReactNode;
|
|
111
|
+
hostStrings?: HostStrings;
|
|
112
|
+
initialLanguage?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const LocalizationProvider = ({
|
|
116
|
+
children,
|
|
117
|
+
hostStrings = {},
|
|
118
|
+
initialLanguage = "en",
|
|
119
|
+
}: LocalizationProviderProps) => {
|
|
120
|
+
const [language, setLanguage] = useState(initialLanguage);
|
|
121
|
+
|
|
122
|
+
const strings = useMemo(() => {
|
|
123
|
+
const languages = new Set([
|
|
124
|
+
"en",
|
|
125
|
+
...Object.keys(commonsCatalog),
|
|
126
|
+
...Object.keys(hostStrings),
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
languages.forEach(lang => {
|
|
130
|
+
i18n.addResourceBundle(
|
|
131
|
+
lang,
|
|
132
|
+
"commons",
|
|
133
|
+
mergeDeepRight(commonsCatalog.en, commonsCatalog[lang] ?? {}),
|
|
134
|
+
true,
|
|
135
|
+
true
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
i18n.addResourceBundle(
|
|
139
|
+
lang,
|
|
140
|
+
"host",
|
|
141
|
+
mergeDeepRight(hostStrings.en ?? {}, hostStrings[lang] ?? {}),
|
|
142
|
+
true,
|
|
143
|
+
true
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return buildStrings(language, hostStrings);
|
|
148
|
+
}, [language, hostStrings]);
|
|
149
|
+
|
|
150
|
+
const changeLanguage = useCallback<ChangeLanguage>(languageCode => {
|
|
151
|
+
if (!languageCode) return;
|
|
152
|
+
|
|
153
|
+
i18n.changeLanguage(languageCode);
|
|
154
|
+
setItem("language", languageCode);
|
|
155
|
+
setLanguage(languageCode);
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const restoreStoredLanguage = async () => {
|
|
160
|
+
try {
|
|
161
|
+
const storedLanguage = await getItem<string>("language");
|
|
162
|
+
if (storedLanguage) changeLanguage(storedLanguage);
|
|
163
|
+
} catch {
|
|
164
|
+
// Keep the initial language when restore fails.
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
restoreStoredLanguage();
|
|
169
|
+
}, [changeLanguage]);
|
|
170
|
+
|
|
171
|
+
const value = useMemo(
|
|
172
|
+
() => ({ strings, code: language }),
|
|
173
|
+
[strings, language]
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<LocalizationContext.Provider value={value}>
|
|
178
|
+
<LocalizationUpdateContext.Provider value={changeLanguage}>
|
|
179
|
+
{children}
|
|
180
|
+
</LocalizationUpdateContext.Provider>
|
|
181
|
+
</LocalizationContext.Provider>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
2
|
+
|
|
3
|
+
const setItem = async (key: string, value: unknown) => {
|
|
4
|
+
try {
|
|
5
|
+
return await AsyncStorage.setItem(key, JSON.stringify(value));
|
|
6
|
+
} catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// The stored value is whatever JSON the caller wrote, so callers name the
|
|
12
|
+
// type they expect: getItem<string>(STORAGE_KEYS.DEVICE_TOKEN).
|
|
13
|
+
const getItem = async <T = unknown>(key: string): Promise<T | null> => {
|
|
14
|
+
try {
|
|
15
|
+
const jsonValue = await AsyncStorage.getItem(key);
|
|
16
|
+
|
|
17
|
+
return jsonValue !== null ? (JSON.parse(jsonValue) as T) : null;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const removeItem = async (key: string) => {
|
|
24
|
+
try {
|
|
25
|
+
return await AsyncStorage.removeItem(key);
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getAllKeys = async () => {
|
|
32
|
+
try {
|
|
33
|
+
return await AsyncStorage.getAllKeys();
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { setItem, getItem, removeItem, getAllKeys };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { formatDateTime } from "../config/dayjs";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as Keychain from "react-native-keychain";
|
|
2
|
+
|
|
3
|
+
// To avoid calling keychain every time we are caching key & value in local cache variable.
|
|
4
|
+
|
|
5
|
+
const cache: Record<string, string> = {};
|
|
6
|
+
export const setSecureValue = async (key: string, value: string) => {
|
|
7
|
+
try {
|
|
8
|
+
await Keychain.setGenericPassword(key, value, { service: key });
|
|
9
|
+
cache[key] = value;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const getSecureValue = async (key: string) => {
|
|
18
|
+
if (cache[key]) {
|
|
19
|
+
return cache[key];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const result = await Keychain.getGenericPassword({ service: key });
|
|
24
|
+
// getGenericPassword resolves to `false` when the service has no entry.
|
|
25
|
+
if (result && result.password) {
|
|
26
|
+
cache[key] = result.password;
|
|
27
|
+
|
|
28
|
+
return result.password;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return false;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const removeSecureValue = async (key: string) => {
|
|
38
|
+
try {
|
|
39
|
+
await Keychain.resetGenericPassword({ service: key });
|
|
40
|
+
delete cache[key];
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NativeEventEmitter,
|
|
3
|
+
NativeModules,
|
|
4
|
+
Platform,
|
|
5
|
+
type EmitterSubscription,
|
|
6
|
+
} from "react-native";
|
|
7
|
+
|
|
8
|
+
import { DEVICE_TOKEN_TIMEOUT_MS } from "./constants";
|
|
9
|
+
import { type NeetoAPNSTokenModule } from "./types";
|
|
10
|
+
|
|
11
|
+
// JS side of the NeetoAPNSToken native module (see
|
|
12
|
+
// ios/neetoCommons/NeetoAPNSToken.m for the reference implementation apps
|
|
13
|
+
// copy). iOS is Firebase-free: UIKit hands the APNs token to the AppDelegate,
|
|
14
|
+
// which forwards it to the module; this resolves it to JS. Resolves null when
|
|
15
|
+
// registration cannot complete (simulator, module not linked, timeout) — the
|
|
16
|
+
// caller already treats a missing token as "skip registration".
|
|
17
|
+
export const getAPNSToken = (): Promise<string | null> => {
|
|
18
|
+
const NeetoAPNSToken = NativeModules.NeetoAPNSToken as
|
|
19
|
+
NeetoAPNSTokenModule | undefined;
|
|
20
|
+
|
|
21
|
+
if (Platform.OS !== "ios" || !NeetoAPNSToken) return Promise.resolve(null);
|
|
22
|
+
|
|
23
|
+
return new Promise(resolve => {
|
|
24
|
+
const emitter = new NativeEventEmitter(
|
|
25
|
+
NeetoAPNSToken as unknown as ConstructorParameters<
|
|
26
|
+
typeof NativeEventEmitter
|
|
27
|
+
>[0]
|
|
28
|
+
);
|
|
29
|
+
let settled = false;
|
|
30
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
31
|
+
let receivedSubscription: EmitterSubscription | null = null;
|
|
32
|
+
let errorSubscription: EmitterSubscription | null = null;
|
|
33
|
+
|
|
34
|
+
const finish = (token: string | null) => {
|
|
35
|
+
if (settled) return;
|
|
36
|
+
settled = true;
|
|
37
|
+
receivedSubscription?.remove();
|
|
38
|
+
errorSubscription?.remove();
|
|
39
|
+
if (timer) clearTimeout(timer);
|
|
40
|
+
resolve(token || null);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
receivedSubscription = emitter.addListener(
|
|
44
|
+
"neetoAPNSTokenReceived",
|
|
45
|
+
({ token }: { token: string }) => finish(token)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
errorSubscription = emitter.addListener("neetoAPNSTokenError", () =>
|
|
49
|
+
finish(null)
|
|
50
|
+
);
|
|
51
|
+
timer = setTimeout(() => finish(null), DEVICE_TOKEN_TIMEOUT_MS);
|
|
52
|
+
|
|
53
|
+
const resolveExistingOrRegister = async () => {
|
|
54
|
+
try {
|
|
55
|
+
const existingToken = await NeetoAPNSToken.getToken();
|
|
56
|
+
|
|
57
|
+
if (existingToken) {
|
|
58
|
+
finish(existingToken);
|
|
59
|
+
} else {
|
|
60
|
+
NeetoAPNSToken.registerForRemoteNotifications();
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
finish(null);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
resolveExistingOrRegister();
|
|
68
|
+
});
|
|
69
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AndroidImportance,
|
|
3
|
+
type AndroidChannel,
|
|
4
|
+
} from "react-native-notify-kit";
|
|
5
|
+
|
|
6
|
+
// Server payloads bake these channel ids into android_channel_id — the ids
|
|
7
|
+
// must exist byte-identically on device.
|
|
8
|
+
export const NOTIFICATION_CHANNELS: AndroidChannel[] = [
|
|
9
|
+
{
|
|
10
|
+
id: "default",
|
|
11
|
+
name: "Default Channel",
|
|
12
|
+
importance: AndroidImportance.HIGH,
|
|
13
|
+
sound: "default",
|
|
14
|
+
vibration: true,
|
|
15
|
+
lights: true,
|
|
16
|
+
badge: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "channel_01",
|
|
20
|
+
name: "Channel 01",
|
|
21
|
+
importance: AndroidImportance.HIGH,
|
|
22
|
+
lights: true,
|
|
23
|
+
vibration: true,
|
|
24
|
+
badge: true,
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const NOTIFICATION_CHANNEL_IDS = NOTIFICATION_CHANNELS.map(
|
|
29
|
+
({ id }) => id
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// How long to wait for the APNs registration callback before treating the
|
|
33
|
+
// token as unavailable (simulators without push entitlement never call back).
|
|
34
|
+
export const DEVICE_TOKEN_TIMEOUT_MS = 10000;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
|
|
3
|
+
import messaging from "@react-native-firebase/messaging";
|
|
4
|
+
|
|
5
|
+
import { getAPNSToken } from "./apnsToken";
|
|
6
|
+
|
|
7
|
+
// iOS keeps the historical APNs-direct backend contract with no Firebase at
|
|
8
|
+
// all — the raw APNs token comes from the NeetoAPNSToken native module.
|
|
9
|
+
// Android registers the FCM token via @react-native-firebase/messaging
|
|
10
|
+
// (Firebase links Android-only; see react-native.config.js).
|
|
11
|
+
export const getDeviceToken = (): Promise<string | null> => {
|
|
12
|
+
if (Platform.OS === "ios") {
|
|
13
|
+
return getAPNSToken();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return messaging().getToken();
|
|
17
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
|
|
3
|
+
import messaging from "@react-native-firebase/messaging";
|
|
4
|
+
import dayjs from "dayjs";
|
|
5
|
+
import notifee, { EventType } from "react-native-notify-kit";
|
|
6
|
+
|
|
7
|
+
import { type NotificationPressHandler } from "./types";
|
|
8
|
+
|
|
9
|
+
import { markReadUnReadNotificationAPI } from "../../api/notifications";
|
|
10
|
+
|
|
11
|
+
// Must be called at module scope (before AppRegistry.registerComponent) —
|
|
12
|
+
// handlers registered inside components are too late for killed-state
|
|
13
|
+
// notifications.
|
|
14
|
+
export const registerBackgroundHandlers = (
|
|
15
|
+
onNotificationPress?: NotificationPressHandler
|
|
16
|
+
) => {
|
|
17
|
+
// FCM background handler — Android-only (iOS is Firebase-free; killed-state
|
|
18
|
+
// APNs alert pushes are displayed by the OS).
|
|
19
|
+
if (Platform.OS === "android") {
|
|
20
|
+
messaging().setBackgroundMessageHandler(async remoteMessage => {
|
|
21
|
+
// Data-only messages need manual display; notification-type messages
|
|
22
|
+
// are displayed by the OS automatically.
|
|
23
|
+
if (remoteMessage.data && !remoteMessage.notification) {
|
|
24
|
+
await notifee.displayNotification({
|
|
25
|
+
// FCM types a data value as string | object | number; the neeto
|
|
26
|
+
// notifications engine only ever sends strings.
|
|
27
|
+
title: (remoteMessage.data.title as string) || "New Notification",
|
|
28
|
+
body:
|
|
29
|
+
(remoteMessage.data.message as string) || "You have a new message",
|
|
30
|
+
data: remoteMessage.data,
|
|
31
|
+
android: {
|
|
32
|
+
channelId: "default",
|
|
33
|
+
smallIcon: "ic_notification",
|
|
34
|
+
sound: "default",
|
|
35
|
+
pressAction: { id: "default" },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
notifee.onBackgroundEvent(async ({ type, detail }) => {
|
|
43
|
+
if (type !== EventType.PRESS) return;
|
|
44
|
+
|
|
45
|
+
const notification = detail.notification;
|
|
46
|
+
const notificationId = notification?.data?.notification_id as
|
|
47
|
+
string | undefined;
|
|
48
|
+
|
|
49
|
+
if (notificationId) {
|
|
50
|
+
try {
|
|
51
|
+
await markReadUnReadNotificationAPI({
|
|
52
|
+
notificationId,
|
|
53
|
+
readAt: dayjs().toISOString(),
|
|
54
|
+
});
|
|
55
|
+
} catch {
|
|
56
|
+
// Best-effort; the list screen will reconcile on next fetch.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
onNotificationPress?.(notification);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// The data bag the neeto_notifications engine sends, normalised across the
|
|
2
|
+
// FCM and APNs payload shapes.
|
|
3
|
+
export interface NotificationData {
|
|
4
|
+
url?: string;
|
|
5
|
+
notification_id?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// What reaches a press handler. notifee delivers `{ notification }` event
|
|
12
|
+
// details; the FCM foreground path delivers the message itself.
|
|
13
|
+
export interface PressedNotification {
|
|
14
|
+
data?: NotificationData;
|
|
15
|
+
notification?: { data?: NotificationData };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type NotificationPressHandler = (
|
|
19
|
+
notification?: PressedNotification
|
|
20
|
+
) => void;
|
|
21
|
+
|
|
22
|
+
// The native module apps copy into their iOS target; absent on Android and
|
|
23
|
+
// on iOS builds that have not added the files yet.
|
|
24
|
+
export interface NeetoAPNSTokenModule {
|
|
25
|
+
getToken: () => Promise<string | null>;
|
|
26
|
+
registerForRemoteNotifications: () => void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { useEffect, useCallback, useState } from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
|
|
4
|
+
import messaging from "@react-native-firebase/messaging";
|
|
5
|
+
import dayjs from "dayjs";
|
|
6
|
+
import notifee, { EventType } from "react-native-notify-kit";
|
|
7
|
+
import { RESULTS, checkNotifications } from "react-native-permissions";
|
|
8
|
+
|
|
9
|
+
import { NOTIFICATION_CHANNELS } from "./constants";
|
|
10
|
+
import { getDeviceToken } from "./getDeviceToken";
|
|
11
|
+
import {
|
|
12
|
+
type NotificationPressHandler,
|
|
13
|
+
type PressedNotification,
|
|
14
|
+
} from "./types";
|
|
15
|
+
|
|
16
|
+
import { bugsnagErrorHandler } from "../../config/bugsnag";
|
|
17
|
+
import { STORAGE_KEYS } from "../../constants";
|
|
18
|
+
import { useNotification } from "../../hooks/query/useNotification";
|
|
19
|
+
import { useAppState } from "../../hooks/useAppState";
|
|
20
|
+
import { Store } from "../../store";
|
|
21
|
+
import { getItem, setItem } from "../asyncStore";
|
|
22
|
+
|
|
23
|
+
let pendingRegistrationToken: string | null = null;
|
|
24
|
+
|
|
25
|
+
interface PushNotificationOptions {
|
|
26
|
+
handleClick: NotificationPressHandler;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const usePushNotification = (
|
|
30
|
+
isLoggedIn: boolean,
|
|
31
|
+
{ handleClick }: PushNotificationOptions = { handleClick: () => {} }
|
|
32
|
+
) => {
|
|
33
|
+
const appState = useAppState();
|
|
34
|
+
const [notificationPermission, setNotificationPermission] = useState(false);
|
|
35
|
+
|
|
36
|
+
const { useRegisterTokenMutation, useMarkReadUnreadNotification } =
|
|
37
|
+
useNotification();
|
|
38
|
+
const { mutate: registerToken } = useRegisterTokenMutation();
|
|
39
|
+
|
|
40
|
+
const { mutate: markReadUnreadNotification } =
|
|
41
|
+
useMarkReadUnreadNotification();
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const checkPermission = async () => {
|
|
45
|
+
try {
|
|
46
|
+
if (Platform.OS === "android" && Platform.Version < 33) {
|
|
47
|
+
setNotificationPermission(true);
|
|
48
|
+
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { status } = await checkNotifications();
|
|
53
|
+
setNotificationPermission(status === RESULTS.GRANTED);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
bugsnagErrorHandler(JSON.stringify(error), "notification");
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (appState === "active") {
|
|
60
|
+
checkPermission();
|
|
61
|
+
}
|
|
62
|
+
}, [appState]);
|
|
63
|
+
|
|
64
|
+
const createChannels = useCallback(async () => {
|
|
65
|
+
if (Platform.OS !== "android") return;
|
|
66
|
+
|
|
67
|
+
for (const channel of NOTIFICATION_CHANNELS) {
|
|
68
|
+
await notifee.createChannel(channel);
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const handleTokenRegistration = useCallback(
|
|
73
|
+
async (token: string | null) => {
|
|
74
|
+
const currentIsLoggedIn = Store.getState()?.auth?.isLoggedIn || false;
|
|
75
|
+
if (!currentIsLoggedIn || !token) return;
|
|
76
|
+
|
|
77
|
+
if (pendingRegistrationToken === token) return;
|
|
78
|
+
pendingRegistrationToken = token;
|
|
79
|
+
|
|
80
|
+
const storedToken = await getItem<string>(STORAGE_KEYS.DEVICE_TOKEN);
|
|
81
|
+
if (storedToken === token) {
|
|
82
|
+
pendingRegistrationToken = null;
|
|
83
|
+
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const payload = {
|
|
88
|
+
device: {
|
|
89
|
+
device_token: token,
|
|
90
|
+
platform: Platform.OS,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
registerToken(payload, {
|
|
95
|
+
onSuccess: async () => {
|
|
96
|
+
await setItem(STORAGE_KEYS.DEVICE_TOKEN, token);
|
|
97
|
+
pendingRegistrationToken = null;
|
|
98
|
+
},
|
|
99
|
+
onError: () => {
|
|
100
|
+
pendingRegistrationToken = null;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
[registerToken]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const markNotificationsAsRead = useCallback(
|
|
108
|
+
(notification: PressedNotification) => {
|
|
109
|
+
const notificationId =
|
|
110
|
+
notification?.data?.notification_id ||
|
|
111
|
+
notification?.notification?.data?.notification_id;
|
|
112
|
+
handleClick(notification);
|
|
113
|
+
if (notificationId) {
|
|
114
|
+
markReadUnreadNotification({
|
|
115
|
+
readAt: dayjs().toISOString(),
|
|
116
|
+
notificationId,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[handleClick, markReadUnreadNotification]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Foreground FCM message → display via notifee. Android-only: iOS is
|
|
124
|
+
// Firebase-free (APNs-direct; foreground presentation is handled by the
|
|
125
|
+
// OS + notifee's UNUserNotificationCenter delegate).
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (Platform.OS !== "android") return undefined;
|
|
128
|
+
|
|
129
|
+
const unsubscribe = messaging().onMessage(async remoteMessage => {
|
|
130
|
+
// FCM types a data value as string | object | number; the neeto
|
|
131
|
+
// notifications engine only ever sends strings.
|
|
132
|
+
const title =
|
|
133
|
+
remoteMessage.notification?.title ||
|
|
134
|
+
(remoteMessage.data?.title as string) ||
|
|
135
|
+
"New Notification";
|
|
136
|
+
|
|
137
|
+
const body =
|
|
138
|
+
remoteMessage.notification?.body ||
|
|
139
|
+
(remoteMessage.data?.message as string) ||
|
|
140
|
+
"You have a new message";
|
|
141
|
+
|
|
142
|
+
await notifee.displayNotification({
|
|
143
|
+
title,
|
|
144
|
+
body,
|
|
145
|
+
data: { url: remoteMessage.data?.url, ...remoteMessage.data } as Record<
|
|
146
|
+
string,
|
|
147
|
+
string | number | object
|
|
148
|
+
>,
|
|
149
|
+
android: {
|
|
150
|
+
channelId: "default",
|
|
151
|
+
smallIcon: "ic_notification",
|
|
152
|
+
sound: "default",
|
|
153
|
+
pressAction: { id: "default" },
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return unsubscribe;
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
161
|
+
// Foreground notification interaction events
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const unsubscribe = notifee.onForegroundEvent(({ type, detail }) => {
|
|
164
|
+
if (type === EventType.PRESS) {
|
|
165
|
+
markNotificationsAsRead(detail.notification as PressedNotification);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return unsubscribe;
|
|
170
|
+
}, [markNotificationsAsRead]);
|
|
171
|
+
|
|
172
|
+
// FCM token refresh listener — Android-only (no Firebase on iOS; the APNs
|
|
173
|
+
// token is re-fetched on every app-active initialize and deduped).
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (Platform.OS !== "android") return undefined;
|
|
176
|
+
|
|
177
|
+
const unsubscribe = messaging().onTokenRefresh(async () => {
|
|
178
|
+
const token = await getDeviceToken();
|
|
179
|
+
await handleTokenRegistration(token);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return unsubscribe;
|
|
183
|
+
}, [handleTokenRegistration]);
|
|
184
|
+
|
|
185
|
+
const initializePN = useCallback(async () => {
|
|
186
|
+
try {
|
|
187
|
+
await createChannels();
|
|
188
|
+
const token = await getDeviceToken();
|
|
189
|
+
if (!token) {
|
|
190
|
+
bugsnagErrorHandler(
|
|
191
|
+
`${Platform.OS === "ios" ? "APNs" : "FCM"} token registration failed — no token returned`,
|
|
192
|
+
"notification"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
await handleTokenRegistration(token);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
bugsnagErrorHandler(JSON.stringify(error), "notification");
|
|
198
|
+
}
|
|
199
|
+
}, [createChannels, handleTokenRegistration]);
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (isLoggedIn && appState === "active" && notificationPermission) {
|
|
203
|
+
initializePN();
|
|
204
|
+
}
|
|
205
|
+
}, [initializePN, isLoggedIn, appState, notificationPermission]);
|
|
206
|
+
|
|
207
|
+
return [];
|
|
208
|
+
};
|