create-du-app 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -9
- package/package.json +6 -5
- package/src/generate.js +15 -2
- package/src/index.js +15 -9
- package/src/prompts.js +1 -1
- package/templates/mobile/expo/.env.example +5 -0
- package/templates/mobile/expo/.eslintrc.js +7 -0
- package/templates/mobile/expo/.prettierrc.js +7 -0
- package/templates/mobile/expo/.svgrrc.js +9 -0
- package/templates/mobile/expo/README.md +70 -7
- package/templates/mobile/expo/_gitignore +20 -0
- package/templates/mobile/expo/_package.json +60 -1
- package/templates/mobile/expo/app.json +26 -0
- package/templates/mobile/expo/babel.config.js +21 -0
- package/templates/mobile/expo/index.js +7 -0
- package/templates/mobile/expo/metro.config.js +31 -0
- package/templates/mobile/expo/src/app/App.tsx +24 -0
- package/templates/mobile/expo/src/app/app-provider.tsx +40 -0
- package/templates/mobile/expo/src/app/config/translation.ts +30 -0
- package/templates/mobile/expo/src/app/index.ts +1 -0
- package/templates/mobile/expo/src/app/navigation/app-route-type.ts +27 -0
- package/templates/mobile/expo/src/app/navigation/bottom-tabs.tsx +34 -0
- package/templates/mobile/expo/src/app/navigation/index.tsx +14 -0
- package/templates/mobile/expo/src/app/stores/auth.store.ts +33 -0
- package/templates/mobile/expo/src/app/stores/common.store.ts +19 -0
- package/templates/mobile/expo/src/app/stores/index.ts +3 -0
- package/templates/mobile/expo/src/app/stores/loading.store.ts +22 -0
- package/templates/mobile/expo/src/assets/Images/empty-list.png +0 -0
- package/templates/mobile/expo/src/assets/Images/index.ts +5 -0
- package/templates/mobile/expo/src/assets/Images/screen-bg-gradian.png +0 -0
- package/templates/mobile/expo/src/assets/i18n/en.json +28 -0
- package/templates/mobile/expo/src/assets/i18n/fr.json +28 -0
- package/templates/mobile/expo/src/assets/lotties/index.ts +3 -0
- package/templates/mobile/expo/src/assets/lotties/loading.json +1 -0
- package/templates/mobile/expo/src/assets/svgs/arrow-left.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/arrow-right.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/calendar.svg +12 -0
- package/templates/mobile/expo/src/assets/svgs/check.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/close.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/eye-hide.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/eye.svg +4 -0
- package/templates/mobile/expo/src/assets/svgs/index.ts +29 -0
- package/templates/mobile/expo/src/assets/svgs/minus.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/plus.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/search.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/toast-error.svg +5 -0
- package/templates/mobile/expo/src/assets/svgs/toast-success.svg +4 -0
- package/templates/mobile/expo/src/core/api/endpoints.ts +8 -0
- package/templates/mobile/expo/src/core/api/example.api.ts +53 -0
- package/templates/mobile/expo/src/core/api/index.ts +4 -0
- package/templates/mobile/expo/src/core/components/common/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/common/list-empty/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/common/list-empty/list-empty.tsx +30 -0
- package/templates/mobile/expo/src/core/components/common/list-empty/list-empty.type.ts +5 -0
- package/templates/mobile/expo/src/core/components/forms/date-time-picker.modal.tsx +116 -0
- package/templates/mobile/expo/src/core/components/forms/hf-date-time.tsx +293 -0
- package/templates/mobile/expo/src/core/components/forms/hf-password-input.tsx +153 -0
- package/templates/mobile/expo/src/core/components/forms/hf-text-input.tsx +59 -0
- package/templates/mobile/expo/src/core/components/forms/hf-time-picker.tsx +116 -0
- package/templates/mobile/expo/src/core/components/forms/index.ts +4 -0
- package/templates/mobile/expo/src/core/components/index.ts +5 -0
- package/templates/mobile/expo/src/core/components/loading/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/loading/loading-app/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/loading/loading-app/loading-app.tsx +50 -0
- package/templates/mobile/expo/src/core/components/offline/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/offline/offline-banner.tsx +186 -0
- package/templates/mobile/expo/src/core/components/screen/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/screen/screen-container/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/screen/screen-container/screen-container.tsx +248 -0
- package/templates/mobile/expo/src/core/components/splash/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/splash/splash-overlay/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/splash/splash-overlay/splash-overlay.tsx +93 -0
- package/templates/mobile/expo/src/core/components/ui/animated-list-item/animated-list-item.tsx +48 -0
- package/templates/mobile/expo/src/core/components/ui/animated-list-item/animated-list-item.type.ts +10 -0
- package/templates/mobile/expo/src/core/components/ui/animated-list-item/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/app-image/app-image.tsx +101 -0
- package/templates/mobile/expo/src/core/components/ui/app-image/app-image.type.ts +19 -0
- package/templates/mobile/expo/src/core/components/ui/app-image/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/asset-placeholder/asset-placeholder.tsx +76 -0
- package/templates/mobile/expo/src/core/components/ui/asset-placeholder/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/avatar-image/avatar-image.tsx +90 -0
- package/templates/mobile/expo/src/core/components/ui/avatar-image/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/bottom-sheet/bottom-sheet.tsx +145 -0
- package/templates/mobile/expo/src/core/components/ui/bottom-sheet/bottom-sheet.type.ts +10 -0
- package/templates/mobile/expo/src/core/components/ui/bottom-sheet/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/button/button.style.ts +146 -0
- package/templates/mobile/expo/src/core/components/ui/button/button.tsx +97 -0
- package/templates/mobile/expo/src/core/components/ui/button/button.type.ts +47 -0
- package/templates/mobile/expo/src/core/components/ui/button/index.ts +4 -0
- package/templates/mobile/expo/src/core/components/ui/checkbox/checkbox.tsx +127 -0
- package/templates/mobile/expo/src/core/components/ui/checkbox/checkbox.type.ts +24 -0
- package/templates/mobile/expo/src/core/components/ui/checkbox/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/collapsible-section/collapsible-section.tsx +141 -0
- package/templates/mobile/expo/src/core/components/ui/collapsible-section/collapsible-section.type.ts +10 -0
- package/templates/mobile/expo/src/core/components/ui/collapsible-section/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/components.registry.json +313 -0
- package/templates/mobile/expo/src/core/components/ui/field/field-frame.tsx +62 -0
- package/templates/mobile/expo/src/core/components/ui/field/field.shared.ts +31 -0
- package/templates/mobile/expo/src/core/components/ui/header/header.tsx +196 -0
- package/templates/mobile/expo/src/core/components/ui/header/header.type.ts +30 -0
- package/templates/mobile/expo/src/core/components/ui/header/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.style.ts +23 -0
- package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.tsx +66 -0
- package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.type.ts +25 -0
- package/templates/mobile/expo/src/core/components/ui/icon-button/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/image-slider/image-slider.tsx +107 -0
- package/templates/mobile/expo/src/core/components/ui/image-slider/image-slider.type.ts +10 -0
- package/templates/mobile/expo/src/core/components/ui/image-slider/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/index.ts +25 -0
- package/templates/mobile/expo/src/core/components/ui/label/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/label/label.tsx +36 -0
- package/templates/mobile/expo/src/core/components/ui/label/label.type.ts +12 -0
- package/templates/mobile/expo/src/core/components/ui/modal/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/modal/modal.tsx +62 -0
- package/templates/mobile/expo/src/core/components/ui/modal/modal.type.ts +11 -0
- package/templates/mobile/expo/src/core/components/ui/otp-input/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/otp-input/otp-input.tsx +129 -0
- package/templates/mobile/expo/src/core/components/ui/otp-input/otp-input.type.ts +20 -0
- package/templates/mobile/expo/src/core/components/ui/page-dots/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/page-dots/page-dots.tsx +60 -0
- package/templates/mobile/expo/src/core/components/ui/radio/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/radio/radio.tsx +121 -0
- package/templates/mobile/expo/src/core/components/ui/radio/radio.type.ts +20 -0
- package/templates/mobile/expo/src/core/components/ui/screen/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/screen/screen-gradient.tsx +33 -0
- package/templates/mobile/expo/src/core/components/ui/search-box/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/search-box/search-box.tsx +162 -0
- package/templates/mobile/expo/src/core/components/ui/search-box/search-box.type.ts +26 -0
- package/templates/mobile/expo/src/core/components/ui/segmented-control/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/segmented-control/segmented-control.tsx +86 -0
- package/templates/mobile/expo/src/core/components/ui/segmented-control/segmented-control.type.ts +22 -0
- package/templates/mobile/expo/src/core/components/ui/skeleton/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/skeleton/skeleton.tsx +106 -0
- package/templates/mobile/expo/src/core/components/ui/skeleton/skeleton.type.ts +8 -0
- package/templates/mobile/expo/src/core/components/ui/success-state/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/success-state/success-state.tsx +68 -0
- package/templates/mobile/expo/src/core/components/ui/tabs/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/tabs/tabs.tsx +273 -0
- package/templates/mobile/expo/src/core/components/ui/tabs/tabs.type.ts +21 -0
- package/templates/mobile/expo/src/core/components/ui/tag-input/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/tag-input/tag-input.tsx +146 -0
- package/templates/mobile/expo/src/core/components/ui/tag-input/tag-input.type.ts +22 -0
- package/templates/mobile/expo/src/core/components/ui/text-area/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/text-area/text-area.tsx +90 -0
- package/templates/mobile/expo/src/core/components/ui/text-area/text-area.type.ts +20 -0
- package/templates/mobile/expo/src/core/components/ui/text-field/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/text-field/text-field.tsx +116 -0
- package/templates/mobile/expo/src/core/components/ui/text-field/text-field.type.ts +21 -0
- package/templates/mobile/expo/src/core/components/ui/toggle/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/toggle/toggle.tsx +110 -0
- package/templates/mobile/expo/src/core/components/ui/toggle/toggle.type.ts +19 -0
- package/templates/mobile/expo/src/core/constants/external-urls.constant.ts +5 -0
- package/templates/mobile/expo/src/core/constants/hard-data.constant.ts +0 -0
- package/templates/mobile/expo/src/core/constants/index.ts +2 -0
- package/templates/mobile/expo/src/core/constants/type.constant.ts +3 -0
- package/templates/mobile/expo/src/core/context/index.ts +1 -0
- package/templates/mobile/expo/src/core/context/shared-transition-context.tsx +35 -0
- package/templates/mobile/expo/src/core/hook/index.ts +8 -0
- package/templates/mobile/expo/src/core/hook/useActiveRouteName.ts +63 -0
- package/templates/mobile/expo/src/core/hook/useAppNavigation.tsx +7 -0
- package/templates/mobile/expo/src/core/hook/useBottomInset.tsx +26 -0
- package/templates/mobile/expo/src/core/hook/useDebounce.tsx +16 -0
- package/templates/mobile/expo/src/core/hook/useEndReached.tsx +46 -0
- package/templates/mobile/expo/src/core/hook/useManualRefetch.ts +56 -0
- package/templates/mobile/expo/src/core/hook/useNetworkStatus.ts +68 -0
- package/templates/mobile/expo/src/core/hook/useTimeout.tsx +30 -0
- package/templates/mobile/expo/src/core/index.ts +7 -0
- package/templates/mobile/expo/src/core/services/api.service.ts +230 -0
- package/templates/mobile/expo/src/core/services/device-id.service.ts +37 -0
- package/templates/mobile/expo/src/core/services/index.ts +3 -0
- package/templates/mobile/expo/src/core/services/session-end.bridge.ts +26 -0
- package/templates/mobile/expo/src/core/theme/dark.theme.ts +10 -0
- package/templates/mobile/expo/src/core/theme/index.ts +5 -0
- package/templates/mobile/expo/src/core/theme/light.theme.ts +44 -0
- package/templates/mobile/expo/src/core/theme/theme-context.tsx +82 -0
- package/templates/mobile/expo/src/core/theme/theme.types.ts +26 -0
- package/templates/mobile/expo/src/core/theme/use-themed-styles.ts +25 -0
- package/templates/mobile/expo/src/core/utils/color.util.tsx +198 -0
- package/templates/mobile/expo/src/core/utils/date.util.ts +97 -0
- package/templates/mobile/expo/src/core/utils/device-locale.util.ts +24 -0
- package/templates/mobile/expo/src/core/utils/emitter/index.ts +161 -0
- package/templates/mobile/expo/src/core/utils/emitter/type.ts +40 -0
- package/templates/mobile/expo/src/core/utils/enum.util.tsx +15 -0
- package/templates/mobile/expo/src/core/utils/font.util.tsx +42 -0
- package/templates/mobile/expo/src/core/utils/func.util.ts +48 -0
- package/templates/mobile/expo/src/core/utils/greeting.util.ts +20 -0
- package/templates/mobile/expo/src/core/utils/image-format.util.ts +117 -0
- package/templates/mobile/expo/src/core/utils/image-picker.util.ts +63 -0
- package/templates/mobile/expo/src/core/utils/index.ts +18 -0
- package/templates/mobile/expo/src/core/utils/linking.util.ts +16 -0
- package/templates/mobile/expo/src/core/utils/navigation.util.tsx +100 -0
- package/templates/mobile/expo/src/core/utils/number-format.ts +28 -0
- package/templates/mobile/expo/src/core/utils/query-client.util.ts +35 -0
- package/templates/mobile/expo/src/core/utils/query-persister.util.ts +31 -0
- package/templates/mobile/expo/src/core/utils/schema.util.tsx +2713 -0
- package/templates/mobile/expo/src/core/utils/size.util.tsx +74 -0
- package/templates/mobile/expo/src/core/utils/storage.util.tsx +53 -0
- package/templates/mobile/expo/src/core/utils/toast.util.tsx +151 -0
- package/templates/mobile/expo/src/core/utils/translator.util.tsx +23 -0
- package/templates/mobile/expo/src/core/utils/typography.util.tsx +69 -0
- package/templates/mobile/expo/src/core/utils/validate.util.tsx +13 -0
- package/templates/mobile/expo/src/declarations.d.ts +54 -0
- package/templates/mobile/expo/src/modules/home/home.screen.tsx +110 -0
- package/templates/mobile/expo/src/modules/profile/profile.screen.tsx +29 -0
- package/templates/mobile/expo/src/scripts/link-fonts.js +60 -0
- package/templates/mobile/expo/src/scripts/sync-images.js +56 -0
- package/templates/mobile/expo/src/scripts/sync-svgs.js +50 -0
- package/templates/mobile/expo/src/types/models.d.ts +24 -0
- package/templates/mobile/expo/tsconfig.json +19 -0
- package/templates/mobile/rn/.bundle/config +2 -0
- package/templates/mobile/rn/.env.example +5 -0
- package/templates/mobile/rn/.eslintrc.js +7 -0
- package/templates/mobile/rn/.prettierrc.js +7 -0
- package/templates/mobile/rn/.svgrrc.js +9 -0
- package/templates/mobile/rn/.watchmanconfig +1 -0
- package/templates/mobile/rn/Gemfile +17 -0
- package/templates/mobile/rn/README.md +72 -7
- package/templates/mobile/rn/_gitignore +24 -0
- package/templates/mobile/rn/_package.json +69 -1
- package/templates/mobile/rn/android/app/build.gradle +126 -0
- package/templates/mobile/rn/android/app/debug.keystore +0 -0
- package/templates/mobile/rn/android/app/proguard-rules.pro +10 -0
- package/templates/mobile/rn/android/app/src/main/AndroidManifest.xml +27 -0
- package/templates/mobile/rn/android/app/src/main/java/com/dumobile/MainActivity.kt +22 -0
- package/templates/mobile/rn/android/app/src/main/java/com/dumobile/MainApplication.kt +27 -0
- package/templates/mobile/rn/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/values/strings.xml +3 -0
- package/templates/mobile/rn/android/app/src/main/res/values/styles.xml +9 -0
- package/templates/mobile/rn/android/build.gradle +21 -0
- package/templates/mobile/rn/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/mobile/rn/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/templates/mobile/rn/android/gradle.properties +44 -0
- package/templates/mobile/rn/android/gradlew +248 -0
- package/templates/mobile/rn/android/gradlew.bat +98 -0
- package/templates/mobile/rn/android/settings.gradle +21 -0
- package/templates/mobile/rn/app.json +4 -0
- package/templates/mobile/rn/babel.config.js +18 -0
- package/templates/mobile/rn/index.js +10 -0
- package/templates/mobile/rn/ios/.xcode.env +11 -0
- package/templates/mobile/rn/ios/DuMobile/AppDelegate.swift +48 -0
- package/templates/mobile/rn/ios/DuMobile/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/templates/mobile/rn/ios/DuMobile/Images.xcassets/Contents.json +6 -0
- package/templates/mobile/rn/ios/DuMobile/Info.plist +59 -0
- package/templates/mobile/rn/ios/DuMobile/LaunchScreen.storyboard +47 -0
- package/templates/mobile/rn/ios/DuMobile/PrivacyInfo.xcprivacy +37 -0
- package/templates/mobile/rn/ios/DuMobile.xcodeproj/project.pbxproj +475 -0
- package/templates/mobile/rn/ios/DuMobile.xcodeproj/xcshareddata/xcschemes/DuMobile.xcscheme +88 -0
- package/templates/mobile/rn/ios/Podfile +34 -0
- package/templates/mobile/rn/metro.config.js +33 -0
- package/templates/mobile/rn/src/app/App.tsx +24 -0
- package/templates/mobile/rn/src/app/app-provider.tsx +41 -0
- package/templates/mobile/rn/src/app/config/translation.ts +29 -0
- package/templates/mobile/rn/src/app/index.ts +1 -0
- package/templates/mobile/rn/src/app/navigation/app-route-type.ts +27 -0
- package/templates/mobile/rn/src/app/navigation/bottom-tabs.tsx +34 -0
- package/templates/mobile/rn/src/app/navigation/index.tsx +14 -0
- package/templates/mobile/rn/src/app/stores/auth.store.ts +33 -0
- package/templates/mobile/rn/src/app/stores/common.store.ts +19 -0
- package/templates/mobile/rn/src/app/stores/index.ts +3 -0
- package/templates/mobile/rn/src/app/stores/loading.store.ts +22 -0
- package/templates/mobile/rn/src/assets/Images/empty-list.png +0 -0
- package/templates/mobile/rn/src/assets/Images/index.ts +5 -0
- package/templates/mobile/rn/src/assets/Images/screen-bg-gradian.png +0 -0
- package/templates/mobile/rn/src/assets/i18n/en.json +22 -0
- package/templates/mobile/rn/src/assets/i18n/fr.json +22 -0
- package/templates/mobile/rn/src/assets/lotties/index.ts +3 -0
- package/templates/mobile/rn/src/assets/lotties/loading.json +1 -0
- package/templates/mobile/rn/src/assets/svgs/arrow-left.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/arrow-right.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/calendar.svg +12 -0
- package/templates/mobile/rn/src/assets/svgs/check.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/close.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/eye-hide.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/eye.svg +4 -0
- package/templates/mobile/rn/src/assets/svgs/index.ts +29 -0
- package/templates/mobile/rn/src/assets/svgs/minus.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/plus.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/search.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/toast-error.svg +5 -0
- package/templates/mobile/rn/src/assets/svgs/toast-success.svg +4 -0
- package/templates/mobile/rn/src/core/api/endpoints.ts +8 -0
- package/templates/mobile/rn/src/core/api/example.api.ts +53 -0
- package/templates/mobile/rn/src/core/api/index.ts +4 -0
- package/templates/mobile/rn/src/core/components/common/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/common/list-empty/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/common/list-empty/list-empty.tsx +30 -0
- package/templates/mobile/rn/src/core/components/common/list-empty/list-empty.type.ts +5 -0
- package/templates/mobile/rn/src/core/components/forms/hf-date-time.tsx +301 -0
- package/templates/mobile/rn/src/core/components/forms/hf-password-input.tsx +153 -0
- package/templates/mobile/rn/src/core/components/forms/hf-text-input.tsx +59 -0
- package/templates/mobile/rn/src/core/components/forms/hf-time-picker.tsx +117 -0
- package/templates/mobile/rn/src/core/components/forms/index.ts +4 -0
- package/templates/mobile/rn/src/core/components/index.ts +5 -0
- package/templates/mobile/rn/src/core/components/loading/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/loading/loading-app/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/loading/loading-app/loading-app.tsx +50 -0
- package/templates/mobile/rn/src/core/components/offline/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/offline/offline-banner.tsx +186 -0
- package/templates/mobile/rn/src/core/components/screen/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/screen/screen-container/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/screen/screen-container/screen-container.tsx +252 -0
- package/templates/mobile/rn/src/core/components/splash/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/splash/splash-overlay/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/splash/splash-overlay/splash-overlay.tsx +93 -0
- package/templates/mobile/rn/src/core/components/ui/animated-list-item/animated-list-item.tsx +48 -0
- package/templates/mobile/rn/src/core/components/ui/animated-list-item/animated-list-item.type.ts +10 -0
- package/templates/mobile/rn/src/core/components/ui/animated-list-item/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/app-image/app-image.tsx +104 -0
- package/templates/mobile/rn/src/core/components/ui/app-image/app-image.type.ts +19 -0
- package/templates/mobile/rn/src/core/components/ui/app-image/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/asset-placeholder/asset-placeholder.tsx +76 -0
- package/templates/mobile/rn/src/core/components/ui/asset-placeholder/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/avatar-image/avatar-image.tsx +90 -0
- package/templates/mobile/rn/src/core/components/ui/avatar-image/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/bottom-sheet/bottom-sheet.tsx +145 -0
- package/templates/mobile/rn/src/core/components/ui/bottom-sheet/bottom-sheet.type.ts +10 -0
- package/templates/mobile/rn/src/core/components/ui/bottom-sheet/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/button/button.style.ts +146 -0
- package/templates/mobile/rn/src/core/components/ui/button/button.tsx +97 -0
- package/templates/mobile/rn/src/core/components/ui/button/button.type.ts +47 -0
- package/templates/mobile/rn/src/core/components/ui/button/index.ts +4 -0
- package/templates/mobile/rn/src/core/components/ui/checkbox/checkbox.tsx +127 -0
- package/templates/mobile/rn/src/core/components/ui/checkbox/checkbox.type.ts +24 -0
- package/templates/mobile/rn/src/core/components/ui/checkbox/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/collapsible-section/collapsible-section.tsx +141 -0
- package/templates/mobile/rn/src/core/components/ui/collapsible-section/collapsible-section.type.ts +10 -0
- package/templates/mobile/rn/src/core/components/ui/collapsible-section/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/components.registry.json +313 -0
- package/templates/mobile/rn/src/core/components/ui/field/field-frame.tsx +62 -0
- package/templates/mobile/rn/src/core/components/ui/field/field.shared.ts +31 -0
- package/templates/mobile/rn/src/core/components/ui/header/header.tsx +196 -0
- package/templates/mobile/rn/src/core/components/ui/header/header.type.ts +30 -0
- package/templates/mobile/rn/src/core/components/ui/header/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.style.ts +23 -0
- package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.tsx +66 -0
- package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.type.ts +25 -0
- package/templates/mobile/rn/src/core/components/ui/icon-button/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/image-slider/image-slider.tsx +107 -0
- package/templates/mobile/rn/src/core/components/ui/image-slider/image-slider.type.ts +10 -0
- package/templates/mobile/rn/src/core/components/ui/image-slider/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/index.ts +25 -0
- package/templates/mobile/rn/src/core/components/ui/label/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/label/label.tsx +36 -0
- package/templates/mobile/rn/src/core/components/ui/label/label.type.ts +12 -0
- package/templates/mobile/rn/src/core/components/ui/modal/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/modal/modal.tsx +62 -0
- package/templates/mobile/rn/src/core/components/ui/modal/modal.type.ts +11 -0
- package/templates/mobile/rn/src/core/components/ui/otp-input/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/otp-input/otp-input.tsx +129 -0
- package/templates/mobile/rn/src/core/components/ui/otp-input/otp-input.type.ts +20 -0
- package/templates/mobile/rn/src/core/components/ui/page-dots/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/page-dots/page-dots.tsx +60 -0
- package/templates/mobile/rn/src/core/components/ui/radio/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/radio/radio.tsx +121 -0
- package/templates/mobile/rn/src/core/components/ui/radio/radio.type.ts +20 -0
- package/templates/mobile/rn/src/core/components/ui/screen/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/screen/screen-gradient.tsx +33 -0
- package/templates/mobile/rn/src/core/components/ui/search-box/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/search-box/search-box.tsx +162 -0
- package/templates/mobile/rn/src/core/components/ui/search-box/search-box.type.ts +26 -0
- package/templates/mobile/rn/src/core/components/ui/segmented-control/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/segmented-control/segmented-control.tsx +86 -0
- package/templates/mobile/rn/src/core/components/ui/segmented-control/segmented-control.type.ts +22 -0
- package/templates/mobile/rn/src/core/components/ui/skeleton/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/skeleton/skeleton.tsx +106 -0
- package/templates/mobile/rn/src/core/components/ui/skeleton/skeleton.type.ts +8 -0
- package/templates/mobile/rn/src/core/components/ui/success-state/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/success-state/success-state.tsx +68 -0
- package/templates/mobile/rn/src/core/components/ui/tabs/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/tabs/tabs.tsx +273 -0
- package/templates/mobile/rn/src/core/components/ui/tabs/tabs.type.ts +21 -0
- package/templates/mobile/rn/src/core/components/ui/tag-input/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/tag-input/tag-input.tsx +146 -0
- package/templates/mobile/rn/src/core/components/ui/tag-input/tag-input.type.ts +22 -0
- package/templates/mobile/rn/src/core/components/ui/text-area/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/text-area/text-area.tsx +90 -0
- package/templates/mobile/rn/src/core/components/ui/text-area/text-area.type.ts +20 -0
- package/templates/mobile/rn/src/core/components/ui/text-field/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/text-field/text-field.tsx +116 -0
- package/templates/mobile/rn/src/core/components/ui/text-field/text-field.type.ts +21 -0
- package/templates/mobile/rn/src/core/components/ui/toggle/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/toggle/toggle.tsx +110 -0
- package/templates/mobile/rn/src/core/components/ui/toggle/toggle.type.ts +19 -0
- package/templates/mobile/rn/src/core/constants/external-urls.constant.ts +5 -0
- package/templates/mobile/rn/src/core/constants/hard-data.constant.ts +0 -0
- package/templates/mobile/rn/src/core/constants/index.ts +2 -0
- package/templates/mobile/rn/src/core/constants/type.constant.ts +3 -0
- package/templates/mobile/rn/src/core/context/index.ts +1 -0
- package/templates/mobile/rn/src/core/context/shared-transition-context.tsx +35 -0
- package/templates/mobile/rn/src/core/hook/index.ts +8 -0
- package/templates/mobile/rn/src/core/hook/useActiveRouteName.ts +63 -0
- package/templates/mobile/rn/src/core/hook/useAppNavigation.tsx +7 -0
- package/templates/mobile/rn/src/core/hook/useBottomInset.tsx +26 -0
- package/templates/mobile/rn/src/core/hook/useDebounce.tsx +16 -0
- package/templates/mobile/rn/src/core/hook/useEndReached.tsx +46 -0
- package/templates/mobile/rn/src/core/hook/useManualRefetch.ts +56 -0
- package/templates/mobile/rn/src/core/hook/useNetworkStatus.ts +68 -0
- package/templates/mobile/rn/src/core/hook/useTimeout.tsx +30 -0
- package/templates/mobile/rn/src/core/index.ts +7 -0
- package/templates/mobile/rn/src/core/services/api.service.ts +230 -0
- package/templates/mobile/rn/src/core/services/device-id.service.ts +23 -0
- package/templates/mobile/rn/src/core/services/index.ts +3 -0
- package/templates/mobile/rn/src/core/services/session-end.bridge.ts +26 -0
- package/templates/mobile/rn/src/core/theme/dark.theme.ts +10 -0
- package/templates/mobile/rn/src/core/theme/index.ts +5 -0
- package/templates/mobile/rn/src/core/theme/light.theme.ts +44 -0
- package/templates/mobile/rn/src/core/theme/theme-context.tsx +82 -0
- package/templates/mobile/rn/src/core/theme/theme.types.ts +26 -0
- package/templates/mobile/rn/src/core/theme/use-themed-styles.ts +25 -0
- package/templates/mobile/rn/src/core/utils/color.util.tsx +198 -0
- package/templates/mobile/rn/src/core/utils/date.util.ts +97 -0
- package/templates/mobile/rn/src/core/utils/device-locale.util.ts +22 -0
- package/templates/mobile/rn/src/core/utils/emitter/index.ts +161 -0
- package/templates/mobile/rn/src/core/utils/emitter/type.ts +40 -0
- package/templates/mobile/rn/src/core/utils/enum.util.tsx +15 -0
- package/templates/mobile/rn/src/core/utils/font.util.tsx +42 -0
- package/templates/mobile/rn/src/core/utils/func.util.ts +48 -0
- package/templates/mobile/rn/src/core/utils/greeting.util.ts +20 -0
- package/templates/mobile/rn/src/core/utils/image-format.util.ts +117 -0
- package/templates/mobile/rn/src/core/utils/image-picker.util.ts +84 -0
- package/templates/mobile/rn/src/core/utils/index.ts +18 -0
- package/templates/mobile/rn/src/core/utils/linking.util.ts +16 -0
- package/templates/mobile/rn/src/core/utils/navigation.util.tsx +100 -0
- package/templates/mobile/rn/src/core/utils/number-format.ts +28 -0
- package/templates/mobile/rn/src/core/utils/query-client.util.ts +35 -0
- package/templates/mobile/rn/src/core/utils/query-persister.util.ts +36 -0
- package/templates/mobile/rn/src/core/utils/schema.util.tsx +2713 -0
- package/templates/mobile/rn/src/core/utils/size.util.tsx +74 -0
- package/templates/mobile/rn/src/core/utils/storage.util.tsx +53 -0
- package/templates/mobile/rn/src/core/utils/toast.util.tsx +151 -0
- package/templates/mobile/rn/src/core/utils/translator.util.tsx +23 -0
- package/templates/mobile/rn/src/core/utils/typography.util.tsx +69 -0
- package/templates/mobile/rn/src/core/utils/validate.util.tsx +13 -0
- package/templates/mobile/rn/src/declarations.d.ts +54 -0
- package/templates/mobile/rn/src/modules/home/home.screen.tsx +67 -0
- package/templates/mobile/rn/src/modules/profile/profile.screen.tsx +29 -0
- package/templates/mobile/rn/src/scripts/link-fonts.js +60 -0
- package/templates/mobile/rn/src/scripts/sync-images.js +56 -0
- package/templates/mobile/rn/src/scripts/sync-svgs.js +50 -0
- package/templates/mobile/rn/src/types/models.d.ts +24 -0
- package/templates/mobile/rn/tsconfig.json +21 -0
- package/templates/shared/src/api-endpoints.ts +8 -0
- package/templates/shared/src/enums.ts +34 -0
- package/templates/shared/src/external-urls.ts +5 -0
- package/templates/shared/src/index.ts +6 -3
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { useAuthStore, useLoadingStore } from '@src/app/stores';
|
|
2
|
+
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
3
|
+
import qs from 'qs';
|
|
4
|
+
import { Alert } from 'react-native';
|
|
5
|
+
import { Screen } from '@src/app/navigation/app-route-type';
|
|
6
|
+
import { NavigationUtil, navigationRef } from '@src/core/utils/navigation.util';
|
|
7
|
+
import { Translator } from '@src/core/utils/translator.util';
|
|
8
|
+
import { getDeviceId } from './device-id.service';
|
|
9
|
+
import { triggerSessionEnd } from './session-end.bridge';
|
|
10
|
+
|
|
11
|
+
// Backend supports French and English; map anything else to English so OTP
|
|
12
|
+
// emails and other localized responses match the user's selected language
|
|
13
|
+
// (LIFEMASTER-369). i18n.language is the source of truth — the profile language
|
|
14
|
+
// switch drives it directly.
|
|
15
|
+
const acceptLanguage = (): string => (Translator.currentLanguage().startsWith('fr') ? 'fr' : 'en');
|
|
16
|
+
|
|
17
|
+
// API Configuration. Expo inlines EXPO_PUBLIC_* env vars at build time (set
|
|
18
|
+
// EXPO_PUBLIC_BASE_URL in .env — see .env.example).
|
|
19
|
+
export const API_VERSION = 'v1';
|
|
20
|
+
export const BaseURL = `${process.env.EXPO_PUBLIC_BASE_URL ?? ''}${API_VERSION}/`;
|
|
21
|
+
// Create axios instance with default configuration
|
|
22
|
+
const axiosInstance = axios.create({
|
|
23
|
+
timeout: 10000,
|
|
24
|
+
baseURL: BaseURL,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
axiosInstance.interceptors.request.use(async (config) => {
|
|
28
|
+
try {
|
|
29
|
+
const deviceId = await getDeviceId();
|
|
30
|
+
config.headers.set('X-Device-Id', deviceId);
|
|
31
|
+
} catch {
|
|
32
|
+
// Device ID resolution failed — proceed without header
|
|
33
|
+
}
|
|
34
|
+
return config;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let sessionExpiredAlertShown = false;
|
|
38
|
+
|
|
39
|
+
const handleSessionExpired = async () => {
|
|
40
|
+
if (sessionExpiredAlertShown) return;
|
|
41
|
+
// Full session teardown on a 401 (token, premium lease, player audio, React
|
|
42
|
+
// Query cache, library, favorites…). Routed through the bridge — the actual
|
|
43
|
+
// `endSession` lives in the app layer and pulls feature stores, so importing
|
|
44
|
+
// it here would create a core → app import cycle.
|
|
45
|
+
triggerSessionEnd();
|
|
46
|
+
// Cold launch: the navigation container isn't mounted yet (App renders null
|
|
47
|
+
// until the bootstrap route resolves), so a 401 from the bootstrap resume
|
|
48
|
+
// fetch must NOT pop a "session expired" alert over Onboarding — bootstrap
|
|
49
|
+
// already routes there. Clear silently and let it own the routing.
|
|
50
|
+
if (!navigationRef.current) return;
|
|
51
|
+
sessionExpiredAlertShown = true;
|
|
52
|
+
NavigationUtil.reset(Screen.SignIn);
|
|
53
|
+
Alert.alert(
|
|
54
|
+
Translator.translate('auth.sessionExpired.title'),
|
|
55
|
+
Translator.translate('auth.sessionExpired.message'),
|
|
56
|
+
[{
|
|
57
|
+
text: Translator.translate('auth.sessionExpired.cta'),
|
|
58
|
+
onPress: () => { sessionExpiredAlertShown = false; },
|
|
59
|
+
}],
|
|
60
|
+
{ cancelable: false, onDismiss: () => { sessionExpiredAlertShown = false; } },
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
axiosInstance.interceptors.response.use(
|
|
65
|
+
(response: AxiosResponse) => response,
|
|
66
|
+
(error) => {
|
|
67
|
+
if (error?.response?.status === 401 && useAuthStore.getState().token) {
|
|
68
|
+
handleSessionExpired();
|
|
69
|
+
}
|
|
70
|
+
return Promise.reject(error);
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Types
|
|
75
|
+
interface ApiError {
|
|
76
|
+
message: string;
|
|
77
|
+
code?: string;
|
|
78
|
+
success: boolean;
|
|
79
|
+
status?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface ApiResponse<T = any> {
|
|
83
|
+
data?: T;
|
|
84
|
+
success: boolean;
|
|
85
|
+
errors?: Array<{ message: string; code?: string }>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Error handling utility
|
|
89
|
+
const handleApiError = (error: any): ApiError => {
|
|
90
|
+
const status = error?.response?.status;
|
|
91
|
+
if (error.response?.data) {
|
|
92
|
+
const { data } = error.response;
|
|
93
|
+
const { errors, success } = data;
|
|
94
|
+
|
|
95
|
+
if (!success && errors?.length > 0) {
|
|
96
|
+
const { message, code } = errors[0];
|
|
97
|
+
return {
|
|
98
|
+
message: message?.message || message || 'An error occurred',
|
|
99
|
+
code,
|
|
100
|
+
success: false,
|
|
101
|
+
status,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { ...data, status };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
message: error.message || 'Network error occurred',
|
|
110
|
+
success: false,
|
|
111
|
+
status,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Response preprocessing utility
|
|
116
|
+
const preprocessResponse = <T>(result: ApiResponse<T>): T | ApiError => {
|
|
117
|
+
const { success, errors } = result || {};
|
|
118
|
+
|
|
119
|
+
if (success) {
|
|
120
|
+
return (result) as T;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (errors && errors.length > 0) {
|
|
124
|
+
return { ...errors[0], success: false };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result as T;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export class ApiService {
|
|
131
|
+
// Get authorization headers
|
|
132
|
+
private static getAuthHeaders(): Record<string, string> {
|
|
133
|
+
const token = useAuthStore.getState().token;
|
|
134
|
+
return {
|
|
135
|
+
Accept: 'application/json',
|
|
136
|
+
'Content-Type': 'application/json',
|
|
137
|
+
'Accept-Language': acceptLanguage(),
|
|
138
|
+
...(token && { Authorization: `Bearer ${token}` }),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Form-data headers. Content-Type is intentionally OMITTED: axios must set it
|
|
143
|
+
// itself so it appends the `; boundary=...` the server needs to split the
|
|
144
|
+
// parts. Hardcoding `multipart/form-data` (no boundary) made the backend fail
|
|
145
|
+
// to parse the file — the avatar upload then errored on some photos but not
|
|
146
|
+
// others depending on the payload (LIFEMASTER-260).
|
|
147
|
+
private static getFormDataHeaders(): Record<string, string> {
|
|
148
|
+
const token = useAuthStore.getState().token;
|
|
149
|
+
return {
|
|
150
|
+
Accept: 'application/json',
|
|
151
|
+
'Accept-Language': acceptLanguage(),
|
|
152
|
+
...(token && { Authorization: `Bearer ${token}` }),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Generic request method. Drives the global loading overlay via the
|
|
157
|
+
// ref-counted loading store; pass `silent: true` on the config to opt out
|
|
158
|
+
// (e.g. background refetch / polling) so it doesn't block the UI.
|
|
159
|
+
private static async makeRequest<T>(
|
|
160
|
+
config: AxiosRequestConfig & { silent?: boolean },
|
|
161
|
+
): Promise<T> {
|
|
162
|
+
const { silent, ...axiosConfig } = config;
|
|
163
|
+
if (!silent) useLoadingStore.getState().showLoading();
|
|
164
|
+
try {
|
|
165
|
+
const response = await axiosInstance(axiosConfig);
|
|
166
|
+
return preprocessResponse(response.data) as T;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw handleApiError(error);
|
|
169
|
+
} finally {
|
|
170
|
+
if (!silent) useLoadingStore.getState().hideLoading();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// GET request — pass silent=true to skip the global loading overlay.
|
|
175
|
+
static async get<T = any>(url: string, params?: any, silent = false): Promise<IResponse<T>> {
|
|
176
|
+
return this.makeRequest<IResponse<T>>({
|
|
177
|
+
method: 'GET',
|
|
178
|
+
url,
|
|
179
|
+
headers: this.getAuthHeaders(),
|
|
180
|
+
params,
|
|
181
|
+
paramsSerializer: (p) => qs.stringify(p, { arrayFormat: 'repeat' }),
|
|
182
|
+
silent,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// POST request
|
|
187
|
+
static async post<T = any>(url: string, data?: any, isFormData = false, silent = false): Promise<IResponse<T>> {
|
|
188
|
+
return this.makeRequest<IResponse<T>>({
|
|
189
|
+
method: 'POST',
|
|
190
|
+
url,
|
|
191
|
+
headers: isFormData ? this.getFormDataHeaders() : this.getAuthHeaders(),
|
|
192
|
+
data,
|
|
193
|
+
silent,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// PUT request
|
|
198
|
+
static async put<T = any>(url: string, data?: any, silent = false): Promise<IResponse<T>> {
|
|
199
|
+
return this.makeRequest<IResponse<T>>({
|
|
200
|
+
method: 'PUT',
|
|
201
|
+
url,
|
|
202
|
+
headers: this.getAuthHeaders(),
|
|
203
|
+
data,
|
|
204
|
+
silent,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// DELETE request
|
|
209
|
+
static async delete<T = any>(url: string, data?: any, silent = false): Promise<IResponse<T>> {
|
|
210
|
+
return this.makeRequest<IResponse<T>>({
|
|
211
|
+
method: 'DELETE',
|
|
212
|
+
url,
|
|
213
|
+
headers: this.getAuthHeaders(),
|
|
214
|
+
data,
|
|
215
|
+
silent,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// PATCH request (added for completeness)
|
|
220
|
+
static async patch<T = any>(url: string, data?: any, silent = false): Promise<IResponse<T>> {
|
|
221
|
+
return this.makeRequest<IResponse<T>>({
|
|
222
|
+
method: 'PATCH',
|
|
223
|
+
url,
|
|
224
|
+
headers: this.getAuthHeaders(),
|
|
225
|
+
data,
|
|
226
|
+
silent,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
+
import * as Application from 'expo-application';
|
|
3
|
+
import * as Crypto from 'expo-crypto';
|
|
4
|
+
import { Platform } from 'react-native';
|
|
5
|
+
|
|
6
|
+
const DEVICE_ID_KEY = 'device_id';
|
|
7
|
+
|
|
8
|
+
let cachedDeviceId: string | null = null;
|
|
9
|
+
|
|
10
|
+
// A platform-stable device identifier, resolved once and persisted so it stays
|
|
11
|
+
// the same across launches. Android exposes a stable id directly; iOS uses the
|
|
12
|
+
// vendor id (can be null on first call), so we fall back to a generated UUID.
|
|
13
|
+
// Both expo-application and expo-crypto ship inside Expo Go — no dev build needed.
|
|
14
|
+
const resolveNativeId = async (): Promise<string> => {
|
|
15
|
+
if (Platform.OS === 'android') {
|
|
16
|
+
return Application.getAndroidId() ?? Crypto.randomUUID();
|
|
17
|
+
}
|
|
18
|
+
const vendorId = await Application.getIosIdForVendorAsync();
|
|
19
|
+
return vendorId ?? Crypto.randomUUID();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const getDeviceId = async (): Promise<string> => {
|
|
23
|
+
if (cachedDeviceId) return cachedDeviceId;
|
|
24
|
+
|
|
25
|
+
const stored = await AsyncStorage.getItem(DEVICE_ID_KEY);
|
|
26
|
+
if (stored) {
|
|
27
|
+
cachedDeviceId = stored;
|
|
28
|
+
return stored;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const nativeId = await resolveNativeId();
|
|
32
|
+
await AsyncStorage.setItem(DEVICE_ID_KEY, nativeId);
|
|
33
|
+
cachedDeviceId = nativeId;
|
|
34
|
+
return nativeId;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const getCachedDeviceId = (): string | null => cachedDeviceId;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-teardown bridge.
|
|
3
|
+
*
|
|
4
|
+
* The 401 response interceptor in `api.service` must trigger a FULL session
|
|
5
|
+
* reset (tokens, premium lease, player audio, React Query cache, library,
|
|
6
|
+
* favorites…). That teardown (`endSession`) lives in the app layer and pulls in
|
|
7
|
+
* feature stores, so importing it here would create a `core → app → core` import
|
|
8
|
+
* cycle — which this codebase deliberately avoids.
|
|
9
|
+
*
|
|
10
|
+
* Instead the app registers its teardown once at startup ({@link App}) and the
|
|
11
|
+
* interceptor fires it through this shim, keeping the import graph one-way
|
|
12
|
+
* (app → core). Until something registers, {@link triggerSessionEnd} is a no-op.
|
|
13
|
+
*/
|
|
14
|
+
type SessionEndHandler = () => void;
|
|
15
|
+
|
|
16
|
+
let handler: SessionEndHandler | null = null;
|
|
17
|
+
|
|
18
|
+
/** Wire the app-level `endSession` in. Called once at app startup. */
|
|
19
|
+
export const registerSessionEndHandler = (fn: SessionEndHandler): void => {
|
|
20
|
+
handler = fn;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Run the registered teardown (no-op if the app hasn't registered one yet). */
|
|
24
|
+
export const triggerSessionEnd = (): void => {
|
|
25
|
+
handler?.();
|
|
26
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Colors } from '@src/core/utils';
|
|
2
|
+
import { ThemeColors } from './theme.types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Dark palette. The existing static `Colors` object already encodes the dark
|
|
6
|
+
* look (light text on dark surfaces), so the dark theme reuses it verbatim —
|
|
7
|
+
* dark mode therefore renders identically to the pre-theme app, with zero
|
|
8
|
+
* visual regression.
|
|
9
|
+
*/
|
|
10
|
+
export const darkColors: ThemeColors = Colors;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { darkColors } from './dark.theme';
|
|
2
|
+
import { ThemeColors } from './theme.types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Light palette. Built by spreading the dark theme and overriding only the
|
|
6
|
+
* neutral surface / text / border tokens with light values — brand tokens
|
|
7
|
+
* (primary purple, secondary blue, semantic colors) stay identical across
|
|
8
|
+
* themes so the brand reads the same in both modes.
|
|
9
|
+
*
|
|
10
|
+
* TODO(design): these light DS values are a pragmatic default derived from the
|
|
11
|
+
* existing legacy light tokens. Confirm the exact light palette against the
|
|
12
|
+
* Figma "Life-Master-Design" light variables when available.
|
|
13
|
+
*/
|
|
14
|
+
export const lightColors: ThemeColors = {
|
|
15
|
+
...darkColors,
|
|
16
|
+
|
|
17
|
+
// --- foreground (text/icon) ---
|
|
18
|
+
fg_neutral_normal: '#0F0F0F', // primary text on light surface
|
|
19
|
+
fg_neutral_faded: '#525252', // muted / disabled text
|
|
20
|
+
fg_neutral_reverse: '#FBFAFF', // text on dark/reverse fill
|
|
21
|
+
fg_neutral_placeholder: '#A8A8A8',
|
|
22
|
+
|
|
23
|
+
// --- background (fill) ---
|
|
24
|
+
bg_elevation_reverse: '#141414', // dark fill (neutral solid) on light
|
|
25
|
+
bg_elevation_level_1_normal: '#FFFFFF', // base surface
|
|
26
|
+
bg_elevation_level_2_normal: '#F5F5F5', // raised / disabled surface
|
|
27
|
+
bg_input: 'rgba(0, 0, 0, 0.06)', // input field fill on light
|
|
28
|
+
bg_disable: '#E5E5E5',
|
|
29
|
+
|
|
30
|
+
// --- border ---
|
|
31
|
+
bd_neutral_normal: '#E5E5E5',
|
|
32
|
+
bd_neutral_faded: '#C6C6C6',
|
|
33
|
+
|
|
34
|
+
// --- danger surfaces (destructive actions) — softened for light surface ---
|
|
35
|
+
bg_danger_faded: '#FDE7EC', // light danger fill (logout button bg)
|
|
36
|
+
bd_danger_faded: '#F43F5E',
|
|
37
|
+
fg_danger_faded_ds: '#BE123C', // readable danger text on light fill
|
|
38
|
+
|
|
39
|
+
// --- translucent card overlays (rows over the light background) ---
|
|
40
|
+
bg_overlay_card: 'rgba(0, 0, 0, 0.04)',
|
|
41
|
+
bg_overlay_card_medium: 'rgba(0, 0, 0, 0.05)',
|
|
42
|
+
bg_overlay_card_strong: 'rgba(0, 0, 0, 0.06)',
|
|
43
|
+
bg_progress_track: 'rgba(0, 0, 0, 0.1)',
|
|
44
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
PropsWithChildren,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { useColorScheme } from 'react-native';
|
|
11
|
+
import { AppStorageUtil, StorageKeys } from '@src/core/utils';
|
|
12
|
+
import { darkColors } from './dark.theme';
|
|
13
|
+
import { lightColors } from './light.theme';
|
|
14
|
+
import {
|
|
15
|
+
ResolvedScheme,
|
|
16
|
+
ThemeContextValue,
|
|
17
|
+
ThemeScheme,
|
|
18
|
+
} from './theme.types';
|
|
19
|
+
|
|
20
|
+
// Only the dark theme is shipped for now — the light palette is a draft
|
|
21
|
+
// pending design. Default to dark and (below) lock the resolved scheme to dark
|
|
22
|
+
// so the OS/light never leaks through. To re-enable light later: set this back
|
|
23
|
+
// to 'system' and remove the `LIGHT_THEME_ENABLED` lock in `resolved`.
|
|
24
|
+
const DEFAULT_SCHEME: ThemeScheme = 'dark';
|
|
25
|
+
const LIGHT_THEME_ENABLED = false;
|
|
26
|
+
|
|
27
|
+
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Provides theme tokens app-wide. Defaults to following the OS appearance and
|
|
31
|
+
* hydrates the persisted user override (AsyncStorage) after mount. Mount this
|
|
32
|
+
* once near the app root, above the navigation tree.
|
|
33
|
+
*/
|
|
34
|
+
export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
|
35
|
+
const osScheme = useColorScheme();
|
|
36
|
+
const [scheme, setSchemeState] = useState<ThemeScheme>(DEFAULT_SCHEME);
|
|
37
|
+
|
|
38
|
+
// Hydrate the persisted preference once on mount (AsyncStorage is async, so
|
|
39
|
+
// the first frame uses the system default and corrects itself here).
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
let active = true;
|
|
42
|
+
AppStorageUtil.getItem(StorageKeys.THEME).then(saved => {
|
|
43
|
+
if (active && (saved === 'light' || saved === 'dark' || saved === 'system')) {
|
|
44
|
+
setSchemeState(saved);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return () => {
|
|
48
|
+
active = false;
|
|
49
|
+
};
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
const setScheme = useCallback((next: ThemeScheme) => {
|
|
53
|
+
setSchemeState(next);
|
|
54
|
+
AppStorageUtil.setItem(StorageKeys.THEME, next);
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
const requested: ResolvedScheme =
|
|
58
|
+
scheme === 'system' ? (osScheme === 'dark' ? 'dark' : 'light') : scheme;
|
|
59
|
+
// Light theme is not shipped yet — force dark until it is.
|
|
60
|
+
const resolved: ResolvedScheme = LIGHT_THEME_ENABLED ? requested : 'dark';
|
|
61
|
+
|
|
62
|
+
const value = useMemo<ThemeContextValue>(
|
|
63
|
+
() => ({
|
|
64
|
+
colors: resolved === 'dark' ? darkColors : lightColors,
|
|
65
|
+
scheme,
|
|
66
|
+
resolved,
|
|
67
|
+
setScheme,
|
|
68
|
+
}),
|
|
69
|
+
[resolved, scheme, setScheme],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Access the active theme. Throws if used outside `ThemeProvider`. */
|
|
76
|
+
export const useTheme = (): ThemeContextValue => {
|
|
77
|
+
const ctx = useContext(ThemeContext);
|
|
78
|
+
if (!ctx) {
|
|
79
|
+
throw new Error('useTheme must be used within a ThemeProvider');
|
|
80
|
+
}
|
|
81
|
+
return ctx;
|
|
82
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Colors } from '@src/core/utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The theme color contract. Both the light and dark palettes must expose the
|
|
5
|
+
* exact same keys as the static `Colors` object, so design-system components
|
|
6
|
+
* can keep referencing token names (`colors.bg_primary_normal`) unchanged —
|
|
7
|
+
* only the *source* of the value switches with the active theme.
|
|
8
|
+
*/
|
|
9
|
+
export type ThemeColors = typeof Colors;
|
|
10
|
+
|
|
11
|
+
/** What the user can pick: an explicit theme or "follow the OS". */
|
|
12
|
+
export type ThemeScheme = 'light' | 'dark' | 'system';
|
|
13
|
+
|
|
14
|
+
/** The concrete theme actually rendered after resolving `system`. */
|
|
15
|
+
export type ResolvedScheme = 'light' | 'dark';
|
|
16
|
+
|
|
17
|
+
export interface ThemeContextValue {
|
|
18
|
+
/** Active color tokens for the resolved scheme. */
|
|
19
|
+
colors: ThemeColors;
|
|
20
|
+
/** The user's preference (may be `system`). */
|
|
21
|
+
scheme: ThemeScheme;
|
|
22
|
+
/** The concrete scheme being rendered (`system` already resolved). */
|
|
23
|
+
resolved: ResolvedScheme;
|
|
24
|
+
/** Persist and apply a new preference. */
|
|
25
|
+
setScheme: (next: ThemeScheme) => void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useTheme } from './theme-context';
|
|
3
|
+
import { ThemeColors } from './theme.types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build a themed StyleSheet that recomputes only when the active theme flips.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```ts
|
|
10
|
+
* const makeStyles = (c: ThemeColors) =>
|
|
11
|
+
* StyleSheet.create({ box: { backgroundColor: c.bg_elevation_level_1_normal } });
|
|
12
|
+
*
|
|
13
|
+
* const Component = () => {
|
|
14
|
+
* const styles = useThemedStyles(makeStyles);
|
|
15
|
+
* return <View style={styles.box} />;
|
|
16
|
+
* };
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The factory must be a stable module-scope function (not an inline arrow) so
|
|
20
|
+
* the memo key stays the theme reference, not a new function each render.
|
|
21
|
+
*/
|
|
22
|
+
export const useThemedStyles = <T>(factory: (colors: ThemeColors) => T): T => {
|
|
23
|
+
const { colors } = useTheme();
|
|
24
|
+
return useMemo(() => factory(colors), [colors, factory]);
|
|
25
|
+
};
|