├── .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 | 66 | 74 | 83 | 84 | 85 | 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 | --------------------------------------------------------------------------------