FEATURE | Create settings screen & add purchases #3
Merged
Vitalik
merged 4 commits from settings-screen
into main
11 months ago
93 changed files with 28304 additions and 26295 deletions
@ -1,3 +1,27 @@
@@ -1,3 +1,27 @@
|
||||
module.exports = { |
||||
presets: ['module:metro-react-native-babel-preset'], |
||||
}; |
||||
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', |
||||
], |
||||
}, |
||||
], |
||||
], |
||||
} |
||||
|
@ -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 { |
||||
transformer: { |
||||
babelTransformerPath: require.resolve('react-native-svg-transformer'), |
||||
}, |
||||
resolver: { |
||||
assetExts: assetExts.filter(ext => ext !== 'svg'), |
||||
sourceExts: [...sourceExts, 'svg'], |
||||
}, |
||||
}; |
||||
})(); |
||||
module.exports = { |
||||
transformer: { |
||||
getTransformOptions: async () => ({ |
||||
transform: { |
||||
experimentalImportSupport: false, |
||||
inlineRequires: true, |
||||
}, |
||||
}), |
||||
}, |
||||
} |
||||
|
@ -1,84 +1,89 @@
@@ -1,84 +1,89 @@
|
||||
{ |
||||
"rnpm": { |
||||
"assets": [ |
||||
"./resources/fonts/" |
||||
] |
||||
}, |
||||
"name": "truth", |
||||
"version": "0.0.1", |
||||
"private": true, |
||||
"scripts": { |
||||
"android": "react-native run-android", |
||||
"ios": "react-native run-ios", |
||||
"start": "react-native start", |
||||
"test": "jest", |
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx" |
||||
}, |
||||
"dependencies": { |
||||
"@react-native-async-storage/async-storage": "^1.17.11", |
||||
"@react-native-firebase/app": "^17.0.0", |
||||
"@react-native-firebase/firestore": "^17.0.0", |
||||
"@react-navigation/native": "^6.0.11", |
||||
"@react-navigation/native-stack": "^6.7.0", |
||||
"@reduxjs/toolkit": "^1.9.2", |
||||
"i18next": "^21.8.14", |
||||
"i18next-react-native-async-storage": "^1.0.4", |
||||
"jet-tools": "^1.3.0", |
||||
"link": "^1.5.1", |
||||
"lodash": "^4.17.21", |
||||
"react": "18.2.0", |
||||
"react-i18next": "^11.18.1", |
||||
"react-native": "0.72.7", |
||||
"react-native-actions-sheet": "*", |
||||
"react-native-animated-loader": "^1.0.0", |
||||
"react-native-gesture-handler": "^2.5.0", |
||||
"react-native-icomoon": "^0.1.1", |
||||
"react-native-keyboard-aware-scroll-view": "^0.9.5", |
||||
"react-native-modal": "^13.0.1", |
||||
"react-native-safe-area-context": "^4.3.1", |
||||
"react-native-screens": "^3.27.0", |
||||
"react-native-splash-screen": "^3.3.0", |
||||
"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" |
||||
}, |
||||
"devDependencies": { |
||||
"@babel/core": "^7.20.0", |
||||
"@babel/preset-env": "^7.20.0", |
||||
"@babel/runtime": "^7.20.0", |
||||
"@react-native/eslint-config": "^0.72.2", |
||||
"@react-native/metro-config": "^0.72.11", |
||||
"@tsconfig/react-native": "^3.0.0", |
||||
"@types/lodash": "^4.14.201", |
||||
"@types/react": "^18.0.24", |
||||
"@types/react-native": "^0.66.15", |
||||
"@types/react-native-vector-icons": "^6.4.12", |
||||
"@types/react-redux": "^7.1.25", |
||||
"@types/react-test-renderer": "^18.0.0", |
||||
"@typescript-eslint/eslint-plugin": "^5.7.0", |
||||
"@typescript-eslint/parser": "^5.7.0", |
||||
"babel-jest": "^29.2.1", |
||||
"eslint": "^8.19.0", |
||||
"jest": "^29.2.1", |
||||
"metro-react-native-babel-preset": "0.76.8", |
||||
"prettier": "^2.4.1", |
||||
"react-test-renderer": "18.2.0", |
||||
"reactotron-react-native": "^5.0.3", |
||||
"typescript": "4.8.4" |
||||
}, |
||||
"resolutions": { |
||||
"@types/react": "^17" |
||||
}, |
||||
"jest": { |
||||
"preset": "react-native", |
||||
"moduleFileExtensions": [ |
||||
"ts", |
||||
"tsx", |
||||
"js", |
||||
"jsx", |
||||
"json", |
||||
"node" |
||||
] |
||||
} |
||||
"rnpm": { |
||||
"assets": [ |
||||
"./resources/fonts/" |
||||
] |
||||
}, |
||||
"name": "truth", |
||||
"version": "0.0.1", |
||||
"private": true, |
||||
"scripts": { |
||||
"android": "react-native run-android", |
||||
"ios": "react-native run-ios", |
||||
"start": "react-native start", |
||||
"test": "jest", |
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx", |
||||
"pod": "cd ./ios && pod install && cd ../" |
||||
}, |
||||
"dependencies": { |
||||
"@react-native-async-storage/async-storage": "^1.17.11", |
||||
"@react-native-firebase/app": "^17.0.0", |
||||
"@react-native-firebase/firestore": "^17.0.0", |
||||
"@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", |
||||
"link": "^1.5.1", |
||||
"lodash": "^4.17.21", |
||||
"react": "18.2.0", |
||||
"react-i18next": "^11.18.1", |
||||
"react-native": "0.72.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", |
||||
"react-native-safe-area-context": "^4.3.1", |
||||
"react-native-screens": "^3.27.0", |
||||
"react-native-splash-screen": "^3.3.0", |
||||
"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", |
||||
"validate.js": "^0.13.1" |
||||
}, |
||||
"devDependencies": { |
||||
"@babel/core": "^7.20.0", |
||||
"@babel/preset-env": "^7.20.0", |
||||
"@babel/runtime": "^7.20.0", |
||||
"@react-native/eslint-config": "^0.72.2", |
||||
"@react-native/metro-config": "^0.72.11", |
||||
"@tsconfig/react-native": "^3.0.0", |
||||
"@types/lodash": "^4.14.201", |
||||
"@types/react": "^18.0.24", |
||||
"@types/react-native": "^0.66.15", |
||||
"@types/react-native-vector-icons": "^6.4.12", |
||||
"@types/react-redux": "^7.1.25", |
||||
"@types/react-test-renderer": "^18.0.0", |
||||
"@typescript-eslint/eslint-plugin": "^5.7.0", |
||||
"@typescript-eslint/parser": "^5.7.0", |
||||
"babel-jest": "^29.2.1", |
||||
"eslint": "^8.19.0", |
||||
"jest": "^29.2.1", |
||||
"metro-react-native-babel-preset": "0.76.8", |
||||
"prettier": "^2.4.1", |
||||
"react-test-renderer": "18.2.0", |
||||
"reactotron-react-native": "^5.0.3", |
||||
"typescript": "4.8.4" |
||||
}, |
||||
"resolutions": { |
||||
"@types/react": "^17" |
||||
}, |
||||
"jest": { |
||||
"preset": "react-native", |
||||
"moduleFileExtensions": [ |
||||
"ts", |
||||
"tsx", |
||||
"js", |
||||
"jsx", |
||||
"json", |
||||
"node" |
||||
] |
||||
} |
||||
} |
||||
|
Before Width: | Height: | Size: 893 B |
Before Width: | Height: | Size: 691 B |
Before Width: | Height: | Size: 511 B |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
interface Validation { |
||||
isRequire: string |
||||
} |
||||
|
||||
export interface Common { |
||||
validate: Validation |
||||
shareMessage: string |
||||
} |
@ -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 |
||||
} |
@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
|
||||
export interface PageTitles { |
||||
setting: string, |
||||
settings: string, |
||||
privacy: string, |
||||
terms: string, |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
export interface PurchasesTranslate { |
||||
alertSuccess: string |
||||
descSuccess: string |
||||
alertError: string |
||||
descError: string |
||||
} |
@ -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 |
||||
} |
||||
} |
@ -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', |
||||
} |
@ -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!', |
||||
} |
||||
title: 'Create custom pack', |
||||
description: |
||||
'Create your own custom pack with questions and task. It all depends on your imagination!', |
||||
} |
||||
|
@ -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, |
||||
}; |
||||
settingTranslation, |
||||
stepTranslation: onBoardingTranslation, |
||||
buttonsTranslation, |
||||
customPack, |
||||
pageTitles, |
||||
purchases, |
||||
common, |
||||
} |
||||
|
@ -1,5 +1,7 @@
@@ -1,5 +1,7 @@
|
||||
export const pageTitles = { |
||||
setting: 'Setting', |
||||
privacy: 'Privacy Policy', |
||||
terms: 'Terms and conditions', |
||||
} |
||||
settings: 'Settings', |
||||
purchases: 'Purchases', |
||||
privacy: 'Privacy Policy', |
||||
terms: 'Terms and conditions', |
||||
writeToUs: 'Write to us', |
||||
} |
||||
|
@ -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.', |
||||
} |
@ -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', |
||||
write: 'Write to us', |
||||
rate: 'Rate us', |
||||
share: 'Share app', |
||||
policy: 'Privacy policy', |
||||
term: 'Terms and conditions', |
||||
information: 'Information', |
||||
}; |
||||
purchases: 'Purchases!', |
||||
language: 'Language', |
||||
notifications: 'Notifications', |
||||
write: 'Write to us', |
||||
rate: 'Rate us', |
||||
share: 'Share app', |
||||
policy: 'Privacy policy', |
||||
label: 'What can we do to help?', |
||||
} |
||||
|
@ -1,19 +1,18 @@
@@ -1,19 +1,18 @@
|
||||
import {OnBoardingLocale} from '../../types/on-boarding.types'; |
||||
import { OnBoardingLocale } from '../../interfaces/on-boarding.types.interface' |
||||
export const onBoardingTranslation: OnBoardingLocale.OnboardingSteps = { |
||||
step1: { |
||||
title: 'Welcome!', |
||||
description: |
||||
'Thank you for downloading. Now you are \n in the best game for the company \n or to play with your loved one', |
||||
}, |
||||
step2: { |
||||
title: 'Relax and enjoy \n the game!', |
||||
description: |
||||
'This game features 5 levels of "spiciness",\n some of which are divided into games for a \n couple or a company. All you have to do is \n add players and you can start playing.\n P.S. You can always create your own \n questions and tasks.', |
||||
}, |
||||
step3: { |
||||
title: 'Premium version!', |
||||
description: |
||||
'Provides unlimited access to Hard and \n Extreme packages. Enjoy intriguing questions \n and exciting action.', |
||||
}, |
||||
}; |
||||
|
||||
step1: { |
||||
title: 'Welcome!', |
||||
description: |
||||
'Thank you for downloading. Now you are \n in the best game for the company \n or to play with your loved one', |
||||
}, |
||||
step2: { |
||||
title: 'Relax and enjoy \n the game!', |
||||
description: |
||||
'This game features 5 levels of "spiciness",\n some of which are divided into games for a \n couple or a company. All you have to do is \n add players and you can start playing.\n P.S. You can always create your own \n questions and tasks.', |
||||
}, |
||||
step3: { |
||||
title: 'Premium version!', |
||||
description: |
||||
'Provides unlimited access to Hard and \n Extreme packages. Enjoy intriguing questions \n and exciting action.', |
||||
}, |
||||
} |
||||
|
@ -1,14 +1,14 @@
@@ -1,14 +1,14 @@
|
||||
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, |
||||
stepTranslation: onBoardingTranslation, |
||||
buttonsTranslation, |
||||
customPack, |
||||
pageTitles, |
||||
}; |
||||
settingTranslation, |
||||
stepTranslation: onBoardingTranslation, |
||||
buttonsTranslation, |
||||
customPack, |
||||
pageTitles, |
||||
} |
||||
|
@ -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: 'Поділіться цим додатком зі своїми друзями', |
||||
} |
@ -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, |
||||
buttonsTranslation, |
||||
customPack, |
||||
pageTitles |
||||
}; |
||||
stepTranslation: onBoardingTranslationUa, |
||||
settingTranslation, |
||||
buttonsTranslation, |
||||
customPack, |
||||
pageTitles, |
||||
purchases, |
||||
common, |
||||
} |
||||
|
@ -1,5 +1,7 @@
@@ -1,5 +1,7 @@
|
||||
export const pageTitles = { |
||||
setting: 'Налаштування', |
||||
privacy: 'Політика \n конфіденційності', |
||||
terms: 'Правила та умови', |
||||
} |
||||
purchases: 'Покупки', |
||||
settings: 'Налаштування', |
||||
privacy: 'Політика \n конфіденційності', |
||||
terms: 'Правила та умови', |
||||
writeToUs: 'Напишіть нам', |
||||
} |
||||
|
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
import { PurchasesTranslate } from "~i18n/interfaces/purchases.interface"; |
||||
|
||||
export const purchases: PurchasesTranslate = { |
||||
alertSuccess: 'Ура! Готово!', |
||||
descSuccess: 'Тепер зіграйте з друзями! Насолоджуйтеся грою 😊', |
||||
alertError: 'Упс, щось не так 😢', |
||||
descError: |
||||
'Виникла помилка обробки вашої покупки. Будь ласка, спробуйте пізніше.', |
||||
} |
||||
|
||||
|
@ -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: 'Магазин!', |
||||
language: 'Мова', |
||||
write: 'Напишіть нам', |
||||
rate: 'Оцініть нас', |
||||
share: 'Поділитися програмою', |
||||
policy: 'Політика конфіденційності', |
||||
term: 'Правила та умови', |
||||
information: 'Інформація', |
||||
}; |
||||
purchases: 'Магазин', |
||||
language: 'Мова', |
||||
notifications: 'Сповіщення', |
||||
write: 'Напишіть нам', |
||||
rate: 'Оцініть нас', |
||||
share: 'Поділитися програмою', |
||||
policy: 'Політика конфіденційності', |
||||
term: 'Правила та умови', |
||||
information: 'Інформація', |
||||
label: 'Чим ми можемо допомогти', |
||||
} |
||||
|
@ -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 |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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), |
||||
}, |
||||
}) |
@ -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, |
||||
}, |
||||
}) |
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
export * from './form-controll-wrap.component'; |
||||
export * from './form-text-controll.component'; |
||||
|
@ -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' |
||||
|
@ -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)), |
||||
} |
||||
} |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 53 KiB |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export * from './icons-svg' |
@ -1 +1,2 @@
@@ -1 +1,2 @@
|
||||
export * from './global-container.tool' |
||||
export * from './validate.tool' |
||||
|
@ -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 } |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
export enum ProductsEnum { |
||||
All = 'ALL', |
||||
Under18 = 'un18', |
||||
Crazy = 'Crz', |
||||
} |
@ -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' |
||||
} |
||||
|
@ -1,4 +1,6 @@
@@ -1,4 +1,6 @@
|
||||
export enum StorageKey { |
||||
OnBoarding = 'ONBOARDING_END', |
||||
Language = 'LANG_SELECTED', |
||||
Purchases = 'Purchases', |
||||
Products = 'Products', |
||||
} |
||||
|
@ -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' |
||||
|
@ -1 +1,2 @@
@@ -1 +1,2 @@
|
||||
export * from './bottom-sheet-view.atom' |
||||
export * from './action-sheet-button.atom' |
||||
|
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export * from './sheets' |
@ -1,2 +1,3 @@
@@ -1,2 +1,3 @@
|
||||
export * from './alert' |
||||
export * from './alert-confirm' |
||||
export * from './bottom-sheet' |
||||
|
@ -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' |
||||
|
@ -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%' |
||||
}, |
||||
}) |
@ -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', |
||||
fontWeight: '600', |
||||
fontSize: 18, |
||||
lineHeight: 28, |
||||
textTransform: 'uppercase', |
||||
}, |
||||
}); |
||||
text: { |
||||
color: colors.purple, |
||||
fontWeight: '600', |
||||
fontSize: $size(18), |
||||
lineHeight: $size(28), |
||||
textTransform: 'uppercase', |
||||
}, |
||||
}) |
||||
|
@ -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} |
||||
/> |
||||
) |
||||
} |
@ -1,2 +1,3 @@
@@ -1,2 +1,3 @@
|
||||
export * from './settings.config' |
||||
export * from './privacy-text.config' |
||||
export * from './purchases.config' |
||||
|
@ -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', |
||||
}, |
||||
} |
@ -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' |
||||
|
@ -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, |
||||
}, |
||||
}) |
@ -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> |
||||
) |
||||
} |
||||
|
@ -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, |
||||
}, |
||||
}) |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export * from './purchases.service' |
@ -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() |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export * from './writeToUs.validator' |
@ -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) |
||||
} |
@ -1,64 +1,23 @@
@@ -1,64 +1,23 @@
|
||||
|
||||
{ |
||||
"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. */ |
||||
"extends": "@tsconfig/react-native/tsconfig.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"baseUrl": ".", |
||||
"paths": { "~*": ["./src/*"] }, |
||||
"strictNullChecks": false, |
||||
"esModuleInterop": true |
||||
// "jsx": "react-native" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, |
||||
}, |
||||
"include": [ |
||||
"src", |
||||
".eslintrc.js", |
||||
"react-native.config.js", |
||||
"metro.config.js", |
||||
"index.js", |
||||
"babel.config.js", |
||||
"jest.config.js" |
||||
], |
||||
|
||||
/* Experimental Options */ |
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ |
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ |
||||
}, |
||||
"exclude": [ |
||||
"node_modules", "babel.config.js", "metro.config.js", "jest.config.js" |
||||
] |
||||
/* Completeness */ |
||||
"skipLibCheck": true |
||||
} |
||||
|
Loading…
Reference in new issue