├── .nvmrc
├── .watchmanconfig
├── .husky
├── commit-msg
└── pre-commit
├── src
├── modules
│ ├── Alerts
│ │ ├── index.ts
│ │ └── screens
│ │ │ ├── index.ts
│ │ │ └── Alerts.tsx
│ ├── Auth
│ │ ├── index.ts
│ │ ├── features
│ │ │ ├── index.ts
│ │ │ ├── config.ts
│ │ │ ├── useSignIn.ts
│ │ │ ├── useAuth.ts
│ │ │ └── auth-observable.ts
│ │ ├── screens
│ │ │ ├── index.ts
│ │ │ └── Auth.tsx
│ │ ├── widgets
│ │ │ └── index.ts
│ │ └── ui
│ │ │ ├── index.ts
│ │ │ ├── AuthMainInformation.tsx
│ │ │ ├── AuthLogo.tsx
│ │ │ ├── AuthFooter.tsx
│ │ │ └── AuthWrapper.tsx
│ ├── Common
│ │ ├── ui
│ │ │ ├── index.ts
│ │ │ └── Modals
│ │ │ │ ├── index.ts
│ │ │ │ ├── UpdateModal
│ │ │ │ ├── index.ts
│ │ │ │ └── UpdateModal.tsx
│ │ │ │ └── config.ts
│ │ ├── models.ts
│ │ ├── index.ts
│ │ └── features
│ │ │ ├── index.ts
│ │ │ ├── useTheme.ts
│ │ │ ├── useLogout.ts
│ │ │ ├── useDynamicTheme.ts
│ │ │ └── useRootNavigator.ts
│ ├── Profile
│ │ ├── index.ts
│ │ └── screens
│ │ │ ├── index.ts
│ │ │ └── Profile.tsx
│ └── index.ts
├── shared
│ ├── ui
│ │ ├── Icon
│ │ │ ├── index.ts
│ │ │ └── Icon.tsx
│ │ ├── Loader
│ │ │ ├── index.ts
│ │ │ └── Loader.tsx
│ │ ├── StatusBar
│ │ │ ├── index.ts
│ │ │ └── StatusBar.tsx
│ │ ├── ErrorMessage
│ │ │ ├── index.ts
│ │ │ └── ErrorMessage.tsx
│ │ ├── AnimatedButton
│ │ │ └── index.ts
│ │ ├── RefreshControl
│ │ │ ├── index.ts
│ │ │ └── RefreshControl.tsx
│ │ ├── AnimatedBackDrop
│ │ │ ├── index.ts
│ │ │ └── AnimatedBackDrop.tsx
│ │ ├── Separator
│ │ │ ├── index.ts
│ │ │ └── HorizontalSeparator.tsx
│ │ ├── ActivityIndicator
│ │ │ ├── index.ts
│ │ │ └── ActivityIndicator.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── ReactiveInput.tsx
│ │ │ ├── types.ts
│ │ │ └── lib.ts
│ │ ├── Keyboard
│ │ │ ├── index.ts
│ │ │ ├── KeyboardToolbar.tsx
│ │ │ └── KeyboardAwareScrollView.tsx
│ │ ├── AnimatedActivityIndicator
│ │ │ ├── index.ts
│ │ │ └── AnimatedActivityIndicator.tsx
│ │ ├── Switch
│ │ │ ├── index.ts
│ │ │ ├── ReactiveSwitch.tsx
│ │ │ ├── lib.ts
│ │ │ └── useSwitch.ts
│ │ └── index.ts
│ ├── stores
│ │ ├── auth
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ ├── selectors.ts
│ │ │ └── store.ts
│ │ ├── user
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ ├── selectors.ts
│ │ │ └── store.ts
│ │ ├── theme
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ ├── selectors.ts
│ │ │ └── store.ts
│ │ ├── language
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ ├── selectors.ts
│ │ │ └── store.ts
│ │ ├── lib.ts
│ │ ├── index.ts
│ │ └── models.ts
│ ├── services
│ │ ├── ToastService
│ │ │ ├── index.ts
│ │ │ └── ToastService.ts
│ │ ├── KeyboardService
│ │ │ ├── index.ts
│ │ │ └── KeyboardService.ts
│ │ ├── ExceptionService
│ │ │ ├── index.ts
│ │ │ └── ExceptionService.ts
│ │ ├── LocalizationService
│ │ │ ├── index.ts
│ │ │ └── LocalizationService.ts
│ │ ├── ModalService
│ │ │ ├── index.ts
│ │ │ ├── models.ts
│ │ │ └── ModalService.ts
│ │ ├── RouteService
│ │ │ ├── index.ts
│ │ │ ├── models
│ │ │ │ ├── index.ts
│ │ │ │ ├── Routes.ts
│ │ │ │ ├── EventEmitterActions.ts
│ │ │ │ └── RootStackParamList.ts
│ │ │ └── RouteService.ts
│ │ ├── StorageService
│ │ │ ├── index.ts
│ │ │ ├── models.ts
│ │ │ └── StorageService.ts
│ │ └── index.ts
│ ├── api
│ │ ├── endpoints.ts
│ │ ├── auth
│ │ │ ├── index.ts
│ │ │ ├── mutations
│ │ │ │ ├── index.ts
│ │ │ │ ├── useTestMutation.auth.ts
│ │ │ │ └── useTestEndpointMutation.auth.ts
│ │ │ ├── queries
│ │ │ │ ├── index.ts
│ │ │ │ ├── useTestQueries.auth.ts
│ │ │ │ ├── useTestPrefetch.auth.ts
│ │ │ │ └── useTestSuspenseQuery.auth.ts
│ │ │ ├── models.ts
│ │ │ └── QueryKeys.ts
│ │ ├── index.ts
│ │ ├── models
│ │ │ ├── QueryKeys.ts
│ │ │ └── index.ts
│ │ ├── queryClient.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useQueryEvents.ts
│ │ │ ├── useQueriesWithOptions.ts
│ │ │ ├── useSuspenseQueryWithOptions.ts
│ │ │ ├── usePrefetchQueryWithOptions.ts
│ │ │ ├── useSuspenseInfiniteQueryWithOptions.ts
│ │ │ ├── useQueryWithOptions.ts
│ │ │ ├── usePrefetchInfiniteQueryWithOptions.ts
│ │ │ └── useMutationEvents.ts
│ │ ├── baseQuery.ts
│ │ └── http-client.ts
│ ├── providers
│ │ ├── LanguageProvider
│ │ │ ├── index.ts
│ │ │ └── LanguageProvider.tsx
│ │ ├── EventEmitterProvider
│ │ │ ├── index.ts
│ │ │ └── EventEmitterProvider.tsx
│ │ └── index.ts
│ ├── themes
│ │ ├── Images.ts
│ │ ├── Breakpoints.ts
│ │ ├── Fonts.ts
│ │ ├── index.ts
│ │ └── Theme.ts
│ ├── lib
│ │ ├── index.ts
│ │ ├── Dimensions.ts
│ │ ├── Environments.ts
│ │ ├── TypeHelpers.ts
│ │ ├── General.ts
│ │ ├── Platforms.ts
│ │ └── Dates.ts
│ ├── types
│ │ ├── react-native-config.d.ts
│ │ ├── react-native-modalfy.d.ts
│ │ ├── icomoon.d.ts
│ │ ├── i18next.d.ts
│ │ ├── react-i18next.d.ts
│ │ └── react-native-unistyles.d.ts
│ ├── hooks
│ │ ├── useLatest.ts
│ │ ├── useFirstRender.ts
│ │ ├── index.ts
│ │ ├── useTypedRoute.ts
│ │ ├── useDebug.ts
│ │ ├── useLayout.ts
│ │ ├── useEvent.ts
│ │ ├── useCombinedRef.ts
│ │ ├── useDebounce.ts
│ │ └── useAppStateEvent.ts
│ └── translations
│ │ └── en.json
├── navigation
│ ├── lib
│ │ ├── index.ts
│ │ └── NavigationOptions.ts
│ ├── index.ts
│ ├── tabs
│ │ ├── index.ts
│ │ ├── AlertsNavigator.tsx
│ │ └── ProfileNavigator.tsx
│ ├── AuthNavigator.tsx
│ ├── MainNavigator.tsx
│ ├── RootNavigator.tsx
│ └── BottomTabBarNavigator.tsx
└── assets
│ ├── images
│ ├── logo.png
│ └── close.svg
│ ├── bootsplash
│ ├── logo.png
│ ├── dark-logo.png
│ ├── logo@1,5x.png
│ ├── logo@2x.png
│ ├── logo@3x.png
│ ├── logo@4x.png
│ ├── dark-logo@1,5x.png
│ ├── dark-logo@2x.png
│ ├── dark-logo@3x.png
│ ├── dark-logo@4x.png
│ └── manifest.json
│ └── fonts
│ ├── icomoon.ttf
│ ├── DMSans-Bold.ttf
│ ├── DMSans-Medium.ttf
│ └── DMSans-Regular.ttf
├── android
├── fastlane
│ ├── metadata
│ │ └── android
│ │ │ └── en-US
│ │ │ └── changelogs
│ │ │ └── default.txt
│ ├── .env.example
│ ├── Appfile
│ └── Pluginfile
├── app
│ ├── debug.keystore
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── values-night
│ │ │ │ │ └── colors.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── drawable-hdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-mdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-night-hdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-night-mdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-night-xhdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-night-xxhdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ ├── drawable-night-xxxhdpi
│ │ │ │ │ └── bootsplash_logo.png
│ │ │ │ └── drawable
│ │ │ │ │ └── rn_edit_text_material.xml
│ │ │ ├── assets
│ │ │ │ └── fonts
│ │ │ │ │ ├── icomoon.ttf
│ │ │ │ │ ├── DMSans-Bold.ttf
│ │ │ │ │ ├── DMSans-Medium.ttf
│ │ │ │ │ └── DMSans-Regular.ttf
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── reactnativetemplate
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── MainApplication.kt
│ │ └── debug
│ │ │ └── AndroidManifest.xml
│ └── proguard-rules.pro
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── Gemfile
├── settings.gradle
├── link-assets-manifest.json
├── build.gradle
└── gradle.properties
├── .yarnrc.yml
├── app.json
├── commitlint.config.js
├── ios
├── ReactNativeTemplate
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ ├── BootSplashLogo-843e90.imageset
│ │ │ ├── logo-843e90.png
│ │ │ ├── logo-843e90@2x.png
│ │ │ ├── logo-843e90@3x.png
│ │ │ ├── dark-logo-843e90.png
│ │ │ ├── dark-logo-843e90@2x.png
│ │ │ ├── dark-logo-843e90@3x.png
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Colors.xcassets
│ │ └── BootSplashBackground-843e90.colorset
│ │ │ └── Contents.json
│ ├── PrivacyInfo.xcprivacy
│ ├── AppDelegate.swift
│ └── Info.plist
├── Gemfile
├── fastlane
│ ├── Pluginfile
│ ├── Appfile
│ ├── .env.example
│ ├── Matchfile
│ ├── README.md
│ └── scripts
│ │ └── util_helper.rb
├── Config.xcconfig
├── ReactNativeTemplate.xcworkspace
│ └── contents.xcworkspacedata
├── .xcode.env
├── link-assets-manifest.json
└── Podfile
├── react-native.config.js
├── .prettierrc.js
├── Gemfile
├── scripts
├── modify-endpoints.sh
├── api-codegen
│ ├── run-codegen.sh
│ ├── compile-all.sh
│ ├── update-index.cpp
│ └── merge-query-keys.cpp
└── icons.sh
├── metro.config.js
├── .github
├── pull_request_template.md
└── workflows
│ ├── lint.yml
│ ├── deploy-ios-stage.yml
│ ├── deploy-ios-dev.yml
│ ├── deploy-ios-prod.yml
│ ├── deploy-android-stage.yml
│ └── deploy-android-dev.yml
├── index.js
├── LICENSE
├── babel.config.js
├── .gitignore
├── tsconfig.json
└── App.tsx
/.nvmrc:
--------------------------------------------------------------------------------
1 | 22.13.0
2 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | npx commitlint --edit $1
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | yarn run lint && yarn run typescript
2 |
--------------------------------------------------------------------------------
/src/modules/Alerts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './screens';
2 |
--------------------------------------------------------------------------------
/src/modules/Auth/index.ts:
--------------------------------------------------------------------------------
1 | export * from './screens';
2 |
--------------------------------------------------------------------------------
/src/modules/Common/ui/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Modals';
2 |
--------------------------------------------------------------------------------
/src/modules/Profile/index.ts:
--------------------------------------------------------------------------------
1 | export * from './screens';
2 |
--------------------------------------------------------------------------------
/src/shared/ui/Icon/index.ts:
--------------------------------------------------------------------------------
1 | export { Icon } from './Icon';
2 |
--------------------------------------------------------------------------------
/android/fastlane/metadata/android/en-US/changelogs/default.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/modules/Alerts/screens/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Alerts';
2 |
--------------------------------------------------------------------------------
/src/modules/Auth/features/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useAuth';
2 |
--------------------------------------------------------------------------------
/src/modules/Auth/screens/index.ts:
--------------------------------------------------------------------------------
1 | export { Auth } from './Auth';
2 |
--------------------------------------------------------------------------------
/src/modules/Profile/screens/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Profile';
2 |
--------------------------------------------------------------------------------
/src/navigation/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './NavigationOptions';
2 |
--------------------------------------------------------------------------------
/src/shared/stores/auth/index.ts:
--------------------------------------------------------------------------------
1 | export * from './selectors';
2 |
--------------------------------------------------------------------------------
/src/shared/stores/user/index.ts:
--------------------------------------------------------------------------------
1 | export * from './selectors';
2 |
--------------------------------------------------------------------------------
/src/shared/ui/Loader/index.ts:
--------------------------------------------------------------------------------
1 | export { Loader } from './Loader';
2 |
--------------------------------------------------------------------------------
/src/shared/services/ToastService/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ToastService';
2 |
--------------------------------------------------------------------------------
/src/shared/ui/StatusBar/index.ts:
--------------------------------------------------------------------------------
1 | export { StatusBar } from './StatusBar';
2 |
--------------------------------------------------------------------------------
/src/modules/Auth/widgets/index.ts:
--------------------------------------------------------------------------------
1 | export { AuthCompose } from './AuthCompose';
2 |
--------------------------------------------------------------------------------
/src/modules/Common/ui/Modals/index.ts:
--------------------------------------------------------------------------------
1 | export { modalStack } from './config';
2 |
--------------------------------------------------------------------------------
/src/shared/api/endpoints.ts:
--------------------------------------------------------------------------------
1 | export type Endpoints = 'api/' | ({} & string);
2 |
--------------------------------------------------------------------------------
/src/shared/services/KeyboardService/index.ts:
--------------------------------------------------------------------------------
1 | export * from './KeyboardService';
2 |
--------------------------------------------------------------------------------
/src/shared/providers/LanguageProvider/index.ts:
--------------------------------------------------------------------------------
1 | export * from './LanguageProvider';
2 |
--------------------------------------------------------------------------------
/src/shared/services/ExceptionService/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ExceptionService';
2 |
--------------------------------------------------------------------------------
/src/shared/ui/ErrorMessage/index.ts:
--------------------------------------------------------------------------------
1 | export { ErrorMessage } from './ErrorMessage';
2 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
3 | yarnPath: .yarn/releases/yarn-4.10.3.cjs
4 |
--------------------------------------------------------------------------------
/src/modules/Common/models.ts:
--------------------------------------------------------------------------------
1 | export type UILayoutPosition = 'head' | 'middle' | 'tail';
2 |
--------------------------------------------------------------------------------
/src/shared/services/LocalizationService/index.ts:
--------------------------------------------------------------------------------
1 | export * from './LocalizationService';
2 |
--------------------------------------------------------------------------------
/src/shared/ui/AnimatedButton/index.ts:
--------------------------------------------------------------------------------
1 | export { AnimatedButton } from './AnimatedButton';
2 |
--------------------------------------------------------------------------------
/src/shared/ui/RefreshControl/index.ts:
--------------------------------------------------------------------------------
1 | export { RefreshControl } from './RefreshControl';
2 |
--------------------------------------------------------------------------------
/src/modules/Common/ui/Modals/UpdateModal/index.ts:
--------------------------------------------------------------------------------
1 | export { UpdateModal } from './UpdateModal';
2 |
--------------------------------------------------------------------------------
/src/shared/providers/EventEmitterProvider/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EventEmitterProvider';
2 |
--------------------------------------------------------------------------------
/src/shared/ui/AnimatedBackDrop/index.ts:
--------------------------------------------------------------------------------
1 | export { AnimatedBackdrop } from './AnimatedBackDrop';
2 |
--------------------------------------------------------------------------------
/src/shared/ui/Separator/index.ts:
--------------------------------------------------------------------------------
1 | export { HorizontalSeparator } from './HorizontalSeparator';
2 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReactNativeTemplate",
3 | "displayName": "ReactNativeTemplate"
4 | }
5 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | };
4 |
--------------------------------------------------------------------------------
/src/shared/ui/ActivityIndicator/index.ts:
--------------------------------------------------------------------------------
1 | export { ActivityIndicator } from './ActivityIndicator';
2 |
--------------------------------------------------------------------------------
/src/navigation/index.ts:
--------------------------------------------------------------------------------
1 | export * from './lib';
2 |
3 | export { RootNavigator } from './RootNavigator';
4 |
--------------------------------------------------------------------------------
/src/shared/services/ModalService/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ModalService';
2 | export * from './models';
3 |
--------------------------------------------------------------------------------
/src/shared/services/RouteService/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RouteService';
2 | export * from './models';
3 |
--------------------------------------------------------------------------------
/src/shared/providers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EventEmitterProvider';
2 | export * from './LanguageProvider';
3 |
--------------------------------------------------------------------------------
/src/shared/services/StorageService/index.ts:
--------------------------------------------------------------------------------
1 | export * from './StorageService';
2 | export * from './models';
3 |
--------------------------------------------------------------------------------
/src/modules/Common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './features';
2 | export * from './models';
3 | export * from './ui';
4 |
--------------------------------------------------------------------------------
/src/shared/ui/Input/index.ts:
--------------------------------------------------------------------------------
1 | export { Input } from './Input';
2 | export { ReactiveInput } from './ReactiveInput';
3 |
--------------------------------------------------------------------------------
/src/shared/ui/Keyboard/index.ts:
--------------------------------------------------------------------------------
1 | export * from './KeyboardAwareScrollView';
2 | export * from './KeyboardToolbar';
3 |
--------------------------------------------------------------------------------
/src/modules/Alerts/screens/Alerts.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Alerts: React.FC = () => null;
4 |
--------------------------------------------------------------------------------
/src/shared/api/auth/index.ts:
--------------------------------------------------------------------------------
1 | export * from './models';
2 | export * from './queries';
3 | export * from './mutations';
4 |
--------------------------------------------------------------------------------
/src/shared/stores/auth/types.ts:
--------------------------------------------------------------------------------
1 | export interface AuthStore {
2 | token: string;
3 | refreshToken: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/shared/stores/theme/index.ts:
--------------------------------------------------------------------------------
1 | export * from './store';
2 | export * from './selectors';
3 | export * from './types';
4 |
--------------------------------------------------------------------------------
/src/shared/themes/Images.ts:
--------------------------------------------------------------------------------
1 | export const Images = {
2 | Close: require('assets/images/close.svg').default,
3 | };
4 |
--------------------------------------------------------------------------------
/src/modules/Profile/screens/Profile.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Profile: React.FC = () => null;
4 |
--------------------------------------------------------------------------------
/src/shared/stores/language/index.ts:
--------------------------------------------------------------------------------
1 | export * from './store';
2 | export * from './selectors';
3 | export * from './types';
4 |
--------------------------------------------------------------------------------
/src/shared/ui/AnimatedActivityIndicator/index.ts:
--------------------------------------------------------------------------------
1 | export { AnimatedActivityIndicator } from './AnimatedActivityIndicator';
2 |
--------------------------------------------------------------------------------
/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/debug.keystore
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/shared/ui/Switch/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Switch';
2 | export * from './useSwitch';
3 | export * from './ReactiveSwitch';
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
3 |
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ReactNativeTemplate
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/bootsplash/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/logo.png
--------------------------------------------------------------------------------
/src/assets/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/src/shared/api/auth/mutations/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useTestMutation.auth';
2 | export * from './useTestEndpointMutation.auth';
3 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 | #000000
3 |
4 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/assets/fonts/DMSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/fonts/DMSans-Bold.ttf
--------------------------------------------------------------------------------
/src/modules/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Auth';
2 | export * from './Common';
3 | export * from './Profile';
4 | export * from './Alerts';
5 |
--------------------------------------------------------------------------------
/src/assets/bootsplash/dark-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/dark-logo.png
--------------------------------------------------------------------------------
/src/assets/bootsplash/logo@1,5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/logo@1,5x.png
--------------------------------------------------------------------------------
/src/assets/bootsplash/logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/logo@2x.png
--------------------------------------------------------------------------------
/src/assets/bootsplash/logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/logo@3x.png
--------------------------------------------------------------------------------
/src/assets/bootsplash/logo@4x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/logo@4x.png
--------------------------------------------------------------------------------
/src/assets/fonts/DMSans-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/fonts/DMSans-Medium.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/DMSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/fonts/DMSans-Regular.ttf
--------------------------------------------------------------------------------
/src/navigation/tabs/index.ts:
--------------------------------------------------------------------------------
1 | export { AlertsNavigator } from './AlertsNavigator';
2 | export { ProfileNavigator } from './ProfileNavigator';
3 |
--------------------------------------------------------------------------------
/src/shared/stores/theme/types.ts:
--------------------------------------------------------------------------------
1 | export type Theme = 'light' | 'dark';
2 |
3 | export interface ThemeStore {
4 | currentTheme: Theme;
5 | }
6 |
--------------------------------------------------------------------------------
/src/shared/stores/language/types.ts:
--------------------------------------------------------------------------------
1 | export type Language = 'en';
2 |
3 | export interface LanguageStore {
4 | currentLanguage: Language;
5 | }
6 |
--------------------------------------------------------------------------------
/react-native.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | project: {
3 | ios: {},
4 | android: {},
5 | },
6 | assets: ['./src/assets/fonts/'],
7 | };
8 |
--------------------------------------------------------------------------------
/src/assets/bootsplash/dark-logo@1,5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/dark-logo@1,5x.png
--------------------------------------------------------------------------------
/src/assets/bootsplash/dark-logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/dark-logo@2x.png
--------------------------------------------------------------------------------
/src/assets/bootsplash/dark-logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/dark-logo@3x.png
--------------------------------------------------------------------------------
/src/assets/bootsplash/dark-logo@4x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/src/assets/bootsplash/dark-logo@4x.png
--------------------------------------------------------------------------------
/src/shared/themes/Breakpoints.ts:
--------------------------------------------------------------------------------
1 | export const breakpoints = {
2 | xs: 0,
3 | sm: 360,
4 | md: 375,
5 | lg: 800,
6 | xl: 1200,
7 | } as const;
8 |
--------------------------------------------------------------------------------
/src/shared/themes/Fonts.ts:
--------------------------------------------------------------------------------
1 | export const FontFamily = {
2 | Bold: 'DMSans-Bold',
3 | Regular: 'DMSans-Regular',
4 | Medium: 'DMSans-Medium',
5 | };
6 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/src/shared/services/RouteService/models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RootStackParamList';
2 | export * from './Routes';
3 | export * from './EventEmitterActions';
4 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/assets/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/DMSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/assets/fonts/DMSans-Bold.ttf
--------------------------------------------------------------------------------
/android/fastlane/.env.example:
--------------------------------------------------------------------------------
1 | ANDROID_KEYSTORE_PASSWORD=""
2 | ANDROID_KEY_PASSWORD=""
3 | ANDROID_KEYSTORE_ALIAS=""
4 | ANDROID_ENVIRONMENT=""
5 | ANDROID_VERSION_NAME=""
6 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'avoid',
3 | bracketSameLine: true,
4 | bracketSpacing: true,
5 | singleQuote: true,
6 | trailingComma: 'all',
7 | };
8 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/DMSans-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/assets/fonts/DMSans-Medium.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/DMSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/assets/fonts/DMSans-Regular.ttf
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/assets/bootsplash/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background": "#ffffff",
3 | "darkBackground": "#000000",
4 | "logo": {
5 | "width": 264,
6 | "height": 48
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/modules/Common/features/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useLogout';
2 | export * from './useTheme';
3 | export * from './useDynamicTheme';
4 | export * from './useRootNavigator';
5 |
--------------------------------------------------------------------------------
/src/shared/stores/lib.ts:
--------------------------------------------------------------------------------
1 | import { resetUserStorePersist } from './user/store';
2 |
3 | export const resetAllStores = async () => {
4 | await resetUserStorePersist();
5 | };
6 |
--------------------------------------------------------------------------------
/src/shared/themes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Colors';
2 | export * from './Fonts';
3 | export * from './Images';
4 | export * from './Theme';
5 | export * from './Breakpoints';
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/shared/stores/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user';
2 | export * from './language';
3 | export * from './theme';
4 | export * from './auth';
5 | export { resetAllStores } from './lib';
6 |
--------------------------------------------------------------------------------
/src/shared/ui/Switch/ReactiveSwitch.tsx:
--------------------------------------------------------------------------------
1 | import { reactive } from '@legendapp/state/react';
2 | import { Switch } from './Switch';
3 |
4 | export const ReactiveSwitch = reactive(Switch);
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-hdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-mdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-xhdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-xxhdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src/modules/Common/features/useTheme.ts:
--------------------------------------------------------------------------------
1 | import { useCurrentTheme } from 'stores';
2 |
3 | export const useTheme = () => {
4 | const theme = useCurrentTheme();
5 |
6 | return theme;
7 | };
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-xxxhdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src/modules/Auth/screens/Auth.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { AuthCompose } from '../widgets';
4 |
5 | export const Auth: React.FC = () => {
6 | return ;
7 | };
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-hdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-night-hdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-mdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-night-mdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xhdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-night-xhdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxhdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-night-xxhdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxxhdpi/bootsplash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/android/app/src/main/res/drawable-night-xxxhdpi/bootsplash_logo.png
--------------------------------------------------------------------------------
/ios/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 |
5 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
6 | eval_gemfile(plugins_path) if File.exist?(plugins_path)
7 |
--------------------------------------------------------------------------------
/android/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 |
5 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
6 | eval_gemfile(plugins_path) if File.exist?(plugins_path)
7 |
--------------------------------------------------------------------------------
/ios/fastlane/Pluginfile:
--------------------------------------------------------------------------------
1 | # Autogenerated by fastlane
2 | #
3 | # Ensure this file is checked in to source control!
4 |
5 | gem 'fastlane-plugin-increment_version_name'
6 | gem 'fastlane-plugin-increment_version_code'
7 |
--------------------------------------------------------------------------------
/src/shared/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './General';
2 | export * from './Dimensions';
3 | export * from './Dates';
4 | export * from './Environments';
5 | export * from './Platforms';
6 | export * from './TypeHelpers';
7 |
--------------------------------------------------------------------------------
/src/shared/services/StorageService/models.ts:
--------------------------------------------------------------------------------
1 | export const StorageKeys = {
2 | AUTH_REMEMBER_ME_FIELDS: 'AUTH_REMEMBER_ME_FIELDS',
3 | } as const;
4 |
5 | export type StorageKey = keyof typeof StorageKeys | (string & {});
6 |
--------------------------------------------------------------------------------
/src/shared/types/react-native-config.d.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 | import { envSchema } from 'lib';
3 |
4 | declare module 'react-native-config' {
5 | interface NativeConfig extends z.infer {}
6 | }
7 |
--------------------------------------------------------------------------------
/src/modules/Auth/ui/index.ts:
--------------------------------------------------------------------------------
1 | export { AuthFooter } from './AuthFooter';
2 | export { AuthLogo } from './AuthLogo';
3 | export { AuthMainInformation } from './AuthMainInformation';
4 | export { AuthWrapper } from './AuthWrapper';
5 |
--------------------------------------------------------------------------------
/android/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | json_key_file("../android/store.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
2 | package_name("com.reactnativetemplate") # e.g. com.krausefx.app
3 |
--------------------------------------------------------------------------------
/src/shared/lib/Dimensions.ts:
--------------------------------------------------------------------------------
1 | import { Dimensions } from 'react-native';
2 |
3 | const { width } = Dimensions.get('window');
4 |
5 | const BASE_WIDTH = 360;
6 |
7 | export const scale = (size: number) => (width / BASE_WIDTH) * size;
8 |
--------------------------------------------------------------------------------
/src/shared/ui/Switch/lib.ts:
--------------------------------------------------------------------------------
1 | export const clamp = (
2 | value: number,
3 | lowerBound: number,
4 | upperBound: number,
5 | ) => {
6 | 'worklet';
7 |
8 | return Math.min(Math.max(lowerBound, value), upperBound);
9 | };
10 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/logo-843e90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/logo-843e90.png
--------------------------------------------------------------------------------
/src/shared/api/index.ts:
--------------------------------------------------------------------------------
1 | // This file is auto-generated. Do not modify manually.
2 |
3 | export * from './auth';
4 |
5 | export * from './queryClient';
6 | export * from './models';
7 | export { useMutationEvents, useQueryEvents } from './hooks';
8 |
--------------------------------------------------------------------------------
/src/shared/types/react-native-modalfy.d.ts:
--------------------------------------------------------------------------------
1 | import 'react-native-modalfy';
2 | import { type ModalStackParams } from 'services';
3 |
4 | declare module 'react-native-modalfy' {
5 | interface ModalfyCustomParams extends ModalStackParams {}
6 | }
7 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/logo-843e90@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/logo-843e90@2x.png
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/logo-843e90@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/logo-843e90@3x.png
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/dark-logo-843e90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/dark-logo-843e90.png
--------------------------------------------------------------------------------
/src/shared/types/icomoon.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-unresolved */
2 | import { IconName as BaseName } from 'assets/resources/selection.json';
3 |
4 | declare module '@react-native-vector-icons/icomoon' {
5 | export type IconName = BaseName;
6 | }
7 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/dark-logo-843e90@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/dark-logo-843e90@2x.png
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/dark-logo-843e90@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumitech-co/lumitech-react-native-template/HEAD/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/dark-logo-843e90@3x.png
--------------------------------------------------------------------------------
/android/fastlane/Pluginfile:
--------------------------------------------------------------------------------
1 | # Autogenerated by fastlane
2 | #
3 | # Ensure this file is checked in to source control!
4 |
5 | gem 'fastlane-plugin-increment_version_name'
6 | gem 'fastlane-plugin-increment_version_code'
7 | gem 'fastlane-plugin-versioning_android'
8 |
--------------------------------------------------------------------------------
/src/shared/api/models/QueryKeys.ts:
--------------------------------------------------------------------------------
1 | import { AUTH_QUERY_KEYS } from '../auth/QueryKeys';
2 |
3 | export const queryKeys = {
4 | ...AUTH_QUERY_KEYS,
5 | };
6 |
7 | export type QueryKeyType = ReturnType<
8 | (typeof queryKeys)[keyof typeof queryKeys]
9 | >;
10 |
--------------------------------------------------------------------------------
/src/shared/stores/user/types.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: string;
3 | email: string;
4 | firstName: string;
5 | lastName: string;
6 | displayName: string;
7 | username: string;
8 | }
9 |
10 | export interface UserStore {
11 | user: User;
12 | }
13 |
--------------------------------------------------------------------------------
/src/shared/services/ModalService/models.ts:
--------------------------------------------------------------------------------
1 | export const MODAL_NAMES = {
2 | UPDATE_MODAL: 'UPDATE_MODAL',
3 | };
4 |
5 | export type ModalNames = keyof typeof MODAL_NAMES;
6 |
7 | export type ModalStackParams = {
8 | UPDATE_MODAL: { title: string; message: string };
9 | };
10 |
--------------------------------------------------------------------------------
/src/shared/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ExceptionService';
2 | export * from './LocalizationService';
3 | export * from './ModalService';
4 | export * from './RouteService';
5 | export * from './StorageService';
6 | export * from './ToastService';
7 | export * from './KeyboardService';
8 |
--------------------------------------------------------------------------------
/ios/Config.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Config.xcconfig
3 | // ReactNativeTemplate
4 | //
5 | // Created by VLAD on 03.03.2025.
6 | //
7 |
8 | // Configuration settings file format documentation can be found at:
9 | // https://help.apple.com/xcode/#/dev745c5c974
10 |
11 |
12 | #include? "tmp.xcconfig"
13 |
--------------------------------------------------------------------------------
/src/shared/hooks/useLatest.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useLayoutEffect } from 'react';
2 |
3 | export const useLatest = (value: Value) => {
4 | const valueRef = useRef(value);
5 |
6 | useLayoutEffect(() => {
7 | valueRef.current = value;
8 | }, [value]);
9 |
10 | return valueRef;
11 | };
12 |
--------------------------------------------------------------------------------
/src/shared/types/i18next.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 | import en from 'translations/en.json';
3 |
4 | const resources = {
5 | en,
6 | } as const;
7 |
8 | declare module 'i18next' {
9 | interface CustomTypeOptions {
10 | resources: typeof resources;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/src/shared/hooks/useFirstRender.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from 'react';
2 |
3 | export const useFirstRender = () => {
4 | const firstRender = useRef(true);
5 |
6 | useEffect(() => {
7 | firstRender.current = false;
8 | }, []);
9 |
10 | return { isFirstRender: firstRender.current };
11 | };
12 |
--------------------------------------------------------------------------------
/src/shared/stores/models.ts:
--------------------------------------------------------------------------------
1 | export const PersistStorageKeys = {
2 | USER: 'USER_STORAGE',
3 | AUTH: 'AUTH_STORAGE',
4 | LANGUAGE: 'LANGUAGE_STORAGE',
5 | THEME: 'THEME_STORAGE',
6 | } as const;
7 |
8 | export type PersistStorageKey =
9 | (typeof PersistStorageKeys)[keyof typeof PersistStorageKeys];
10 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/shared/services/KeyboardService/KeyboardService.ts:
--------------------------------------------------------------------------------
1 | import { KeyboardController } from 'react-native-keyboard-controller';
2 |
3 | const dismiss = () => KeyboardController.dismiss();
4 |
5 | const show = () => KeyboardController.setFocusTo('prev');
6 |
7 | export const KeyboardService = {
8 | dismiss,
9 | show,
10 | };
11 |
--------------------------------------------------------------------------------
/src/shared/ui/Keyboard/KeyboardToolbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | KeyboardToolbar as BaseToolBar,
5 | KeyboardToolbarProps,
6 | } from 'react-native-keyboard-controller';
7 |
8 | export const KeyboardToolbar: React.FC = props => {
9 | return ;
10 | };
11 |
--------------------------------------------------------------------------------
/ios/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"]) # The bundle identifier of your app
2 |
3 | apple_id(ENV["FASTLANE_APPLE_ID"]) #Your Apple Developer Portal username
4 |
5 | itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"]) # App Store Connect Team ID
6 |
7 | team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"]) #Developer Portal Team ID
8 |
--------------------------------------------------------------------------------
/src/shared/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useFirstRender';
2 | export * from './useEvent';
3 | export * from './useLayout';
4 | export * from './useLatest';
5 | export * from './useAppStateEvent';
6 | export * from './useCombinedRef';
7 | export * from './useDebounce';
8 | export * from './useDebug';
9 | export * from './useTypedRoute';
10 |
--------------------------------------------------------------------------------
/src/shared/hooks/useTypedRoute.ts:
--------------------------------------------------------------------------------
1 | import { useRoute, RouteProp } from '@react-navigation/native';
2 | import { RootStackParamList } from 'services';
3 |
4 | export const useTypedRoute = (
5 | _: T,
6 | ): RouteProp => {
7 | return useRoute>();
8 | };
9 |
--------------------------------------------------------------------------------
/src/shared/stores/auth/selectors.ts:
--------------------------------------------------------------------------------
1 | import { use$ } from '@legendapp/state/react';
2 | import { authStore$ } from './store';
3 |
4 | export const useAuthStore = () => {
5 | return authStore$;
6 | };
7 |
8 | export const getAuthStoreInstance = () => {
9 | return authStore$;
10 | };
11 |
12 | export const useToken = () => {
13 | return use$(authStore$.token);
14 | };
15 |
--------------------------------------------------------------------------------
/src/shared/stores/user/selectors.ts:
--------------------------------------------------------------------------------
1 | import { use$ } from '@legendapp/state/react';
2 | import { userStore$ } from './store';
3 |
4 | export const useUserStore = () => {
5 | return userStore$;
6 | };
7 |
8 | export const getUserStoreInstance = () => {
9 | return userStore$;
10 | };
11 |
12 | export const useUserId = () => {
13 | return use$(userStore$.user.id);
14 | };
15 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
2 | plugins { id("com.facebook.react.settings") }
3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
4 | rootProject.name = 'ReactNativeTemplate'
5 | include ':app'
6 | includeBuild('../node_modules/@react-native/gradle-plugin')
7 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
--------------------------------------------------------------------------------
/src/shared/types/react-i18next.d.ts:
--------------------------------------------------------------------------------
1 | // /* Use this d.ts file in development */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 |
4 | import en from 'translations/en.json';
5 |
6 | const resources = {
7 | en,
8 | } as const;
9 |
10 | declare module 'i18next' {
11 | interface CustomTypeOptions {
12 | defaultNS: 'en';
13 | resources: typeof resources;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/shared/stores/theme/selectors.ts:
--------------------------------------------------------------------------------
1 | import { use$ } from '@legendapp/state/react';
2 | import { themeStore$ } from './store';
3 |
4 | export const useThemeStore = () => {
5 | return themeStore$;
6 | };
7 |
8 | export const getThemeStoreInstance = () => {
9 | return themeStore$;
10 | };
11 |
12 | export const useCurrentTheme = () => {
13 | return use$(themeStore$.currentTheme);
14 | };
15 |
--------------------------------------------------------------------------------
/src/shared/ui/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ActivityIndicator';
2 | export * from './AnimatedActivityIndicator';
3 | export * from './AnimatedBackDrop';
4 | export * from './Icon';
5 | export * from './Input';
6 | export * from './Keyboard';
7 | export * from './Loader';
8 | export * from './RefreshControl';
9 | export * from './StatusBar';
10 | export * from './Switch';
11 | export * from './AnimatedButton';
12 |
--------------------------------------------------------------------------------
/src/navigation/AuthNavigator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Auth } from '../modules';
3 | import { Stack } from './lib';
4 |
5 | export const AuthNavigator: React.FC = () => {
6 | return (
7 |
8 |
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/src/shared/ui/StatusBar/StatusBar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | StatusBar as BaseBar,
4 | StatusBarProps as BaseProps,
5 | } from 'react-native';
6 |
7 | interface StatusBarProps extends BaseProps {}
8 |
9 | export const StatusBar: React.FC = ({
10 | barStyle = 'dark-content',
11 | ...rest
12 | }) => {
13 | return ;
14 | };
15 |
--------------------------------------------------------------------------------
/src/navigation/tabs/AlertsNavigator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alerts } from '../../modules';
3 | import { Stack } from '../lib';
4 |
5 | export const AlertsNavigator: React.FC = () => (
6 |
7 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/src/navigation/tabs/ProfileNavigator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Profile } from '../../modules';
3 | import { Stack } from '../lib';
4 |
5 | export const ProfileNavigator: React.FC = () => (
6 |
7 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/src/shared/stores/language/selectors.ts:
--------------------------------------------------------------------------------
1 | import { use$ } from '@legendapp/state/react';
2 | import { languageStore$ } from './store';
3 |
4 | export const useLanguageStore = () => {
5 | return languageStore$;
6 | };
7 |
8 | export const getLanguageStoreInstance = () => {
9 | return languageStore$;
10 | };
11 |
12 | export const useCurrentLanguage = () => {
13 | return use$(languageStore$.currentLanguage);
14 | };
15 |
--------------------------------------------------------------------------------
/src/modules/Common/features/useLogout.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { resetAllStores } from 'stores';
3 |
4 | export const useLogout = () => {
5 | const [isLoading, setIsLoading] = useState(false);
6 |
7 | const onLogout = async () => {
8 | setIsLoading(true);
9 |
10 | await resetAllStores();
11 |
12 | setIsLoading(false);
13 | };
14 |
15 | return { onLogout, isLoading };
16 | };
17 |
--------------------------------------------------------------------------------
/src/shared/services/RouteService/models/Routes.ts:
--------------------------------------------------------------------------------
1 | export const Routes = {
2 | MAIN_NAVIGATOR: 'MAIN_NAVIGATOR',
3 |
4 | AUTH_NAVIGATOR: 'AUTH_NAVIGATOR',
5 |
6 | BOTTOM_TAB_BAR_NAVIGATOR: 'BOTTOM_TAB_BAR_NAVIGATOR',
7 |
8 | ALERTS_NAVIGATOR: 'ALERTS_NAVIGATOR',
9 |
10 | PROFILE_NAVIGATOR: 'PROFILE_NAVIGATOR',
11 |
12 | AUTH: 'AUTH',
13 |
14 | PROFILE: 'PROFILE',
15 |
16 | ALERTS: 'ALERTS',
17 | } as const;
18 |
--------------------------------------------------------------------------------
/src/shared/api/queryClient.ts:
--------------------------------------------------------------------------------
1 | import { QueryClient } from '@tanstack/react-query';
2 |
3 | const garbageCollectionTime = 1000 * 60 * 5;
4 |
5 | export const queryClient = new QueryClient({
6 | defaultOptions: {
7 | mutations: {
8 | gcTime: garbageCollectionTime,
9 | },
10 | queries: {
11 | gcTime: garbageCollectionTime,
12 | },
13 | },
14 | });
15 |
16 | export const getQueryClient = () => queryClient;
17 |
--------------------------------------------------------------------------------
/src/shared/types/react-native-unistyles.d.ts:
--------------------------------------------------------------------------------
1 | import { LightTheme, DarkTheme, breakpoints } from 'themes';
2 |
3 | interface AppThemes {
4 | light: typeof LightTheme;
5 | dark: typeof DarkTheme;
6 | }
7 |
8 | type AppBreakpoints = typeof breakpoints;
9 |
10 | declare module 'react-native-unistyles' {
11 | export interface UnistylesThemes extends AppThemes {}
12 | export interface UnistylesBreakpoints extends AppBreakpoints {}
13 | }
14 |
--------------------------------------------------------------------------------
/src/shared/ui/Input/ReactiveInput.tsx:
--------------------------------------------------------------------------------
1 | import { NativeSyntheticEvent, TextInputChangeEventData } from 'react-native';
2 | import { reactive } from '@legendapp/state/react';
3 | import { Input } from './Input';
4 |
5 | export const ReactiveInput = reactive(Input, undefined, {
6 | value: {
7 | handler: 'onChange',
8 | getValue: (event: NativeSyntheticEvent) =>
9 | event.nativeEvent.text,
10 | defaultValue: '',
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/src/shared/ui/Switch/useSwitch.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef } from 'react';
2 | import { SwitchComponentRefProps } from 'ui';
3 |
4 | export const useSwitch = () => {
5 | const switchRef = useRef(null);
6 |
7 | const onOutsideAnimationStart = useCallback((isStart: boolean) => {
8 | switchRef?.current?.outsideAnimationStart(isStart);
9 | }, []);
10 |
11 | return {
12 | switchRef,
13 | onOutsideAnimationStart,
14 | };
15 | };
16 |
--------------------------------------------------------------------------------
/src/shared/ui/Separator/HorizontalSeparator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { StyleSheet } from 'react-native-unistyles';
4 |
5 | export const HorizontalSeparator: React.FC = () => {
6 | return ;
7 | };
8 |
9 | const styles = StyleSheet.create(theme => ({
10 | separator: {
11 | height: 1,
12 | width: '100%',
13 | backgroundColor: theme.colors.primary_separator,
14 | },
15 | }));
16 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useMutationEvents';
2 | export * from './useQueryEvents';
3 | export * from './useQueryWithOptions';
4 | export * from './useInfiniteQueryWithOptions';
5 | export * from './usePrefetchQueryWithOptions';
6 | export * from './usePrefetchInfiniteQueryWithOptions';
7 | export * from './useQueriesWithOptions';
8 | export * from './useSuspenseQueryWithOptions';
9 | export * from './useSuspenseInfiniteQueryWithOptions';
10 | export * from './syncedQuery';
11 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/src/assets/images/close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/navigation/MainNavigator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Stack } from './lib';
3 | import { BottomTabBarNavigator } from './BottomTabBarNavigator';
4 |
5 | export const MainNavigator: React.FC = () => {
6 | return (
7 |
8 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/shared/lib/Environments.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import Config from 'react-native-config';
3 | import { z } from 'zod';
4 | import { isDev } from './General';
5 |
6 | export const envSchema = z.object({
7 | API_URL: z.string(),
8 | LICENSE_KEY: z.string().optional(),
9 | });
10 |
11 | export const parseEnv = () => {
12 | if (!isDev) {
13 | return;
14 | }
15 |
16 | try {
17 | envSchema.parse(Config);
18 | } catch (error) {
19 | console.warn(error);
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/ios/.xcode.env:
--------------------------------------------------------------------------------
1 | # This `.xcode.env` file is versioned and is used to source the environment
2 | # used when running script phases inside Xcode.
3 | # To customize your local environment, you can create an `.xcode.env.local`
4 | # file that is not versioned.
5 |
6 | # NODE_BINARY variable contains the PATH to the node executable.
7 | #
8 | # Customize the NODE_BINARY variable here.
9 | # For example, to use nvm with brew, add the following line
10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use
11 | export NODE_BINARY=$(command -v node)
12 |
--------------------------------------------------------------------------------
/src/shared/hooks/useDebug.ts:
--------------------------------------------------------------------------------
1 | import { useMMKVDevTools } from '@rozenite/mmkv-plugin';
2 | import { useNetworkActivityDevTools } from '@rozenite/network-activity-plugin';
3 | import { useTanStackQueryDevTools } from '@rozenite/tanstack-query-plugin';
4 | import { queryClient } from 'api';
5 | import { storage } from 'services';
6 |
7 | export const useDebug = () => {
8 | useTanStackQueryDevTools(queryClient);
9 |
10 | useNetworkActivityDevTools();
11 |
12 | useMMKVDevTools({
13 | storages: [storage],
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/src/shared/hooks/useLayout.ts:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from 'react';
2 | import { LayoutChangeEvent, LayoutRectangle } from 'react-native';
3 |
4 | export const useLayout = () => {
5 | const [layout, setLayout] = useState({
6 | x: 0,
7 | y: 0,
8 | width: 0,
9 | height: 0,
10 | });
11 |
12 | const onLayout = useCallback(
13 | (event: LayoutChangeEvent) => setLayout(event.nativeEvent.layout),
14 | [],
15 | );
16 |
17 | return {
18 | onLayout,
19 | layout,
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/src/shared/hooks/useEvent.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useLayoutEffect, useRef } from 'react';
2 |
3 | type UseEventOptions = (...args: any[]) => any;
4 |
5 | export const useEvent = (fn: T) => {
6 | const latestRef = useRef(fn);
7 |
8 | useLayoutEffect(() => {
9 | latestRef.current = fn;
10 | }, [fn]);
11 |
12 | const eventCallback = useCallback(
13 | (...args: Parameters) => latestRef.current.apply(null, args),
14 | [latestRef],
15 | );
16 |
17 | return eventCallback as unknown as T;
18 | };
19 |
--------------------------------------------------------------------------------
/ios/fastlane/.env.example:
--------------------------------------------------------------------------------
1 | APPLE_ISSUER_ID=""
2 | APPLE_KEY_CONTENT="-----BEGIN PRIVATE KEY-----
3 | //Private Key in Base64 format
4 | -----END PRIVATE KEY-----"
5 | APPLE_KEY_ID=""
6 | APP_STORE_CONNECT_TEAM_ID=""
7 | DEVELOPER_APP_ID=""
8 | DEVELOPER_APP_IDENTIFIER=""
9 | DEVELOPER_PORTAL_TEAM_ID=""
10 | MATCH_PASSWORD=""
11 | PROVISIONING_PROFILE_SPECIFIER="match AppStore "
12 | TEMP_KEYCHAIN_PASSWORD=""
13 | TEMP_KEYCHAIN_USER=""
14 | GIT_AUTHORIZATION=":"
15 | IOS_ENVIRONMENT=""
16 | IOS_VERSION_NUMBER=""
17 |
--------------------------------------------------------------------------------
/src/modules/Auth/features/config.ts:
--------------------------------------------------------------------------------
1 | import i18next from 'i18next';
2 | import { z } from 'zod';
3 |
4 | export const LoginInSchema = z.object({
5 | email: z
6 | .string({ required_error: i18next.t('validations.email-required') })
7 | .email({ message: i18next.t('validations.email-invalid') }),
8 | password: z.string().nonempty({
9 | message: i18next.t('validations.please-provide-password'),
10 | }),
11 | rememberMe: z.boolean(),
12 | isFaceIdEnabled: z.boolean(),
13 | });
14 |
15 | export type LoginValues = z.infer;
16 |
--------------------------------------------------------------------------------
/src/shared/api/models/index.ts:
--------------------------------------------------------------------------------
1 | export { queryKeys } from './QueryKeys';
2 | export { type QueryKeyType } from './QueryKeys';
3 | export {
4 | type QueryError,
5 | type UseQueryWithOptionsParams,
6 | type UseInfiniteQueryWithOptionsParams,
7 | type QueryFetchParams,
8 | type InfiniteQueryFetchParams,
9 | type PrefetchInfiniteQueryFetchParams,
10 | type UsePrefetchQueryWithOptionsParams,
11 | type UseSuspenseQueryWithOptionsParams,
12 | type UseSuspenseInfiniteQueryWithOptionsParams,
13 | type UseQueriesWithOptionsParams,
14 | } from './types';
15 |
--------------------------------------------------------------------------------
/ios/fastlane/Matchfile:
--------------------------------------------------------------------------------
1 | git_url("")
2 | git_branch("")
3 |
4 | app_identifier("")
5 |
6 | storage_mode("git")
7 |
8 | type("appstore") # The default type, can be: appstore, adhoc, enterprise or development
9 |
10 | # app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])
11 | # username("user@fastlane.tools") # Your Apple Developer Portal username
12 |
13 | # For all available options run `fastlane match --help`
14 | # Remove the # in the beginning of the line to enable the other options
15 |
16 | # The docs are available on https://docs.fastlane.tools/actions/match
17 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4 | ruby ">= 2.6.10"
5 |
6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures.
7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
9 |
10 | gem 'xcodeproj', '< 1.26.0'
11 | gem 'concurrent-ruby', '< 1.3.4'
12 |
13 | # Ruby 3.4.0 has removed some libraries from the standard library.
14 | gem 'bigdecimal'
15 | gem 'logger'
16 | gem 'benchmark'
17 | gem 'mutex_m'
18 |
--------------------------------------------------------------------------------
/scripts/modify-endpoints.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | openapi_schema_file="src/shared/api/endpoints.ts"
4 |
5 | if [[ ! -f "$openapi_schema_file" ]]; then
6 | echo "File not found!"
7 | exit 1
8 | fi
9 |
10 | paths=$(grep -o '"/[^"]*":' "$openapi_schema_file" | sed 's/"://g' | tr -d '"')
11 |
12 | union="export type Endpoints = "
13 |
14 | for path in $paths; do
15 | union+="\"$path\" | "
16 | done
17 |
18 | union+="({} & string);"
19 |
20 | echo -e "$union" > "$openapi_schema_file"
21 |
22 | echo "Endpoints type has been added to $openapi_schema_file with | ({} & string) at the end."
--------------------------------------------------------------------------------
/src/modules/Common/ui/Modals/config.ts:
--------------------------------------------------------------------------------
1 | import { ModalOptions, createModalStack } from 'react-native-modalfy';
2 | import { ModalNames } from 'services';
3 | import { UpdateModal } from './UpdateModal';
4 |
5 | const UpdateModalConfig: ModalOptions = {
6 | modal: UpdateModal,
7 | backBehavior: 'none',
8 | disableFlingGesture: true,
9 | };
10 |
11 | const modalConfig: Record = {
12 | UPDATE_MODAL: UpdateModalConfig,
13 | };
14 |
15 | const defaultOptions = { backdropOpacity: 0.6 };
16 |
17 | export const modalStack = createModalStack(modalConfig, defaultOptions);
18 |
--------------------------------------------------------------------------------
/src/shared/services/ExceptionService/ExceptionService.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import { z } from 'zod';
3 |
4 | export const ErrorSchema = z.object({
5 | message: z.string(),
6 | successful: z.boolean(),
7 | status: z.string(),
8 | data: z.null(),
9 | });
10 |
11 | const errorResolver = (error: unknown) => {
12 | const parsedError = ErrorSchema.safeParse(error);
13 |
14 | if (parsedError.success) {
15 | return parsedError.data.message;
16 | }
17 |
18 | return i18n.t('errors.server-unable');
19 | };
20 |
21 | export const ExceptionService = {
22 | errorResolver,
23 | };
24 |
--------------------------------------------------------------------------------
/src/shared/services/ModalService/ModalService.ts:
--------------------------------------------------------------------------------
1 | import { modalfy } from 'react-native-modalfy';
2 | import { ModalNames, ModalStackParams } from './models';
3 |
4 | const open = (name: T, params?: ModalStackParams[T]) => {
5 | modalfy().openModal(name, params);
6 | };
7 |
8 | const close = (name: T, callback?: () => void) => {
9 | modalfy().closeModal(name, callback);
10 | };
11 |
12 | const closeAllModals = () => {
13 | modalfy().closeAllModals();
14 | };
15 |
16 | export const ModalService = {
17 | open,
18 | close,
19 | closeAllModals,
20 | };
21 |
--------------------------------------------------------------------------------
/src/shared/api/auth/queries/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useTestQuery.auth';
2 | export * from './useTestInfiniteQuery.auth';
3 | export * from './useTestSuspenseQuery.auth';
4 | export * from './useTestSuspenseInfiniteQuery.auth';
5 | export * from './useTestQueries.auth';
6 | export * from './useTestPrefetch.auth';
7 | export * from './useTestPrefetchInfiniteQuery.auth';
8 | export * from './useTestQueryWithCustomClient.auth';
9 | export * from './useTestQueryWithFetch.auth';
10 | export * from './useTestVoidRequest.auth';
11 | export * from './useTestSpecialRequest.auth';
12 | export * from './useTestEndpointQuery.auth';
13 |
--------------------------------------------------------------------------------
/ios/link-assets-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "migIndex": 1,
3 | "data": [
4 | {
5 | "path": "src/assets/fonts/DMSans-Bold.ttf",
6 | "sha1": "ef47944cd61dfd6889e7f0bb7596d87849d0e6cb"
7 | },
8 | {
9 | "path": "src/assets/fonts/DMSans-Medium.ttf",
10 | "sha1": "afe77e497ca69c2e83ade71a6acb5036393db1f1"
11 | },
12 | {
13 | "path": "src/assets/fonts/DMSans-Regular.ttf",
14 | "sha1": "f1cf9ed15e1ec1db951c401233655dbbd304c732"
15 | },
16 | {
17 | "path": "src/assets/fonts/icomoon.ttf",
18 | "sha1": "a9819caab15ee3f8cb8ade72ac3a858b2e330690"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/android/link-assets-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "migIndex": 1,
3 | "data": [
4 | {
5 | "path": "src/assets/fonts/DMSans-Bold.ttf",
6 | "sha1": "ef47944cd61dfd6889e7f0bb7596d87849d0e6cb"
7 | },
8 | {
9 | "path": "src/assets/fonts/DMSans-Medium.ttf",
10 | "sha1": "afe77e497ca69c2e83ade71a6acb5036393db1f1"
11 | },
12 | {
13 | "path": "src/assets/fonts/DMSans-Regular.ttf",
14 | "sha1": "f1cf9ed15e1ec1db951c401233655dbbd304c732"
15 | },
16 | {
17 | "path": "src/assets/fonts/icomoon.ttf",
18 | "sha1": "a9819caab15ee3f8cb8ade72ac3a858b2e330690"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/src/shared/hooks/useCombinedRef.ts:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react';
2 |
3 | type RefItem =
4 | | ((element: T | null) => void)
5 | | React.MutableRefObject
6 | | null
7 | | undefined;
8 |
9 | export const useCombinedRef = (...refs: RefItem[]) => {
10 | const callback = useCallback((element: T | null) => {
11 | refs.forEach(ref => {
12 | if (!ref) {
13 | return;
14 | }
15 |
16 | if (typeof ref === 'function') {
17 | ref(element);
18 | } else {
19 | ref.current = element;
20 | }
21 | });
22 | }, refs);
23 |
24 | return callback;
25 | };
26 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | buildToolsVersion = "35.0.0"
4 | minSdkVersion = 24
5 | compileSdkVersion = 35
6 | targetSdkVersion = 35
7 | ndkVersion = "27.1.12297006"
8 | kotlinVersion = "2.0.21"
9 | }
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle")
16 | classpath("com.facebook.react:react-native-gradle-plugin")
17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
18 | }
19 | }
20 |
21 | apply plugin: "com.facebook.react.rootproject"
22 |
--------------------------------------------------------------------------------
/src/shared/hooks/useDebounce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react';
2 | import { debounce } from 'lib';
3 | import { useEvent } from './useEvent';
4 |
5 | export const useDebounce = any>(
6 | fn: Fn,
7 | ms: number,
8 | ) => {
9 | const memoizedFn = useEvent(fn);
10 |
11 | const debouncedFn = useMemo(
12 | () =>
13 | debounce((...args: Parameters) => {
14 | memoizedFn(...args);
15 | }, ms),
16 | [ms],
17 | );
18 |
19 | useEffect(
20 | () => () => {
21 | debouncedFn.cancel();
22 | },
23 | [debouncedFn],
24 | );
25 |
26 | return debouncedFn;
27 | };
28 |
--------------------------------------------------------------------------------
/src/shared/services/RouteService/models/EventEmitterActions.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'eventemitter3';
2 |
3 | export const EVENT_EMITTER_ACTIONS = {
4 | SELECT_COUNTRY_CODE: 'SELECT_COUNTRY_CODE',
5 | LOGOUT: 'LOGOUT',
6 | } as const;
7 |
8 | export type EmitterActionsType = keyof typeof EVENT_EMITTER_ACTIONS;
9 |
10 | export type EventPayloads = {
11 | [EVENT_EMITTER_ACTIONS.SELECT_COUNTRY_CODE]: string;
12 | [EVENT_EMITTER_ACTIONS.LOGOUT]: void;
13 | };
14 |
15 | export class TypedEventEmitter extends EventEmitter<
16 | keyof EventPayloads,
17 | EventPayloads
18 | > {}
19 |
20 | export const eventEmitter = new TypedEventEmitter();
21 |
--------------------------------------------------------------------------------
/src/shared/services/RouteService/models/RootStackParamList.ts:
--------------------------------------------------------------------------------
1 | import { RouteProp } from '@react-navigation/native';
2 | import { Routes } from './Routes';
3 |
4 | export type RouteType = keyof typeof Routes;
5 |
6 | export type RootStackParamList = {
7 | [Routes.MAIN_NAVIGATOR]: undefined;
8 |
9 | [Routes.AUTH_NAVIGATOR]: undefined;
10 |
11 | [Routes.BOTTOM_TAB_BAR_NAVIGATOR]: undefined;
12 |
13 | [Routes.ALERTS_NAVIGATOR]: undefined;
14 |
15 | [Routes.PROFILE_NAVIGATOR]: undefined;
16 |
17 | [Routes.AUTH]: undefined;
18 |
19 | [Routes.PROFILE]: undefined;
20 |
21 | [Routes.ALERTS]: undefined;
22 | };
23 |
24 | export type SignInProp = RouteProp;
25 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 |
3 | const { withRozenite } = require('@rozenite/metro');
4 | const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
5 |
6 | const defaultConfig = getDefaultConfig(__dirname);
7 | const { assetExts, sourceExts } = defaultConfig.resolver;
8 |
9 | const config = {
10 | transformer: {
11 | babelTransformerPath: require.resolve(
12 | 'react-native-svg-transformer/react-native',
13 | ),
14 | },
15 | resolver: {
16 | assetExts: assetExts.filter(ext => ext !== 'svg'),
17 | sourceExts: [...sourceExts, 'svg'],
18 | },
19 | };
20 |
21 | module.exports = withRozenite(mergeConfig(defaultConfig, config));
22 |
--------------------------------------------------------------------------------
/src/shared/services/StorageService/StorageService.ts:
--------------------------------------------------------------------------------
1 | import { MMKV } from 'react-native-mmkv';
2 | import { StorageKey } from './models';
3 |
4 | export const storage = new MMKV();
5 |
6 | export const StorageService = {
7 | setItem: (key: StorageKey, value: string | number | boolean | Uint8Array) => {
8 | storage.set(key, value);
9 |
10 | return Promise.resolve(true);
11 | },
12 | getItem: (key: StorageKey) => {
13 | const value = storage.getString(key);
14 |
15 | return Promise.resolve(value);
16 | },
17 | removeItem: (key: StorageKey) => {
18 | storage.delete(key);
19 |
20 | return Promise.resolve();
21 | },
22 | removeAllItems: () => {
23 | storage.clearAll();
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/src/shared/ui/ErrorMessage/ErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text } from 'react-native';
3 | import { StyleSheet } from 'react-native-unistyles';
4 |
5 | interface ErrorMessageProps {
6 | message: string;
7 | }
8 |
9 | export const ErrorMessage: React.FC = ({ message }) => {
10 | return (
11 |
12 | {message}
13 |
14 | );
15 | };
16 |
17 | const styles = StyleSheet.create(theme => ({
18 | errorMessage: {
19 | color: theme.colors.danger_500,
20 | flexShrink: 1,
21 | fontSize: 12,
22 | lineHeight: 16,
23 | fontWeight: '400',
24 | fontFamily: theme.fonts.Regular,
25 | },
26 | }));
27 |
--------------------------------------------------------------------------------
/src/modules/Common/features/useDynamicTheme.ts:
--------------------------------------------------------------------------------
1 | import { UnistylesRuntime } from 'react-native-unistyles';
2 | import { RouteService } from 'services';
3 | import { useCurrentTheme, useThemeStore } from 'stores';
4 |
5 | export const useDynamicTheme = () => {
6 | const theme = useCurrentTheme();
7 |
8 | const themeStore$ = useThemeStore();
9 |
10 | const switchTheme = () => {
11 | const newTheme = theme === 'light' ? 'dark' : 'light';
12 |
13 | UnistylesRuntime.setTheme(newTheme);
14 |
15 | themeStore$.currentTheme.set(newTheme);
16 | };
17 |
18 | const onBackPress = () => {
19 | RouteService.goBack();
20 | };
21 |
22 | return {
23 | switchTheme,
24 | onBackPress,
25 | theme,
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/src/shared/ui/Input/types.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { AllOrNothing } from 'lib';
3 | import { TextInputProps } from 'react-native';
4 |
5 | export type InputVariant = 'primary' | 'search';
6 |
7 | export interface AdditionalProps {
8 | onFocusReceive?: () => void;
9 | onRightPress?: () => void;
10 | onLeftPress?: () => void;
11 | type?: InputVariant;
12 | }
13 |
14 | export type InputProps = TextInputProps &
15 | AdditionalProps &
16 | AllOrNothing<{
17 | isError: boolean;
18 | errorMessage: string;
19 | }> &
20 | AllOrNothing<{
21 | isLeftIconShown: boolean;
22 | LeftIcon: React.ReactNode;
23 | }> &
24 | AllOrNothing<{
25 | isRightIconShown: boolean;
26 | RightIcon: React.ReactNode;
27 | }>;
28 |
--------------------------------------------------------------------------------
/src/navigation/lib/NavigationOptions.ts:
--------------------------------------------------------------------------------
1 | import { createNativeStackNavigator } from '@react-navigation/native-stack';
2 | import { DefaultTheme, Theme } from '@react-navigation/native';
3 | import { LightColors } from 'themes';
4 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
5 | import { RootStackParamList } from 'services';
6 |
7 | export const Stack = createNativeStackNavigator();
8 |
9 | export const Tab = createBottomTabNavigator();
10 |
11 | export const navigationTheme: Theme = {
12 | dark: false,
13 | colors: {
14 | ...DefaultTheme.colors,
15 | background: LightColors.basic_100,
16 | border: 'transparent',
17 | },
18 | fonts: {
19 | ...DefaultTheme.fonts,
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/src/shared/api/auth/mutations/useTestMutation.auth.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from '@tanstack/react-query';
2 | import { QueryError, queryKeys } from '../../models';
3 | import { AuthService } from '../AuthService';
4 |
5 | import { Test, CreateAccountResponse } from '../models';
6 |
7 | const testMutationMutationFnAuthService = async (params: Test) => {
8 | const response = await AuthService.testMutation(params);
9 |
10 | return response;
11 | };
12 |
13 | const getMutationKey = () => queryKeys.testMutationAuthService();
14 |
15 | export const useTestMutationMutationAuthService = () => {
16 | return useMutation({
17 | mutationFn: testMutationMutationFnAuthService,
18 | mutationKey: getMutationKey(),
19 | });
20 | };
21 |
--------------------------------------------------------------------------------
/src/modules/Auth/ui/AuthMainInformation.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text } from 'react-native';
3 | import { useTranslation } from 'react-i18next';
4 | import { StyleSheet } from 'react-native-unistyles';
5 |
6 | export const AuthMainInformation: React.FC = () => {
7 | const { t } = useTranslation();
8 |
9 | return (
10 |
11 | {t('auth.sign-in-with-your')}
12 | {t('auth.manulife-id')}
13 |
14 | );
15 | };
16 |
17 | const styles = StyleSheet.create(theme => ({
18 | line: {
19 | fontFamily: theme.fonts.Regular,
20 | fontSize: 32,
21 | color: theme.colors.puissant_purple,
22 | fontWeight: '400',
23 | letterSpacing: 1,
24 | },
25 | }));
26 |
--------------------------------------------------------------------------------
/src/shared/lib/TypeHelpers.ts:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 |
3 | export type PropsFromReactComponent = TComponent extends React.FC<
4 | infer Props
5 | >
6 | ? Props
7 | : never;
8 |
9 | export type ToUndefinedObject = Partial<
10 | Record
11 | >;
12 |
13 | export type AllOrNothing = T | ToUndefinedObject;
14 |
15 | export type TypedForwardRef = (
16 | render: (props: P, ref: React.Ref) => React.ReactNode,
17 | ) => (props: P & React.RefAttributes) => React.ReactNode;
18 |
19 | export const typedForwardRef = forwardRef as TypedForwardRef;
20 |
21 | export const objectKeys = (object: T): Array =>
22 | Object.keys(object) as Array;
23 |
--------------------------------------------------------------------------------
/src/modules/Auth/features/useSignIn.ts:
--------------------------------------------------------------------------------
1 | import { useObservable } from '@legendapp/state/react';
2 | import { withDelay } from 'lib';
3 | import { useTestQueryQueryAuthService } from 'api';
4 |
5 | interface Params {
6 | email: string;
7 | password: string;
8 | }
9 |
10 | export const useSignIn = () => {
11 | const isLoading$ = useObservable(false);
12 |
13 | useTestQueryQueryAuthService({
14 | pageParam: 'test12',
15 | token: 'test12',
16 | test: 'test12',
17 | options: {
18 | select: data => data[0].email,
19 | },
20 | });
21 |
22 | const onSignIn = async (_: Params) => {
23 | isLoading$.set(true);
24 |
25 | await withDelay(5000).then(() => {
26 | isLoading$.set(false);
27 | });
28 | };
29 |
30 | return { onSignIn, isLoading$ };
31 | };
32 |
--------------------------------------------------------------------------------
/src/modules/Auth/ui/AuthLogo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Image } from 'react-native';
3 | import { StyleSheet } from 'react-native-unistyles';
4 |
5 | export const AuthLogo: React.FC = () => {
6 | return (
7 |
8 |
13 |
14 | );
15 | };
16 |
17 | const styles = StyleSheet.create(theme => ({
18 | wrapper: {
19 | flexDirection: 'row',
20 | justifyContent: 'center',
21 | alignItems: 'center',
22 | },
23 | logo: {
24 | width: 120,
25 | height: 120,
26 | },
27 | label: {
28 | marginLeft: 30,
29 | fontSize: 32,
30 | fontFamily: theme.fonts.Bold,
31 | },
32 | }));
33 |
--------------------------------------------------------------------------------
/src/shared/providers/EventEmitterProvider/EventEmitterProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, ReactNode, useContext } from 'react';
2 | import { eventEmitter } from 'services';
3 |
4 | const EventContext = createContext(null);
5 |
6 | interface EventEmitterProviderProps {
7 | children: ReactNode;
8 | }
9 |
10 | export const EventEmitterProvider = ({
11 | children,
12 | }: EventEmitterProviderProps) => {
13 | return (
14 |
15 | {children}
16 |
17 | );
18 | };
19 |
20 | export const useEventEmitter = () => {
21 | const context = useContext(EventContext);
22 |
23 | if (!context) {
24 | throw new Error('useEventEmitter must be used within an EventProvider');
25 | }
26 |
27 | return context;
28 | };
29 |
--------------------------------------------------------------------------------
/src/shared/providers/LanguageProvider/LanguageProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { I18nextProvider } from 'react-i18next';
3 | import { LocalizationService } from '../../services';
4 | import { useLanguageStore } from '../../stores';
5 |
6 | export const LanguageProvider = ({
7 | children,
8 | }: {
9 | children: React.ReactNode;
10 | }) => {
11 | const languageStore = useLanguageStore();
12 |
13 | useEffect(() => {
14 | const initializeLanguage = () => {
15 | const currentLanguage = languageStore.currentLanguage.get();
16 |
17 | LocalizationService.changeLanguage(currentLanguage);
18 | };
19 |
20 | initializeLanguage();
21 | }, []);
22 |
23 | return (
24 |
25 | {children}
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # 📋 Overview
2 |
3 | - Outline the main changes introduced in this PR.
4 | - Include links to any relevant tickets, tasks, or specifications.
5 | - Mention any context or background information that is necessary to understand the changes.
6 | - Mention if any bugfixes were made
7 |
8 | # 👷 Testing & Screenshots
9 |
10 | - Attach any relevant images or media files that help illustrate the changes.
11 | - Describe devices used for testing like following:
12 |
13 | This update has been thoroughly tested on the following devices:
14 |
15 | - **iOS:** iPhone SE
16 | - **Android:** Samsung Galaxy S22
17 |
18 | # 💬 Review messaging guide for the PR Reviewers
19 |
20 | Use the following emojis to make your comment more explicit:
21 |
22 | - 🚨 - Change Request
23 | - 📢 - Warning
24 | - 💡 - Suggestion
25 | - ❓ - Question
26 |
--------------------------------------------------------------------------------
/src/shared/api/auth/mutations/useTestEndpointMutation.auth.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from '@tanstack/react-query';
2 | import { QueryError, queryKeys } from '../../models';
3 | import { AuthService } from '../AuthService';
4 |
5 | import { testEndpointMutation } from '../models';
6 |
7 | const testEndpointMutationMutationFnAuthService = async (
8 | params: testEndpointMutation,
9 | ) => {
10 | const response = await AuthService.testEndpointMutation(params);
11 |
12 | return response;
13 | };
14 |
15 | const getMutationKey = () => queryKeys.testEndpointMutationAuthService();
16 |
17 | export const useTestEndpointMutationMutationAuthService = () => {
18 | return useMutation({
19 | mutationFn: testEndpointMutationMutationFnAuthService,
20 | mutationKey: getMutationKey(),
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import { AppRegistry, TextInput, Text, LogBox } from 'react-native';
2 | import { App } from './App';
3 | import { setDefaultProps, parseEnv } from './src/shared/lib';
4 | import { name as appName } from './app.json';
5 |
6 | setDefaultProps(Text, { allowFontScaling: false });
7 | setDefaultProps(TextInput, { allowFontScaling: false });
8 |
9 | parseEnv();
10 |
11 | LogBox.ignoreLogs([
12 | `[Reanimated] Reduced motion setting is overwritten with mode 'never'`,
13 | 'Sending `onAnimatedValueUpdate` with no listeners registered.',
14 | '[Reanimated] Reading from `value` during component render.',
15 | '[Reanimated] Reading from `value` during component render. Please ensure that you do not access the `value` property or use `get` method of a shared value while React is rendering a component.',
16 | ]);
17 |
18 | AppRegistry.registerComponent(appName, () => App);
19 |
--------------------------------------------------------------------------------
/scripts/api-codegen/run-codegen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "🚀 Running all C++ checkers..."
3 |
4 | SCRIPT_DIR=$(dirname "$0")
5 | CPP_EXECUTABLES=("builder-name-check" "check-generics" "check-unique-types" "query-keys" "server-hooks" "merge-query-keys" "update-index")
6 |
7 | for FILE in "${CPP_EXECUTABLES[@]}"; do
8 | EXEC_PATH="$SCRIPT_DIR/$FILE"
9 |
10 | if [[ -x "$EXEC_PATH" ]]; then
11 | echo "▶️ Running $FILE..."
12 | "$EXEC_PATH"
13 | if [[ $? -ne 0 ]]; then
14 | echo "❌ Execution failed: $FILE"
15 | exit 1
16 | fi
17 | echo "✅ $FILE executed successfully!"
18 | else
19 | echo "⚠️ Skipping $FILE: Executable not found or not executable!"
20 | fi
21 | done
22 |
23 | echo -e "🔧 Running lint fix..."
24 | yarn run lint:fix
25 | echo -e "✅ Linting and formatting completed!\n"
26 |
27 | echo -e "\n🎉 All C++ checkers executed successfully!"
28 |
--------------------------------------------------------------------------------
/src/shared/stores/theme/store.ts:
--------------------------------------------------------------------------------
1 | import { syncObservable } from '@legendapp/state/sync';
2 | import { observable, syncState } from '@legendapp/state';
3 | import { ObservablePersistMMKV } from '@legendapp/state/persist-plugins/mmkv';
4 | import { ThemeStore } from './types';
5 | import { PersistStorageKeys } from '../models';
6 |
7 | const initialState: ThemeStore = {
8 | currentTheme: 'light',
9 | };
10 |
11 | export const themeStore$ = observable(initialState);
12 |
13 | syncObservable(themeStore$, {
14 | persist: {
15 | name: PersistStorageKeys.THEME,
16 | plugin: ObservablePersistMMKV,
17 | },
18 | });
19 |
20 | const themeStoreSyncState$ = syncState(themeStore$);
21 |
22 | export const resetThemeStorePersist = async () => {
23 | await themeStoreSyncState$.resetPersistence();
24 |
25 | themeStore$.set({
26 | currentTheme: 'light',
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/scripts/icons.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | PATTERN="selection.json"
3 | DECL_POSTFIX=".d.ts"
4 | TARGET_DIRECTORY="./src/assets/resources"
5 |
6 | echo "Current Directory: $(pwd)"
7 | echo "Checking for files in: $TARGET_DIRECTORY"
8 |
9 | JSONS=($(find "$TARGET_DIRECTORY" -type f -name "$PATTERN"))
10 |
11 | echo "Files found: ${JSONS[@]}"
12 |
13 | if [ ${#JSONS[@]} -eq 0 ]; then
14 | echo "No files found. Exiting."
15 | exit 1
16 | fi
17 |
18 | for file in "${JSONS[@]}"
19 | do
20 | echo "Processing file: $file"
21 |
22 | if git check-ignore --quiet "$file"; then
23 | continue
24 | fi
25 |
26 | ICON_NAMES=$(jq -r '.icons[].properties.name' "$file" | awk -v ORS=' | ' '{print "\"" $0 "\""}' | sed 's/ | $//')
27 |
28 | printf "/** Generated with \`./icons.sh\` */\nexport type IconName = $ICON_NAMES | (string & {});\n" > "$file$DECL_POSTFIX"
29 | echo "Generated .d.ts for: $file"
30 | done
--------------------------------------------------------------------------------
/ios/fastlane/README.md:
--------------------------------------------------------------------------------
1 | fastlane documentation
2 | ----
3 |
4 | # Installation
5 |
6 | Make sure you have the latest version of the Xcode command line tools installed:
7 |
8 | ```sh
9 | xcode-select --install
10 | ```
11 |
12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
13 |
14 | # Available Actions
15 |
16 | ## iOS
17 |
18 | ### ios deploy_to_testflight
19 |
20 | ```sh
21 | [bundle exec] fastlane ios deploy_to_testflight
22 | ```
23 |
24 | Submit a new build to app store
25 |
26 | ----
27 |
28 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
29 |
30 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
31 |
32 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
33 |
--------------------------------------------------------------------------------
/src/modules/Auth/ui/AuthFooter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, Pressable } from 'react-native';
3 | import { useTranslation } from 'react-i18next';
4 | import { StyleSheet } from 'react-native-unistyles';
5 |
6 | export const AuthFooter: React.FC = () => {
7 | const { t } = useTranslation();
8 |
9 | return (
10 |
11 |
12 |
13 | {t('auth.forgot-your-username-and-password')}
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | const styles = StyleSheet.create(theme => ({
21 | wrapper: {
22 | marginTop: 16,
23 | },
24 | linkText: {
25 | textAlign: 'center',
26 | fontFamily: theme.fonts.Regular,
27 | fontWeight: '400',
28 | fontSize: 14,
29 | lineHeight: 16,
30 | textDecorationLine: 'underline',
31 | },
32 | }));
33 |
--------------------------------------------------------------------------------
/src/shared/stores/auth/store.ts:
--------------------------------------------------------------------------------
1 | import { syncObservable } from '@legendapp/state/sync';
2 | import { observable, syncState } from '@legendapp/state';
3 | import { ObservablePersistMMKV } from '@legendapp/state/persist-plugins/mmkv';
4 | import { AuthStore } from './types';
5 | import { PersistStorageKeys } from '../models';
6 |
7 | const initialState: AuthStore = {
8 | refreshToken: '',
9 | token: '',
10 | };
11 |
12 | export const authStore$ = observable(initialState);
13 |
14 | syncObservable(authStore$, {
15 | persist: {
16 | name: PersistStorageKeys.AUTH,
17 | plugin: ObservablePersistMMKV,
18 | },
19 | });
20 |
21 | const authStoreSyncState$ = syncState(authStore$);
22 |
23 | export const resetAuthStorePersist = async () => {
24 | await authStoreSyncState$.resetPersistence();
25 |
26 | authStore$.set({
27 | refreshToken: '',
28 | token: '',
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/src/shared/stores/language/store.ts:
--------------------------------------------------------------------------------
1 | import { syncObservable } from '@legendapp/state/sync';
2 | import { observable, syncState } from '@legendapp/state';
3 | import { ObservablePersistMMKV } from '@legendapp/state/persist-plugins/mmkv';
4 | import { LanguageStore } from './types';
5 | import { PersistStorageKeys } from '../models';
6 |
7 | const initialState: LanguageStore = {
8 | currentLanguage: 'en',
9 | };
10 |
11 | export const languageStore$ = observable(initialState);
12 |
13 | syncObservable(languageStore$, {
14 | persist: {
15 | name: PersistStorageKeys.LANGUAGE,
16 | plugin: ObservablePersistMMKV,
17 | },
18 | });
19 |
20 | const languageStoreSyncState$ = syncState(languageStore$);
21 |
22 | export const resetLanguageStorePersist = async () => {
23 | await languageStoreSyncState$.resetPersistence();
24 |
25 | languageStore$.set({
26 | currentLanguage: 'en',
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/src/shared/ui/RefreshControl/RefreshControl.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | RefreshControl as RNRefreshControl,
4 | RefreshControlProps,
5 | } from 'react-native';
6 | import { withUnistyles } from 'react-native-unistyles';
7 | import { ColorsType } from 'themes';
8 |
9 | interface RefreshProps extends Omit {
10 | color?: ColorsType;
11 | }
12 |
13 | const UniStyleRefreshControl = withUnistyles(RNRefreshControl);
14 |
15 | export const RefreshControl: React.FC = ({
16 | color = 'primary',
17 | refreshing = false,
18 | onRefresh,
19 | ...props
20 | }) => {
21 | return (
22 | ({
26 | colors: [theme.colors[color]],
27 | tintColor: theme.colors[color],
28 | })}
29 | {...props}
30 | />
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/shared/ui/Input/lib.ts:
--------------------------------------------------------------------------------
1 | const TEXT_INPUT_PADDINGS = {
2 | BASE_PADDING: 14,
3 | LEFT_PADDING: 45,
4 | RIGHT_PADDING: 45,
5 | } as const;
6 |
7 | interface LeftPaddingParams {
8 | isLeftIconShown: boolean;
9 | }
10 |
11 | interface RightPaddingParams {
12 | isRightIconShown: boolean;
13 | }
14 |
15 | export interface InputConstructorParams {
16 | isRightIconShown: boolean;
17 | isLeftIconShown: boolean;
18 | }
19 |
20 | export const constructPaddingLeft = ({
21 | isLeftIconShown,
22 | }: LeftPaddingParams) => {
23 | if (isLeftIconShown) {
24 | return TEXT_INPUT_PADDINGS.LEFT_PADDING;
25 | }
26 |
27 | return TEXT_INPUT_PADDINGS.BASE_PADDING;
28 | };
29 |
30 | export const constructPaddingRight = ({
31 | isRightIconShown,
32 | }: RightPaddingParams) => {
33 | if (isRightIconShown) {
34 | return TEXT_INPUT_PADDINGS.RIGHT_PADDING;
35 | }
36 |
37 | return TEXT_INPUT_PADDINGS.BASE_PADDING;
38 | };
39 |
--------------------------------------------------------------------------------
/src/shared/ui/Loader/Loader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ActivityIndicator, ActivityIndicatorProps, View } from 'react-native';
3 | import { StyleSheet, withUnistyles } from 'react-native-unistyles';
4 | import { ColorsType } from 'themes';
5 |
6 | interface LoaderProps {
7 | color?: ColorsType;
8 | size?: ActivityIndicatorProps['size'];
9 | }
10 |
11 | const UniStyleActivityIndicator = withUnistyles(ActivityIndicator);
12 |
13 | export const Loader: React.FC = ({
14 | color = 'primary',
15 | size = 'small',
16 | }) => {
17 | return (
18 |
19 | ({ color: theme.colors[color] })}
23 | />
24 |
25 | );
26 | };
27 |
28 | const styles = StyleSheet.create(() => ({
29 | wrapper: {
30 | justifyContent: 'center',
31 | alignItems: 'center',
32 | },
33 | }));
34 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Colors.xcassets/BootSplashBackground-843e90.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors": [
3 | {
4 | "idiom": "universal",
5 | "color": {
6 | "color-space": "srgb",
7 | "components": {
8 | "blue": "1.00000000000000",
9 | "green": "1.00000000000000",
10 | "red": "1.00000000000000",
11 | "alpha": "1.000"
12 | }
13 | }
14 | },
15 | {
16 | "appearances": [
17 | {
18 | "appearance": "luminosity",
19 | "value": "dark"
20 | }
21 | ],
22 | "idiom": "universal",
23 | "color": {
24 | "color-space": "srgb",
25 | "components": {
26 | "red": "0.00000000000000",
27 | "green": "0.00000000000000",
28 | "blue": "0.00000000000000",
29 | "alpha": "1.000"
30 | }
31 | }
32 | }
33 | ],
34 | "info": {
35 | "author": "xcode",
36 | "version": 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/shared/services/LocalizationService/LocalizationService.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import { initReactI18next } from 'react-i18next';
3 |
4 | import en from 'translations/en.json';
5 |
6 | export type Language = 'en';
7 |
8 | const resources = {
9 | en: { translation: en },
10 | };
11 |
12 | i18n.use(initReactI18next).init({
13 | compatibilityJSON: 'v3',
14 | resources,
15 | fallbackLng: 'en',
16 | lng: 'en',
17 | interpolation: {
18 | escapeValue: false,
19 | },
20 | });
21 |
22 | const changeLanguage = async (newLanguage: Language) => {
23 | await i18n.changeLanguage(newLanguage);
24 | };
25 |
26 | const getCurrentLanguage = (): Language => {
27 | return i18n.language as Language;
28 | };
29 |
30 | const getAvailableLanguages = (): Language[] => {
31 | return ['en'];
32 | };
33 |
34 | export const i18nLocale = i18n;
35 |
36 | export const LocalizationService = {
37 | changeLanguage,
38 | getCurrentLanguage,
39 | getAvailableLanguages,
40 | i18n,
41 | };
42 |
--------------------------------------------------------------------------------
/src/shared/ui/Icon/Icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withUnistyles } from 'react-native-unistyles';
3 | import createIconSet, { IconName } from '@react-native-vector-icons/icomoon';
4 | import { ColorsType } from 'themes';
5 | import { ViewProps } from 'react-native';
6 |
7 | export const IcomoonConfig = require('../../../assets/resources/selection.json');
8 |
9 | const IcomoonIcon = createIconSet(IcomoonConfig);
10 |
11 | const DEFAULT_ICON_SIZE = 16;
12 |
13 | interface BaseProps extends ViewProps {
14 | name: IconName;
15 | size?: number;
16 | color?: ColorsType;
17 | }
18 |
19 | const UniIcon = withUnistyles(IcomoonIcon);
20 |
21 | export const Icon: React.FC = ({
22 | name,
23 | size,
24 | color = 'black',
25 | ...rest
26 | }) => {
27 | return (
28 | ({
32 | color: theme.colors[color],
33 | })}
34 | {...rest}
35 | />
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/useQueryEvents.ts:
--------------------------------------------------------------------------------
1 | import type { UseQueryResult } from '@tanstack/react-query';
2 | import { useLatest } from 'hooks';
3 | import { useEffect } from 'react';
4 |
5 | type QueryEvents = {
6 | onSuccess?: (data: TData) => unknown;
7 | onError?: (error: TError) => unknown;
8 | };
9 |
10 | export const useQueryEvents = (
11 | query: UseQueryResult,
12 | callbacks: Partial>,
13 | ) => {
14 | const { onSuccess, onError } = callbacks;
15 |
16 | const onSuccessRef = useLatest(onSuccess);
17 | const onErrorRef = useLatest(onError);
18 |
19 | useEffect(() => {
20 | if (query.isSuccess && onSuccessRef.current && query.data) {
21 | onSuccessRef.current(query.data);
22 | }
23 | }, [query.isSuccess, query.data]);
24 |
25 | useEffect(() => {
26 | if (query.isError && onErrorRef.current && query.error) {
27 | onErrorRef.current(query.error);
28 | }
29 | }, [query.isError, query.error]);
30 | };
31 |
--------------------------------------------------------------------------------
/ios/fastlane/scripts/util_helper.rb:
--------------------------------------------------------------------------------
1 | module Fastlane
2 | module Helper
3 | class UtilHelper
4 | # Redirects standard output and standard error to a file
5 | def self.redirect_stdout_to_file(filename)
6 | original_stdout = $stdout.clone
7 | original_stderr = $stderr.clone
8 | $stderr.reopen(File.new(filename, 'w'))
9 | $stdout.reopen(File.new(filename, 'w'))
10 | yield
11 | ensure
12 | $stdout.reopen(original_stdout)
13 | $stderr.reopen(original_stderr)
14 | end
15 |
16 | # Captures and returns Fastlane output as a string
17 | def self.capture_fastlane_output
18 | require 'tempfile'
19 | temp_file = Tempfile.new('fastlane_output')
20 | redirect_stdout_to_file(temp_file.path) do
21 | yield
22 | end
23 | temp_file.rewind
24 | temp_file.read
25 | ensure
26 | temp_file.close
27 | temp_file.unlink
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/src/modules/Common/ui/Modals/UpdateModal/UpdateModal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text } from 'react-native';
3 | import { useTranslation } from 'react-i18next';
4 | import { StyleSheet } from 'react-native-unistyles';
5 |
6 | export const UpdateModal: React.FC = () => {
7 | const { t } = useTranslation();
8 |
9 | return (
10 |
11 | {t('update-modal.title')}
12 |
13 | {t('update-modal.description')}
14 |
15 | );
16 | };
17 |
18 | const styles = StyleSheet.create(theme => ({
19 | container: {
20 | alignItems: 'center',
21 | },
22 | title: {
23 | textAlign: 'center',
24 | fontFamily: theme.fonts.Regular,
25 | fontWeight: '500',
26 | color: theme.colors.black,
27 | fontSize: 16,
28 | },
29 | description: {
30 | marginTop: 16,
31 | textAlign: 'center',
32 | color: theme.colors.black,
33 | fontFamily: theme.fonts.Regular,
34 | fontSize: 14,
35 | },
36 | }));
37 |
--------------------------------------------------------------------------------
/src/shared/api/auth/models.ts:
--------------------------------------------------------------------------------
1 | export interface CreateAccountRequest {
2 | firstName: string;
3 | lastName: string;
4 | password: string;
5 | email: string;
6 | phoneNumber: string;
7 | }
8 |
9 | export interface CreateAccountResponse {
10 | firstName: string;
11 | lastName: string;
12 | password: string;
13 | email: string;
14 | phoneNumber: string;
15 | userId: number;
16 | }
17 |
18 | export interface GetUserResponse {
19 | firstName: string;
20 | lastName: string;
21 | email: string;
22 | phoneNumber: string;
23 | userId: number;
24 | }
25 |
26 | export interface GetUserRequest {
27 | id: number;
28 | }
29 |
30 | export interface UserResponse {}
31 |
32 | export interface User {
33 | test: string;
34 | }
35 |
36 | export interface Test {
37 | test: string;
38 | pageParam: string | number | unknown;
39 | token: string;
40 | }
41 |
42 | export type SpecialRequest = number;
43 |
44 | export type SpecialResponse = number;
45 |
46 | export interface testEndpointMutation {
47 | test: string;
48 | }
49 | export interface testEndpointQuery {
50 | test: string;
51 | }
52 |
--------------------------------------------------------------------------------
/src/shared/stores/user/store.ts:
--------------------------------------------------------------------------------
1 | import { syncObservable } from '@legendapp/state/sync';
2 | import { observable, syncState } from '@legendapp/state';
3 | import { ObservablePersistMMKV } from '@legendapp/state/persist-plugins/mmkv';
4 | import { User, UserStore } from './types';
5 | import { PersistStorageKeys } from '../models';
6 |
7 | const initialUser: User = {
8 | id: '',
9 | lastName: '',
10 | firstName: '',
11 | email: '',
12 | displayName: '',
13 | username: '',
14 | };
15 |
16 | export const userStore$ = observable({
17 | user: initialUser,
18 | });
19 |
20 | syncObservable(userStore$, {
21 | persist: {
22 | name: PersistStorageKeys.USER,
23 | plugin: ObservablePersistMMKV,
24 | },
25 | });
26 |
27 | const userStoreSyncState$ = syncState(userStore$);
28 |
29 | export const resetUserStorePersist = async () => {
30 | await userStoreSyncState$.resetPersistence();
31 |
32 | userStore$.set({
33 | user: {
34 | id: '',
35 | lastName: '',
36 | firstName: '',
37 | email: '',
38 | displayName: '',
39 | username: '',
40 | },
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 LUMITECH FZCO
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "scale" : "1x",
46 | "size" : "1024x1024"
47 | }
48 | ],
49 | "info" : {
50 | "author" : "xcode",
51 | "version" : 1
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:@react-native/babel-preset'],
3 | plugins: [
4 | 'react-native-reanimated/plugin',
5 | [
6 | 'babel-plugin-module-resolver',
7 | {
8 | root: ['./src/'],
9 | alias: [
10 | { navigation: './src/navigation' },
11 | { screens: './src/screens' },
12 | { widgets: './src/widgets' },
13 | { features: './src/features' },
14 | { types: './src/types' },
15 | { api: './src/shared/api' },
16 | { hooks: './src/shared/hooks' },
17 | { lib: './src/shared/lib' },
18 | { services: './src/shared/services' },
19 | { stores: './src/shared/stores' },
20 | { themes: './src/shared/themes' },
21 | { translations: './src/shared/translations' },
22 | { ui: './src/shared/ui' },
23 | { providers: './src/shared/providers' },
24 | ],
25 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
26 | },
27 | ],
28 | [
29 | 'react-native-unistyles/plugin',
30 | {
31 | root: 'src',
32 | },
33 | ],
34 | ],
35 | };
36 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/shared/ui/Keyboard/KeyboardAwareScrollView.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { ScrollViewProps } from 'react-native';
3 | import { KeyboardAwareScrollView as KeyboardAwareScrollViewComponent } from 'react-native-keyboard-controller';
4 |
5 | interface KeyboardAwareScrollViewProps extends ScrollViewProps {
6 | bottomOffset?: number;
7 | enabled?: boolean;
8 | }
9 |
10 | const BOTTOM_OFFSET = 70;
11 |
12 | export const KeyboardAwareScrollView: FC = ({
13 | children,
14 | showsVerticalScrollIndicator = false,
15 | alwaysBounceVertical = false,
16 | keyboardDismissMode = 'none',
17 | keyboardShouldPersistTaps = 'handled',
18 | bottomOffset = BOTTOM_OFFSET,
19 | ...rest
20 | }) => {
21 | return (
22 |
29 | {children}
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/modules/Auth/ui/AuthWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { KeyboardAwareScrollView } from 'react-native-keyboard-controller';
4 | import { StyleSheet } from 'react-native-unistyles';
5 |
6 | interface AuthWrapperProps {
7 | children: React.ReactNode;
8 | bottomOffset?: number;
9 | }
10 |
11 | const BOTTOM_OFFSET = 100;
12 |
13 | export const AuthWrapper: React.FC = ({
14 | children,
15 | bottomOffset = BOTTOM_OFFSET,
16 | }) => {
17 | return (
18 |
23 | {children}
24 |
25 | );
26 | };
27 |
28 | const styles = StyleSheet.create((theme, runtime) => ({
29 | contentContainer: {
30 | flexGrow: 1,
31 | backgroundColor: theme.colors.primary_background,
32 | },
33 | wrapper: {
34 | flex: 1,
35 | paddingTop: theme.inset(runtime.insets.top),
36 | justifyContent: 'space-between',
37 | },
38 | }));
39 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
23 |
24 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/useQueriesWithOptions.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-redeclare */
2 | /* eslint-disable func-style */
3 |
4 | import {
5 | useQueries,
6 | QueriesOptions,
7 | QueriesResults,
8 | } from '@tanstack/react-query';
9 |
10 | interface UseQueriesWithOptionsParams> {
11 | queries: readonly [...QueriesOptions];
12 | options?: {
13 | combine?: (result: QueriesResults) => any;
14 | subscribed?: boolean;
15 | };
16 | }
17 |
18 | export function useQueriesWithOptions<
19 | T extends Array,
20 | TCombinedResult = QueriesResults,
21 | >(
22 | params: UseQueriesWithOptionsParams & {
23 | options: {
24 | combine: (result: QueriesResults) => TCombinedResult;
25 | subscribed?: boolean;
26 | };
27 | },
28 | ): TCombinedResult;
29 |
30 | export function useQueriesWithOptions>(
31 | params: UseQueriesWithOptionsParams,
32 | ): QueriesResults;
33 |
34 | export function useQueriesWithOptions>(
35 | params: UseQueriesWithOptionsParams,
36 | ): QueriesResults | any {
37 | const { queries, options } = params;
38 |
39 | return useQueries({
40 | queries,
41 | ...options,
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/src/shared/themes/Theme.ts:
--------------------------------------------------------------------------------
1 | import { LightColors, DarkColors } from './Colors';
2 | import { FontFamily } from './Fonts';
3 |
4 | const DEFAULT_GAP = 8;
5 | const DEFAULT_INSET = 16;
6 |
7 | export const LightTheme = {
8 | colors: LightColors,
9 | fonts: FontFamily,
10 | borderRadius: {
11 | base: 0,
12 | },
13 | gap: (value: number) => value * DEFAULT_GAP,
14 | inset: (insetValue: number) => {
15 | return Math.max(insetValue, DEFAULT_INSET);
16 | },
17 | shadow: {
18 | base: {
19 | shadowColor: LightColors.black,
20 | shadowOpacity: 0.06,
21 | shadowOffset: { width: 0, height: 1 },
22 | shadowRadius: 4,
23 | elevation: 2,
24 | },
25 | },
26 | } as const;
27 |
28 | export const DarkTheme = {
29 | colors: DarkColors,
30 | fonts: FontFamily,
31 | borderRadius: {
32 | base: 0,
33 | },
34 | gap: (value: number) => value * DEFAULT_GAP,
35 | inset: (insetValue: number) => {
36 | return Math.max(insetValue, DEFAULT_INSET);
37 | },
38 | shadow: {
39 | base: {
40 | shadowColor: DarkColors.black,
41 | shadowOpacity: 0.06,
42 | shadowOffset: { width: 0, height: 1 },
43 | shadowRadius: 4,
44 | elevation: 2,
45 | },
46 | },
47 | } as const;
48 |
--------------------------------------------------------------------------------
/src/shared/ui/ActivityIndicator/ActivityIndicator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ActivityIndicator as BaseIndicator, View } from 'react-native';
3 | import { Portal } from 'react-native-teleport';
4 | import { StyleSheet, withUnistyles } from 'react-native-unistyles';
5 | import { AnimatedBackdrop } from '../AnimatedBackDrop';
6 |
7 | interface ActivityIndicatorProps {
8 | isVisible: boolean;
9 | }
10 |
11 | const UniStyleActivityIndicator = withUnistyles(BaseIndicator);
12 |
13 | export const ActivityIndicator: React.FC = ({
14 | isVisible,
15 | }) => {
16 | return (
17 |
18 |
19 |
20 | {isVisible && (
21 |
22 | ({ color: theme.colors.primary })}
25 | />
26 |
27 | )}
28 |
29 | );
30 | };
31 |
32 | const styles = StyleSheet.create((_, runtime) => ({
33 | container: {
34 | flex: 1,
35 | position: 'absolute',
36 | alignSelf: 'center',
37 | justifyContent: 'center',
38 | zIndex: 999,
39 | top: runtime.screen.height / 2,
40 | },
41 | }));
42 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Resolve react_native_pods.rb with node to allow for hoisting
2 | require Pod::Executable.execute_command('node', ['-p',
3 | 'require.resolve(
4 | "react-native/scripts/react_native_pods.rb",
5 | {paths: [process.argv[1]]},
6 | )', __dir__]).strip
7 |
8 | platform :ios, min_ios_version_supported
9 | prepare_react_native_project!
10 |
11 | linkage = ENV['USE_FRAMEWORKS']
12 | if linkage != nil
13 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
14 | use_frameworks! :linkage => linkage.to_sym
15 | end
16 |
17 | target 'ReactNativeTemplate' do
18 | config = use_native_modules!
19 |
20 | use_react_native!(
21 | :path => config[:reactNativePath],
22 | # An absolute path to your application root.
23 | :app_path => "#{Pod::Config.instance.installation_root}/..",
24 | :hermes_enabled => true,
25 | :fabric_enabled => true,
26 | :new_arch_enabled => true
27 | )
28 |
29 | post_install do |installer|
30 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
31 | react_native_post_install(
32 | installer,
33 | config[:reactNativePath],
34 | :mac_catalyst_enabled => false,
35 | # :ccache_enabled => true
36 | )
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Run Lint on PR
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - synchronize
8 | - edited
9 | - reopened
10 |
11 | jobs:
12 | linting:
13 | name: Linting
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout Code
18 | uses: actions/checkout@v3
19 |
20 | - name: Setup Node.js
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version-file: ".nvmrc"
24 |
25 | - name: Enable Corepack and Set Yarn Version
26 | run: |
27 | corepack enable
28 | corepack prepare yarn@4.10.3 --activate
29 |
30 | - name: Setup config
31 | run: yarn config set -H enableImmutableInstalls false
32 |
33 | - name: Cache Yarn dependencies
34 | uses: actions/cache@v3
35 | with:
36 | path: |
37 | ~/.cache/yarn
38 | node_modules
39 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
40 | restore-keys: |
41 | ${{ runner.os }}-yarn-
42 |
43 | - name: Install Dependencies
44 | run: yarn install
45 |
46 | - name: Code Linting
47 | run: yarn run lint
48 |
49 | - name: Type Linting
50 | run: yarn run typescript
51 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reactnativetemplate/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativetemplate
2 |
3 | import com.facebook.react.ReactActivity
4 | import com.facebook.react.ReactActivityDelegate
5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
6 | import com.facebook.react.defaults.DefaultReactActivityDelegate
7 |
8 | import android.os.Bundle
9 | import com.zoontek.rnbootsplash.RNBootSplash
10 |
11 | class MainActivity : ReactActivity() {
12 |
13 | /**
14 | * Returns the name of the main component registered from JavaScript. This is used to schedule
15 | * rendering of the component.
16 | */
17 | override fun getMainComponentName(): String = "ReactNativeTemplate"
18 |
19 | /**
20 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
21 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
22 | */
23 | override fun createReactActivityDelegate(): ReactActivityDelegate =
24 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
25 |
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | RNBootSplash.init(this, R.style.BootTheme) // ⬅️ initialize the splash screen
28 | super.onCreate(null) // super.onCreate(savedInstanceState) without react-native-screens
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Images.xcassets/BootSplashLogo-843e90.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "idiom": "universal",
5 | "filename": "logo-843e90.png",
6 | "scale": "1x"
7 | },
8 | {
9 | "idiom": "universal",
10 | "filename": "logo-843e90@2x.png",
11 | "scale": "2x"
12 | },
13 | {
14 | "idiom": "universal",
15 | "filename": "logo-843e90@3x.png",
16 | "scale": "3x"
17 | },
18 | {
19 | "appearances": [
20 | {
21 | "appearance": "luminosity",
22 | "value": "dark"
23 | }
24 | ],
25 | "idiom": "universal",
26 | "filename": "dark-logo-843e90.png",
27 | "scale": "1x"
28 | },
29 | {
30 | "appearances": [
31 | {
32 | "appearance": "luminosity",
33 | "value": "dark"
34 | }
35 | ],
36 | "idiom": "universal",
37 | "filename": "dark-logo-843e90@2x.png",
38 | "scale": "2x"
39 | },
40 | {
41 | "appearances": [
42 | {
43 | "appearance": "luminosity",
44 | "value": "dark"
45 | }
46 | ],
47 | "idiom": "universal",
48 | "filename": "dark-logo-843e90@3x.png",
49 | "scale": "3x"
50 | }
51 | ],
52 | "info": {
53 | "author": "xcode",
54 | "version": 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/shared/api/baseQuery.ts:
--------------------------------------------------------------------------------
1 | import Config from 'react-native-config';
2 | import { eventEmitter, ExceptionService, ToastService } from 'services';
3 | import { getAuthStoreInstance, resetAllStores } from 'stores';
4 | import axios from 'axios';
5 | import { Mutex } from 'async-mutex';
6 | import { createAxiosClient } from './http-client';
7 |
8 | export const refreshMutex = new Mutex();
9 |
10 | export const axiosBaseQuery = axios.create({ baseURL: '' });
11 |
12 | export const baseQuery = createAxiosClient({
13 | baseURL: Config.API_URL || '',
14 | getToken: () => {
15 | const { token } = getAuthStoreInstance();
16 |
17 | return token.get();
18 | },
19 | onError: error => {
20 | ToastService.onDanger({
21 | title: ExceptionService.errorResolver(error),
22 | });
23 | },
24 | onUnauthorized: async error => {
25 | if (refreshMutex.isLocked()) {
26 | await refreshMutex.waitForUnlock();
27 | } else {
28 | const release = await refreshMutex.acquire();
29 |
30 | try {
31 | // your tokens request here
32 | } catch {
33 | ToastService.onDanger({
34 | title: ExceptionService.errorResolver(error),
35 | });
36 |
37 | eventEmitter.emit('LOGOUT');
38 |
39 | resetAllStores();
40 | } finally {
41 | release();
42 | }
43 | }
44 | },
45 | });
46 |
--------------------------------------------------------------------------------
/src/shared/lib/General.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable */
4 | import i18next from 'i18next';
5 | import { Linking } from 'react-native';
6 | import { ToastService } from 'services';
7 |
8 | export type AnyType = any;
9 |
10 | export const isDev = __DEV__;
11 |
12 | export const noop = () => {};
13 |
14 | export const withDelay = (ms: number) => {
15 | return new Promise(resolve => {
16 | setTimeout(resolve, ms);
17 | });
18 | };
19 |
20 | export const openLink = async (link: string) => {
21 | try {
22 | await Linking.openURL(link);
23 | } catch {
24 | ToastService.onDanger({ title: i18next.t('errors.server-unable') });
25 | }
26 | };
27 |
28 | export const debounce = any>(
29 | fn: T,
30 | ms: number,
31 | ) => {
32 | let timeoutId: ReturnType | null = null;
33 |
34 | function debounced(...args: Parameters) {
35 | if (timeoutId !== null) {
36 | clearTimeout(timeoutId);
37 | }
38 |
39 | timeoutId = setTimeout(() => {
40 | timeoutId = null;
41 | fn(...args);
42 | }, ms);
43 | }
44 |
45 | debounced.cancel = () => {
46 | if (timeoutId !== null) {
47 | clearTimeout(timeoutId);
48 | timeoutId = null;
49 | }
50 | };
51 |
52 | return debounced;
53 | };
54 |
--------------------------------------------------------------------------------
/src/modules/Common/features/useRootNavigator.ts:
--------------------------------------------------------------------------------
1 | import { DefaultTheme, Theme } from '@react-navigation/native';
2 | import { useEventEmitter } from 'providers';
3 | import { useEffect } from 'react';
4 | import { useUnistyles } from 'react-native-unistyles';
5 | import { resetAllStores, useCurrentTheme, useToken } from 'stores';
6 |
7 | export const useRootNavigator = () => {
8 | const token = useToken();
9 |
10 | const currentTheme = useCurrentTheme();
11 |
12 | const eventEmitter = useEventEmitter();
13 |
14 | const { theme } = useUnistyles();
15 |
16 | const navigationTheme = {
17 | dark: currentTheme === 'dark',
18 | colors: {
19 | ...DefaultTheme.colors,
20 | background: theme.colors.primary_background,
21 | border: 'transparent',
22 | primary: theme.colors.primary,
23 | card: theme.colors.primary_background,
24 | text: theme.colors.secondary,
25 | notification: theme.colors.primary,
26 | },
27 | fonts: {
28 | ...DefaultTheme.fonts,
29 | ...theme.fonts,
30 | },
31 | } satisfies Theme;
32 |
33 | useEffect(() => {
34 | const listener = eventEmitter.addListener('LOGOUT', () => {
35 | resetAllStores();
36 | });
37 |
38 | return () => {
39 | listener.removeListener('LOGOUT');
40 | };
41 | }, []);
42 |
43 | return {
44 | currentTheme,
45 | token,
46 | navigationTheme,
47 | };
48 | };
49 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategorySystemBootTime
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | 35F9.1
13 |
14 |
15 |
16 | NSPrivacyAccessedAPIType
17 | NSPrivacyAccessedAPICategoryUserDefaults
18 | NSPrivacyAccessedAPITypeReasons
19 |
20 | CA92.1
21 |
22 |
23 |
24 | NSPrivacyAccessedAPIType
25 | NSPrivacyAccessedAPICategoryFileTimestamp
26 | NSPrivacyAccessedAPITypeReasons
27 |
28 | C617.1
29 |
30 |
31 |
32 | NSPrivacyAccessedAPIType
33 | NSPrivacyAccessedAPICategoryDiskSpace
34 | NSPrivacyAccessedAPITypeReasons
35 |
36 | 85F4.1
37 |
38 |
39 |
40 | NSPrivacyCollectedDataTypes
41 |
42 | NSPrivacyTracking
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/shared/lib/Platforms.ts:
--------------------------------------------------------------------------------
1 | import { Platform } from 'react-native';
2 | import React from 'react';
3 | import {
4 | hasNotch as hasTopOffset,
5 | getVersion,
6 | getBundleId,
7 | getBuildNumber,
8 | } from 'react-native-device-info';
9 | import { UnistylesRuntime } from 'react-native-unistyles';
10 |
11 | export const hasNotch = hasTopOffset();
12 |
13 | export const bundleId = getBundleId();
14 |
15 | export const appVersion = getVersion();
16 |
17 | export const appBuildNumber = getBuildNumber();
18 |
19 | export const touchableConfig = {
20 | delayPressIn: 0,
21 | delayPressOut: 0,
22 | activeOpacity: 0.8,
23 | };
24 |
25 | export const isIOS = Platform.OS === 'ios';
26 |
27 | type ReactComponent = React.ComponentType & {
28 | defaultProps?: Partial;
29 | };
30 |
31 | export const setDefaultProps = (
32 | Component: T,
33 | additionalProps: React.ComponentProps,
34 | ) => {
35 | Component.defaultProps = {
36 | ...(Component.defaultProps || {}),
37 | ...additionalProps,
38 | };
39 | };
40 |
41 | export const platformOS = Platform.OS;
42 |
43 | const DEFAULT_INSET = 16;
44 |
45 | export const PLATFORM_INSETS = {
46 | TOP: Math.max(UnistylesRuntime.insets.top, DEFAULT_INSET),
47 | BOTTOM: Math.max(UnistylesRuntime.insets.bottom, DEFAULT_INSET),
48 | LEFT: Math.max(UnistylesRuntime.insets.left, DEFAULT_INSET),
49 | RIGHT: Math.max(UnistylesRuntime.insets.right, DEFAULT_INSET),
50 | };
51 |
--------------------------------------------------------------------------------
/src/shared/api/auth/queries/useTestQueries.auth.ts:
--------------------------------------------------------------------------------
1 | import {
2 | QueryKeyType,
3 | UseQueriesWithOptionsParams,
4 | queryKeys,
5 | } from '../../models';
6 | import { useQueriesWithOptions } from '../../hooks';
7 | import { AuthService } from '../AuthService';
8 |
9 | import { CreateAccountResponse, Test } from '../models';
10 |
11 | interface QueryFnParams {
12 | params: Test;
13 | meta?: Record | undefined;
14 | queryKey?: QueryKeyType;
15 | signal?: AbortSignal;
16 | }
17 |
18 | const testQueriesQueryFnAuthService = async ({
19 | params,
20 | signal,
21 | }: QueryFnParams) => {
22 | const response = await AuthService.testQueries(params, { signal });
23 |
24 | return response;
25 | };
26 |
27 | const getQueryKey = (params: Test) => queryKeys.testQueriesAuthService(params);
28 |
29 | interface HookParams {
30 | params: Test[];
31 | options?: UseQueriesWithOptionsParams<
32 | Array<{
33 | queryKey: QueryKeyType;
34 | queryFn: () => Promise;
35 | }>
36 | >['options'];
37 | }
38 |
39 | export const useTestQueriesQueriesAuthService = ({
40 | params,
41 | options,
42 | }: HookParams) => {
43 | const queries = params.map(param => ({
44 | queryKey: getQueryKey(param),
45 | queryFn: ({ signal }: { signal?: AbortSignal }) =>
46 | testQueriesQueryFnAuthService({ params: param, signal }),
47 | }));
48 |
49 | return useQueriesWithOptions({
50 | queries,
51 | options,
52 | });
53 | };
54 |
--------------------------------------------------------------------------------
/src/modules/Auth/features/useAuth.ts:
--------------------------------------------------------------------------------
1 | import { useObserve } from '@legendapp/state/react';
2 | import { KeyboardService } from 'services';
3 | import { LoginInSchema } from './config';
4 | import { useAuthFormStore } from './auth-observable';
5 | import { useSignIn } from './useSignIn';
6 |
7 | export const useAuth = () => {
8 | const authFormStore$ = useAuthFormStore();
9 |
10 | const { onSignIn, isLoading$ } = useSignIn();
11 |
12 | useObserve(() => {
13 | if (!authFormStore$.didSubmit.get()) {
14 | return;
15 | }
16 |
17 | const result = LoginInSchema.safeParse(authFormStore$.formFields.get());
18 |
19 | if (result.success) {
20 | authFormStore$.errors.set({ email: '', password: '' });
21 |
22 | return;
23 | }
24 |
25 | const { fieldErrors } = result.error.flatten();
26 |
27 | authFormStore$.errors.set({
28 | email: fieldErrors.email?.[0] ?? '',
29 | password: fieldErrors.password?.[0] ?? '',
30 | });
31 | });
32 |
33 | const onSubmit = () => {
34 | KeyboardService.dismiss();
35 |
36 | authFormStore$.didSubmit.set(true);
37 |
38 | const formData = authFormStore$.formFields.get();
39 |
40 | const result = LoginInSchema.safeParse(formData);
41 |
42 | if (!result.success) {
43 | return;
44 | }
45 |
46 | const { email, password } = formData;
47 |
48 | onSignIn({ email, password });
49 | };
50 |
51 | return {
52 | onSubmit,
53 | isLoading$,
54 | authFormStore$,
55 | };
56 | };
57 |
--------------------------------------------------------------------------------
/src/navigation/RootNavigator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavigationContainer } from '@react-navigation/native';
3 | import { RouteService } from 'services';
4 | import RNBootSplash from 'react-native-bootsplash';
5 | import { withUnistyles } from 'react-native-unistyles';
6 | import { StatusBar } from 'react-native';
7 | import { useRootNavigator } from 'modules';
8 | import { MainNavigator } from './MainNavigator';
9 | import { AuthNavigator } from './AuthNavigator';
10 | import { Stack } from './lib';
11 |
12 | const UniStatusBar = withUnistyles(StatusBar);
13 |
14 | export const RootNavigator: React.FC = () => {
15 | const { navigationTheme, token } = useRootNavigator();
16 |
17 | return (
18 | RNBootSplash.hide({ fade: true })}
21 | theme={navigationTheme}>
22 | ({
24 | barStyle:
25 | runtime.themeName === 'light' ? 'dark-content' : 'light-content',
26 | backgroundColor: theme.colors.transparent,
27 | })}
28 | animated
29 | translucent
30 | />
31 |
32 |
33 | {token ? (
34 |
35 | ) : (
36 |
37 | )}
38 |
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/scripts/api-codegen/compile-all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "🔨 Compiling all C++ files..."
4 |
5 | SCRIPT_DIR=$(dirname "$0")
6 |
7 | g++ -o "$SCRIPT_DIR/builder-name-check" "$SCRIPT_DIR/builder-name-check.cpp" -std=c++17 -O2
8 | if [[ $? -ne 0 ]]; then
9 | echo "❌ Compilation failed: builder-name-check.cpp"
10 | exit 1
11 | fi
12 |
13 | g++ -o "$SCRIPT_DIR/check-generics" "$SCRIPT_DIR/check-generics.cpp" -std=c++17 -O2
14 | if [[ $? -ne 0 ]]; then
15 | echo "❌ Compilation failed: check-generics.cpp"
16 | exit 1
17 | fi
18 |
19 | g++ -o "$SCRIPT_DIR/check-unique-types" "$SCRIPT_DIR/check-unique-types.cpp" -std=c++17 -O2
20 | if [[ $? -ne 0 ]]; then
21 | echo "❌ Compilation failed: check-unique-types.cpp"
22 | exit 1
23 | fi
24 |
25 | g++ -o "$SCRIPT_DIR/query-keys" "$SCRIPT_DIR/query-keys.cpp" -std=c++17 -O2
26 | if [[ $? -ne 0 ]]; then
27 | echo "❌ Compilation failed: query-keys.cpp"
28 | exit 1
29 | fi
30 |
31 | g++ -o "$SCRIPT_DIR/server-hooks" "$SCRIPT_DIR/server-hooks.cpp" -std=c++17 -O2
32 | if [[ $? -ne 0 ]]; then
33 | echo "❌ Compilation failed: server-hooks.cpp"
34 | exit 1
35 | fi
36 |
37 | g++ -o "$SCRIPT_DIR/merge-query-keys" "$SCRIPT_DIR/merge-query-keys.cpp" -std=c++17 -O2
38 | if [[ $? -ne 0 ]]; then
39 | echo "❌ Compilation failed: merge-query-keys.cpp"
40 | exit 1
41 | fi
42 |
43 | g++ -o "$SCRIPT_DIR/update-index" "$SCRIPT_DIR/update-index.cpp" -std=c++17 -O2
44 | if [[ $? -ne 0 ]]; then
45 | echo "❌ Compilation failed: update-index.cpp"
46 | exit 1
47 | fi
48 |
49 | echo "✅ All files compiled successfully!"
50 |
--------------------------------------------------------------------------------
/src/modules/Auth/features/auth-observable.ts:
--------------------------------------------------------------------------------
1 | import { observable, syncState } from '@legendapp/state';
2 | import { syncObservable } from '@legendapp/state/sync';
3 | import { ObservablePersistMMKV } from '@legendapp/state/persist-plugins/mmkv';
4 | import { StorageKeys } from 'services';
5 | import { LoginValues } from './config';
6 |
7 | interface AuthFormStore {
8 | formFields: LoginValues;
9 | errors: {
10 | email: string;
11 | password: string;
12 | };
13 | isSecureModeEnabled: boolean;
14 | didSubmit: boolean;
15 | }
16 |
17 | export const authFormStore$ = observable({
18 | formFields: {
19 | email: '',
20 | password: '',
21 | rememberMe: false,
22 | isFaceIdEnabled: false,
23 | },
24 | errors: {
25 | email: '',
26 | password: '',
27 | },
28 | isSecureModeEnabled: true,
29 | didSubmit: false,
30 | });
31 |
32 | syncObservable(authFormStore$, {
33 | persist: {
34 | plugin: ObservablePersistMMKV,
35 | name: StorageKeys.AUTH_REMEMBER_ME_FIELDS,
36 | },
37 | });
38 |
39 | const useFormSyncState$ = syncState(authFormStore$);
40 |
41 | export const resetAuthFormPersist = async () => {
42 | await useFormSyncState$.resetPersistence();
43 |
44 | authFormStore$.set({
45 | formFields: {
46 | email: '',
47 | password: '',
48 | rememberMe: false,
49 | isFaceIdEnabled: false,
50 | },
51 | errors: {
52 | email: '',
53 | password: '',
54 | },
55 | isSecureModeEnabled: true,
56 | didSubmit: false,
57 | });
58 | };
59 |
60 | export const useAuthFormStore = () => authFormStore$;
61 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/useSuspenseQueryWithOptions.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-redeclare */
2 | /* eslint-disable func-style */
3 |
4 | import {
5 | useSuspenseQuery,
6 | UseSuspenseQueryOptions,
7 | UseSuspenseQueryResult,
8 | QueryFunction,
9 | } from '@tanstack/react-query';
10 | import { QueryKeyType } from 'api';
11 |
12 | interface UseSuspenseQueryWithOptionsParams<
13 | TQueryFnData,
14 | TError,
15 | TData,
16 | TQueryKey extends QueryKeyType,
17 | > {
18 | queryKey: TQueryKey;
19 | queryFn: QueryFunction;
20 | options?: Omit<
21 | UseSuspenseQueryOptions,
22 | 'queryFn' | 'queryKey'
23 | >;
24 | }
25 |
26 | export function useSuspenseQueryWithOptions<
27 | TQueryFnData,
28 | TError,
29 | TData = TQueryFnData,
30 | TQueryKey extends QueryKeyType = QueryKeyType,
31 | >(
32 | params: UseSuspenseQueryWithOptionsParams<
33 | TQueryFnData,
34 | TError,
35 | TData,
36 | TQueryKey
37 | >,
38 | ): UseSuspenseQueryResult;
39 |
40 | export function useSuspenseQueryWithOptions<
41 | TQueryFnData,
42 | TError,
43 | TData = TQueryFnData,
44 | TQueryKey extends QueryKeyType = QueryKeyType,
45 | >(
46 | params: UseSuspenseQueryWithOptionsParams<
47 | TQueryFnData,
48 | TError,
49 | TData,
50 | TQueryKey
51 | >,
52 | ): UseSuspenseQueryResult {
53 | const { queryKey, queryFn, options } = params;
54 |
55 | return useSuspenseQuery({
56 | queryKey,
57 | queryFn,
58 | ...options,
59 | });
60 | }
61 |
--------------------------------------------------------------------------------
/src/shared/ui/AnimatedBackDrop/AnimatedBackDrop.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import React from 'react';
3 | import { ViewProps } from 'react-native';
4 | import Animated, {
5 | AnimatedProps,
6 | useAnimatedProps,
7 | useAnimatedStyle,
8 | useDerivedValue,
9 | withTiming,
10 | } from 'react-native-reanimated';
11 | import { StyleSheet } from 'react-native-unistyles';
12 |
13 | interface AnimatedBackdropProps {
14 | onBackdropPress?: () => void;
15 | isVisible: boolean;
16 | }
17 |
18 | export const AnimatedBackdrop: React.FC = React.memo(
19 | ({ onBackdropPress, isVisible }) => {
20 | const visibility = useDerivedValue(() => {
21 | return isVisible ? 1 : 0;
22 | }, [isVisible]);
23 |
24 | const animatedStyle = useAnimatedStyle(() => {
25 | return {
26 | opacity: withTiming(visibility.value),
27 | };
28 | });
29 |
30 | const animatedProps = useAnimatedProps>(() => {
31 | return {
32 | pointerEvents: visibility.value > 0 ? 'auto' : 'none',
33 | };
34 | });
35 |
36 | return (
37 |
42 | );
43 | },
44 | );
45 |
46 | const styles = StyleSheet.create((theme, runtime) => ({
47 | container: {
48 | ...StyleSheet.absoluteFillObject,
49 | width: runtime.screen.width,
50 | height: runtime.screen.height,
51 | backgroundColor: theme.colors.black_30,
52 | zIndex: 99999,
53 | },
54 | }));
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # VSCode
6 | .vscode
7 |
8 | # Xcode
9 | #
10 | build/
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata
20 | *.xccheckout
21 | *.moved-aside
22 | DerivedData
23 | *.hmap
24 | *.ipa
25 | *.xcuserstate
26 | **/.xcode.env.local
27 |
28 | # react-native-config codegen
29 | ios/tmp.xcconfig
30 |
31 |
32 | # Android/IntelliJ
33 | #
34 | build/
35 | .idea
36 | .gradle
37 | local.properties
38 | *.iml
39 | *.hprof
40 | .cxx/
41 | *.keystore
42 | !debug.keystore
43 | .kotlin/
44 |
45 | # node.js
46 | #
47 | node_modules/
48 | npm-debug.log
49 | yarn-error.log
50 |
51 | # fastlane
52 | #
53 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
54 | # screenshots whenever they are needed.
55 | # For more information about the recommended setup visit:
56 | # https://docs.fastlane.tools/best-practices/source-control/
57 |
58 | **/fastlane/report.xml
59 | **/fastlane/Preview.html
60 | **/fastlane/screenshots
61 | **/fastlane/test_output
62 |
63 | # Bundle artifact
64 | *.jsbundle
65 |
66 | # Ruby / CocoaPods
67 | **/Pods/
68 | /vendor/bundle/
69 |
70 | # Temporary files created by Metro to check the health of the file watcher
71 | .metro-health-check*
72 |
73 | # testing
74 | /coverage
75 |
76 | # Yarn
77 | .yarn/*
78 | !.yarn/patches
79 | !.yarn/plugins
80 | !.yarn/releases
81 | !.yarn/sdks
82 | !.yarn/versions
83 |
84 | # Env
85 | .env.develop
86 | .env.release
87 | .env.stage
88 | .env
89 |
90 | builder-name-check
91 | check-generics
92 | check-unique-types
93 | merge-query-keys
94 | query-keys
95 | server-hooks
96 | update-index
97 |
--------------------------------------------------------------------------------
/src/shared/services/ToastService/ToastService.ts:
--------------------------------------------------------------------------------
1 | import { toast } from 'sonner-native';
2 | import { LightTheme } from 'themes';
3 | import { ViewStyle } from 'react-native';
4 |
5 | interface ToastParams {
6 | position?: 'top-center' | 'bottom-center';
7 | title: string;
8 | description?: string;
9 | }
10 |
11 | const style: ViewStyle = {
12 | shadowOffset: {
13 | height: 1,
14 | width: 2,
15 | },
16 | shadowOpacity: 0.2,
17 | elevation: 5,
18 | borderRadius: 22,
19 | shadowColor: LightTheme.colors.basic_100,
20 | };
21 |
22 | const duration = 3500;
23 |
24 | const onSuccess = ({ position, title, description }: ToastParams) => {
25 | toast.success(title, {
26 | position,
27 | description,
28 | style,
29 | styles: {
30 | title: {
31 | fontFamily: LightTheme.fonts.Regular,
32 | },
33 | },
34 | duration,
35 | closeButton: true,
36 | });
37 | };
38 |
39 | const onDanger = ({ position, title, description }: ToastParams) => {
40 | toast.error(title, {
41 | position,
42 | description,
43 | style,
44 | styles: {
45 | title: {
46 | fontFamily: LightTheme.fonts.Regular,
47 | },
48 | },
49 | duration,
50 | closeButton: true,
51 | });
52 | };
53 |
54 | const onWarning = ({ position, title, description }: ToastParams) => {
55 | toast.warning(title, {
56 | position,
57 | description,
58 | style,
59 | styles: {
60 | title: {
61 | fontFamily: LightTheme.fonts.Regular,
62 | },
63 | },
64 | duration,
65 | closeButton: true,
66 | });
67 | };
68 |
69 | const onHide = () => toast.dismiss();
70 |
71 | export const ToastService = {
72 | onSuccess,
73 | onDanger,
74 | onHide,
75 | onWarning,
76 | };
77 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ReactNativeTemplate
4 | //
5 | // Created by VLAD on 01.09.2025.
6 | //
7 |
8 | import Foundation
9 |
10 | import UIKit
11 | import React
12 | import React_RCTAppDelegate
13 | import ReactAppDependencyProvider
14 | import RNBootSplash
15 |
16 | @main
17 | class AppDelegate: UIResponder, UIApplicationDelegate {
18 | var window: UIWindow?
19 |
20 | var reactNativeDelegate: ReactNativeDelegate?
21 | var reactNativeFactory: RCTReactNativeFactory?
22 |
23 | func application(
24 | _ application: UIApplication,
25 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
26 | ) -> Bool {
27 | let delegate = ReactNativeDelegate()
28 | let factory = RCTReactNativeFactory(delegate: delegate)
29 | delegate.dependencyProvider = RCTAppDependencyProvider()
30 |
31 | reactNativeDelegate = delegate
32 | reactNativeFactory = factory
33 |
34 | window = UIWindow(frame: UIScreen.main.bounds)
35 |
36 | factory.startReactNative(
37 | withModuleName: "ReactNativeTemplate",
38 | in: window,
39 | launchOptions: launchOptions
40 | )
41 |
42 | return true
43 | }
44 | }
45 |
46 | class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
47 | override func sourceURL(for bridge: RCTBridge) -> URL? {
48 | self.bundleURL()
49 | }
50 |
51 | override func bundleURL() -> URL? {
52 | #if DEBUG
53 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
54 | #else
55 | Bundle.main.url(forResource: "main", withExtension: "jsbundle")
56 | #endif
57 | }
58 |
59 | override func customize(_ rootView: RCTRootView) {
60 | super.customize(rootView)
61 | RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/shared/translations/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "application": {
3 | "name": "React Native Template"
4 | },
5 | "empty-state": {
6 | "no-activities": "No activities found"
7 | },
8 | "errors": {
9 | "server-unable": "An error occurred. Please try again later"
10 | },
11 | "update-modal": {
12 | "title": "New App Version Available",
13 | "description": "New features and improvements are now available. Please update your application to the latest version."
14 | },
15 | "screens": {
16 | "alerts": "Alerts",
17 | "account": "Account",
18 | "forgot-password": "Forgot password",
19 | "sign-in": "Log in",
20 | "reset-password": "Change password",
21 | "create-account": "Registration",
22 | "home": "Home",
23 | "accounts": "Accounts",
24 | "activity": "Activity",
25 | "documents": "Documents",
26 | "more": "More"
27 | },
28 | "labels": {
29 | "show-more": "Show more",
30 | "show-less": "Show less"
31 | },
32 | "buttons": {
33 | "sign-in": "Sign in",
34 | "remember-me": "Remember me",
35 | "face-id": "Face ID",
36 | "see-all-activity": "See all activity",
37 | "see-all-holdings": "See all holdings"
38 | },
39 | "placeholders": {
40 | "enter-your-email": "Enter your email",
41 | "enter-your-password": "Enter your password"
42 | },
43 | "auth": {
44 | "manulife": "Manulife",
45 | "sign-in-with-your": "Sign in with your",
46 | "manulife-id": "Login",
47 | "forgot-your-username-and-password": "Forgot your username and password?",
48 | "dont-have-a-manulife-id": "Don't have a login ID?",
49 | "click-here": "Click here"
50 | },
51 | "validations": {
52 | "email-invalid": "Please enter a valid email address",
53 | "email-required": "Please provide email",
54 | "please-provide-password": "Please provide a valid password"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/shared/hooks/useAppStateEvent.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | import { AppState, AppStateStatus } from 'react-native';
3 |
4 | const AppStateStatuses: Record = {
5 | active: 'active',
6 | background: 'background',
7 | inactive: 'inactive',
8 | };
9 |
10 | interface AppStateProps {
11 | onChange?: (status: AppStateStatus) => void;
12 | onForeground?: () => void;
13 | onBackground?: () => void;
14 | }
15 |
16 | export const useAppStateEvent = (props?: AppStateProps) => {
17 | const appState = useRef(AppState.currentState);
18 |
19 | const { onChange, onForeground, onBackground } = props || {};
20 |
21 | const savedOnChange = useRef(onChange);
22 | const savedOnForeground = useRef(onForeground);
23 | const savedOnBackground = useRef(onBackground);
24 |
25 | useEffect(() => {
26 | savedOnChange.current = onChange;
27 | savedOnForeground.current = onForeground;
28 | savedOnBackground.current = onBackground;
29 | }, [onBackground, onChange, onForeground]);
30 |
31 | useEffect(() => {
32 | const handleAppStateChange = (nextAppState: AppStateStatus) => {
33 | const { active } = AppStateStatuses;
34 |
35 | if (nextAppState === active && savedOnForeground.current) {
36 | savedOnForeground.current();
37 | }
38 |
39 | if (
40 | appState.current === active &&
41 | savedOnBackground.current &&
42 | /inactive|background/.test(nextAppState)
43 | ) {
44 | savedOnBackground.current();
45 | }
46 |
47 | if (savedOnChange.current) {
48 | savedOnChange.current(nextAppState);
49 | }
50 |
51 | appState.current = nextAppState;
52 | };
53 |
54 | const subscription = AppState.addEventListener(
55 | 'change',
56 | handleAppStateChange,
57 | );
58 |
59 | return () => subscription?.remove();
60 | }, []);
61 | };
62 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 |
25 | # Use this property to specify which architecture you want to build.
26 | # You can also override it from the CLI using
27 | # ./gradlew -PreactNativeArchitectures=x86_64
28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
29 |
30 | # Use this property to enable support to the new architecture.
31 | # This will allow you to use TurboModules and the Fabric render in
32 | # your application. You should enable this flag either if you want
33 | # to write custom TurboModules/Fabric components OR use libraries that
34 | # are providing them.
35 | newArchEnabled=true
36 |
37 | # Use this property to enable or disable the Hermes JS engine.
38 | # If set to false, you will be using JSC instead.
39 | hermesEnabled=true
40 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reactnativetemplate/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativetemplate
2 |
3 | import android.app.Application
4 | import com.facebook.react.PackageList
5 | import com.facebook.react.ReactApplication
6 | import com.facebook.react.ReactHost
7 | import com.facebook.react.ReactNativeHost
8 | import com.facebook.react.ReactPackage
9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
11 | import com.facebook.react.defaults.DefaultReactNativeHost
12 | import com.facebook.react.soloader.OpenSourceMergedSoMapping
13 | import com.facebook.soloader.SoLoader
14 |
15 | class MainApplication : Application(), ReactApplication {
16 |
17 | override val reactNativeHost: ReactNativeHost =
18 | object : DefaultReactNativeHost(this) {
19 | override fun getPackages(): List =
20 | PackageList(this).packages.apply {
21 | // Packages that cannot be autolinked yet can be added manually here, for example:
22 | // add(MyReactNativePackage())
23 | }
24 |
25 | override fun getJSMainModuleName(): String = "index"
26 |
27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
28 |
29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
31 | }
32 |
33 | override val reactHost: ReactHost
34 | get() = getDefaultReactHost(applicationContext, reactNativeHost)
35 |
36 | override fun onCreate() {
37 | super.onCreate()
38 | SoLoader.init(this, OpenSourceMergedSoMapping)
39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
40 | // If you opted-in for the New Architecture, we load the native entry point for this app.
41 | load()
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/shared/api/http-client.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosError, AxiosInstance } from 'axios';
2 | import { HTTPClient, Params } from './types';
3 |
4 | export const createAxiosClient = ({
5 | baseURL,
6 | getToken,
7 | onUnauthorized,
8 | onError,
9 | }: {
10 | baseURL: string;
11 | getToken?: () => string | null;
12 | onUnauthorized?: (error: AxiosError) => void;
13 | onError?: (error: AxiosError) => void;
14 | }): HTTPClient => {
15 | const instance: AxiosInstance = axios.create({ baseURL });
16 |
17 | instance.interceptors.request.use(config => {
18 | const token = getToken?.();
19 |
20 | if (token) {
21 | config.headers.Authorization = `Bearer ${token}`;
22 | }
23 |
24 | return config;
25 | });
26 |
27 | instance.interceptors.response.use(
28 | response => response,
29 | (error: AxiosError) => {
30 | const globalToast =
31 | error.config?.headers?.['x-disable-global-toast'] === 'true';
32 |
33 | const isBaseError = error.response?.status !== 401 && globalToast;
34 |
35 | if (error.response?.status === 401) {
36 | onUnauthorized?.(error);
37 | }
38 |
39 | if (isBaseError) {
40 | onError?.(error);
41 | }
42 |
43 | return Promise.reject(error);
44 | },
45 | );
46 |
47 | return {
48 | async request({
49 | method,
50 | url,
51 | data,
52 | headers,
53 | params,
54 | signal,
55 | }: {
56 | method: 'get' | 'post' | 'put' | 'patch' | 'delete';
57 | url: string;
58 | data?: TBody;
59 | headers?: Record;
60 | params?: Params;
61 | signal?: AbortSignal;
62 | }): Promise<{ data: TResponse }> {
63 | const response = await instance.request({
64 | method,
65 | url,
66 | data,
67 | headers,
68 | params,
69 | signal,
70 | });
71 |
72 | return { data: response.data };
73 | },
74 | };
75 | };
76 |
--------------------------------------------------------------------------------
/scripts/api-codegen/update-index.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | const std::filesystem::path API_DIR =
8 | std::filesystem::current_path() / "src/shared/api";
9 | const std::filesystem::path INDEX_FILE = API_DIR / "index.ts";
10 | const std::unordered_set EXCLUDED_FOLDERS = {"models", "hooks"};
11 |
12 | void updateAPIIndexFile()
13 | {
14 | std::cout << "\n🔍 Updating API index file..." << std::endl;
15 | std::cout << "📂 Target file: " << INDEX_FILE << "\n"
16 | << std::endl;
17 |
18 | std::ofstream tempFile("temp_index.ts");
19 | if (!tempFile.is_open())
20 | {
21 | std::cerr << "❌ Error: Unable to create temporary file.\n";
22 | return;
23 | }
24 |
25 | tempFile << "// This file is auto-generated. Do not modify manually.\n\n";
26 |
27 | std::cout << "⏳ Scanning API subdirectories...\n"
28 | << std::endl;
29 |
30 | for (const auto &entry : std::filesystem::directory_iterator(API_DIR))
31 | {
32 | if (entry.is_directory())
33 | {
34 | std::string moduleName = entry.path().filename().string();
35 | if (EXCLUDED_FOLDERS.find(moduleName) != EXCLUDED_FOLDERS.end())
36 | {
37 | continue;
38 | }
39 |
40 | tempFile << "export * from './" << moduleName << "';\n";
41 | std::cout << "✅ Added module: " << moduleName << std::endl;
42 | }
43 | }
44 |
45 | tempFile << "\nexport * from './queryClient';\n";
46 | tempFile << "export * from './models';\n";
47 | tempFile << "export { useMutationEvents, useQueryEvents } from './hooks';\n";
48 |
49 | tempFile.close();
50 | std::filesystem::rename("temp_index.ts", INDEX_FILE);
51 |
52 | std::cout << "\n✅ " << INDEX_FILE << " has been updated successfully!\n"
53 | << std::endl;
54 |
55 | std::cout << "🎉 API index file update completed!\n"
56 | << std::endl;
57 | }
58 |
59 | int main()
60 | {
61 | updateAPIIndexFile();
62 | return 0;
63 | }
64 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/usePrefetchQueryWithOptions.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-redeclare */
2 | /* eslint-disable func-style */
3 |
4 | import { usePrefetchQuery } from '@tanstack/react-query';
5 | import type {
6 | DefinedInitialDataOptions,
7 | UndefinedInitialDataOptions,
8 | } from '@tanstack/react-query';
9 | import { QueryKeyType, UsePrefetchQueryWithOptionsParams } from '../models';
10 |
11 | export function usePrefetchQueryWithOptions<
12 | TQueryFnData,
13 | TError,
14 | TData = TQueryFnData,
15 | TQueryKey extends QueryKeyType = QueryKeyType,
16 | >(
17 | params: UsePrefetchQueryWithOptionsParams<
18 | TQueryFnData,
19 | TError,
20 | TData,
21 | TQueryKey
22 | > & {
23 | options: Omit<
24 | DefinedInitialDataOptions,
25 | 'queryFn' | 'queryKey'
26 | >;
27 | },
28 | ): void;
29 |
30 | export function usePrefetchQueryWithOptions<
31 | TQueryFnData,
32 | TError,
33 | TData = TQueryFnData,
34 | TQueryKey extends QueryKeyType = QueryKeyType,
35 | >(
36 | params: UsePrefetchQueryWithOptionsParams<
37 | TQueryFnData,
38 | TError,
39 | TData,
40 | TQueryKey
41 | > & {
42 | options: Omit<
43 | UndefinedInitialDataOptions,
44 | 'queryFn' | 'queryKey'
45 | >;
46 | },
47 | ): void;
48 |
49 | export function usePrefetchQueryWithOptions<
50 | TQueryFnData,
51 | TError,
52 | TData = TQueryFnData,
53 | TQueryKey extends QueryKeyType = QueryKeyType,
54 | >(
55 | params: UsePrefetchQueryWithOptionsParams<
56 | TQueryFnData,
57 | TError,
58 | TData,
59 | TQueryKey
60 | >,
61 | ): void;
62 |
63 | export function usePrefetchQueryWithOptions<
64 | TQueryFnData,
65 | TError,
66 | TData = TQueryFnData,
67 | TQueryKey extends QueryKeyType = QueryKeyType,
68 | >(
69 | params: UsePrefetchQueryWithOptionsParams<
70 | TQueryFnData,
71 | TError,
72 | TData,
73 | TQueryKey
74 | >,
75 | ): void {
76 | const { queryKey, queryFn, options } = params;
77 |
78 | usePrefetchQuery({
79 | queryKey,
80 | queryFn,
81 | ...options,
82 | });
83 | }
84 |
--------------------------------------------------------------------------------
/ios/ReactNativeTemplate/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleDisplayName
10 | ReactNativeTemplate
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(MARKETING_VERSION)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(CURRENT_PROJECT_VERSION)
27 | ITSAppUsesNonExemptEncryption
28 |
29 | LSRequiresIPhoneOS
30 |
31 | NSAppTransportSecurity
32 |
33 | NSAllowsArbitraryLoads
34 |
35 | NSAllowsLocalNetworking
36 |
37 |
38 | NSLocationWhenInUseUsageDescription
39 |
40 | UIAppFonts
41 |
42 | DMSans-Bold.ttf
43 | DMSans-Medium.ttf
44 | DMSans-Regular.ttf
45 | icomoon.ttf
46 |
47 | UILaunchStoryboardName
48 | BootSplash
49 | UIRequiredDeviceCapabilities
50 |
51 | arm64
52 |
53 | UIStatusBarStyle
54 | UIStatusBarStyleLightContent
55 | UISupportedInterfaceOrientations
56 |
57 | UIInterfaceOrientationPortrait
58 |
59 | UISupportedInterfaceOrientations~ipad
60 |
61 | UIInterfaceOrientationLandscapeLeft
62 | UIInterfaceOrientationLandscapeRight
63 | UIInterfaceOrientationPortrait
64 |
65 | UIViewControllerBasedStatusBarAppearance
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./src/",
4 | "paths": {
5 | "assets": ["assets/index"],
6 | "assets/*": ["assets/*"],
7 |
8 | "navigation": ["navigation/index"],
9 | "navigation/*": ["navigation/*"],
10 |
11 | "api": ["shared/api/index"],
12 | "api/*": ["shared/api/*"],
13 |
14 | "hooks": ["shared/hooks/index"],
15 | "hooks/*": ["shared/hooks/*"],
16 |
17 | "lib": ["shared/lib/index"],
18 | "lib/*": ["shared/lib/*"],
19 |
20 | "providers": ["shared/providers/index"],
21 | "providers/*": ["shared/providers/*"],
22 |
23 | "services": ["shared/services/index"],
24 | "services/*": ["shared/services/*"],
25 |
26 | "stores": ["shared/stores/index"],
27 | "stores/*": ["shared/stores/*"],
28 |
29 | "themes": ["shared/themes/index"],
30 | "themes/*": ["shared/themes/*"],
31 |
32 | "translations": ["shared/translations/index"],
33 | "translations/*": ["shared/translations/*"],
34 |
35 | "types": ["shared/types/index"],
36 | "types/*": ["shared/types/*"],
37 |
38 | "ui": ["shared/ui/index"],
39 | "ui/*": ["shared/ui/*"],
40 |
41 | "screens": ["screens/index"],
42 | "screens/*": ["screens/*"],
43 |
44 | "widgets": ["widgets/index"],
45 | "widgets/*": ["widgets/*"],
46 |
47 | "shared": ["shared/index"],
48 | "shared/*": ["shared/*"]
49 | },
50 | "target": "esnext",
51 | "module": "ES2022",
52 | "types": ["react-native"],
53 | "lib": [
54 | "es2019",
55 | "es2020.bigint",
56 | "es2020.date",
57 | "es2020.number",
58 | "es2020.promise",
59 | "es2020.string",
60 | "es2020.symbol.wellknown",
61 | "es2021.promise",
62 | "es2021.string",
63 | "es2021.weakref",
64 | "es2022.array",
65 | "es2022.object",
66 | "es2022.string"
67 | ],
68 | "allowJs": true,
69 | "jsx": "react-native",
70 | "noEmit": true,
71 | "isolatedModules": true,
72 | "strict": true,
73 | "moduleResolution": "node",
74 | "resolveJsonModule": true,
75 | "allowSyntheticDefaultImports": true,
76 | "forceConsistentCasingInFileNames": false,
77 | "esModuleInterop": true,
78 | "skipLibCheck": true
79 | },
80 | "exclude": [
81 | "node_modules",
82 | "babel.config.js",
83 | "metro.config.js",
84 | "jest.config.js"
85 | ]
86 | }
87 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/useSuspenseInfiniteQueryWithOptions.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-redeclare */
2 | /* eslint-disable func-style */
3 |
4 | import {
5 | useSuspenseInfiniteQuery,
6 | UseSuspenseInfiniteQueryOptions,
7 | UseSuspenseInfiniteQueryResult,
8 | InfiniteData,
9 | } from '@tanstack/react-query';
10 | import { QueryKeyType } from 'api';
11 |
12 | interface UseSuspenseInfiniteQueryWithOptionsParams<
13 | TQueryFnData,
14 | TError,
15 | TData,
16 | TQueryKey extends QueryKeyType,
17 | TPageParam,
18 | > {
19 | queryKey: TQueryKey;
20 | queryFn: UseSuspenseInfiniteQueryOptions<
21 | TQueryFnData,
22 | TError,
23 | TData,
24 | TQueryKey,
25 | TPageParam
26 | >['queryFn'];
27 | initialPageParam: TPageParam;
28 | getNextPageParam: UseSuspenseInfiniteQueryOptions<
29 | TQueryFnData,
30 | TError,
31 | TData,
32 | TQueryKey,
33 | TPageParam
34 | >['getNextPageParam'];
35 | options?: Omit<
36 | UseSuspenseInfiniteQueryOptions<
37 | TQueryFnData,
38 | TError,
39 | TData,
40 | TQueryKey,
41 | TPageParam
42 | >,
43 | 'queryFn' | 'queryKey' | 'initialPageParam' | 'getNextPageParam'
44 | >;
45 | }
46 |
47 | export function useSuspenseInfiniteQueryWithOptions<
48 | TQueryFnData,
49 | TError,
50 | TData = InfiniteData,
51 | TQueryKey extends QueryKeyType = QueryKeyType,
52 | TPageParam = unknown,
53 | >(
54 | params: UseSuspenseInfiniteQueryWithOptionsParams<
55 | TQueryFnData,
56 | TError,
57 | TData,
58 | TQueryKey,
59 | TPageParam
60 | >,
61 | ): UseSuspenseInfiniteQueryResult;
62 |
63 | export function useSuspenseInfiniteQueryWithOptions<
64 | TQueryFnData,
65 | TError,
66 | TData = InfiniteData,
67 | TQueryKey extends QueryKeyType = QueryKeyType,
68 | TPageParam = unknown,
69 | >(
70 | params: UseSuspenseInfiniteQueryWithOptionsParams<
71 | TQueryFnData,
72 | TError,
73 | TData,
74 | TQueryKey,
75 | TPageParam
76 | >,
77 | ): UseSuspenseInfiniteQueryResult {
78 | const { queryKey, queryFn, initialPageParam, getNextPageParam, options } =
79 | params;
80 |
81 | return useSuspenseInfiniteQuery<
82 | TQueryFnData,
83 | TError,
84 | TData,
85 | TQueryKey,
86 | TPageParam
87 | >({
88 | queryKey,
89 | queryFn,
90 | initialPageParam,
91 | getNextPageParam,
92 | ...options,
93 | });
94 | }
95 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/useQueryWithOptions.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-redeclare */
2 | /* eslint-disable func-style */
3 |
4 | import {
5 | useQuery,
6 | UseQueryOptions,
7 | QueryFunction,
8 | } from '@tanstack/react-query';
9 | import { QueryKeyType } from 'api';
10 | import type {
11 | DefinedUseQueryResult,
12 | UseQueryResult,
13 | DefinedInitialDataOptions,
14 | UndefinedInitialDataOptions,
15 | } from '@tanstack/react-query';
16 |
17 | interface UseQueryWithOptionsParams<
18 | TQueryFnData,
19 | TError,
20 | TData,
21 | TQueryKey extends QueryKeyType,
22 | > {
23 | queryKey: TQueryKey;
24 | queryFn: QueryFunction;
25 | options?: Omit<
26 | UseQueryOptions,
27 | 'queryFn' | 'queryKey'
28 | >;
29 | }
30 |
31 | export function useQueryWithOptions<
32 | TQueryFnData,
33 | TError,
34 | TData = TQueryFnData,
35 | TQueryKey extends QueryKeyType = QueryKeyType,
36 | >(
37 | params: UseQueryWithOptionsParams & {
38 | options: Omit<
39 | DefinedInitialDataOptions,
40 | 'queryFn' | 'queryKey'
41 | >;
42 | },
43 | ): DefinedUseQueryResult;
44 |
45 | export function useQueryWithOptions<
46 | TQueryFnData,
47 | TError,
48 | TData = TQueryFnData,
49 | TQueryKey extends QueryKeyType = QueryKeyType,
50 | >(
51 | params: UseQueryWithOptionsParams & {
52 | options: Omit<
53 | UndefinedInitialDataOptions,
54 | 'queryFn' | 'queryKey'
55 | >;
56 | },
57 | ): UseQueryResult;
58 |
59 | export function useQueryWithOptions<
60 | TQueryFnData,
61 | TError,
62 | TData = TQueryFnData,
63 | TQueryKey extends QueryKeyType = QueryKeyType,
64 | >(
65 | params: UseQueryWithOptionsParams,
66 | ): UseQueryResult;
67 |
68 | export function useQueryWithOptions<
69 | TQueryFnData,
70 | TError,
71 | TData = TQueryFnData,
72 | TQueryKey extends QueryKeyType = QueryKeyType,
73 | >(
74 | params: UseQueryWithOptionsParams,
75 | ): UseQueryResult {
76 | const { queryKey, queryFn, options } = params;
77 |
78 | return useQuery({
79 | queryKey,
80 | queryFn,
81 | ...options,
82 | });
83 | }
84 |
--------------------------------------------------------------------------------
/src/shared/lib/Dates.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs';
2 | import utc from 'dayjs/plugin/utc';
3 | import timezone from 'dayjs/plugin/timezone';
4 | import customParseFormat from 'dayjs/plugin/customParseFormat';
5 |
6 | dayjs.extend(utc);
7 | dayjs.extend(timezone);
8 | dayjs.extend(customParseFormat);
9 |
10 | export const FULL_DATE_FORMAT = 'dddd, MMMM DD, YYYY';
11 | export const TIME_FORMAT = 'h:mm A';
12 | export const PICKER_FORMAT = 'YYYY-MM-DD[T]HH:mm:ss';
13 |
14 | export const SHORT_DATE = 'YYYY-MM-DD';
15 |
16 | export const DATE_FORMAT = 'MM/DD/YY';
17 |
18 | export const BASIC_FORMAT = 'YYYY-MM-DD h:mm A';
19 |
20 | export const DATE_TIME_FORMAT = 'MM/DD/YY [at] H:mm A';
21 | export const DATE_TIME_WEEKDAY_FORMAT = 'dddd [at] H:mm A';
22 |
23 | export const timezoneIdentifier = dayjs.tz.guess();
24 |
25 | export const getFullDateFormat = (date?: string) =>
26 | dayjs(date).format(FULL_DATE_FORMAT);
27 |
28 | export const getTimeFormat = (date: string) =>
29 | dayjs(date).tz(timezoneIdentifier).format(TIME_FORMAT);
30 |
31 | export const getDateFormat = (date: string) => {
32 | return dayjs(date).tz(timezoneIdentifier).format(DATE_FORMAT);
33 | };
34 |
35 | export const isAfterCurrentTime = (date: string) =>
36 | dayjs(date)
37 | .tz(timezoneIdentifier)
38 | .isAfter(dayjs().tz(timezoneIdentifier), 'minute');
39 |
40 | export const getStringWithTimezone = (date?: string) =>
41 | dayjs(date).format(PICKER_FORMAT);
42 |
43 | export const getShortDate = () => dayjs().format(SHORT_DATE);
44 |
45 | export const getBasicDate = (date?: string) => dayjs(date).format(BASIC_FORMAT);
46 |
47 | export const getDateTimeFormat = (
48 | date: string,
49 | timeZone = timezoneIdentifier,
50 | ) => {
51 | const now = dayjs().tz(timeZone);
52 |
53 | if (now.isSame(date, 'day')) {
54 | return `Today at ${dayjs(date).tz(timeZone).format(TIME_FORMAT)}`;
55 | }
56 |
57 | const formatString = now.isSame(date, 'week')
58 | ? DATE_TIME_WEEKDAY_FORMAT
59 | : DATE_TIME_FORMAT;
60 |
61 | return dayjs(date).tz(timeZone).format(formatString);
62 | };
63 |
64 | export const addDay = (date: string) =>
65 | dayjs(date).add(1, 'day').format(SHORT_DATE);
66 |
67 | export const reduceDay = (date: string) =>
68 | dayjs(date).subtract(1, 'day').format(SHORT_DATE);
69 |
70 | export const isCurrentMonth = (date: string) =>
71 | dayjs(date).isSame(dayjs(), 'month');
72 |
73 | export const isToday = (date: string) => dayjs(date).isSame(dayjs(), 'day');
74 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/usePrefetchInfiniteQueryWithOptions.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-redeclare */
2 | /* eslint-disable func-style */
3 |
4 | import {
5 | usePrefetchInfiniteQuery,
6 | FetchInfiniteQueryOptions,
7 | GetNextPageParamFunction,
8 | } from '@tanstack/react-query';
9 | import { QueryKeyType } from 'api';
10 |
11 | interface UsePrefetchInfiniteQueryWithOptionsParams<
12 | TQueryFnData,
13 | TError,
14 | TData,
15 | TQueryKey extends QueryKeyType,
16 | TPageParam,
17 | > {
18 | queryKey: TQueryKey;
19 | queryFn: FetchInfiniteQueryOptions<
20 | TQueryFnData,
21 | TError,
22 | TData,
23 | TQueryKey,
24 | TPageParam
25 | >['queryFn'];
26 | initialPageParam: TPageParam;
27 | getNextPageParam: GetNextPageParamFunction;
28 | options?: Omit<
29 | FetchInfiniteQueryOptions<
30 | TQueryFnData,
31 | TError,
32 | TData,
33 | TQueryKey,
34 | TPageParam
35 | >,
36 | 'queryFn' | 'queryKey' | 'initialPageParam' | 'getNextPageParam'
37 | >;
38 | }
39 |
40 | export function usePrefetchInfiniteQueryWithOptions<
41 | TQueryFnData,
42 | TError,
43 | TData = TQueryFnData,
44 | TQueryKey extends QueryKeyType = QueryKeyType,
45 | TPageParam = unknown,
46 | >(
47 | params: UsePrefetchInfiniteQueryWithOptionsParams<
48 | TQueryFnData,
49 | TError,
50 | TData,
51 | TQueryKey,
52 | TPageParam
53 | >,
54 | ): void;
55 |
56 | export function usePrefetchInfiniteQueryWithOptions<
57 | TQueryFnData,
58 | TError,
59 | TData = TQueryFnData,
60 | TQueryKey extends QueryKeyType = QueryKeyType,
61 | TPageParam = unknown,
62 | >(
63 | params: UsePrefetchInfiniteQueryWithOptionsParams<
64 | TQueryFnData,
65 | TError,
66 | TData,
67 | TQueryKey,
68 | TPageParam
69 | >,
70 | ): void;
71 |
72 | export function usePrefetchInfiniteQueryWithOptions<
73 | TQueryFnData,
74 | TError,
75 | TData = TQueryFnData,
76 | TQueryKey extends QueryKeyType = QueryKeyType,
77 | TPageParam = unknown,
78 | >(
79 | params: UsePrefetchInfiniteQueryWithOptionsParams<
80 | TQueryFnData,
81 | TError,
82 | TData,
83 | TQueryKey,
84 | TPageParam
85 | >,
86 | ): void {
87 | const { queryKey, queryFn, initialPageParam, getNextPageParam, ...options } =
88 | params;
89 |
90 | usePrefetchInfiniteQuery({
91 | queryKey,
92 | queryFn,
93 | initialPageParam,
94 | getNextPageParam,
95 | ...options,
96 | });
97 | }
98 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet as RNStylesheet, View } from 'react-native';
3 | import { GestureHandlerRootView } from 'react-native-gesture-handler';
4 | import { SafeAreaProvider } from 'react-native-safe-area-context';
5 | import { KeyboardProvider } from 'react-native-keyboard-controller';
6 | import { StyleSheet } from 'react-native-unistyles';
7 | import {
8 | PortalProvider as TeleportProvider,
9 | PortalHost,
10 | } from 'react-native-teleport';
11 | import { QueryClientProvider } from '@tanstack/react-query';
12 | import { Toaster } from 'sonner-native';
13 | import { ReducedMotionConfig, ReduceMotion } from 'react-native-reanimated';
14 | import { ModalProvider } from 'react-native-modalfy';
15 | import { queryClient } from 'api';
16 | import { breakpoints, DarkTheme, LightTheme } from 'themes';
17 | import { RootNavigator } from 'navigation';
18 | import { EventEmitterProvider, LanguageProvider } from 'providers';
19 | import { themeStore$ } from 'stores';
20 | import { useDebug } from 'hooks';
21 | import { modalStack } from './src/modules';
22 |
23 | StyleSheet.configure({
24 | themes: {
25 | light: LightTheme,
26 | dark: DarkTheme,
27 | },
28 | breakpoints,
29 | settings: {
30 | initialTheme: () => {
31 | return themeStore$.currentTheme.get();
32 | },
33 | },
34 | });
35 |
36 | export const App: React.FC = () => {
37 | useDebug();
38 |
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | const styles = RNStylesheet.create({
66 | layout: {
67 | flex: 1,
68 | },
69 | overlay: {
70 | ...RNStylesheet.absoluteFillObject,
71 | zIndex: 9999,
72 | },
73 | });
74 |
--------------------------------------------------------------------------------
/src/shared/api/auth/queries/useTestPrefetch.auth.ts:
--------------------------------------------------------------------------------
1 | import { InvalidateQueryFilters } from '@tanstack/react-query';
2 | import { getQueryClient } from '../../queryClient';
3 | import {
4 | QueryError,
5 | QueryKeyType,
6 | UsePrefetchQueryWithOptionsParams,
7 | QueryFetchParams,
8 | queryKeys,
9 | } from '../../models';
10 | import { usePrefetchQueryWithOptions } from '../../hooks';
11 | import { AuthService } from '../AuthService';
12 |
13 | import { CreateAccountResponse, Test } from '../models';
14 |
15 | interface HookParams extends Test {
16 | options?: UsePrefetchQueryWithOptionsParams<
17 | CreateAccountResponse,
18 | QueryError,
19 | TData,
20 | QueryKeyType
21 | >['options'];
22 | }
23 |
24 | interface QueryFnParams {
25 | params: Test;
26 | meta?: Record | undefined;
27 | queryKey?: QueryKeyType;
28 | signal?: AbortSignal;
29 | }
30 |
31 | const testPrefetchQueryFnAuthService = async ({
32 | params,
33 | signal,
34 | }: QueryFnParams) => {
35 | const response = await AuthService.testPrefetch(params, { signal });
36 |
37 | return response;
38 | };
39 |
40 | const getQueryKey = (params: Test) => queryKeys.testPrefetchAuthService(params);
41 |
42 | export const testPrefetchPrefetchQueryAuthService = <
43 | TData = CreateAccountResponse,
44 | TError = QueryError,
45 | >({
46 | params,
47 | fetchOptions,
48 | }: QueryFetchParams) => {
49 | const queryClient = getQueryClient();
50 |
51 | return queryClient.prefetchQuery<
52 | CreateAccountResponse,
53 | TError,
54 | TData,
55 | QueryKeyType
56 | >({
57 | queryKey: getQueryKey(params),
58 | queryFn: ({ signal }) => testPrefetchQueryFnAuthService({ params, signal }),
59 | ...fetchOptions,
60 | });
61 | };
62 |
63 | export const useTestPrefetchPrefetchQueryAuthService = <
64 | TData = CreateAccountResponse,
65 | >({
66 | options,
67 | ...params
68 | }: HookParams) => {
69 | return usePrefetchQueryWithOptions<
70 | CreateAccountResponse,
71 | QueryError,
72 | TData,
73 | QueryKeyType
74 | >({
75 | queryFn: ({ signal }) => testPrefetchQueryFnAuthService({ params, signal }),
76 | queryKey: getQueryKey(params),
77 | options,
78 | });
79 | };
80 |
81 | export const invalidateTestPrefetchQueryAuthService = (
82 | params: Test,
83 | options?: Omit,
84 | ) => {
85 | const queryClient = getQueryClient();
86 |
87 | return queryClient.invalidateQueries({
88 | queryKey: getQueryKey(params),
89 | ...options,
90 | });
91 | };
92 |
--------------------------------------------------------------------------------
/src/shared/ui/AnimatedActivityIndicator/AnimatedActivityIndicator.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/style-prop-object */
2 | import {
3 | BlurMask,
4 | Canvas,
5 | Path,
6 | Skia,
7 | SweepGradient,
8 | } from '@shopify/react-native-skia';
9 | import React, { useEffect, useMemo } from 'react';
10 | import Animated, {
11 | Easing,
12 | interpolate,
13 | useAnimatedStyle,
14 | useDerivedValue,
15 | useSharedValue,
16 | withRepeat,
17 | withTiming,
18 | } from 'react-native-reanimated';
19 | import { StyleSheet, useUnistyles } from 'react-native-unistyles';
20 |
21 | const CANVAS_SIZE = 120;
22 |
23 | const CIRCLE_SIZE = 64;
24 |
25 | const STROKE_WIDTH = 10;
26 |
27 | const CIRCLE_RADIUS = (CIRCLE_SIZE - STROKE_WIDTH) / 2;
28 |
29 | export const AnimatedActivityIndicator = () => {
30 | const progress = useSharedValue(0);
31 |
32 | const { theme } = useUnistyles();
33 |
34 | useEffect(() => {
35 | progress.value = withRepeat(
36 | withTiming(1, {
37 | duration: 1000,
38 | easing: Easing.linear,
39 | }),
40 | -1,
41 | false,
42 | );
43 | }, [progress]);
44 |
45 | const circlePath = useMemo(() => {
46 | const skiaPath = Skia.Path.Make();
47 |
48 | skiaPath.addCircle(CANVAS_SIZE / 2, CANVAS_SIZE / 2, CIRCLE_RADIUS);
49 |
50 | return skiaPath;
51 | }, []);
52 |
53 | const animatedStyle = useAnimatedStyle(() => {
54 | return {
55 | transform: [{ rotate: `${progress.value * 2 * Math.PI}rad` }],
56 | };
57 | }, []);
58 |
59 | const startAnimated = useDerivedValue(() => {
60 | return interpolate(progress.value, [0, 0.5, 1], [0.3, 0.6, 0.3]);
61 | }, []);
62 |
63 | return (
64 |
65 |
86 |
87 | );
88 | };
89 |
90 | const styles = StyleSheet.create(() => ({
91 | canvas: {
92 | width: CANVAS_SIZE,
93 | height: CANVAS_SIZE,
94 | },
95 | container: {
96 | zIndex: 999,
97 | },
98 | }));
99 |
--------------------------------------------------------------------------------
/src/shared/api/auth/queries/useTestSuspenseQuery.auth.ts:
--------------------------------------------------------------------------------
1 | import { InvalidateQueryFilters } from '@tanstack/react-query';
2 | import { getQueryClient } from '../../queryClient';
3 | import {
4 | QueryError,
5 | QueryKeyType,
6 | UseSuspenseQueryWithOptionsParams,
7 | QueryFetchParams,
8 | queryKeys,
9 | } from '../../models';
10 | import { useSuspenseQueryWithOptions } from '../../hooks';
11 | import { AuthService } from '../AuthService';
12 |
13 | import { CreateAccountResponse, Test } from '../models';
14 |
15 | interface HookParams extends Test {
16 | options?: UseSuspenseQueryWithOptionsParams<
17 | CreateAccountResponse,
18 | QueryError,
19 | TData,
20 | QueryKeyType
21 | >['options'];
22 | }
23 |
24 | interface QueryFnParams {
25 | params: Test;
26 | meta?: Record | undefined;
27 | queryKey?: QueryKeyType;
28 | signal?: AbortSignal;
29 | }
30 |
31 | const testSuspenseQueryQueryFnAuthService = async ({
32 | params,
33 | signal,
34 | }: QueryFnParams) => {
35 | const response = await AuthService.testSuspenseQuery(params, { signal });
36 |
37 | return response;
38 | };
39 |
40 | const getQueryKey = (params: Test) =>
41 | queryKeys.testSuspenseQueryAuthService(params);
42 |
43 | export const testSuspenseQuerySuspenseQueryAuthService = <
44 | TData = CreateAccountResponse,
45 | TError = QueryError,
46 | >({
47 | params,
48 | fetchOptions,
49 | }: QueryFetchParams) => {
50 | const queryClient = getQueryClient();
51 |
52 | return queryClient.fetchQuery<
53 | CreateAccountResponse,
54 | TError,
55 | TData,
56 | QueryKeyType
57 | >({
58 | queryKey: getQueryKey(params),
59 | queryFn: ({ signal }) =>
60 | testSuspenseQueryQueryFnAuthService({ params, signal }),
61 | ...fetchOptions,
62 | });
63 | };
64 |
65 | export const useTestSuspenseQuerySuspenseQueryAuthService = <
66 | TData = CreateAccountResponse,
67 | >({
68 | options,
69 | ...params
70 | }: HookParams) => {
71 | return useSuspenseQueryWithOptions<
72 | CreateAccountResponse,
73 | QueryError,
74 | TData,
75 | QueryKeyType
76 | >({
77 | queryFn: ({ signal }) =>
78 | testSuspenseQueryQueryFnAuthService({ params, signal }),
79 | queryKey: getQueryKey(params),
80 | options,
81 | });
82 | };
83 |
84 | export const invalidateTestSuspenseQuerySuspenseQueryAuthService = (
85 | params: Test,
86 | options?: Omit,
87 | ) => {
88 | const queryClient = getQueryClient();
89 |
90 | return queryClient.invalidateQueries({
91 | queryKey: getQueryKey(params),
92 | ...options,
93 | });
94 | };
95 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-ios-stage.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Staging (iOS)
2 |
3 | on:
4 | push:
5 | branches:
6 | - stage
7 |
8 | jobs:
9 | build-develop-ios:
10 | name: Build & Upload iOS App (Staging)
11 | runs-on: macos-14
12 |
13 | steps:
14 | - name: Select Xcode 16.2
15 | uses: maxim-lobanov/setup-xcode@v1
16 | with:
17 | xcode-version: "16.2"
18 |
19 | - name: Checkout repository
20 | uses: actions/checkout@v3
21 |
22 | - name: Set up Node
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version-file: ".nvmrc"
26 |
27 | - name: Enable Corepack and Set Yarn Version
28 | run: |
29 | corepack enable
30 | corepack prepare yarn@latest --activate
31 | yarn set version 4.10.3
32 | yarn --version
33 |
34 | - name: Install node modules
35 | run: yarn install
36 | env:
37 | CI: true
38 |
39 | - name: Decode base64 .env file from secrets
40 | uses: akiojin/decode-base64-github-action@v0.1.0
41 | id: decode-base64
42 | with:
43 | base64: ${{ secrets.STAGE_APPLICATION_ENV }}
44 | output-path: ${{ runner.temp }}/.env
45 |
46 | - name: Install CocoaPods dependencies
47 | run: |
48 | cd ios
49 | pod install --repo-update
50 | cd ..
51 |
52 | - name: Set up Ruby
53 | uses: ruby/setup-ruby@v1
54 | with:
55 | ruby-version: 3.1.2
56 | bundler: 2.3.7
57 | bundler-cache: true
58 |
59 | - name: Install Fastlane dependencies
60 | run: |
61 | cd ios
62 | bundle install
63 | env:
64 | CI: true
65 |
66 | - name: Install Fastlane plugins
67 | run: |
68 | cd ios
69 | bundle exec fastlane install_plugins
70 | env:
71 | CI: true
72 |
73 | - name: Build and Upload to App Store (TestFlight)
74 | uses: maierj/fastlane-action@v3.0.0
75 | with:
76 | lane: "deploy_to_testflight"
77 | subdirectory: "ios"
78 | env:
79 | APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
80 | APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
81 | APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
82 | APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
83 | DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
84 | DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
85 | DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
86 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
87 | PROVISIONING_PROFILE_SPECIFIER: ${{ secrets.PROVISIONING_PROFILE_SPECIFIER }}
88 | GIT_AUTHORIZATION: ${{ secrets.GIT_AUTHORIZATION }}
89 | IOS_VERSION_NUMBER: ${{ secrets.IOS_VERSION_NUMBER }}
90 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-ios-dev.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Development (iOS)
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | build-develop-ios:
10 | name: Build & Upload iOS App (Development)
11 | runs-on: macos-14
12 |
13 | steps:
14 | - name: Select Xcode 16.2
15 | uses: maxim-lobanov/setup-xcode@v1
16 | with:
17 | xcode-version: "16.2"
18 |
19 | - name: Checkout repository
20 | uses: actions/checkout@v3
21 |
22 | - name: Set up Node
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version-file: ".nvmrc"
26 |
27 | - name: Enable Corepack and Set Yarn Version
28 | run: |
29 | corepack enable
30 | corepack prepare yarn@latest --activate
31 | yarn set version 4.10.3
32 | yarn --version
33 |
34 | - name: Install node modules
35 | run: yarn install
36 | env:
37 | CI: true
38 |
39 | - name: Decode base64 .env file from secrets
40 | uses: akiojin/decode-base64-github-action@v0.1.0
41 | id: decode-base64
42 | with:
43 | base64: ${{ secrets.DEV_APPLICATION_ENV }}
44 | output-path: ${{ runner.temp }}/.env
45 |
46 | - name: Install CocoaPods dependencies
47 | run: |
48 | cd ios
49 | pod install --repo-update
50 | cd ..
51 |
52 | - name: Set up Ruby
53 | uses: ruby/setup-ruby@v1
54 | with:
55 | ruby-version: 3.1.2
56 | bundler: 2.3.7
57 | bundler-cache: true
58 |
59 | - name: Install Fastlane dependencies
60 | run: |
61 | cd ios
62 | bundle install
63 | env:
64 | CI: true
65 |
66 | - name: Install Fastlane plugins
67 | run: |
68 | cd ios
69 | bundle exec fastlane install_plugins
70 | env:
71 | CI: true
72 |
73 | - name: Build and Upload to App Store (TestFlight)
74 | uses: maierj/fastlane-action@v3.0.0
75 | with:
76 | lane: "deploy_to_testflight"
77 | subdirectory: "ios"
78 | env:
79 | APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
80 | APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
81 | APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
82 | APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
83 | DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
84 | DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
85 | DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
86 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
87 | PROVISIONING_PROFILE_SPECIFIER: ${{ secrets.PROVISIONING_PROFILE_SPECIFIER }}
88 | GIT_AUTHORIZATION: ${{ secrets.GIT_AUTHORIZATION }}
89 | IOS_VERSION_NUMBER: ${{ secrets.IOS_VERSION_NUMBER }}
90 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-ios-prod.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Production (iOS)
2 |
3 | on:
4 | push:
5 | branches:
6 | - "release/**"
7 |
8 | jobs:
9 | build-develop-ios:
10 | name: Build & Upload iOS App (Production)
11 | runs-on: macos-14
12 |
13 | steps:
14 | - name: Select Xcode 16.2
15 | uses: maxim-lobanov/setup-xcode@v1
16 | with:
17 | xcode-version: "16.2"
18 |
19 | - name: Checkout repository
20 | uses: actions/checkout@v3
21 |
22 | - name: Set up Node
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version-file: ".nvmrc"
26 |
27 | - name: Enable Corepack and Set Yarn Version
28 | run: |
29 | corepack enable
30 | corepack prepare yarn@latest --activate
31 | yarn set version 4.10.3
32 | yarn --version
33 |
34 | - name: Install node modules
35 | run: yarn install
36 | env:
37 | CI: true
38 |
39 | - name: Decode base64 .env file from secrets
40 | uses: akiojin/decode-base64-github-action@v0.1.0
41 | id: decode-base64
42 | with:
43 | base64: ${{ secrets.PROD_APPLICATION_ENV }}
44 | output-path: ${{ runner.temp }}/.env
45 |
46 | - name: Install CocoaPods dependencies
47 | run: |
48 | cd ios
49 | pod install --repo-update
50 | cd ..
51 |
52 | - name: Set up Ruby
53 | uses: ruby/setup-ruby@v1
54 | with:
55 | ruby-version: 3.1.2
56 | bundler: 2.3.7
57 | bundler-cache: true
58 |
59 | - name: Install Fastlane dependencies
60 | run: |
61 | cd ios
62 | bundle install
63 | env:
64 | CI: true
65 |
66 | - name: Install Fastlane plugins
67 | run: |
68 | cd ios
69 | bundle exec fastlane install_plugins
70 | env:
71 | CI: true
72 |
73 | - name: Build and Upload to App Store (TestFlight)
74 | uses: maierj/fastlane-action@v3.0.0
75 | with:
76 | lane: "deploy_to_testflight"
77 | subdirectory: "ios"
78 | env:
79 | APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
80 | APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
81 | APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
82 | APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
83 | DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
84 | DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
85 | DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
86 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
87 | PROVISIONING_PROFILE_SPECIFIER: ${{ secrets.PROVISIONING_PROFILE_SPECIFIER }}
88 | GIT_AUTHORIZATION: ${{ secrets.GIT_AUTHORIZATION }}
89 | IOS_VERSION_NUMBER: ${{ secrets.IOS_VERSION_NUMBER }}
90 |
--------------------------------------------------------------------------------
/src/shared/api/auth/QueryKeys.ts:
--------------------------------------------------------------------------------
1 | import { Test, testEndpointQuery } from './models';
2 |
3 | const QUERY_KEYS = {
4 | TEST_QUERY_AUTH_SERVICE: 'TEST_QUERY_AUTH_SERVICE',
5 | TEST_INFINITE_QUERY_AUTH_SERVICE: 'TEST_INFINITE_QUERY_AUTH_SERVICE',
6 | TEST_SUSPENSE_QUERY_AUTH_SERVICE: 'TEST_SUSPENSE_QUERY_AUTH_SERVICE',
7 | TEST_SUSPENSE_INFINITE_QUERY_AUTH_SERVICE:
8 | 'TEST_SUSPENSE_INFINITE_QUERY_AUTH_SERVICE',
9 | TEST_QUERIES_AUTH_SERVICE: 'TEST_QUERIES_AUTH_SERVICE',
10 | TEST_PREFETCH_AUTH_SERVICE: 'TEST_PREFETCH_AUTH_SERVICE',
11 | TEST_PREFETCH_INFINITE_QUERY_AUTH_SERVICE:
12 | 'TEST_PREFETCH_INFINITE_QUERY_AUTH_SERVICE',
13 | TEST_QUERY_WITH_CUSTOM_CLIENT_AUTH_SERVICE:
14 | 'TEST_QUERY_WITH_CUSTOM_CLIENT_AUTH_SERVICE',
15 | TEST_QUERY_WITH_FETCH_AUTH_SERVICE: 'TEST_QUERY_WITH_FETCH_AUTH_SERVICE',
16 | TEST_VOID_REQUEST_AUTH_SERVICE: 'TEST_VOID_REQUEST_AUTH_SERVICE',
17 | TEST_SPECIAL_REQUEST_AUTH_SERVICE: 'TEST_SPECIAL_REQUEST_AUTH_SERVICE',
18 | TEST_ENDPOINT_QUERY_AUTH_SERVICE: 'TEST_ENDPOINT_QUERY_AUTH_SERVICE',
19 | TEST_MUTATION_AUTH_SERVICE: 'TEST_MUTATION_AUTH_SERVICE',
20 | TEST_ENDPOINT_MUTATION_AUTH_SERVICE: 'TEST_ENDPOINT_MUTATION_AUTH_SERVICE',
21 | } as const;
22 |
23 | export const AUTH_QUERY_KEYS = {
24 | testQueryAuthService: (params: Test) =>
25 | [QUERY_KEYS.TEST_QUERY_AUTH_SERVICE, params] as const,
26 | testInfiniteQueryAuthService: (params: Test) =>
27 | [QUERY_KEYS.TEST_INFINITE_QUERY_AUTH_SERVICE, params] as const,
28 | testSuspenseQueryAuthService: (params: Test) =>
29 | [QUERY_KEYS.TEST_SUSPENSE_QUERY_AUTH_SERVICE, params] as const,
30 | testSuspenseInfiniteQueryAuthService: (params: Test) =>
31 | [QUERY_KEYS.TEST_SUSPENSE_INFINITE_QUERY_AUTH_SERVICE, params] as const,
32 | testQueriesAuthService: (params: Test) =>
33 | [QUERY_KEYS.TEST_QUERIES_AUTH_SERVICE, params] as const,
34 | testPrefetchAuthService: (params: Test) =>
35 | [QUERY_KEYS.TEST_PREFETCH_AUTH_SERVICE, params] as const,
36 | testPrefetchInfiniteQueryAuthService: (params: Test) =>
37 | [QUERY_KEYS.TEST_PREFETCH_INFINITE_QUERY_AUTH_SERVICE, params] as const,
38 | testQueryWithCustomClientAuthService: (params: Test) =>
39 | [QUERY_KEYS.TEST_QUERY_WITH_CUSTOM_CLIENT_AUTH_SERVICE, params] as const,
40 | testQueryWithFetchAuthService: (params: Test) =>
41 | [QUERY_KEYS.TEST_QUERY_WITH_FETCH_AUTH_SERVICE, params] as const,
42 | testVoidRequestAuthService: () =>
43 | [QUERY_KEYS.TEST_VOID_REQUEST_AUTH_SERVICE] as const,
44 | testSpecialRequestAuthService: () =>
45 | [QUERY_KEYS.TEST_SPECIAL_REQUEST_AUTH_SERVICE] as const,
46 | testEndpointQueryAuthService: (params: testEndpointQuery) =>
47 | [QUERY_KEYS.TEST_ENDPOINT_QUERY_AUTH_SERVICE, params] as const,
48 | testMutationAuthService: () =>
49 | [QUERY_KEYS.TEST_MUTATION_AUTH_SERVICE] as const,
50 | testEndpointMutationAuthService: () =>
51 | [QUERY_KEYS.TEST_ENDPOINT_MUTATION_AUTH_SERVICE] as const,
52 | };
53 |
--------------------------------------------------------------------------------
/src/navigation/BottomTabBarNavigator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text } from 'react-native';
3 | import { IconName } from '@react-native-vector-icons/icomoon';
4 | import { StyleSheet, useUnistyles } from 'react-native-unistyles';
5 | import { Icon } from 'ui';
6 | import { RouteService, RouteType, Routes } from 'services';
7 | import { useTranslation } from 'react-i18next';
8 | import { Tab } from './lib';
9 | import { AlertsNavigator, ProfileNavigator } from './tabs';
10 |
11 | const tabIcons: Record = {
12 | [Routes.ALERTS_NAVIGATOR]: 'alerts',
13 | [Routes.PROFILE_NAVIGATOR]: 'account',
14 | };
15 |
16 | export const BottomTabBarNavigator: React.FC = () => {
17 | const { theme } = useUnistyles();
18 |
19 | const { t } = useTranslation();
20 |
21 | const titles: Record = {
22 | [Routes.ALERTS_NAVIGATOR]: t('screens.alerts'),
23 | [Routes.PROFILE_NAVIGATOR]: t('screens.account'),
24 | };
25 |
26 | const handleLongPress = (routeName: RouteType) => {
27 | RouteService.navigate(routeName);
28 | };
29 |
30 | return (
31 | ({
33 | tabBarHideOnKeyboard: true,
34 | tabBarActiveTintColor: theme.colors.basic_1000,
35 | tabBarInactiveTintColor: theme.colors.black,
36 | headerShown: false,
37 | tabBarItemStyle: styles.tabBarItemStyle,
38 | lazy: true,
39 | tabBarLabel: () => (
40 | {titles[route.name]}
41 | ),
42 | tabBarIcon: ({ focused }) => {
43 | const iconColor = focused ? 'basic_400' : 'basic_300';
44 |
45 | return (
46 |
47 | );
48 | },
49 | })}
50 | backBehavior="history"
51 | screenListeners={({ route }) => ({
52 | tabLongPress: () => handleLongPress(route.name),
53 | })}>
54 |
59 |
60 |
65 |
66 | );
67 | };
68 |
69 | const styles = StyleSheet.create(theme => ({
70 | tabBarItemStyle: {
71 | height: 42,
72 | marginTop: 10,
73 | justifyContent: 'center',
74 | alignItems: 'center',
75 | flexDirection: 'column',
76 | },
77 | tabBarBadgeStyle: {
78 | top: -8,
79 | fontSize: 10,
80 | justifyContent: 'center',
81 | alignItems: 'center',
82 | lineHeight: 14,
83 | width: 16,
84 | height: 16,
85 | borderRadius: 8,
86 | minWidth: 0,
87 | },
88 | text: {
89 | fontSize: 10,
90 | lineHeight: 12,
91 | fontWeight: '500',
92 | fontFamily: theme.fonts.Medium,
93 | color: theme.colors.black,
94 | },
95 | }));
96 |
--------------------------------------------------------------------------------
/src/shared/api/hooks/useMutationEvents.ts:
--------------------------------------------------------------------------------
1 | import type { UseMutationResult } from '@tanstack/react-query';
2 | import { useLatest } from 'hooks';
3 | import { useEffect } from 'react';
4 |
5 | type MutationEvents<
6 | TData = unknown,
7 | TError = unknown,
8 | TVariables = unknown,
9 | TContext = unknown,
10 | > = {
11 | onSuccess?: (
12 | data: TData,
13 | variables: TVariables,
14 | context: TContext | undefined,
15 | ) => unknown;
16 | onError?: (
17 | error: TError,
18 | variables: TVariables,
19 | context: TContext | undefined,
20 | ) => unknown;
21 | onSettled?: (
22 | data: TData | undefined,
23 | error: TError | null,
24 | variables: TVariables,
25 | context: TContext | undefined,
26 | ) => unknown;
27 | onMutate?: (variables: TVariables) => unknown;
28 | };
29 |
30 | export const useMutationEvents = <
31 | TData = unknown,
32 | TError = unknown,
33 | TVariables = unknown,
34 | TContext = unknown,
35 | >(
36 | mutation: UseMutationResult,
37 | callbacks: Partial>,
38 | ) => {
39 | const { onSuccess, onError, onSettled, onMutate } = callbacks;
40 |
41 | const onSuccessRef = useLatest(onSuccess);
42 | const onErrorRef = useLatest(onError);
43 | const onSettledRef = useLatest(onSettled);
44 | const onMutateRef = useLatest(onMutate);
45 |
46 | useEffect(() => {
47 | if (
48 | mutation.isSuccess &&
49 | onSuccessRef.current &&
50 | mutation.data &&
51 | mutation.variables
52 | ) {
53 | onSuccessRef.current(mutation.data, mutation.variables, mutation.context);
54 | }
55 | }, [mutation.isSuccess, mutation.variables, mutation.context]);
56 |
57 | useEffect(() => {
58 | if (
59 | mutation.isError &&
60 | onErrorRef.current &&
61 | mutation.error &&
62 | mutation.variables
63 | ) {
64 | onErrorRef.current(mutation.error, mutation.variables, mutation.context);
65 | }
66 | }, [mutation.isError, mutation.error, mutation.variables, mutation.context]);
67 |
68 | useEffect(() => {
69 | if (
70 | (mutation.isSuccess || mutation.isError) &&
71 | onSettledRef.current &&
72 | mutation.variables
73 | ) {
74 | onSettledRef.current(
75 | mutation.data,
76 | mutation.error,
77 | mutation.variables,
78 | mutation.context,
79 | );
80 | }
81 | }, [
82 | mutation.isSuccess,
83 | mutation.isError,
84 | mutation.data,
85 | mutation.error,
86 | mutation.variables,
87 | mutation.context,
88 | ]);
89 |
90 | useEffect(() => {
91 | if (
92 | mutation.status === 'pending' &&
93 | onMutateRef.current &&
94 | mutation.variables
95 | ) {
96 | onMutateRef.current(mutation.variables);
97 | }
98 | }, [mutation.status, mutation.variables]);
99 | };
100 |
--------------------------------------------------------------------------------
/scripts/api-codegen/merge-query-keys.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | const std::filesystem::path API_DIR =
10 | std::filesystem::current_path() / "src/shared/api";
11 | const std::filesystem::path OUTPUT_FILE = API_DIR / "models/QueryKeys.ts";
12 | const std::unordered_set EXCLUDED_FOLDERS = {"models"};
13 |
14 | std::string toSnakeCaseUpper(const std::string &input) {
15 | std::ostringstream result;
16 | for (size_t i = 0; i < input.size(); ++i) {
17 | if (std::isupper(input[i]) && i > 0) {
18 | result << "_";
19 | }
20 | result << (char)std::toupper(input[i]);
21 | }
22 | return result.str();
23 | }
24 |
25 | void generateQueryKeysFile() {
26 | std::cout << "\n🔍 Initializing QueryKeys generation..." << std::endl;
27 | std::cout << "📂 Writing to: " << OUTPUT_FILE << "\n" << std::endl;
28 |
29 | std::ofstream outputFile(OUTPUT_FILE);
30 | if (!outputFile.is_open()) {
31 | std::cerr << "❌ Error: Unable to create QueryKeys file: " << OUTPUT_FILE
32 | << std::endl;
33 | return;
34 | }
35 |
36 | outputFile << "\n";
37 | std::vector allQueryKeysExports;
38 |
39 | for (const auto &entry : std::filesystem::directory_iterator(API_DIR)) {
40 | if (!entry.is_directory()) {
41 | continue;
42 | }
43 |
44 | std::filesystem::path serviceDir = entry.path();
45 | if (EXCLUDED_FOLDERS.find(serviceDir.filename().string()) !=
46 | EXCLUDED_FOLDERS.end()) {
47 | std::cout << "⚠️ Skipping directory: " << serviceDir << "\n" << std::endl;
48 | continue;
49 | }
50 |
51 | std::filesystem::path queryKeysFile = serviceDir / "QueryKeys.ts";
52 | if (!std::filesystem::exists(queryKeysFile)) {
53 | continue;
54 | }
55 |
56 | std::string serviceName = serviceDir.filename().string();
57 | std::string serviceConstName = toSnakeCaseUpper(serviceName);
58 | std::string importPath = "../" + serviceName;
59 |
60 | std::cout << "⏳ Processing QueryKeys for service: " << serviceName
61 | << std::endl;
62 |
63 | outputFile << "import { " << serviceConstName << "_QUERY_KEYS } from '"
64 | << importPath << "/QueryKeys';\n";
65 | allQueryKeysExports.push_back("..." + serviceConstName + "_QUERY_KEYS");
66 | }
67 |
68 | outputFile << "\nexport const queryKeys = {\n";
69 | for (const auto &exportEntry : allQueryKeysExports) {
70 | outputFile << " " << exportEntry << ",\n";
71 | }
72 | outputFile << "};\n\n";
73 |
74 | outputFile << "export type QueryKeyType = ReturnType<\n"
75 | << " (typeof queryKeys)[keyof typeof queryKeys]\n"
76 | << ">;\n\n";
77 |
78 | outputFile.close();
79 | std::cout << "✅ Merged QueryKeys successfully written to: " << OUTPUT_FILE
80 | << "\n"
81 | << std::endl;
82 | }
83 |
84 | int main() {
85 | generateQueryKeysFile();
86 | return 0;
87 | }
88 |
--------------------------------------------------------------------------------
/src/shared/services/RouteService/RouteService.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CommonActions,
3 | createNavigationContainerRef,
4 | DrawerActions,
5 | StackActions,
6 | } from '@react-navigation/native';
7 | import { RootStackParamList, RouteType } from './models';
8 |
9 | const navigationRef = createNavigationContainerRef();
10 |
11 | const navigate = (
12 | name: T,
13 | params?: RootStackParamList[T],
14 | ) => {
15 | if (navigationRef?.current) {
16 | navigationRef.current?.dispatch(CommonActions.navigate({ name, params }));
17 | }
18 | };
19 |
20 | const openDrawer = () => {
21 | if (navigationRef?.current) {
22 | navigationRef.current?.dispatch(DrawerActions.openDrawer());
23 | }
24 | };
25 |
26 | const closeDrawer = () => {
27 | if (navigationRef?.current) {
28 | navigationRef.current?.dispatch(DrawerActions.closeDrawer());
29 | }
30 | };
31 |
32 | const goBack = () => {
33 | if (navigationRef?.current && navigationRef.current?.canGoBack()) {
34 | navigationRef.current?.dispatch(CommonActions.goBack());
35 | }
36 | };
37 |
38 | const pop = (screenCount?: number) => {
39 | if (navigationRef?.current && navigationRef.current?.canGoBack()) {
40 | navigationRef.current?.dispatch(StackActions.pop(screenCount));
41 | }
42 | };
43 |
44 | const popToTop = () => {
45 | if (navigationRef?.current && navigationRef.current?.canGoBack()) {
46 | navigationRef.current?.dispatch(StackActions.popToTop());
47 | }
48 | };
49 |
50 | const push = (name: T, params?: RootStackParamList[T]) => {
51 | if (navigationRef?.current) {
52 | navigationRef.current?.dispatch(StackActions.push(name, params));
53 | }
54 | };
55 |
56 | const setParams = (params: object) => {
57 | navigationRef.current?.dispatch(CommonActions.setParams(params));
58 | };
59 |
60 | const replace = (
61 | name: T,
62 | params?: RootStackParamList[T],
63 | ) => {
64 | if (navigationRef?.current) {
65 | navigationRef.current?.dispatch(StackActions.replace(name, params));
66 | }
67 | };
68 |
69 | const reset = (
70 | name: T,
71 | params?: RootStackParamList[T],
72 | ) => {
73 | if (navigationRef?.current) {
74 | navigationRef.current.dispatch(
75 | CommonActions.reset({
76 | index: 0,
77 | routes: [{ name, params }],
78 | }),
79 | );
80 | }
81 | };
82 |
83 | const navigateToNestedNavigatorScreen = <
84 | N extends RouteType,
85 | S extends RouteType,
86 | >(
87 | navigator: N,
88 | screen: S,
89 | params?: RootStackParamList[S],
90 | ) => {
91 | if (navigationRef?.current) {
92 | navigationRef.current?.dispatch(
93 | CommonActions.navigate(navigator, { screen, params }),
94 | );
95 | }
96 | };
97 |
98 | export const RouteService = {
99 | navigationRef,
100 | navigate,
101 | goBack,
102 | pop,
103 | popToTop,
104 | push,
105 | reset,
106 | replace,
107 | openDrawer,
108 | closeDrawer,
109 | setParams,
110 | navigateToNestedNavigatorScreen,
111 | };
112 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-android-stage.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Staging (Android)
2 |
3 | on:
4 | push:
5 | branches:
6 | - stage
7 |
8 | jobs:
9 | build-develop-android:
10 | name: Build & Upload Android App (Staging)
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - name: Set up JDK 17
15 | uses: actions/setup-java@v3
16 | with:
17 | distribution: zulu
18 | java-version: 17
19 |
20 | - name: Set up NODE
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version-file: ".nvmrc"
24 |
25 | - name: Cache Gradle Wrapper
26 | uses: actions/cache@v3
27 | with:
28 | path: ~/.gradle/wrapper
29 | key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
30 | - name: Cache Gradle Dependencies
31 | uses: actions/cache@v3
32 | with:
33 | path: ~/.gradle/caches
34 | key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
35 | restore-keys: |
36 | ${{ runner.os }}-gradle-caches-
37 |
38 | - name: Enable Corepack and Set Yarn Version
39 | run: |
40 | corepack enable
41 | corepack prepare yarn@latest --activate
42 | yarn set version 4.10.3
43 | yarn --version
44 |
45 | - name: Install node modules
46 | run: yarn install
47 | env:
48 | CI: true
49 |
50 | - uses: akiojin/decode-base64-github-action@v0.1.0
51 | id: decode-base64
52 | with:
53 | base64: ${{ secrets.STAGE_APPLICATION_ENV }}
54 | output-path: ${{ runner.temp }}/.env
55 |
56 | - name: Set up Ruby
57 | uses: ruby/setup-ruby@v1
58 | with:
59 | ruby-version: 3.1.2
60 | bundler: 2.3.7
61 | bundler-cache: true
62 |
63 | - name: Install Fastlane dependencies
64 | run: |
65 | cd android
66 | bundle install
67 | env:
68 | CI: true
69 |
70 | - name: Decode Android Keystore
71 | run: |
72 | echo ${{ secrets.ANDROID_KEYSTORE_FILE }} | base64 --decode > android/app/release.keystore
73 | env:
74 | ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }}
75 |
76 | - name: Decode Android Service Account File
77 | run: |
78 | echo ${{ secrets.ANDROID_SERVICE_ACCOUNT_FILE }} | base64 --decode > android/store.json
79 | env:
80 | ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_SERVICE_ACCOUNT_FILE }}
81 |
82 | - name: Build and Upload to Play Store
83 | uses: maierj/fastlane-action@v3.0.0
84 | with:
85 | lane: "deploy_to_play_store"
86 | subdirectory: "android"
87 | env:
88 | ANDROID_KEYSTORE_FILE: "${{ github.workspace }}/android/app/release.keystore"
89 | ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
90 | ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
91 | ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEYSTORE_ALIAS }}
92 | ANDROID_ENVIRONMENT: ${{ secrets.ANDROID_ENVIRONMENT }}
93 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-android-dev.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Development (Android)
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | build-develop-android:
10 | name: Build & Upload Android App (Development)
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - name: Set up JDK 17
15 | uses: actions/setup-java@v3
16 | with:
17 | distribution: zulu
18 | java-version: 17
19 |
20 | - name: Set up NODE
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version-file: ".nvmrc"
24 |
25 | - name: Cache Gradle Wrapper
26 | uses: actions/cache@v3
27 | with:
28 | path: ~/.gradle/wrapper
29 | key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
30 | - name: Cache Gradle Dependencies
31 | uses: actions/cache@v3
32 | with:
33 | path: ~/.gradle/caches
34 | key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
35 | restore-keys: |
36 | ${{ runner.os }}-gradle-caches-
37 |
38 | - name: Enable Corepack and Set Yarn Version
39 | run: |
40 | corepack enable
41 | corepack prepare yarn@latest --activate
42 | yarn set version 4.10.3
43 | yarn --version
44 |
45 | - name: Install node modules
46 | run: yarn install
47 | env:
48 | CI: true
49 |
50 | - uses: akiojin/decode-base64-github-action@v0.1.0
51 | id: decode-base64
52 | with:
53 | base64: ${{ secrets.DEV_APPLICATION_ENV }}
54 | output-path: ${{ runner.temp }}/.env
55 |
56 | - name: Set up Ruby
57 | uses: ruby/setup-ruby@v1
58 | with:
59 | ruby-version: 3.1.2
60 | bundler: 2.3.7
61 | bundler-cache: true
62 |
63 | - name: Install Fastlane dependencies
64 | run: |
65 | cd android
66 | bundle install
67 | env:
68 | CI: true
69 |
70 | - name: Decode Android Keystore
71 | run: |
72 | echo ${{ secrets.ANDROID_KEYSTORE_FILE }} | base64 --decode > android/app/release.keystore
73 | env:
74 | ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }}
75 |
76 | - name: Decode Android Service Account File
77 | run: |
78 | echo ${{ secrets.ANDROID_SERVICE_ACCOUNT_FILE }} | base64 --decode > android/store.json
79 | env:
80 | ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_SERVICE_ACCOUNT_FILE }}
81 |
82 | - name: Build and Upload to Play Store
83 | uses: maierj/fastlane-action@v3.0.0
84 | with:
85 | lane: "deploy_to_play_store"
86 | subdirectory: "android"
87 | env:
88 | ANDROID_KEYSTORE_FILE: "${{ github.workspace }}/android/app/release.keystore"
89 | ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
90 | ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
91 | ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEYSTORE_ALIAS }}
92 | ANDROID_ENVIRONMENT: ${{ secrets.ANDROID_ENVIRONMENT }}
93 |
--------------------------------------------------------------------------------