Browse Source

FEATURE | Create settings screen & add purchases (#3)

Co-authored-by: Vlad <vlad960706@gmail.com>
Reviewed-on: #3
Co-authored-by: Vlad Narizhnyi <vlad960706@gmail.com>
Co-committed-by: Vlad Narizhnyi <vlad960706@gmail.com>
pull/4/head
Vlad Narizhnyi 11 months ago committed by Vitalik Yatsenko
parent
commit
8bfb4e28f5
  1. 3
      App.tsx
  2. 26
      babel.config.js
  3. 12
      ios/Podfile.lock
  4. 4
      ios/Truth.xcodeproj/project.pbxproj
  5. 16
      ios/Truth/Info.plist
  6. 37
      metro.config.js
  7. 1032
      package-lock.json
  8. 9
      package.json
  9. BIN
      src/assets/icons/ENG.png
  10. 16
      src/assets/icons/ENG.svg
  11. BIN
      src/assets/icons/UAE.png
  12. 14
      src/assets/icons/UAE.svg
  13. BIN
      src/assets/icons/UKR.png
  14. 11
      src/assets/icons/UKR.svg
  15. 19
      src/assets/image/winners-cup.svg
  16. 0
      src/i18n/interfaces/buttons.interface.ts
  17. 8
      src/i18n/interfaces/common.interface.ts
  18. 0
      src/i18n/interfaces/custom-pack.interface.ts
  19. 17
      src/i18n/interfaces/index.ts
  20. 0
      src/i18n/interfaces/on-boarding.types.interface.ts
  21. 2
      src/i18n/interfaces/page-titles.interface.ts
  22. 6
      src/i18n/interfaces/purchases.interface.ts
  23. 12
      src/i18n/interfaces/settings.types.interface.ts
  24. 11
      src/i18n/locales/en/common.translation.ts
  25. 3
      src/i18n/locales/en/custom-pack.translation.ts
  26. 19
      src/i18n/locales/en/index.ts
  27. 4
      src/i18n/locales/en/page-title.translation.ts
  28. 9
      src/i18n/locales/en/purchases.translation.ts
  29. 9
      src/i18n/locales/en/settings.translation.ts
  30. 5
      src/i18n/locales/en/steps.translation.ts
  31. 14
      src/i18n/locales/hi/index.tsx
  32. 4
      src/i18n/locales/hi/settings.translation.ts
  33. 5
      src/i18n/locales/hi/steps.translation.ts
  34. 11
      src/i18n/locales/ua/common.translation.ts
  35. 23
      src/i18n/locales/ua/index.ts
  36. 4
      src/i18n/locales/ua/page-title.translation.ts
  37. 11
      src/i18n/locales/ua/purchases.translation.ts
  38. 9
      src/i18n/locales/ua/settings.translation.ts
  39. 12
      src/i18n/types/index.ts
  40. 12
      src/i18n/types/settings.types.ts
  41. 7
      src/module/common/components/buttons/button-primary.component.tsx
  42. 60
      src/module/common/components/form/form-controll-wrap.component.tsx
  43. 117
      src/module/common/components/form/form-text-controll.component.tsx
  44. 3
      src/module/common/components/form/index.ts
  45. 11
      src/module/common/components/header/header.component.tsx
  46. 14
      src/module/common/components/index.ts
  47. 10
      src/module/common/components/layout/screen-layout.component.tsx
  48. 1
      src/module/common/hooks/index.ts
  49. 67
      src/module/common/hooks/use-form.hook.ts
  50. 1
      src/module/common/index.ts
  51. 118
      src/module/common/svg-icons/icons-svg.tsx
  52. 1
      src/module/common/svg-icons/index.ts
  53. 1
      src/module/common/tools/index.ts
  54. 67
      src/module/common/tools/validate.tool.ts
  55. 1
      src/module/common/typing/enums/index.ts
  56. 5
      src/module/common/typing/enums/products.enum.ts
  57. 10
      src/module/common/typing/enums/route-keys.enum.ts
  58. 2
      src/module/common/typing/enums/storage-key.enum.ts
  59. 2
      src/module/common/typing/interfaces/index.ts
  60. 5
      src/module/common/widgets/alert/alert-widget.component.tsx
  61. 24
      src/module/common/widgets/bottom-sheet/atoms/action-sheet-button.atom.tsx
  62. 23
      src/module/common/widgets/bottom-sheet/atoms/bottom-sheet-view.atom.tsx
  63. 1
      src/module/common/widgets/bottom-sheet/atoms/index.ts
  64. 1
      src/module/common/widgets/bottom-sheet/index.ts
  65. 1
      src/module/common/widgets/index.ts
  66. 17
      src/module/game/screens/game.screen.tsx
  67. 3
      src/module/game/screens/questions.screen.tsx
  68. 11
      src/module/packages/screens/packages-list.screen.tsx
  69. 2
      src/module/root/atoms/on-boarding-button.component.tsx
  70. 12
      src/module/root/config/on-boarding.config.ts
  71. 45
      src/module/root/navigations-groups/user.group.tsx
  72. 16
      src/module/root/screens/language-select.screen.tsx
  73. 4
      src/module/root/screens/loading-screen.tsx
  74. 13
      src/module/root/screens/on-boarding.screen.tsx
  75. 4
      src/module/settings/atoms/index.ts
  76. 88
      src/module/settings/atoms/purchases.atom.tsx
  77. 32
      src/module/settings/atoms/selected-language-in-settings.atom.tsx
  78. 22
      src/module/settings/atoms/switch-notifications.atom.tsx
  79. 30
      src/module/settings/components/settings-item.component.tsx
  80. 1
      src/module/settings/config/index.ts
  81. 16
      src/module/settings/config/purchases.config.ts
  82. 13
      src/module/settings/config/settings.config.tsx
  83. 2
      src/module/settings/index.ts
  84. 2
      src/module/settings/screens/index.ts
  85. 11
      src/module/settings/screens/privacy-policy.tsx
  86. 133
      src/module/settings/screens/purchases.screen.tsx
  87. 92
      src/module/settings/screens/settings.screen.tsx
  88. 61
      src/module/settings/screens/write-to-us.screen.tsx
  89. 1
      src/module/settings/services/index.ts
  90. 121
      src/module/settings/services/purchases.service.ts
  91. 1
      src/module/settings/validator/index.ts
  92. 15
      src/module/settings/validator/writeToUs.validator.ts
  93. 79
      tsconfig.json

3
App.tsx

@ -10,6 +10,7 @@ import { @@ -10,6 +10,7 @@ import {
SafeAreaProvider,
initialWindowMetrics,
} from 'react-native-safe-area-context'
import { SheetProvider } from 'react-native-actions-sheet'
const App = () => {
useEffect(() => {
@ -20,9 +21,11 @@ const App = () => { @@ -20,9 +21,11 @@ const App = () => {
<GestureHandlerRootView style={{ flex: 1 }}>
<NavigationContainer>
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<SheetProvider>
<Provider store={store}>
<Root />
</Provider>
</SheetProvider>
</SafeAreaProvider>
</NavigationContainer>
</GestureHandlerRootView>

26
babel.config.js

@ -1,3 +1,27 @@ @@ -1,3 +1,27 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};
plugins: [
[
require.resolve('babel-plugin-module-resolver'),
{
root: ['./'],
alias: {
/**
* Regular expression is used to match all files inside `./src` directory and map each `.src/folder/[..]` to `~folder/[..]` path
*/
'^~(.+)': './src/\\1',
},
extensions: [
'.ios.js',
'.android.js',
'.js',
'.jsx',
'.json',
'.tsx',
'.ts',
'.native.js',
],
},
],
],
}

12
ios/Podfile.lock

@ -1271,9 +1271,13 @@ PODS: @@ -1271,9 +1271,13 @@ PODS:
- nanopb (< 2.30910.0, >= 2.30908.0)
- React-Core
- RNFBApp
- RNFlashList (1.6.3):
- React-Core
- RNGestureHandler (2.13.4):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- RNIap (12.11.0):
- React-Core
- RNScreens (3.27.0):
- RCT-Folly (= 2021.07.22.00)
- React-Core
@ -1358,7 +1362,9 @@ DEPENDENCIES: @@ -1358,7 +1362,9 @@ DEPENDENCIES:
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
- "RNFBFirestore (from `../node_modules/@react-native-firebase/firestore`)"
- "RNFlashList (from `../node_modules/@shopify/flash-list`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNIap (from `../node_modules/react-native-iap`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSVG (from `../node_modules/react-native-svg`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
@ -1485,8 +1491,12 @@ EXTERNAL SOURCES: @@ -1485,8 +1491,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/app"
RNFBFirestore:
:path: "../node_modules/@react-native-firebase/firestore"
RNFlashList:
:path: "../node_modules/@shopify/flash-list"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNIap:
:path: "../node_modules/react-native-iap"
RNScreens:
:path: "../node_modules/react-native-screens"
RNSVG:
@ -1568,7 +1578,9 @@ SPEC CHECKSUMS: @@ -1568,7 +1578,9 @@ SPEC CHECKSUMS:
RNCAsyncStorage: f2974eca860c16a3e56eea5771fda8d12e2d2057
RNFBApp: 0d8bf86673bbad0524d1ceac3944d71ccf48a0e4
RNFBFirestore: f16efbd47cd136fe84cb93dc87ad4155807db221
RNFlashList: 4b4b6b093afc0df60ae08f9cbf6ccd4c836c667a
RNGestureHandler: 6e46dde1f87e5f018a54fe5d40cd0e0b942b49ee
RNIap: fc9af04ee706894a80c9d8f979bae930b0dee191
RNScreens: 3c2d122f5e08c192e254c510b212306da97d2581
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8

4
ios/Truth.xcodeproj/project.pbxproj

@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
3F5CDF352995282400BD4B7F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3F5CDF342995282400BD4B7F /* GoogleService-Info.plist */; };
52BCE0F228C76D5A008C74BC /* Fonts in Resources */ = {isa = PBXBuildFile; fileRef = 52BCE0EE28C76941008C74BC /* Fonts */; };
52BCE0F328C77143008C74BC /* fontello.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DA10DFDF3E05483BA976401B /* fontello.ttf */; };
63120D812B0DE8C900E76BCD /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63120D802B0DE8C900E76BCD /* StoreKit.framework */; };
637B62A42B07BAFB008D8917 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 637B62A32B07BAFB008D8917 /* Roboto-Bold.ttf */; };
637B62A62B07BB0B008D8917 /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 637B62A52B07BB0B008D8917 /* Roboto-Regular.ttf */; };
8D22E0E3287C39ED0031C6E5 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D22E0E2287C39ED0031C6E5 /* Launch Screen.storyboard */; };
@ -47,6 +48,7 @@ @@ -47,6 +48,7 @@
3F5CDF302995158300BD4B7F /* ios */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ios; sourceTree = "<group>"; };
3F5CDF342995282400BD4B7F /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
52BCE0EE28C76941008C74BC /* Fonts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Fonts; sourceTree = "<group>"; };
63120D802B0DE8C900E76BCD /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
637B62A12B07B836008D8917 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Roboto-Regular.ttf"; path = "../src/assets/resources/fonts/Roboto-Regular.ttf"; sourceTree = "<group>"; };
637B62A22B07B836008D8917 /* Roboto-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Roboto-Bold.ttf"; path = "../src/assets/resources/fonts/Roboto-Bold.ttf"; sourceTree = "<group>"; };
637B62A32B07BAFB008D8917 /* Roboto-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Roboto-Bold.ttf"; path = "../src/assets/resources/fonts/Roboto-Bold.ttf"; sourceTree = "<group>"; };
@ -75,6 +77,7 @@ @@ -75,6 +77,7 @@
buildActionMask = 2147483647;
files = (
BE14AC86F1757C0EC41F56B3 /* libPods-Truth.a in Frameworks */,
63120D812B0DE8C900E76BCD /* StoreKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -115,6 +118,7 @@ @@ -115,6 +118,7 @@
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
63120D802B0DE8C900E76BCD /* StoreKit.framework */,
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
7A2B08B3350C2BC99B4477A9 /* libPods-Truth.a */,
28F2047B035775EA39D23C93 /* libPods-Truth-TruthTests.a */,

16
ios/Truth/Info.plist

@ -28,6 +28,20 @@ @@ -28,6 +28,20 @@
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>New Exception Domain</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>New Exception Domain 1</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
@ -36,7 +50,7 @@ @@ -36,7 +50,7 @@
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string/>
<string></string>
<key>UIAppFonts</key>
<array>
<string>fontello.ttf</string>

37
metro.config.js

@ -1,33 +1,10 @@ @@ -1,33 +1,10 @@
/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* @format
*/
//module.exports = {
// transformer: {
// getTransformOptions: async () => ({
// transform: {
// experimentalImportSupport: false,
// inlineRequires: true,
// },
// }),
// },
//};
const { getDefaultConfig } = require('metro-config');
module.exports = (async() => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig();
return {
module.exports = {
transformer: {
babelTransformerPath: require.resolve('react-native-svg-transformer'),
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'],
}),
},
};
})();
}

1032
package-lock.json generated

File diff suppressed because it is too large Load Diff

9
package.json

@ -12,7 +12,8 @@ @@ -12,7 +12,8 @@
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"pod": "cd ./ios && pod install && cd ../"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.17.11",
@ -21,6 +22,8 @@ @@ -21,6 +22,8 @@
"@react-navigation/native": "^6.0.11",
"@react-navigation/native-stack": "^6.7.0",
"@reduxjs/toolkit": "^1.9.2",
"@shopify/flash-list": "^1.6.3",
"babel-plugin-module-resolver": "^5.0.0",
"i18next": "^21.8.14",
"i18next-react-native-async-storage": "^1.0.4",
"jet-tools": "^1.3.0",
@ -32,6 +35,7 @@ @@ -32,6 +35,7 @@
"react-native-actions-sheet": "*",
"react-native-animated-loader": "^1.0.0",
"react-native-gesture-handler": "^2.5.0",
"react-native-iap": "^12.11.0",
"react-native-icomoon": "^0.1.1",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-modal": "^13.0.1",
@ -41,7 +45,8 @@ @@ -41,7 +45,8 @@
"react-native-svg": "^12.5.1",
"react-native-svg-transformer": "^1.0.0",
"react-native-vector-icons": "^9.2.0",
"react-redux": "^8.0.5"
"react-redux": "^8.0.5",
"validate.js": "^0.13.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",

BIN
src/assets/icons/ENG.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 893 B

16
src/assets/icons/ENG.svg

@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_836_1331)">
<rect width="24" height="24" rx="12" fill="#1A47B8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-1.22563 0H-4.79999V4L25.2063 24L28.8 24V20L-1.22563 0Z" fill="white"/>
<path d="M-3.60783 0L28.8 21.6567V24H27.6358L-4.79999 2.32089V0H-3.60783Z" fill="#F93939"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.6 0H28.8V4C28.8 4 8.0157 17.3249 -1.59999 24H-4.79999V20L25.6 0Z" fill="white"/>
<path d="M28.8 0H27.7148L-4.79999 21.6753V24H-3.60783L28.8 2.33842V0Z" fill="#F93939"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.41933 0H16.6086V7.40291H28.8V16.5922H16.6086V24H7.41933V16.5922H-4.79999V7.40291H7.41933V0Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.34738 0H14.6526V9.23077H28.8V14.7692H14.6526V24H9.34738V14.7692H-4.79999V9.23077H9.34738V0Z" fill="#F93939"/>
</g>
<defs>
<clipPath id="clip0_836_1331">
<rect width="24" height="24" rx="12" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

BIN
src/assets/icons/UAE.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

14
src/assets/icons/UAE.svg

@ -1,14 +0,0 @@ @@ -1,14 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_836_1381)">
<rect width="24" height="24" rx="12" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 16C14.208 16 16 14.208 16 12C16 9.792 14.208 8 12 8C9.79201 8 8.00001 9.792 8.00001 12C8.00001 14.208 9.79201 16 12 16ZM12 14.4C13.3248 14.4 14.4 13.3248 14.4 12C14.4 10.6752 13.3248 9.6 12 9.6C10.6752 9.6 9.60001 10.6752 9.60001 12C9.60001 13.3248 10.6752 14.4 12 14.4Z" fill="#1A47B8"/>
<path d="M12 12.8C12.4418 12.8 12.8 12.4418 12.8 12C12.8 11.5582 12.4418 11.2 12 11.2C11.5582 11.2 11.2 11.5582 11.2 12C11.2 12.4418 11.5582 12.8 12 12.8Z" fill="#1A47B8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-4.79999 16H28.8V24H-4.79999V16Z" fill="#249F58"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-4.79999 0H28.8V8H-4.79999V0Z" fill="#FF6C2D"/>
</g>
<defs>
<clipPath id="clip0_836_1381">
<rect width="24" height="24" rx="12" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1021 B

BIN
src/assets/icons/UKR.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 B

11
src/assets/icons/UKR.svg

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_835_1324)">
<path d="M24 0H0V12.8H24V0Z" fill="#3A99FF"/>
<path d="M24 12H0V24.8H24V12Z" fill="#FFDA2C"/>
</g>
<defs>
<clipPath id="clip0_835_1324">
<rect width="24" height="24" rx="12" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 349 B

19
src/assets/image/winners-cup.svg

@ -1,19 +0,0 @@ @@ -1,19 +0,0 @@
<svg width="194" height="257" viewBox="0 0 194 237" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M152.5 43.5C152.5 43.5686 152.472 43.678 152.335 43.8353C152.194 43.9959 151.962 44.1786 151.616 44.3751C150.923 44.7679 149.869 45.163 148.467 45.5479C145.67 46.3156 141.598 47.0124 136.54 47.5995C126.431 48.773 112.451 49.5 97 49.5C81.549 49.5 67.5692 48.773 57.4597 47.5995C52.4016 47.0124 48.3304 46.3156 45.5331 45.5479C44.1305 45.163 43.0769 44.7679 42.3844 44.3751C42.038 44.1786 41.8059 43.9959 41.6654 43.8353C41.5277 43.678 41.5 43.5686 41.5 43.5C41.5 43.4314 41.5277 43.322 41.6654 43.1647C41.8059 43.0041 42.038 42.8214 42.3844 42.6249C43.0769 42.2321 44.1305 41.837 45.5331 41.4521C48.3304 40.6844 52.4016 39.9876 57.4597 39.4005C67.5692 38.227 81.549 37.5 97 37.5C112.451 37.5 126.431 38.227 136.54 39.4005C141.598 39.9876 145.67 40.6844 148.467 41.4521C149.869 41.837 150.923 42.2321 151.616 42.6249C151.962 42.8214 152.194 43.0041 152.335 43.1647C152.472 43.322 152.5 43.4314 152.5 43.5Z" stroke="#9AC4F8"/>
<path d="M54.9439 144.124C59.3854 144.124 62.5687 143.914 62.8546 143.895L62.2447 135.095C62.054 135.095 41.9247 136.415 31.574 132.226C23.3393 128.859 14.2467 105.616 10.2246 85.0895C7.67035 72.0811 9.06189 67.9873 10.1294 66.6865C10.3581 66.4187 10.9681 65.6726 13.2936 65.6726H21.3187C23.6443 72.6933 27.3613 79.7522 32.9655 79.7522H49.1491V70.9716H33.6136C32.2793 69.6325 30.0872 64.697 28.81 60.1058L27.9141 56.892H13.2936C11.4209 56.7803 9.54784 57.1057 7.82143 57.8425C6.09503 58.5794 4.56222 59.7076 3.34329 61.1388C-0.469093 65.7491 -0.983743 73.6689 1.60868 86.8303C3.6956 97.4254 6.80462 107.792 10.8918 117.782C16.172 130.255 22.024 137.888 28.2763 140.432C35.9773 143.531 47.3573 144.124 54.9439 144.124Z" fill="#9AC4F8"/>
<path opacity="0.2" d="M36.3015 70.9333H49.1683V79.7522H38.2839L36.3015 70.9333Z" fill="black"/>
<path opacity="0.2" d="M62.8546 143.895C62.5115 143.895 59.3853 144.124 54.9439 144.124C54.2005 144.124 53.438 144.124 52.6374 144.124L50.7312 135.286C57.1169 135.478 62.1684 135.152 62.3209 135.133L62.8546 143.895Z" fill="black"/>
<path d="M139.16 144.124C134.718 144.124 131.535 143.914 131.249 143.895L131.84 135.095C132.049 135.095 152.179 136.415 162.51 132.226C170.764 128.859 179.838 105.616 183.879 85.0894C186.414 72.0811 185.023 67.9873 183.879 66.6865C183.65 66.4186 183.04 65.6726 180.715 65.6726H172.689C170.364 72.6932 166.628 79.7522 161.043 79.7522H144.84V70.9716H160.375C162.618 67.6686 164.252 63.9882 165.198 60.1058L166.094 56.892H180.715C182.587 56.7818 184.46 57.1078 186.186 57.8446C187.912 58.5813 189.445 59.7088 190.665 61.1388C194.477 65.7491 194.973 73.6689 192.399 86.8302C190.306 97.426 187.191 107.792 183.097 117.782C177.836 130.255 171.984 137.888 165.732 140.432C158.107 143.531 146.727 144.124 139.16 144.124Z" fill="#9AC4F8"/>
<path opacity="0.2" d="M157.802 70.9333H144.935V79.7522H155.82L157.802 70.9333Z" fill="black"/>
<path opacity="0.2" d="M131.249 143.895C131.592 143.895 134.718 144.124 139.16 144.124C139.903 144.124 140.665 144.124 141.466 144.124L143.372 135.286C136.986 135.478 131.935 135.152 131.783 135.133L131.249 143.895Z" fill="black"/>
<path d="M51.2268 232.065V237H142.858V232.065H135.004L129.171 223.647L102.503 212.992V160.767H91.6V212.992L64.9323 223.647L59.0803 232.065H51.2268Z" fill="#9AC4F8"/>
<path d="M88.5881 167.731H105.515V162.24H88.5881V167.731Z" fill="#9AC4F8"/>
<path opacity="0.2" d="M88.5881 165.741L105.515 164.995V162.24H88.5881V165.741Z" fill="black"/>
<path d="M40.9715 44C40.9715 44 45.2604 122.144 56.4307 141.81C67.601 161.475 91.4475 163.139 97.0136 163.139C102.58 163.139 126.407 161.456 137.577 141.81C148.748 122.163 153.037 44 153.037 44C153.037 44 147 49 97.004 49C47.0081 49 40.9715 44 40.9715 44Z" fill="#9AC4F8"/>
<path opacity="0.3" d="M88.6262 162.336C79.0952 160.71 64.551 155.851 56.564 141.81C45.3937 122.144 41.1048 44 41.1048 44L52 47C53.4487 61.9405 55.935 123.005 64.0172 139.61C70.9939 154.129 87.4062 161.915 88.6262 162.336Z" fill="white"/>
<path opacity="0.2" d="M135.004 232.064H59.0804L61.1773 229.042L131.058 226.383L135.004 232.064Z" fill="black"/>
<path d="M97.0064 88L101.024 96.2391L110 97.5501L103.503 103.951L105.042 113L97.0064 108.733L88.9712 113L90.5095 103.951L84 97.5501L92.9887 96.2391L97.0064 88Z" fill="white"/>
<path d="M17.0059 163L20.7144 170.58L29 171.786L23.0029 177.675L24.423 186L17.0059 182.074L9.58876 186L11.0088 177.675L5 171.786L13.2973 170.58L17.0059 163Z" fill="#A798FF"/>
<path d="M97.0059 0L100.714 7.57995L109 8.78611L103.003 14.6751L104.423 23L97.0059 19.074L89.5888 23L91.0088 14.6751L85 8.78611L93.2973 7.57995L97.0059 0Z" fill="#A798FF"/>
<path d="M175.006 163L178.714 170.58L187 171.786L181.003 177.675L182.423 186L175.006 182.074L167.589 186L169.009 177.675L163 171.786L171.297 170.58L175.006 163Z" fill="#A798FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

0
src/i18n/types/buttons.types.ts → src/i18n/interfaces/buttons.interface.ts

8
src/i18n/interfaces/common.interface.ts

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
interface Validation {
isRequire: string
}
export interface Common {
validate: Validation
shareMessage: string
}

0
src/module/common/typing/interfaces/custom-pack.ts → src/i18n/interfaces/custom-pack.interface.ts

17
src/i18n/interfaces/index.ts

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
import { Buttons } from './buttons.interface'
import { Common } from './common.interface'
import { CustomPack } from './custom-pack.interface'
import { OnBoardingLocale } from './on-boarding.types.interface'
import { PageTitles } from './page-titles.interface'
import { PurchasesTranslate } from './purchases.interface'
import { SettingLocale } from './settings.types.interface'
export interface MainLocaleModule {
stepTranslation: OnBoardingLocale.OnboardingSteps
settingTranslation: SettingLocale.Core
buttonsTranslation: Buttons
customPack: CustomPack
pageTitles: PageTitles
common: Common
purchases: PurchasesTranslate
}

0
src/i18n/types/on-boarding.types.ts → src/i18n/interfaces/on-boarding.types.interface.ts

2
src/module/common/typing/interfaces/page-titles.ts → src/i18n/interfaces/page-titles.interface.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
export interface PageTitles {
setting: string,
settings: string,
privacy: string,
terms: string,
}

6
src/i18n/interfaces/purchases.interface.ts

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
export interface PurchasesTranslate {
alertSuccess: string
descSuccess: string
alertError: string
descError: string
}

12
src/i18n/interfaces/settings.types.interface.ts

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
export namespace SettingLocale {
export interface Core {
purchases: string
language: string
notifications: string
write: string
rate: string
share: string
policy: string
label: string
}
}

11
src/i18n/locales/en/common.translation.ts

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
import { Common } from '../../interfaces/common.interface'
const Validation = {
isRequire: 'Field is require',
length: 'At least 5 characters',
}
export const common: Common = {
validate: Validation,
shareMessage: 'Share this app with your friends',
}

3
src/i18n/locales/en/custom-pack.translation.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
export const customPack = {
title: 'Create custom pack',
description: 'Create your own custom pack with questions and task. It all depends on your imagination!',
description:
'Create your own custom pack with questions and task. It all depends on your imagination!',
}

19
src/i18n/locales/en/index.ts

@ -1,13 +1,18 @@ @@ -1,13 +1,18 @@
import {MainLocaleModule} from '../../types';
import {settingTranslation} from './settings.translation';
import {onBoardingTranslation} from './steps.translation';
import {buttonsTranslation} from './onBoardingButton.translation';
import {customPack} from './custom-pack.translation';
import {pageTitles} from './page-title.translation';
import { MainLocaleModule } from '../../interfaces'
import { settingTranslation } from './settings.translation'
import { onBoardingTranslation } from './steps.translation'
import { buttonsTranslation } from './onBoardingButton.translation'
import { customPack } from './custom-pack.translation'
import { pageTitles } from './page-title.translation'
import { common } from './common.translation'
import { purchases } from './purchases.translation'
export const en: MainLocaleModule = {
settingTranslation,
stepTranslation: onBoardingTranslation,
buttonsTranslation,
customPack,
pageTitles,
};
purchases,
common,
}

4
src/i18n/locales/en/page-title.translation.ts

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
export const pageTitles = {
setting: 'Setting',
settings: 'Settings',
purchases: 'Purchases',
privacy: 'Privacy Policy',
terms: 'Terms and conditions',
writeToUs: 'Write to us',
}

9
src/i18n/locales/en/purchases.translation.ts

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
import { PurchasesTranslate } from '~i18n/interfaces/purchases.interface'
export const purchases: PurchasesTranslate = {
alertSuccess: 'Ready!',
descSuccess: 'Now play with your friends! Enjoy the game 😊',
alertError: 'Ops, purchase failed 😢',
descError:
'There was an error processing your purchase. Please try again later.',
}

9
src/i18n/locales/en/settings.translation.ts

@ -1,11 +1,12 @@ @@ -1,11 +1,12 @@
import {SettingLocale} from '../../types/settings.types';
import { SettingLocale } from '../../interfaces/settings.types.interface'
export const settingTranslation: SettingLocale.Core = {
purchases: 'Purchases!',
language: 'Language',
notifications: 'Notifications',
write: 'Write to us',
rate: 'Rate us',
share: 'Share app',
policy: 'Privacy policy',
term: 'Terms and conditions',
information: 'Information',
};
label: 'What can we do to help?',
}

5
src/i18n/locales/en/steps.translation.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import {OnBoardingLocale} from '../../types/on-boarding.types';
import { OnBoardingLocale } from '../../interfaces/on-boarding.types.interface'
export const onBoardingTranslation: OnBoardingLocale.OnboardingSteps = {
step1: {
title: 'Welcome!',
@ -15,5 +15,4 @@ export const onBoardingTranslation: OnBoardingLocale.OnboardingSteps = { @@ -15,5 +15,4 @@ export const onBoardingTranslation: OnBoardingLocale.OnboardingSteps = {
description:
'Provides unlimited access to Hard and \n Extreme packages. Enjoy intriguing questions \n and exciting action.',
},
};
}

14
src/i18n/locales/hi/index.tsx

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
import {MainLocaleModule} from '../../types';
import {settingTranslation} from './settings.translation';
import {onBoardingTranslation} from './steps.translation';
import { buttonsTranslation } from './onBoardingButton.translation';
import {customPack} from './custom-pack.translation';
import {pageTitles} from './page-title.translation';
import { MainLocaleModule } from '../../interfaces'
import { settingTranslation } from './settings.translation'
import { onBoardingTranslation } from './steps.translation'
import { buttonsTranslation } from './onBoardingButton.translation'
import { customPack } from './custom-pack.translation'
import { pageTitles } from './page-title.translation'
export const hi: MainLocaleModule = {
settingTranslation,
@ -11,4 +11,4 @@ export const hi: MainLocaleModule = { @@ -11,4 +11,4 @@ export const hi: MainLocaleModule = {
buttonsTranslation,
customPack,
pageTitles,
};
}

4
src/i18n/locales/hi/settings.translation.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import {SettingLocale} from '../../types/settings.types';
import { SettingLocale } from '../../interfaces/settings.types.interface'
export const settingTranslation: SettingLocale.Core = {
purchases: 'खरद!',
language: 'भ',
@ -8,4 +8,4 @@ export const settingTranslation: SettingLocale.Core = { @@ -8,4 +8,4 @@ export const settingTranslation: SettingLocale.Core = {
policy: 'गपनयति',
term: 'नियम और शर',
information: 'जनक',
};
}

5
src/i18n/locales/hi/steps.translation.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import {OnBoardingLocale} from '../../types/on-boarding.types';
import { OnBoardingLocale } from '../../interfaces/on-boarding.types.interface'
export const onBoardingTranslation: OnBoardingLocale.OnboardingSteps = {
step1: {
title: 'सगत!',
@ -15,5 +15,4 @@ export const onBoardingTranslation: OnBoardingLocale.OnboardingSteps = { @@ -15,5 +15,4 @@ export const onBoardingTranslation: OnBoardingLocale.OnboardingSteps = {
description:
'हड और \n एकसटम पज तक असित पहच परदन करत। दिलचसप सव \n और रचक एकशन क आनद ल।',
},
};
}

11
src/i18n/locales/ua/common.translation.ts

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
import { Common } from '../../interfaces/common.interface'
const Validation = {
isRequire: 'Це поле обовʼязкове',
length: 'Має бути мінімум 5 символів',
}
export const common: Common = {
validate: Validation,
shareMessage: 'Поділіться цим додатком зі своїми друзями',
}

23
src/i18n/locales/ua/index.ts

@ -1,15 +1,18 @@ @@ -1,15 +1,18 @@
import {MainLocaleModule} from '../../types';
import {settingTranslation} from './settings.translation';
import {onBoardingTranslationUa} from './step.translation';
import { buttonsTranslation } from './onBoardingButton.translation';
import {customPack} from './custom-pack.translation';
import {pageTitles} from './page-title.translation';
import { MainLocaleModule } from '../../interfaces'
import { settingTranslation } from './settings.translation'
import { onBoardingTranslationUa } from './step.translation'
import { buttonsTranslation } from './onBoardingButton.translation'
import { customPack } from './custom-pack.translation'
import { pageTitles } from './page-title.translation'
import { common } from './common.translation'
import { purchases } from './purchases.translation'
export const ua: MainLocaleModule = {
stepTranslation: onBoardingTranslationUa,
settingTranslation: settingTranslation,
settingTranslation,
buttonsTranslation,
customPack,
pageTitles
};
pageTitles,
purchases,
common,
}

4
src/i18n/locales/ua/page-title.translation.ts

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
export const pageTitles = {
setting: 'Налаштування',
purchases: 'Покупки',
settings: 'Налаштування',
privacy: 'Політика \n конфіденційності',
terms: 'Правила та умови',
writeToUs: 'Напишіть нам',
}

11
src/i18n/locales/ua/purchases.translation.ts

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
import { PurchasesTranslate } from "~i18n/interfaces/purchases.interface";
export const purchases: PurchasesTranslate = {
alertSuccess: 'Ура! Готово!',
descSuccess: 'Тепер зіграйте з друзями! Насолоджуйтеся грою 😊',
alertError: 'Упс, щось не так 😢',
descError:
'Виникла помилка обробки вашої покупки. Будь ласка, спробуйте пізніше.',
}

9
src/i18n/locales/ua/settings.translation.ts

@ -1,11 +1,14 @@ @@ -1,11 +1,14 @@
import {SettingLocale} from '../../types/settings.types';
import { SettingLocale } from '../../interfaces/settings.types.interface'
export const settingTranslation: SettingLocale.Core = {
purchases: 'Магазин!',
purchases: 'Магазин',
language: 'Мова',
notifications: 'Сповіщення',
write: 'Напишіть нам',
rate: 'Оцініть нас',
share: 'Поділитися програмою',
policy: 'Політика конфіденційності',
term: 'Правила та умови',
information: 'Інформація',
};
label: 'Чим ми можемо допомогти',
}

12
src/i18n/types/index.ts

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
import { CustomPack, PageTitles } from '../../module/common'
import { Buttons } from './buttons.types'
import { OnBoardingLocale } from './on-boarding.types'
import { SettingLocale } from './settings.types'
export interface MainLocaleModule {
stepTranslation: OnBoardingLocale.OnboardingSteps
settingTranslation: SettingLocale.Core
buttonsTranslation: Buttons
customPack: CustomPack
pageTitles: PageTitles
}

12
src/i18n/types/settings.types.ts

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
export namespace SettingLocale {
export interface Core {
purchases: string;
language: string;
write: string;
rate: string;
share: string;
policy: string;
term: string;
information: string;
}
}

7
src/module/common/components/buttons/button-primary.component.tsx

@ -60,7 +60,12 @@ export const ButtonPrimary: FC<PropsWithChildren<IButtonPrimaryProps>> = ({ @@ -60,7 +60,12 @@ export const ButtonPrimary: FC<PropsWithChildren<IButtonPrimaryProps>> = ({
activeOpacity={0.6}
disabled={disabled}
onPress={onPress}
style={[styles.container, style, { marginBottom: mb, width }]}>
style={[
styles.container,
style,
{ marginBottom: mb, width },
disabled && { backgroundColor: colors.blue },
]}>
<Txt
font={txtFont}
mod={txtMod}

60
src/module/common/components/form/form-controll-wrap.component.tsx

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
import React, { FC, PropsWithChildren } from 'react'
import { StyleSheet, View, ViewStyle } from 'react-native'
import { Txt } from '../txt'
import { $size } from '../../helpers'
import { colors } from '../../colors'
interface FormControllWrapProps {
label?: string
error?: string
style?: ViewStyle
rightLabel?: string
}
export const FormControllWrap: FC<PropsWithChildren<FormControllWrapProps>> = ({
label,
error,
children,
style,
rightLabel,
}) => {
return (
<View style={[styles.container, style]}>
<View style={styles.labelWrap}>
{label ? (
<Txt mod="lg" style={styles.label}>
{label}
</Txt>
) : null}
{rightLabel ? (
<Txt style={styles.label}>{rightLabel}</Txt>
) : null}
</View>
{children}
{error ? <Txt style={styles.error}>{error}</Txt> : null}
</View>
)
}
const styles = StyleSheet.create({
container: {
marginBottom: $size(12),
width: '100%',
},
labelWrap: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: $size(5),
},
label: {
color: colors.secondaryText,
marginBottom: $size(8),
},
error: {
color: colors.red,
fontSize: $size(13),
marginTop: $size(5),
},
})

117
src/module/common/components/form/form-text-controll.component.tsx

@ -0,0 +1,117 @@ @@ -0,0 +1,117 @@
import React, { FC } from 'react'
import {
StyleSheet,
TextInput,
TextInputProps,
View,
ViewStyle,
} from 'react-native'
import { Txt } from '../txt'
import { FormControllWrap } from './form-controll-wrap.component'
import { Font } from '../../typing'
import { $size } from '../../helpers'
import { colors } from '../../colors'
interface FormTextControllProps {
value: string
onChange: (value: string) => void
inputProps?: Omit<TextInputProps, 'value' | 'onChange'>
label?: string
error?: string
postfix?: string
subtext?: string
inputStyle?: ViewStyle
style?: ViewStyle
}
export const FormTextControll: FC<FormTextControllProps> = ({
value,
onChange,
inputProps = {},
label,
error,
postfix,
subtext,
inputStyle,
style,
}) => {
const renderPostfix = () => {
if (postfix)
return (
<View style={styles.rightComponent}>
<Txt style={styles.postfix}>{postfix}</Txt>
</View>
)
}
return (
<FormControllWrap label={label} error={error} style={style}>
<View style={styles.inputContainer}>
<TextInput
style={[
styles.input,
error ? styles.inputActive : null,
inputStyle,
]}
value={value}
onChangeText={onChange}
placeholderTextColor="#A0A3BD"
{...inputProps}
/>
{renderPostfix()}
</View>
{subtext ? <Txt style={styles.subtext}>{subtext}</Txt> : null}
</FormControllWrap>
)
}
const styles = StyleSheet.create({
containerActive: {
borderColor: '#FB5450',
},
inputContainer: {
position: 'relative',
paddingRight: 1,
},
input: {
borderColor: colors.secondaryText,
borderWidth: 1,
height: $size(50),
paddingHorizontal: $size(18),
borderRadius: 16,
color: colors.textPrimary,
fontFamily: Font.Roboto400,
fontSize: $size(15),
lineHeight: $size(20),
},
inputActive: {
borderColor: '#FB5450',
},
rightComponent: {
position: 'absolute',
right: 0,
top: 0,
height: 56,
justifyContent: 'center',
alignItems: 'center',
paddingRight: 20,
// width: 100,
},
postfix: {
color: '#9693AC',
fontSize: $size(15),
},
subtext: {
color: '#808080',
fontSize: $size(12),
marginBottom: 4,
},
})

3
src/module/common/components/form/index.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
export * from './form-controll-wrap.component';
export * from './form-text-controll.component';

11
src/module/common/components/header/header.component.tsx

@ -5,6 +5,7 @@ import { Icon } from '../icon' @@ -5,6 +5,7 @@ import { Icon } from '../icon'
import { $size } from '../../helpers'
import { Txt } from '../txt'
import { Font } from '../../typing'
import { useNav } from '~module/common/hooks'
interface IProps {
onPressLeft?: () => any
@ -16,19 +17,25 @@ interface IProps { @@ -16,19 +17,25 @@ interface IProps {
}
export const Header: FC<IProps> = ({
onPressLeft,
leftIcon,
leftIcon = 'arrow',
rightIcon,
title,
gamer,
onPressRight,
}) => {
const nav = useNav()
const goBack = () => {
nav.goBack()
}
return (
<View style={styles.header}>
<View style={styles.button}>
{leftIcon && (
<TouchableOpacity
style={styles.button}
onPress={onPressLeft}>
onPress={onPressLeft || goBack}>
<Icon
name={leftIcon}
size={$size(24)}

14
src/module/common/components/index.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
export * from './icon';
export * from './buttons';
export * from './header';
export * from './layout';
export * from './modal';
export * from './txt';
export * from './icon'
export * from './buttons'
export * from './header'
export * from './layout'
export * from './modal'
export * from './txt'
export * from './form'

10
src/module/common/components/layout/screen-layout.component.tsx

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import React, { ReactElement } from 'react'
import {
ColorValue,
Platform,
StatusBar,
StyleSheet,
View,
@ -49,6 +50,14 @@ export const ScreenLayout = ({ @@ -49,6 +50,14 @@ export const ScreenLayout = ({
$size(44),
)
const safeAreaLayOut = () => {
if (bottomSafeArea) {
return insets.bottom + 5
} else {
return 0
}
}
return (
<>
<View
@ -70,6 +79,7 @@ export const ScreenLayout = ({ @@ -70,6 +79,7 @@ export const ScreenLayout = ({
background,
colors.primaryColor,
),
paddingBottom: safeAreaLayOut(),
},
]}>
<ScreenLayoutContent

1
src/module/common/hooks/index.ts

@ -2,3 +2,4 @@ export * from './use-nav.hook' @@ -2,3 +2,4 @@ export * from './use-nav.hook'
export * from './use-dispatch.hook'
export * from './use-events-listener.hook'
export * from './use-selector.hook'
export * from './use-form.hook'

67
src/module/common/hooks/use-form.hook.ts

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
import _ from 'lodash'
import React, { useState } from 'react'
export interface IUseFormState {
[key: string]: string | number | boolean | any
}
type IValidateMethod<T> = (data: T) => FormErrors<T> | null
export type FormErrors<T> = Partial<Record<keyof T, string>>
export interface IForm<T> {
values: T
setForm: (form: T) => void
errors: FormErrors<T>
setFormField: (key: keyof T, value: any) => any
setFormError: (key: keyof T, error: string) => void
setFormErrors: (errors: Record<keyof T, string>) => void
onSubmit: (callback: Function) => Function
hasErrors: Boolean
}
export const useForm = <T extends IUseFormState>(
initValue: Partial<T>,
validateMethod: IValidateMethod<T>,
): IForm<T> => {
const [values, setForm] = useState(initValue as T)
const [errors, setErrors] = useState<FormErrors<T>>({})
const setFormError = (f: keyof T, e: any) => {
setErrors(oldErrors => {
return { ...oldErrors, [f]: e }
})
}
const setFormField = (f: keyof T, v: any) => {
setForm(oldForm => {
return { ...oldForm, [f]: v }
})
setFormError(f, null)
}
const validate = () => {
const _errors = validateMethod(values)
if (_errors) {
setErrors(_errors)
return true
}
}
const onSubmit = (callback: Function): any => {
if (validate && validate()) {
return
}
callback()
}
return {
values,
setForm,
errors,
setFormField,
setFormError,
setFormErrors: setErrors,
onSubmit,
hasErrors: !_.isEmpty(_.omitBy(errors, _.isNil)),
}
}

1
src/module/common/index.ts

@ -6,3 +6,4 @@ export * from './hooks' @@ -6,3 +6,4 @@ export * from './hooks'
export * from './events'
export * from './tools'
export * from './widgets'
export * from './svg-icons'

118
src/assets/image/glass.svg → src/module/common/svg-icons/icons-svg.tsx

@ -1,3 +1,71 @@ @@ -1,3 +1,71 @@
import React from 'react'
import { SvgXml } from 'react-native-svg'
import { $size } from '../helpers'
export const EngSvg = () => {
const svgXml = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_836_1331)">
<rect width="24" height="24" rx="12" fill="#1A47B8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-1.22563 0H-4.79999V4L25.2063 24L28.8 24V20L-1.22563 0Z" fill="white"/>
<path d="M-3.60783 0L28.8 21.6567V24H27.6358L-4.79999 2.32089V0H-3.60783Z" fill="#F93939"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.6 0H28.8V4C28.8 4 8.0157 17.3249 -1.59999 24H-4.79999V20L25.6 0Z" fill="white"/>
<path d="M28.8 0H27.7148L-4.79999 21.6753V24H-3.60783L28.8 2.33842V0Z" fill="#F93939"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.41933 0H16.6086V7.40291H28.8V16.5922H16.6086V24H7.41933V16.5922H-4.79999V7.40291H7.41933V0Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.34738 0H14.6526V9.23077H28.8V14.7692H14.6526V24H9.34738V14.7692H-4.79999V9.23077H9.34738V0Z" fill="#F93939"/>
</g>
<defs>
<clipPath id="clip0_836_1331">
<rect width="24" height="24" rx="12" fill="white"/>
</clipPath>
</defs>
</svg>
`
return <SvgXml width={$size(24)} height={$size(24)} xml={svgXml} />
}
export const UaeSvg = () => {
const svgXml = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_836_1381)">
<rect width="24" height="24" rx="12" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 16C14.208 16 16 14.208 16 12C16 9.792 14.208 8 12 8C9.79201 8 8.00001 9.792 8.00001 12C8.00001 14.208 9.79201 16 12 16ZM12 14.4C13.3248 14.4 14.4 13.3248 14.4 12C14.4 10.6752 13.3248 9.6 12 9.6C10.6752 9.6 9.60001 10.6752 9.60001 12C9.60001 13.3248 10.6752 14.4 12 14.4Z" fill="#1A47B8"/>
<path d="M12 12.8C12.4418 12.8 12.8 12.4418 12.8 12C12.8 11.5582 12.4418 11.2 12 11.2C11.5582 11.2 11.2 11.5582 11.2 12C11.2 12.4418 11.5582 12.8 12 12.8Z" fill="#1A47B8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-4.79999 16H28.8V24H-4.79999V16Z" fill="#249F58"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M-4.79999 0H28.8V8H-4.79999V0Z" fill="#FF6C2D"/>
</g>
<defs>
<clipPath id="clip0_836_1381">
<rect width="24" height="24" rx="12" fill="white"/>
</clipPath>
</defs>
</svg>
`
return <SvgXml width={$size(24)} height={$size(24)} xml={svgXml} />
}
export const UaSvg = () => {
const svgXml = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_835_1324)">
<path d="M24 0H0V12.8H24V0Z" fill="#3A99FF"/>
<path d="M24 12H0V24.8H24V12Z" fill="#FFDA2C"/>
</g>
<defs>
<clipPath id="clip0_835_1324">
<rect width="24" height="24" rx="12" fill="white"/>
</clipPath>
</defs>
</svg>
`
return <SvgXml width={$size(24)} height={$size(24)} xml={svgXml} />
}
export const GlassSvg = () => {
const svgXml = `
<svg width="375" height="230" viewBox="0 0 120 230" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M112.526 30.3075C99.5502 23.5561 72.1029 22.9649 60.8434 23.029C49.5768 22.9649 22.1366 23.5561 9.15364 30.3075C8.01832 30.916 7.08653 31.8443 6.47381 32.9773C5.86108 34.1103 5.59436 35.3983 5.70671 36.6815C7.13106 55.369 13.4338 135.297 16.0902 142.96C18.7894 150.751 25.8043 166.611 55.0036 170.022V175.527C55.0036 175.527 47.7251 179.865 48.594 192.15C48.594 192.15 47.8818 205.617 57.3965 206.735V209.007L31.2097 222.161C30.7243 222.394 30.3264 222.777 30.0742 223.253C29.8221 223.729 29.729 224.273 29.8087 224.806C29.8883 225.338 30.1364 225.831 30.5167 226.213C30.897 226.594 31.3895 226.844 31.9219 226.925L40.1048 228.214C53.8494 230.394 67.8516 230.394 81.5962 228.214L89.9002 226.904C90.4303 226.82 90.92 226.57 91.298 226.189C91.6761 225.809 91.9228 225.317 92.0022 224.786C92.0817 224.256 91.9897 223.714 91.7398 223.239C91.4898 222.764 91.0948 222.381 90.6124 222.146L64.974 209V206.706C74.8448 206.137 73.805 191.858 73.805 191.858C74.5172 177.757 66.6833 175.478 66.6833 175.478V169.972C95.8825 166.568 102.897 150.708 105.597 142.91C108.246 135.247 114.534 55.312 115.98 36.6316C116.082 35.3558 115.809 34.078 115.196 32.9548C114.582 31.8315 113.654 30.9115 112.526 30.3075Z" fill="#9AC4F8"/>
<path d="M17.3508 82.4958C19.2024 103.014 21.2321 137.333 22.4215 140.666C24.7361 147.24 30.7611 160.607 55.8226 163.456H65.8714C90.9328 160.607 96.9579 147.226 99.2724 140.666C100.547 137.049 103.788 99.9441 105.746 77.8738L17.3508 82.4958Z" fill="#99EDCC"/>
@ -108,3 +176,53 @@ @@ -108,3 +176,53 @@
<path d="M148.142 64.2712C147.992 64.2712 147.8 63.7228 147.337 62.7685C146.552 61.1253 145.449 59.6539 144.092 58.4389C142.735 57.2239 141.152 56.2895 139.432 55.6895C136.927 54.8555 134.245 54.7031 131.662 55.2479C129.051 55.7803 126.588 56.8792 124.448 58.467C122.687 59.7928 121.117 61.3554 119.783 63.1103C117.504 66.1513 116.578 68.4089 116.301 68.2879C116.187 68.2451 116.301 67.6327 116.635 66.5858C117.168 65.1057 117.863 63.6893 118.708 62.3626C119.982 60.3797 121.572 58.6187 123.415 57.1494C125.713 55.3108 128.415 54.0461 131.299 53.4604C134.214 52.8528 137.241 53.0824 140.03 54.1227C142.284 55.001 144.274 56.4458 145.806 58.3174C146.838 59.5684 147.59 61.0252 148.014 62.5905C148.292 63.6231 148.242 64.2712 148.142 64.2712Z" fill="white"/>
<path d="M64.8957 166.604C64.9455 166.839 63.6351 167.359 61.4131 167.75C55.5693 168.698 49.5784 168.071 44.0574 165.934C41.9636 165.087 40.7956 164.311 40.8882 164.09C41.1018 163.577 46.2153 165.514 52.81 166.155C59.4048 166.796 64.796 166.063 64.8957 166.604Z" fill="white"/>
</svg>
`
return <SvgXml xml={svgXml} />
}
export const HeartsSvg = () => {
const svgXml = `
<svg width="375" height="310" viewBox="0 0 375 310" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M111.43 181.66C110.713 180.536 109.665 179.663 108.43 179.16C107.26 178.615 105.95 178.447 104.68 178.68C104.022 178.836 103.403 179.122 102.857 179.52C102.311 179.918 101.85 180.422 101.5 181C101.08 181.697 100.836 182.487 100.79 183.3C100.194 182.873 99.5109 182.583 98.79 182.45C98.1169 182.333 97.4271 182.353 96.7621 182.51C96.0971 182.666 95.4705 182.955 94.92 183.36C93.906 184.163 93.1654 185.259 92.8 186.5C92.3858 187.75 92.3648 189.097 92.74 190.36C93.2508 191.965 94.2595 193.366 95.62 194.36C97.1128 195.304 98.8103 195.877 100.57 196.03C103.46 196.37 107.22 196.73 107.22 196.73C107.22 196.73 108.89 193.73 110.54 191.3C111.585 189.874 112.271 188.217 112.54 186.47C112.698 184.789 112.308 183.102 111.43 181.66Z" fill="#99EDCC"/>
<path d="M157.957 240.082C157.24 238.958 156.191 238.085 154.957 237.582C153.787 237.037 152.476 236.869 151.207 237.102C150.549 237.258 149.929 237.544 149.384 237.942C148.838 238.341 148.376 238.844 148.027 239.422C147.606 240.12 147.363 240.909 147.317 241.722C146.721 241.296 146.038 241.005 145.317 240.872C144.644 240.755 143.954 240.776 143.289 240.932C142.624 241.088 141.997 241.378 141.447 241.782C140.433 242.585 139.692 243.682 139.327 244.922C138.913 246.172 138.892 247.52 139.267 248.782C139.778 250.388 140.786 251.789 142.147 252.782C143.64 253.726 145.337 254.299 147.097 254.452C149.987 254.792 153.747 255.152 153.747 255.152C153.747 255.152 155.417 252.152 157.067 249.722C158.112 248.296 158.798 246.639 159.067 244.892C159.224 243.211 158.835 241.524 157.957 240.082Z" fill="#99EDCC"/>
<path d="M236.13 114.37C236.143 112.674 235.641 111.014 234.69 109.61C233.811 108.214 232.522 107.125 231 106.49C229.684 105.988 228.239 105.933 226.889 106.334C225.539 106.736 224.358 107.571 223.53 108.71C223.182 107.829 222.642 107.037 221.95 106.39C221.305 105.796 220.546 105.338 219.719 105.045C218.893 104.751 218.016 104.627 217.14 104.68C215.487 104.833 213.923 105.501 212.67 106.59C211.349 107.652 210.386 109.094 209.91 110.72C209.342 112.804 209.457 115.016 210.24 117.03C211.193 119.089 212.622 120.892 214.41 122.29C217.3 124.67 221.11 127.68 221.11 127.68C221.11 127.68 225.03 125.57 228.49 124.13C230.606 123.31 232.495 121.997 234 120.3C235.33 118.605 236.078 116.525 236.13 114.37Z" fill="#99EDCC"/>
<path d="M89.31 95.2C87.7297 91.9782 85.1993 89.3185 82.06 87.58C79.0565 85.7632 75.56 84.931 72.06 85.2C69.083 85.4976 66.2874 86.7694 64.1068 88.8179C61.9263 90.8665 60.4827 93.5773 60 96.53C57.8512 94.6204 55.1511 93.4431 52.2896 93.1681C49.4281 92.8931 46.5532 93.5346 44.08 95C41.0957 96.8616 38.7727 99.6149 37.44 102.87C35.9481 106.138 35.4977 109.787 36.15 113.32C37.0527 117.806 39.3777 121.881 42.78 124.94C46.35 128.02 51.1 129.88 55.7 130.94C63.43 132.69 73.52 134.76 73.52 134.76C73.52 134.76 78.92 127 84.09 121C87.16 117.42 89.86 113.1 90.9 108.5C91.8133 104.011 91.2558 99.3472 89.31 95.2Z" fill="#9AC4F8"/>
<path d="M305.4 77.22C305.145 73.98 303.898 70.8971 301.83 68.39C299.906 65.8528 297.234 63.9847 294.19 63.05C291.589 62.3162 288.818 62.4604 286.307 63.4601C283.797 64.4597 281.685 66.2592 280.3 68.58C279.481 66.9548 278.314 65.5308 276.88 64.41C275.541 63.3913 274.011 62.6529 272.38 62.2388C270.75 61.8247 269.053 61.7435 267.39 62C264.259 62.5788 261.387 64.1246 259.18 66.42C256.837 68.6732 255.242 71.5913 254.61 74.78C253.875 78.8598 254.475 83.0678 256.32 86.78C258.491 90.5613 261.536 93.7674 265.2 96.13C271.12 100.13 278.93 105.28 278.93 105.28C278.93 105.28 286.06 100.57 292.41 97.28C296.32 95.3575 299.714 92.529 302.31 89.03C304.587 85.5299 305.671 81.3869 305.4 77.22Z" fill="#9AC4F8"/>
<path d="M168.31 73.61C168.368 71.8098 167.868 70.0358 166.88 68.53C165.975 67.0206 164.617 65.8345 163 65.14C162.133 64.7977 161.206 64.6345 160.275 64.6603C159.343 64.6861 158.427 64.9003 157.58 65.29C156.564 65.7546 155.678 66.4621 155 67.35C154.648 66.4064 154.092 65.5525 153.37 64.85C152.695 64.2062 151.898 63.7052 151.025 63.377C150.152 63.0488 149.222 62.9002 148.29 62.94C146.534 63.0752 144.864 63.7532 143.51 64.88C142.09 65.9818 141.043 67.4933 140.51 69.21C139.861 71.4089 139.938 73.7584 140.73 75.91C141.696 78.1207 143.182 80.0657 145.06 81.58C148.06 84.17 152.06 87.44 152.06 87.44C152.06 87.44 156.27 85.27 159.96 83.82C162.232 82.9812 164.269 81.6094 165.9 79.82C167.345 78.0602 168.19 75.8841 168.31 73.61Z" fill="#99EDCC"/>
<path d="M248.402 232.509C249.052 230.994 249.218 229.315 248.877 227.702C248.61 226.119 247.85 224.661 246.707 223.534C245.719 222.598 244.449 222.014 243.095 221.872C241.741 221.731 240.378 222.039 239.217 222.75C239.212 221.838 239.003 220.938 238.606 220.116C238.249 219.346 237.74 218.657 237.109 218.089C236.479 217.521 235.74 217.087 234.938 216.812C233.402 216.336 231.756 216.357 230.232 216.871C228.661 217.332 227.269 218.264 226.245 219.541C224.961 221.188 224.246 223.207 224.208 225.294C224.298 227.49 224.91 229.632 225.992 231.545C227.696 234.735 229.996 238.846 229.996 238.846C229.996 238.846 234.282 238.376 237.894 238.394C240.091 238.448 242.268 237.974 244.244 237.014C246.079 236 247.538 234.42 248.402 232.509Z" fill="#99EDCC"/>
<path d="M194 126.72C192.056 122.351 188.817 118.686 184.72 116.22C180.801 113.647 176.173 112.37 171.49 112.57C167.534 112.843 163.778 114.408 160.799 117.024C157.819 119.64 155.782 123.163 155 127.05C152.231 124.395 148.689 122.69 144.886 122.183C141.083 121.676 137.218 122.393 133.85 124.23C129.789 126.568 126.564 130.122 124.63 134.39C122.487 138.666 121.712 143.498 122.41 148.23C123.389 154.23 126.267 159.757 130.62 164C135.23 168.27 141.46 171 147.53 172.6C157.73 175.32 171.06 178.55 171.06 178.55C171.06 178.55 178.63 168.55 185.8 160.77C190.07 156.16 193.88 150.53 195.48 144.46C196.903 138.541 196.384 132.321 194 126.72Z" fill="#9AC4F8"/>
<path d="M291.2 167.39C291.94 165.03 291.94 162.5 291.2 160.14C290.562 157.782 289.202 155.684 287.31 154.14C286.288 153.367 285.122 152.808 283.88 152.495C282.638 152.183 281.346 152.123 280.08 152.32C278.573 152.569 277.149 153.18 275.93 154.1C275.813 152.718 275.389 151.378 274.69 150.18C274.039 149.076 273.172 148.115 272.141 147.354C271.11 146.592 269.937 146.047 268.69 145.75C266.312 145.269 263.843 145.545 261.63 146.54C259.334 147.466 257.379 149.075 256.03 151.15C254.339 153.832 253.563 156.99 253.82 160.15C254.274 163.439 255.512 166.57 257.43 169.28C260.43 173.83 264.53 179.66 264.53 179.66C264.53 179.66 270.91 178.36 276.35 177.81C279.658 177.571 282.861 176.542 285.69 174.81C288.281 172.998 290.215 170.394 291.2 167.39Z" fill="#9AC4F8"/>
</svg>
`
return <SvgXml xml={svgXml} />
}
export const WinnersCupSvg = () => {
const svgXml = `
<svg width="194" height="257" viewBox="0 0 194 237" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M152.5 43.5C152.5 43.5686 152.472 43.678 152.335 43.8353C152.194 43.9959 151.962 44.1786 151.616 44.3751C150.923 44.7679 149.869 45.163 148.467 45.5479C145.67 46.3156 141.598 47.0124 136.54 47.5995C126.431 48.773 112.451 49.5 97 49.5C81.549 49.5 67.5692 48.773 57.4597 47.5995C52.4016 47.0124 48.3304 46.3156 45.5331 45.5479C44.1305 45.163 43.0769 44.7679 42.3844 44.3751C42.038 44.1786 41.8059 43.9959 41.6654 43.8353C41.5277 43.678 41.5 43.5686 41.5 43.5C41.5 43.4314 41.5277 43.322 41.6654 43.1647C41.8059 43.0041 42.038 42.8214 42.3844 42.6249C43.0769 42.2321 44.1305 41.837 45.5331 41.4521C48.3304 40.6844 52.4016 39.9876 57.4597 39.4005C67.5692 38.227 81.549 37.5 97 37.5C112.451 37.5 126.431 38.227 136.54 39.4005C141.598 39.9876 145.67 40.6844 148.467 41.4521C149.869 41.837 150.923 42.2321 151.616 42.6249C151.962 42.8214 152.194 43.0041 152.335 43.1647C152.472 43.322 152.5 43.4314 152.5 43.5Z" stroke="#9AC4F8"/>
<path d="M54.9439 144.124C59.3854 144.124 62.5687 143.914 62.8546 143.895L62.2447 135.095C62.054 135.095 41.9247 136.415 31.574 132.226C23.3393 128.859 14.2467 105.616 10.2246 85.0895C7.67035 72.0811 9.06189 67.9873 10.1294 66.6865C10.3581 66.4187 10.9681 65.6726 13.2936 65.6726H21.3187C23.6443 72.6933 27.3613 79.7522 32.9655 79.7522H49.1491V70.9716H33.6136C32.2793 69.6325 30.0872 64.697 28.81 60.1058L27.9141 56.892H13.2936C11.4209 56.7803 9.54784 57.1057 7.82143 57.8425C6.09503 58.5794 4.56222 59.7076 3.34329 61.1388C-0.469093 65.7491 -0.983743 73.6689 1.60868 86.8303C3.6956 97.4254 6.80462 107.792 10.8918 117.782C16.172 130.255 22.024 137.888 28.2763 140.432C35.9773 143.531 47.3573 144.124 54.9439 144.124Z" fill="#9AC4F8"/>
<path opacity="0.2" d="M36.3015 70.9333H49.1683V79.7522H38.2839L36.3015 70.9333Z" fill="black"/>
<path opacity="0.2" d="M62.8546 143.895C62.5115 143.895 59.3853 144.124 54.9439 144.124C54.2005 144.124 53.438 144.124 52.6374 144.124L50.7312 135.286C57.1169 135.478 62.1684 135.152 62.3209 135.133L62.8546 143.895Z" fill="black"/>
<path d="M139.16 144.124C134.718 144.124 131.535 143.914 131.249 143.895L131.84 135.095C132.049 135.095 152.179 136.415 162.51 132.226C170.764 128.859 179.838 105.616 183.879 85.0894C186.414 72.0811 185.023 67.9873 183.879 66.6865C183.65 66.4186 183.04 65.6726 180.715 65.6726H172.689C170.364 72.6932 166.628 79.7522 161.043 79.7522H144.84V70.9716H160.375C162.618 67.6686 164.252 63.9882 165.198 60.1058L166.094 56.892H180.715C182.587 56.7818 184.46 57.1078 186.186 57.8446C187.912 58.5813 189.445 59.7088 190.665 61.1388C194.477 65.7491 194.973 73.6689 192.399 86.8302C190.306 97.426 187.191 107.792 183.097 117.782C177.836 130.255 171.984 137.888 165.732 140.432C158.107 143.531 146.727 144.124 139.16 144.124Z" fill="#9AC4F8"/>
<path opacity="0.2" d="M157.802 70.9333H144.935V79.7522H155.82L157.802 70.9333Z" fill="black"/>
<path opacity="0.2" d="M131.249 143.895C131.592 143.895 134.718 144.124 139.16 144.124C139.903 144.124 140.665 144.124 141.466 144.124L143.372 135.286C136.986 135.478 131.935 135.152 131.783 135.133L131.249 143.895Z" fill="black"/>
<path d="M51.2268 232.065V237H142.858V232.065H135.004L129.171 223.647L102.503 212.992V160.767H91.6V212.992L64.9323 223.647L59.0803 232.065H51.2268Z" fill="#9AC4F8"/>
<path d="M88.5881 167.731H105.515V162.24H88.5881V167.731Z" fill="#9AC4F8"/>
<path opacity="0.2" d="M88.5881 165.741L105.515 164.995V162.24H88.5881V165.741Z" fill="black"/>
<path d="M40.9715 44C40.9715 44 45.2604 122.144 56.4307 141.81C67.601 161.475 91.4475 163.139 97.0136 163.139C102.58 163.139 126.407 161.456 137.577 141.81C148.748 122.163 153.037 44 153.037 44C153.037 44 147 49 97.004 49C47.0081 49 40.9715 44 40.9715 44Z" fill="#9AC4F8"/>
<path opacity="0.3" d="M88.6262 162.336C79.0952 160.71 64.551 155.851 56.564 141.81C45.3937 122.144 41.1048 44 41.1048 44L52 47C53.4487 61.9405 55.935 123.005 64.0172 139.61C70.9939 154.129 87.4062 161.915 88.6262 162.336Z" fill="white"/>
<path opacity="0.2" d="M135.004 232.064H59.0804L61.1773 229.042L131.058 226.383L135.004 232.064Z" fill="black"/>
<path d="M97.0064 88L101.024 96.2391L110 97.5501L103.503 103.951L105.042 113L97.0064 108.733L88.9712 113L90.5095 103.951L84 97.5501L92.9887 96.2391L97.0064 88Z" fill="white"/>
<path d="M17.0059 163L20.7144 170.58L29 171.786L23.0029 177.675L24.423 186L17.0059 182.074L9.58876 186L11.0088 177.675L5 171.786L13.2973 170.58L17.0059 163Z" fill="#A798FF"/>
<path d="M97.0059 0L100.714 7.57995L109 8.78611L103.003 14.6751L104.423 23L97.0059 19.074L89.5888 23L91.0088 14.6751L85 8.78611L93.2973 7.57995L97.0059 0Z" fill="#A798FF"/>
<path d="M175.006 163L178.714 170.58L187 171.786L181.003 177.675L182.423 186L175.006 182.074L167.589 186L169.009 177.675L163 171.786L171.297 170.58L175.006 163Z" fill="#A798FF"/>
</svg>
`
return <SvgXml xml={svgXml} />
}

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 53 KiB

1
src/module/common/svg-icons/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './icons-svg'

1
src/module/common/tools/index.ts

@ -1 +1,2 @@ @@ -1 +1,2 @@
export * from './global-container.tool'
export * from './validate.tool'

67
src/module/common/tools/validate.tool.ts

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
import _ from 'lodash'
import _validate from 'validate.js'
export const prepareValidatorResult = <T extends Record<string, any>>(
result: T,
): Record<keyof T, string> | null => {
if (_.isEmpty(result)) {
return null
}
_.each(result, (it, key, arr: any) => {
arr[key] = it[0]
})
return result
}
const presenceCost = {
allowEmpty: false,
message: '^Field is required',
messageStatic: '^Field is required',
}
const validate = <T extends Record<string, any>>(
values: T,
constraints: any,
) => {
const result = _validate(values, constraints)
return prepareValidatorResult<T>(result)
}
_validate.validators.array = (
arrayItems: any[],
options: { length: number; message: string; key?: string },
) => {
if (_.isEmpty(arrayItems)) {
return presenceCost.message
}
if (arrayItems.length < options.length) {
return options.message
}
if (options.key) {
if (!arrayItems[0][options.key]) {
return options.message
}
}
return null
}
_validate.validators.characters = (
value: string,
options: {
message: string
},
) => {
if (String(value).search(/[^a-zA-Zа-яА-я0-9а-яієїйьЇІ"'.)(, -]+/) !== -1) {
return options.message
? options.message
: '^common.validations.characters'
}
return null
}
export { validate, presenceCost }

1
src/module/common/typing/enums/index.ts

@ -2,3 +2,4 @@ export * from './route-keys.enum' @@ -2,3 +2,4 @@ export * from './route-keys.enum'
export * from './fonts.enum'
export * from './storage-key.enum'
export * from './language.enum'
export * from './products.enum';

5
src/module/common/typing/enums/products.enum.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
export enum ProductsEnum {
All = 'ALL',
Under18 = 'un18',
Crazy = 'Crz',
}

10
src/module/common/typing/enums/route-keys.enum.ts

@ -1,10 +1,16 @@ @@ -1,10 +1,16 @@
export enum RouteKey {
Onboarding = 'Onboarding',
LanguageSelect = 'LanguageSelect',
Setting = 'Setting',
SettingsGroup = 'SettingsGroup',
Game = 'Game',
Loading = 'Loading',
Package = 'Package',
Packages = 'Packages',
Questions = 'Questions',
}
export enum SettingsKey {
Settings = 'Settings',
PrivacyPolicy = 'PrivacyPolicy',
Purchases = 'Purchases',
WriteToUs = 'WriteToUs'
}

2
src/module/common/typing/enums/storage-key.enum.ts

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
export enum StorageKey {
OnBoarding = 'ONBOARDING_END',
Language = 'LANG_SELECTED',
Purchases = 'Purchases',
Products = 'Products',
}

2
src/module/common/typing/interfaces/index.ts

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
export * from './custom-pack'
export * from './dare'
export * from './game-item'
export * from './page-titles'
export * from './truth'

5
src/module/common/widgets/alert/alert-widget.component.tsx

@ -32,7 +32,7 @@ export const AlertWidget = () => { @@ -32,7 +32,7 @@ export const AlertWidget = () => {
'alert',
data => {
settingsRef.current = {
onClose: () => data?.onClose,
onClose: data?.onClose as any,
}
setContent({
title: data?.title,
@ -65,10 +65,11 @@ export const AlertWidget = () => { @@ -65,10 +65,11 @@ export const AlertWidget = () => {
return (
<ModalComponent
coverScreen={false}
backdropOpacity={0.8}
useNativeDriverForBackdrop={true}
backdropTransitionOutTiming={400}
hideModalContentWhileAnimating={true}
backdropColor={'#787878'}
backdropColor={'black'}
animationIn="pulse"
isVisible={Boolean(isVisible)}
onBackdropPress={close}

24
src/module/common/widgets/bottom-sheet/atoms/action-sheet-button.atom.tsx

@ -2,29 +2,34 @@ import _ from 'lodash' @@ -2,29 +2,34 @@ import _ from 'lodash'
import React, { FC } from 'react'
import { StyleSheet } from 'react-native'
import { TouchableOpacity } from 'react-native'
import { Icon } from '~modules/common'
import { Txt } from '~modules/common/components/typography'
import { $size } from '~modules/common/helpers'
import { Icon, Txt } from '../../../components'
import { $size } from '../../../helpers'
import { colors } from '../../../colors'
interface IProps {
iconName: string
icon?: () => React.ReactElement
onPress: () => void
label: string
showBottomBorder: boolean
}
export const ActionSheetButtonAtom: FC<IProps> = ({
iconName,
icon,
onPress,
showBottomBorder,
label,
}) => {
const renderIcon = () => {
if (!iconName) {
return null
}
if (iconName) {
return <Icon name={iconName} size={$size(28)} color={'#007AFF'} />
}
if (icon) {
return icon()
}
}
return (
<TouchableOpacity
onPress={onPress}
@ -33,7 +38,7 @@ export const ActionSheetButtonAtom: FC<IProps> = ({ @@ -33,7 +38,7 @@ export const ActionSheetButtonAtom: FC<IProps> = ({
{ borderBottomWidth: showBottomBorder ? 1 : 0 },
]}>
{renderIcon()}
<Txt style={styles.label} color="#FFFFFF" mod="md">
<Txt mod="lg" style={styles.label}>
{label}
</Txt>
</TouchableOpacity>
@ -44,9 +49,8 @@ const styles = StyleSheet.create({ @@ -44,9 +49,8 @@ const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#161616',
padding: $size(10),
borderBottomColor: 'rgba(212, 212, 212, 0.1)',
backgroundColor: colors.lightPurple,
padding: $size(15),
},
label: {
marginLeft: $size(10),

23
src/module/common/widgets/bottom-sheet/atoms/bottom-sheet-view.atom.tsx

@ -1,17 +1,18 @@ @@ -1,17 +1,18 @@
import React, { FC } from 'react'
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { StyleSheet, TouchableOpacity, View } from 'react-native'
import ActionSheet, { SheetProps } from 'react-native-actions-sheet'
import { Txt } from '~modules/common/components/typography'
import { SheetManager } from 'react-native-actions-sheet'
import { $size } from '~modules/common/helpers'
import { ActionSheetButtonAtom } from './action-sheet-button.atom'
import _ from 'lodash'
import { Txt } from '../../../components'
import { $size } from '../../../helpers'
import { ActionSheetButtonAtom } from './action-sheet-button.atom'
import { colors } from '../../../colors'
interface IPayloadSheet {
iconName?: string
iconName: string
onPress: () => void
label: string
icon?: () => React.ReactElement
}
export const BottomSheetView: FC<SheetProps<IPayloadSheet[]>> = ({
@ -28,13 +29,14 @@ export const BottomSheetView: FC<SheetProps<IPayloadSheet[]>> = ({ @@ -28,13 +29,14 @@ export const BottomSheetView: FC<SheetProps<IPayloadSheet[]>> = ({
statusBarTranslucent={true}
animated>
<View style={styles.actionContainer}>
{payload.map((it, index) => {
{payload?.map((it, index) => {
const showBottomBorder = payload.length - 1 !== index
return (
<ActionSheetButtonAtom
key={it.label}
icon={it?.icon}
onPress={it.onPress}
iconName={it.iconName}
iconName={it?.iconName}
label={it.label}
showBottomBorder={showBottomBorder}
/>
@ -55,11 +57,11 @@ const styles = StyleSheet.create({ @@ -55,11 +57,11 @@ const styles = StyleSheet.create({
paddingVertical: $size(18),
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#161616',
backgroundColor: colors.darkPurple,
borderRadius: 16,
},
container: {
backgroundColor: 'rgba(0,0,0,0.1)',
backgroundColor: colors.lightPurple,
paddingHorizontal: $size(12),
paddingBottom: $size(35),
},
@ -70,7 +72,6 @@ const styles = StyleSheet.create({ @@ -70,7 +72,6 @@ const styles = StyleSheet.create({
actionContainer: {
borderRadius: 16,
padding: $size(10),
backgroundColor: '#161616',
flexDirection: 'column',
marginBottom: $size(16),
},

1
src/module/common/widgets/bottom-sheet/atoms/index.ts

@ -1 +1,2 @@ @@ -1 +1,2 @@
export * from './bottom-sheet-view.atom'
export * from './action-sheet-button.atom'

1
src/module/common/widgets/bottom-sheet/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './sheets'

1
src/module/common/widgets/index.ts

@ -1,2 +1,3 @@ @@ -1,2 +1,3 @@
export * from './alert'
export * from './alert-confirm'
export * from './bottom-sheet'

17
src/module/game/screens/game.screen.tsx

@ -1,8 +1,7 @@ @@ -1,8 +1,7 @@
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { StyleSheet, TouchableOpacity, View } from 'react-native'
import {
$size,
ButtonPrimary,
ButtonWithIcon,
colors,
Header,
RouteKey,
@ -21,10 +20,6 @@ export const GameScreen: FC = () => { @@ -21,10 +20,6 @@ export const GameScreen: FC = () => {
const { params }: any = useRoute()
const packageName = params.packageName
const goBack = () => {
nav.navigate(RouteKey.Package)
}
const randomGame = () => {
const isQuestions = Math.random() < 0.5
nav.navigate(RouteKey.Questions, { isQuestions, packageName })
@ -39,15 +34,7 @@ export const GameScreen: FC = () => { @@ -39,15 +34,7 @@ export const GameScreen: FC = () => {
}
return (
<ScreenLayout
headerComponent={
<Header
leftIcon="arrow"
onPressLeft={() => goBack()}
title={packageName}
gamer
/>
}>
<ScreenLayout headerComponent={<Header title={packageName} gamer />}>
<Txt mod="xxl" style={styles.playerName}>
Player
</Txt>

3
src/module/game/screens/questions.screen.tsx

@ -27,7 +27,7 @@ export const QuestionsScreen: React.FC = () => { @@ -27,7 +27,7 @@ export const QuestionsScreen: React.FC = () => {
}
const goGameScreen = () => {
nav.navigate(RouteKey.Package)
nav.navigate(RouteKey.Packages)
dispatch(nextStep())
}
@ -40,7 +40,6 @@ export const QuestionsScreen: React.FC = () => { @@ -40,7 +40,6 @@ export const QuestionsScreen: React.FC = () => {
<ScreenLayout
headerComponent={
<Header
leftIcon="arrow"
gamer
onPressLeft={() => goBack()}
title={packageName}

11
src/module/packages/screens/packages-list.screen.tsx

@ -9,7 +9,6 @@ import { CustomPackage, PackagesPageSeparator } from '../atoms' @@ -9,7 +9,6 @@ import { CustomPackage, PackagesPageSeparator } from '../atoms'
export const PackagesListScreen: FC = () => {
const nav = useNav()
const { i18n } = useTranslation()
const lang = i18n.language
return (
<ScreenLayout
@ -17,14 +16,14 @@ export const PackagesListScreen: FC = () => { @@ -17,14 +16,14 @@ export const PackagesListScreen: FC = () => {
headerComponent={
<Header
rightIcon="setting"
onPressRight={() => nav.navigate(RouteKey.Setting)}
onPressRight={() => nav.navigate(RouteKey.SettingsGroup)}
/>
}>
<View style={styles.container}>
{packageListConfig.map((item: any, index) => (
<PackageItem
packageName={item.title[lang]}
description={item.description[lang]}
packageName={item.title[i18n.language]}
description={item.description[i18n.language]}
questions={item.questions}
actions={item.actions}
image={item.image}
@ -39,5 +38,7 @@ export const PackagesListScreen: FC = () => { @@ -39,5 +38,7 @@ export const PackagesListScreen: FC = () => {
}
const styles = StyleSheet.create({
container: {},
container: {
marginBottom: 30,
},
})

2
src/module/root/atoms/on-boarding-button.component.tsx

@ -22,7 +22,7 @@ export const OnBoardingBottom: FC<IProps> = ({ isLastBlock, onPressSkip }) => { @@ -22,7 +22,7 @@ export const OnBoardingBottom: FC<IProps> = ({ isLastBlock, onPressSkip }) => {
const onBoardFinish = async () => {
await AsyncStorage.setItem(StorageKey.OnBoarding, 'true')
nav.navigate(RouteKey.Package)
nav.navigate(RouteKey.Packages)
}
return (

12
src/module/root/config/on-boarding.config.ts

@ -1,7 +1,5 @@ @@ -1,7 +1,5 @@
import ImageHearts from '../../../assets/image/hearts.svg'
import ImageGlass from '../../../assets/image/glass.svg'
import ImageCup from '../../../assets/image/winners-cup.svg'
import { OnBoardingLocale } from '../../../i18n/types/on-boarding.types'
import { OnBoardingLocale } from '../../../i18n/interfaces/on-boarding.types.interface'
import { GlassSvg, HeartsSvg, WinnersCupSvg } from '~module/common'
const translatePath = (
itemKey: keyof OnBoardingLocale.OnboardingSteps,
@ -12,16 +10,16 @@ export const onBoardingConfig = [ @@ -12,16 +10,16 @@ export const onBoardingConfig = [
{
title: translatePath('step1', 'title'),
description: translatePath('step1', 'description'),
image: ImageHearts,
image: HeartsSvg,
},
{
title: translatePath('step2', 'title'),
description: translatePath('step2', 'description'),
image: ImageGlass,
image: GlassSvg,
},
{
title: translatePath('step3', 'title'),
description: translatePath('step3', 'description'),
image: ImageCup,
image: WinnersCupSvg,
},
]

45
src/module/root/navigations-groups/user.group.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import React, { FC } from 'react'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { RouteKey } from '../../common'
import { RouteKey, SettingsKey } from '../../common'
import { GameScreen, QuestionsScreen } from '../../game'
import { PackagesListScreen } from '../../packages'
import {
@ -9,10 +9,16 @@ import { @@ -9,10 +9,16 @@ import {
SettingsScreen,
} from '../screens'
import { LoadingScreen } from '../screens/loading-screen'
import { PrivacyPolicyScreen } from '../../settings'
import {
PrivacyPolicyScreen,
PurchasesScreen,
WriteToUsScreen,
} from '../../settings'
const Stack = createNativeStackNavigator()
const SettingsStack = createNativeStackNavigator()
export const UserNavigationGroup: FC = () => {
return (
<Stack.Navigator
@ -26,7 +32,7 @@ export const UserNavigationGroup: FC = () => { @@ -26,7 +32,7 @@ export const UserNavigationGroup: FC = () => {
component={QuestionsScreen}
/>
<Stack.Screen
name={RouteKey.Package}
name={RouteKey.Packages}
component={PackagesListScreen}
/>
<Stack.Screen
@ -39,11 +45,38 @@ export const UserNavigationGroup: FC = () => { @@ -39,11 +45,38 @@ export const UserNavigationGroup: FC = () => {
component={LanguageSelectScreen}
/>
<Stack.Screen name={RouteKey.Game} component={GameScreen} />
<Stack.Screen name={RouteKey.Setting} component={SettingsScreen} />
<Stack.Screen
name={RouteKey.PrivacyPolicy}
component={PrivacyPolicyScreen}
name={RouteKey.SettingsGroup}
component={UserSettingsNavigationGroup}
/>
</Stack.Navigator>
)
}
const UserSettingsNavigationGroup = () => {
return (
<SettingsStack.Navigator
initialRouteName={SettingsKey.Settings}
screenOptions={{
headerShown: false,
animation: 'slide_from_left',
}}>
<SettingsStack.Screen
name={SettingsKey.Settings}
component={SettingsScreen}
/>
<Stack.Screen
name={SettingsKey.PrivacyPolicy}
component={PrivacyPolicyScreen}
/>
<Stack.Screen
name={SettingsKey.Purchases}
component={PurchasesScreen}
/>
<Stack.Screen
name={SettingsKey.WriteToUs}
component={WriteToUsScreen}
/>
</SettingsStack.Navigator>
)
}

16
src/module/root/screens/language-select.screen.tsx

@ -11,28 +11,22 @@ import { @@ -11,28 +11,22 @@ import {
StorageKey,
Txt,
$size,
EngSvg,
UaSvg,
} from '../../common'
import { LanguageItem } from '../components'
import UALogo from '../../../assets/icons/UKR.svg'
import UAELogo from '../../../assets/icons/UAE.svg'
import ENGLogo from '../../../assets/icons/ENG.svg'
const languageList = [
{
name: 'Українська',
icon: <UALogo />,
icon: <UaSvg />,
key: Language.UA,
},
{
name: 'English',
icon: <ENGLogo />,
icon: <EngSvg />,
key: Language.EN,
},
{
name: 'Hindi',
icon: <UAELogo />,
key: Language.HI,
},
]
export const LanguageSelectScreen: FC = () => {
const { i18n } = useTranslation()
@ -45,7 +39,7 @@ export const LanguageSelectScreen: FC = () => { @@ -45,7 +39,7 @@ export const LanguageSelectScreen: FC = () => {
}
return (
<ScreenLayout horizontalPadding={24}>
<ScreenLayout>
<View style={styles.container}>
<Txt style={styles.title}>Choose your language</Txt>
{languageList.map(el => (

4
src/module/root/screens/loading-screen.tsx

@ -9,6 +9,7 @@ import { @@ -9,6 +9,7 @@ import {
} from '../../common'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useTranslation } from 'react-i18next'
import { PurchasesService, purchasesService } from '../../settings'
import { fetchPostsAsync } from '../../../store/slices'
export const LoadingScreen: FC = () => {
@ -39,7 +40,8 @@ export const LoadingScreen: FC = () => { @@ -39,7 +40,8 @@ export const LoadingScreen: FC = () => {
}
if (isOnBoard && language) {
nav.navigate(RouteKey.Package)
nav.navigate(RouteKey.Packages)
purchasesService.init()
} else if (language && !isOnBoard) {
nav.navigate(RouteKey.Onboarding)
} else if (!language) {

13
src/module/root/screens/on-boarding.screen.tsx

@ -38,16 +38,11 @@ export const OnboardingScreen: FC = () => { @@ -38,16 +38,11 @@ export const OnboardingScreen: FC = () => {
const isLastBlock = onBoardingConfig.length - 1 === currentIndex
return (
<ScreenLayout
headerComponent={
<Header leftIcon="arrow" onPressLeft={() => goBack()} />
}>
<ScreenLayout headerComponent={<Header />}>
<View style={styles.container}>
<Picture
width={'100%'}
height={$size(240)}
style={{ marginTop: $size(-50) }}
/>
<View style={{ alignItems: 'center' }}>
<Picture />
</View>
<Txt style={styles.title}>
{t(onBoardingConfig[currentIndex].title)}

4
src/module/settings/atoms/index.ts

@ -1 +1,3 @@ @@ -1 +1,3 @@
export * from './selected-language-in-settings.atom';
export * from './selected-language-in-settings.atom'
export * from './switch-notifications.atom'
export * from './purchases.atom'

88
src/module/settings/atoms/purchases.atom.tsx

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
import React, { FC } from 'react'
import { StyleSheet, View } from 'react-native'
import { $size, ButtonWithIcon, Icon, Txt, colors } from '../../common'
import { TouchableOpacity } from 'react-native-gesture-handler'
interface IProps {
title: string
price: string
iconName: string
hasDiscount: boolean
isPurchased: boolean
onPress: () => void
}
export const PurchaseAtom: FC<IProps> = ({
title,
price,
hasDiscount,
iconName,
isPurchased,
onPress,
}) => {
const renderDiscountAtom = () => {
return (
<View style={styles.discount}>
<Txt style={styles.discountTxt}>-30%</Txt>
</View>
)
}
return (
<TouchableOpacity style={styles.container} onPress={onPress}>
<View style={styles.row}>
<Icon name={iconName} size={$size(24)} color={colors.purple} />
<Txt mod="lg" color={colors.purple}>
{title}
</Txt>
{hasDiscount && renderDiscountAtom()}
</View>
{isPurchased ? (
<ButtonWithIcon
styleBtn={styles.iconPlay}
iconName="play"
onPress={() => null}
/>
) : (
<Txt mod="lg" style={styles.price}>
{price + ' $'}
</Txt>
)}
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
height: $size(28),
marginBottom: $size(24),
},
row: {
flexDirection: 'row',
alignItems: 'center',
columnGap: 8,
},
price: {
fontWeight: '600',
color: colors.purple,
},
discount: {
width: $size(49),
borderRadius: 40,
backgroundColor: colors.red,
justifyContent: 'center',
alignItems: 'center',
},
discountTxt: {
fontSize: $size(14),
lineHeight: $size(28),
fontWeight: '900',
},
iconPlay: {
width: $size(60),
height: '100%'
},
})

32
src/module/settings/atoms/selected-language-in-settings.atom.tsx

@ -1,25 +1,21 @@ @@ -1,25 +1,21 @@
import React from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {useTranslation} from 'react-i18next';
import React from 'react'
import { StyleSheet } from 'react-native'
import { useTranslation } from 'react-i18next'
import { $size, Txt, colors } from '../../common'
export const SelectedLanguage = () => {
const {t, i18n} = useTranslation();
return (
<View style={styles.curentLang}>
<Text style={styles.text}>{i18n.language}</Text>
</View>
);
};
const { i18n } = useTranslation()
console.log(i18n.language)
return <Txt style={styles.text}>{i18n.language}</Txt>
}
const styles = StyleSheet.create({
curentLang: {
flex: 0,
alignSelf: 'center',
},
text: {
color: '#A798FF',
color: colors.purple,
fontWeight: '600',
fontSize: 18,
lineHeight: 28,
fontSize: $size(18),
lineHeight: $size(28),
textTransform: 'uppercase',
},
});
})

22
src/module/settings/atoms/switch-notifications.atom.tsx

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
import React, { useState } from 'react'
import { Switch } from 'react-native'
import { $size, colors } from '../../common'
export const SwitchNotificationsAtom = () => {
const [isEnabled, setIsEnabled] = useState(false)
const toggleSwitch = () => {
setIsEnabled(previousState => !previousState)
}
return (
<Switch
trackColor={{ true: colors.purple }}
thumbColor={isEnabled ? colors.turquoise : colors.darkPurple}
ios_backgroundColor={colors.purple}
onValueChange={toggleSwitch}
style={{ width: $size(51) }}
value={isEnabled}
/>
)
}

30
src/module/settings/components/settings-item.component.tsx

@ -1,33 +1,26 @@ @@ -1,33 +1,26 @@
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, View, TouchableOpacity } from 'react-native'
import { $size, Icon, RouteKey, Txt, colors, useNav } from '../../common'
import { $size, Icon, Txt, colors } from '../../common'
interface IProps {
title: string
iconName: string
component?: () => JSX.Element
onPressSettingItem: () => void
}
export const SettingsItem: FC<IProps> = ({ title, iconName, component }) => {
const { t } = useTranslation()
const nav = useNav()
const goTo = () => {
console.log(title)
switch (title) {
case 'settingTranslation.policy':
nav.navigate(RouteKey.PrivacyPolicy)
break
}
}
export const SettingsItem: FC<IProps> = ({
title,
iconName,
component,
onPressSettingItem,
}) => {
return (
<TouchableOpacity style={styles.container} onPress={goTo}>
<TouchableOpacity style={styles.container} onPress={onPressSettingItem}>
<View style={styles.row}>
<Icon name={iconName} size={$size(24)} color={colors.purple} />
<Txt mod="lg" style={styles.text}>
{t(title)}
{title}
</Txt>
</View>
{component && component()}
@ -41,14 +34,13 @@ const styles = StyleSheet.create({ @@ -41,14 +34,13 @@ const styles = StyleSheet.create({
},
text: {
color: colors.purple,
lineHeight: $size(24),
marginLeft: $size(10),
},
container: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: $size(24),
height: $size(24),
height: $size(28),
justifyContent: 'space-between',
},
})

1
src/module/settings/config/index.ts

@ -1,2 +1,3 @@ @@ -1,2 +1,3 @@
export * from './settings.config'
export * from './privacy-text.config'
export * from './purchases.config'

16
src/module/settings/config/purchases.config.ts

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
import { ProductsEnum } from '../../common'
export const purchasesConfig: any = {
[ProductsEnum.Under18]: {
name: 'Open package "Under 18"',
icon: 'ghost',
},
[ProductsEnum.Crazy]: {
name: 'Open package "Crazy"',
icon: 'crazy',
},
[ProductsEnum.All]: {
name: 'Open all packages',
icon: 'all_packages',
},
}

13
src/module/settings/config/settings.config.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import React from 'react'
import { SelectedLanguage } from '../atoms/selected-language-in-settings.atom'
import { SettingLocale } from '../../../i18n/types/settings.types'
import { SettingLocale } from '../../../i18n/interfaces/settings.types.interface'
import { SelectedLanguage, SwitchNotificationsAtom } from '../atoms'
const translatePath = (itemKey: keyof SettingLocale.Core) =>
`settingTranslation.${itemKey}`
@ -15,6 +15,11 @@ export const settingsConfig = [ @@ -15,6 +15,11 @@ export const settingsConfig = [
image: 'lang',
component: () => <SelectedLanguage />,
},
{
title: translatePath('notifications'),
image: 'notification',
component: () => <SwitchNotificationsAtom />,
},
{
title: translatePath('write'),
image: 'message',
@ -31,8 +36,4 @@ export const settingsConfig = [ @@ -31,8 +36,4 @@ export const settingsConfig = [
title: translatePath('policy'),
image: 'privacy-policy',
},
{
title: translatePath('information'),
image: 'information',
},
]

2
src/module/settings/index.ts

@ -2,3 +2,5 @@ export * from './screens' @@ -2,3 +2,5 @@ export * from './screens'
export * from './atoms'
export * from './components'
export * from './config'
export * from './validator'
export * from './services'

2
src/module/settings/screens/index.ts

@ -1,2 +1,4 @@ @@ -1,2 +1,4 @@
export * from './privacy-policy'
export * from './settings.screen'
export * from './purchases.screen'
export * from './write-to-us.screen'

11
src/module/settings/screens/privacy-policy.tsx

@ -1,22 +1,15 @@ @@ -1,22 +1,15 @@
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, View } from 'react-native'
import { $size, colors, Header, ScreenLayout, Txt, useNav } from '../../common'
import { $size, colors, Header, ScreenLayout, Txt } from '../../common'
import { privacyConfig } from '../config'
export const PrivacyPolicyScreen: FC = () => {
const { t, i18n } = useTranslation()
const nav = useNav()
return (
<ScreenLayout
headerComponent={
<Header
leftIcon="arrow"
title={t('pageTitles.privacy')}
onPressLeft={() => nav.goBack()}
/>
}>
headerComponent={<Header title={t('pageTitles.privacy')} />}>
<View style={styles.container}>
<Txt mod="xl" style={styles.description}>
{privacyConfig[i18n.language]}

133
src/module/settings/screens/purchases.screen.tsx

@ -0,0 +1,133 @@ @@ -0,0 +1,133 @@
import React, { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
ActivityIndicator,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import {
$size,
appEvents,
colors,
Font,
Header,
Icon,
ModalComponent,
ProductsEnum,
RouteKey,
ScreenLayout,
Txt,
useNav,
} from '../../common'
import { purchasesService } from '../services'
import { PurchaseAtom } from '../atoms'
export const PurchasesScreen: FC = () => {
const { t } = useTranslation()
const nav = useNav()
const [isLoading, setLoading] = useState(false)
const purchaseProduct = async (productId: ProductsEnum) => {
try {
setLoading(true)
await purchasesService.purchaseProduct(productId)
appEvents.emit('alert', {
title: t('purchases.alertSuccess'),
subtitle: t('purchases.descSuccess'),
})
} catch (error) {
appEvents.emit('alert', {
title: t('purchases.alertError'),
subtitle: t('purchases.descError'),
})
} finally {
setLoading(false)
}
}
return (
<ScreenLayout
headerComponent={<Header title={t('pageTitles.purchases')} />}>
{isLoading && (
<ModalComponent
onClose={() => null}
isVisible={isLoading}
style={styles.modal}>
<View style={styles.body}>
<Txt
mod="xl"
font={Font.Roboto700}
style={{ marginBottom: 10 }}>
Loading...
</Txt>
<ActivityIndicator color={colors.textPrimary} />
</View>
</ModalComponent>
)}
<>
{purchasesService.products.map(it => {
return (
<PurchaseAtom
key={it.productId}
title={it.name}
price={it.price}
hasDiscount={it.productId === ProductsEnum.All}
iconName={it.icon}
isPurchased={it.isPurchased}
onPress={() =>
it.isPurchased
? nav.navigate(RouteKey.Packages)
: purchaseProduct(it.productId)
}
/>
)
})}
</>
<TouchableOpacity style={styles.row}>
<Icon name="restore" size={$size(24)} color={colors.purple} />
<Txt mod="lg" color={colors.purple}>
Restore purchases
</Txt>
</TouchableOpacity>
</ScreenLayout>
)
}
const styles = StyleSheet.create({
container: {
backgroundColor: colors.darkPurple,
borderRadius: 20,
padding: 20,
},
description: {
color: colors.purple,
lineHeight: $size(30),
},
row: {
flexDirection: 'row',
alignItems: 'center',
columnGap: 8,
},
modal: {
position: 'absolute',
top: 0,
bottom: $size(100),
left: 0,
right: 0,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
body: {
width: '90%',
backgroundColor: colors.primaryColor,
height: $size(100),
justifyContent: 'center',
alignItems: 'center',
borderColor: colors.lightPurple,
borderRadius: 12,
borderWidth: 1,
},
})

92
src/module/settings/screens/settings.screen.tsx

@ -1,33 +1,99 @@ @@ -1,33 +1,99 @@
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { View } from 'react-native'
import { Share } from 'react-native'
import { SettingsItem } from '../components/settings-item.component'
import { settingsConfig } from '../config/settings.config'
import { Header, colors, ScreenLayout, useNav } from '../../common'
import {
EngSvg,
Header,
Language,
ScreenLayout,
SettingsKey,
UaSvg,
useNav,
} from '../../common'
import { SheetManager } from 'react-native-actions-sheet'
export const SettingsScreen: FC = () => {
const { t, i18n } = useTranslation()
const nav = useNav()
const { t } = useTranslation()
const configActions = [
{
icon: () => <EngSvg />,
onPress: () => onChangeLanguage(Language.EN),
label: 'English',
},
{
icon: () => <UaSvg />,
onPress: () => onChangeLanguage(Language.UA),
label: 'Українська',
},
]
const shareApp = async () => {
try {
const shareOptions = {
message: 'Share this cool app with your friends',
}
await Share.share(shareOptions)
} catch (error) {
console.log(error)
}
}
const onChangeLanguage = (language: Language) => {
SheetManager.hide('bottom-sheet')
return i18n.changeLanguage(language)
}
const openBottomSheetForChangeLanguage = () => {
SheetManager.show('bottom-sheet', {
payload: configActions,
})
}
const onPressSettingItem = (key: string) => {
switch (key) {
case 'purchases':
nav.navigate(SettingsKey.Purchases)
break
case 'lang':
openBottomSheetForChangeLanguage()
break
case 'notification':
break
case 'message':
nav.navigate(SettingsKey.WriteToUs)
break
case 'privacy-policy':
nav.navigate(SettingsKey.PrivacyPolicy)
break
case 'rate':
break
case 'share':
shareApp()
break
}
}
return (
<ScreenLayout
headerComponent={
<Header
leftIcon="arrow"
title={t('pageTitles.setting')}
onPressLeft={() => nav.goBack()}
/>
}>
<View>
headerComponent={<Header title={t('pageTitles.settings')} />}>
<>
{settingsConfig.map((item, index) => (
<SettingsItem
key={index}
title={item.title}
title={t(item.title)}
iconName={item.image}
component={item.component}
onPressSettingItem={() =>
onPressSettingItem(item.image)
}
/>
))}
</View>
</>
</ScreenLayout>
)
}

61
src/module/settings/screens/write-to-us.screen.tsx

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import {
$size,
appEvents,
ButtonPrimary,
colors,
FormTextControll,
Header,
ScreenLayout,
useForm,
useNav,
} from '../../common'
import { writeToUsValidator } from '../validator'
interface WriteUsForm {
message: string
}
export const WriteToUsScreen: FC = () => {
const { t } = useTranslation()
const nav = useNav()
const form = useForm<WriteUsForm>({}, writeToUsValidator)
const onSendText = () => {
appEvents.emit('alert', {
title: 'Aga, thanks',
subtitle: 'We rozberemosya',
onClose: () => nav.goBack(),
})
}
return (
<ScreenLayout
bottomSafeArea
headerComponent={<Header title={t('pageTitles.writeToUs')} />}>
<FormTextControll
label={t('settingTranslation.label')}
value={form.values.message}
onChange={val => form.setFormField('message', val)}
inputProps={{ multiline: true }}
inputStyle={{ height: $size(100) }}
error={form.errors['message']}
/>
<ButtonPrimary
style={styles.button}
disabled={!form.values.message}
onPress={() => form.onSubmit(onSendText)}>
Send to us
</ButtonPrimary>
</ScreenLayout>
)
}
const styles = StyleSheet.create({
button: {
marginTop: $size(20),
backgroundColor: colors.lightPurple,
},
})

1
src/module/settings/services/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './purchases.service'

121
src/module/settings/services/purchases.service.ts

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
import {
initConnection,
getProducts,
Product,
requestPurchase,
purchaseUpdatedListener,
finishTransaction,
} from 'react-native-iap'
import { ProductsEnum, StorageKey, appEvents } from '../../common'
import { purchasesConfig } from '../config'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { Alert } from 'react-native'
const ID_PRODUCTS = [ProductsEnum.All, ProductsEnum.Crazy, ProductsEnum.Under18]
interface ProductItem {
productId: ProductsEnum
price: string
name: string
icon: string
isPurchased: boolean
}
export class PurchasesService {
public products: ProductItem[] = []
public purchasedProducts: ProductsEnum[]
public init() {
this.initializeIAP()
this.loadProducts()
this.getPurchasedProducts()
}
public async getProducts() {
const res = await AsyncStorage.getItem(StorageKey.Products)
return JSON.parse(res)
}
private async initializeIAP() {
try {
await initConnection()
} catch (error) {
console.error('Failed to initialize IAP:', error)
throw error
}
}
public async loadProducts() {
try {
const products: Product[] = await getProducts({ skus: ID_PRODUCTS })
const productss = this.transformProductsData(products)
this.products = productss
await this.saveProductInStore(productss)
} catch (error) {
console.error('Error loading products:', error)
}
}
private async saveProductInStore(products: ProductItem[]) {
await AsyncStorage.setItem(
StorageKey.Products,
JSON.stringify(products),
)
}
public async purchaseProduct(productId: ProductsEnum) {
try {
await requestPurchase({
sku: productId,
})
this.purchaseListener()
await this.savePurchase(productId)
await purchasesService.loadProducts()
} catch (error) {
console.error('Purchase error:', error)
}
}
public async savePurchase(productId: ProductsEnum) {
const newProductsId = [...this.purchasedProducts, productId]
const newProducts = JSON.stringify(newProductsId)
this.purchasedProducts = newProductsId
await AsyncStorage.setItem(StorageKey.Purchases, newProducts)
}
protected async getPurchasedProducts() {
const response = await AsyncStorage.getItem(StorageKey.Purchases)
this.purchasedProducts = response ? JSON.parse(response) : []
}
private purchaseListener() {
purchaseUpdatedListener(purchase => {
finishTransaction({ purchase })
})
}
private transformProductsData = (products: Product[]) => {
console.log('transformProductsData', this.purchasedProducts)
return products
.map(product => {
const isPurchased = this.purchasedProducts.some(
it => product.productId === it,
)
return {
...purchasesConfig[product.productId],
productId: product.productId,
price: product.price,
isPurchased,
}
})
.sort((a, b) => a.productId.localeCompare(b.productId))
}
}
export const purchasesService = new PurchasesService()

1
src/module/settings/validator/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './writeToUs.validator'

15
src/module/settings/validator/writeToUs.validator.ts

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
import validate from 'validate.js'
import { presenceCost } from '../../common'
const constraints = {
message: {
presence: presenceCost,
length: {
minimum: 5,
},
},
}
export const writeToUsValidator = (data: any) => {
return validate(data, constraints)
}

79
tsconfig.json

@ -1,64 +1,23 @@ @@ -1,64 +1,23 @@
{
"extends": "@tsconfig/react-native/tsconfig.json",
"compilerOptions": {
/* Basic Options */
"target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": ["es2017"], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react-native", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
// "incremental": true, /* Enable incremental compilation */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
"isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"resolveJsonModule": true /* Allows importing modules with a .json extension, which is a common practice in node projects. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"jsx": "react",
"baseUrl": ".",
"paths": { "~*": ["./src/*"] },
"strictNullChecks": false,
"esModuleInterop": true
// "jsx": "react-native" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
},
"exclude": [
"node_modules", "babel.config.js", "metro.config.js", "jest.config.js"
]
"include": [
"src",
".eslintrc.js",
"react-native.config.js",
"metro.config.js",
"index.js",
"babel.config.js",
"jest.config.js"
],
/* Completeness */
"skipLibCheck": true
}

Loading…
Cancel
Save