diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 000000000..e8511eaea --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no-install commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 000000000..6700f5128 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" diff --git a/package.json b/package.json index ca2ae928e..65d9e964a 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "run:admin": "yarn --cwd ./packages/admin-web-angular start", "run:merchant": "yarn --cwd ./packages/merchant-tablet-ionic start", "run:shopmobile": "yarn --cwd ./packages/shop-mobile-ionic start", + "run:shopexpo": "yarn --cwd ./packages/shop-mobile-expo start", "run:shopweb": "yarn --cwd ./packages/shop-web-angular start", "run:carrier": "yarn --cwd ./packages/carrier-mobile-ionic start", "build:server": "yarn run build:common && yarn lerna run build --scope @ever-platform/core", @@ -284,4 +285,4 @@ "prefix": "ever", "framework": "angular" } -} \ No newline at end of file +} diff --git a/packages/shop-mobile-expo/.env.template b/packages/shop-mobile-expo/.env.template new file mode 100644 index 000000000..924719990 --- /dev/null +++ b/packages/shop-mobile-expo/.env.template @@ -0,0 +1,78 @@ +# NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +# We are using dotenv (.env) for consistency with other Platform projects +# This is expo app and all settings will be loaded into the client browser! + +# Don't forget to update scripts/*.ts and src/environments/*.ts on changes! + +VERSION=1.0.0 + +# 'slides' | 'list' +PRODUCTS_VIEW_TYPE=slides + +# 'popup' or 'page' +ORDER_INFO_TYPE=page + +API_FILE_UPLOAD_URL=https://api.cloudinary.com/v1_1/evereq/upload + +INVITE_BY_CODE_LOGO=assets/imgs/ever-logo.svg +NO_INTERNET_LOGO=assets/imgs/logo.png + +COMPANY_NAME=Ever Co. LTD + +GOOGLE_MAPS_API_KEY= + +GOOGLE_ANALYTICS_API_KEY= +FAKE_UUID= + +# Not secret MixPanel Token +MIXPANEL_API_KEY= + +DEFAULT_LANGUAGE=en-US +DEFAULT_LOCALE=en-US + +DELIVERY_TIME_MIN=30 +DELIVERY_TIME_MAX=60 + +SUPPORT_NUMBER=0888888888 + +STRIPE_PUBLISHABLE_KEY= + +# TODO: replace logo with recent one! +STRIPE_POP_UP_LOGO=https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2016/Jan/30/1263967991-1-everbie-avatar.png + +MAP_MERCHANT_ICON_LINK=http://maps.google.com/mapfiles/kml/pal3/icon21.png +MAP_USER_ICON_LINK=http://maps.google.com/mapfiles/kml/pal3/icon48.png +MAP_CARRIER_ICON_LINK=http://maps.google.com/mapfiles/kml/pal4/icon54.png + +DEFAULT_LATITUDE=42.6459136 +DEFAULT_LONGITUDE=23.3332736 + +# Graphql endpoints for apollo services +GQL_ENDPOINT=http://localhost:8443/graphql +GQL_SUBSCRIPTIONS_ENDPOINT=ws://localhost:2086/subscriptions +SERVICES_ENDPOINT=http://localhost:5500 +HTTPS_SERVICES_ENDPOINT=https://localhost:2087 + +FAKE_INVITE_ID=1ae9d04f9010d834f8906881 +FAKE_INVITE_CITY=Ashdod +FAKE_INVITE_POSTCODE=77452 +FAKE_INVITE_ADDRESS=HaAtsmaut +FAKE_INVITE_HOUSE=126 +FAKE_INVITE_CREATED_AT=2018-05-02T14:50:55.658Z +FAKE_INVITE_UPDATED_AT=2018-05-02T14:50:55.658Z +FAKE_INVITE_APARTMENT=3 +FAKE_INVITE_CODE=8321 +FAKE_INVITE_COUNTRY_ID=102 + +# For maintenance micro service. Ever maintanance API URL: https://maintenance.ever.co/status +SETTINGS_APP_TYPE=shop-mobile +SETTINGS_MAINTENANCE_API_URL= + +# For "single" merchant (multiple branches) +MERCHANT_IDS=[] + +PORT=4201 +WEB_MEMORY=4096 +WEB_CONCURRENCY=1 + +SHOPPING_CART=false diff --git a/packages/shop-mobile-expo/.eslintrc.json b/packages/shop-mobile-expo/.eslintrc.json new file mode 100644 index 000000000..fec5aa1bc --- /dev/null +++ b/packages/shop-mobile-expo/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "root": true, + "extends": "@react-native-community", + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "rules": { + "@typescript-eslint/no-shadow": ["error"], + "no-shadow": "off", + "no-undef": "off", + "jsx-quotes": ["error", "prefer-single"], + "no-spaced-func": "off", + "spaced-comment": [ + "error", + "always", + { "block": { "balanced": true } } + ], + "no-mixed-spaces-and-tabs": ["off", "smart-tabs"], + "no-extra-semi": "error", + "semi": "off" + } + } + ] +} diff --git a/packages/shop-mobile-expo/.expo-shared/assets.json b/packages/shop-mobile-expo/.expo-shared/assets.json new file mode 100644 index 000000000..1e6decfbb --- /dev/null +++ b/packages/shop-mobile-expo/.expo-shared/assets.json @@ -0,0 +1,4 @@ +{ + "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, + "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true +} diff --git a/packages/shop-mobile-expo/.gitignore b/packages/shop-mobile-expo/.gitignore new file mode 100644 index 000000000..23f7961ac --- /dev/null +++ b/packages/shop-mobile-expo/.gitignore @@ -0,0 +1,30 @@ +node_modules/ +.expo/ +dist/ +npm-debug.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision +*.orig.* +*.lock +web-build/ + +# macOS +.DS_Store + +# Do not store autogenerated docs in repo +docs/ + +# Do net store aotogenerate jest caches +jest/ + +# environment files +.env* +!.env.template +*/environments/environment* + +# others +.editorconfig +tslint.json diff --git a/packages/shop-mobile-expo/.prettierrc.json b/packages/shop-mobile-expo/.prettierrc.json new file mode 100644 index 000000000..09b20d33f --- /dev/null +++ b/packages/shop-mobile-expo/.prettierrc.json @@ -0,0 +1,11 @@ +{ + "semi": true, + "useTabs": true, + "tabWidth": 4, + "bracketSpacing": true, + "jsxBracketSameLine": true, + "singleQuote": true, + "jsxSingleQuote": true, + "trailingComma": "all", + "arrowParens": "always" +} diff --git a/packages/shop-mobile-expo/.watchmanconfig b/packages/shop-mobile-expo/.watchmanconfig new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/shop-mobile-expo/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/packages/shop-mobile-expo/App.spec.tsx b/packages/shop-mobile-expo/App.spec.tsx new file mode 100644 index 000000000..b4eb670f0 --- /dev/null +++ b/packages/shop-mobile-expo/App.spec.tsx @@ -0,0 +1,26 @@ +/** + * @format + */ + +import 'react-native'; +import React from 'react'; +import renderer from 'react-test-renderer'; +// Note: test renderer must be required after react-native. +import { View, Text } from 'react-native'; + +// COMPONENTS +// import App from './src/App'; + +export default function App() { + return ( + + hi + + ); +} + +describe('Demo', function () { + it('should renders correctly', () => { + renderer.create(); + }); +}); diff --git a/packages/shop-mobile-expo/App.tsx b/packages/shop-mobile-expo/App.tsx new file mode 100644 index 000000000..fe136f8f2 --- /dev/null +++ b/packages/shop-mobile-expo/App.tsx @@ -0,0 +1,14 @@ +import { registerRootComponent } from 'expo'; +import { LogBox } from 'react-native'; +import ENV from './src/environments/environment'; + +// APP COMPONENT +import App from './src/App'; + +// registerRootComponent calls AppRegistry.registerComponent('main', () => App); +// It also ensures that whether you load the app in Expo Go or in a native build, +// the environment is set up appropriately +registerRootComponent(App); +if (ENV.PRODUCTION || !__DEV__) { + LogBox.ignoreAllLogs(); +} diff --git a/packages/shop-mobile-expo/README.md b/packages/shop-mobile-expo/README.md new file mode 100644 index 000000000..7cfb6a7c7 --- /dev/null +++ b/packages/shop-mobile-expo/README.md @@ -0,0 +1,84 @@ +# @ever-platform/shop-mobile-expo + +> 🛑 **NOT YET COMPATIBLE**: Currently this package don't works fine with Lerna mono-repo logic. +To be able to run this package locally, you've to extract the package outside of @ever-platform and then install dependencies (see [setup](#-setup) section). +But this package depend on @ever-platform server, don't forget to start it... + +## 📋 Contents + +- [Contents](#-contents) +- [Demo](#-demo) +- [Setup](#-setup) +- [Testing](#-testing) +- [Contribution](#-contribution) + +## 🤳 Demo + +> We used expo to test our application easily and quickly. +> So you must first install expo on your mobile and then test the demo version. + +Copy that link -> **[link not available](https://docs.expo.io/)** and past it into your expo app to preview demo. + +## ⚡ Setup + + +### 📋 Package structure + + . + ├── .expo-shared + ├── script + ├── src + ├── assets # Contain app assets (imgs, fonts, ...) + ├── client # Contain ApolloGraphQL queries, mutations and relative types + ├── components # Contain components used in the app + ├── constants + ├── helpers # Class/Functions that provides useful feature + ├── environments + ├── router + ├── screens + ├── store + ├── types # Contain global types + └── App.tsx + ├── .env.template + ├── .eslintrc.json + ├── .gitignore + ├── .prettierrc.json + ├── .watchmanconfig + ├── app.json + ├── App.spec.tsx + ├── App.tsx + ├── babel.config.js + ├── metro.config.js + ├── package.json + ├── README.md + └── tsconfig.json + +### 🌟 Before install + +**🚧 Before installing et running project locally, make sure you have :** + +- The latest Yarn version installed in your computer **(note that we're only use Yarn)** +- Node.js V14.x.x or higher installed in your computer +- Expo 5.x.x or higher installed in your computer +- The latest version of expo app installed in your phone (or virtual device) + +### ⚡ Installation & Running + +- Install dependencies `Yarn`. run `yarn add`. +- Create environment files by running `yarn run config`. +- After these steps, run `yarn run start` command to start bundle server + +> 🚧 If you can't connect to the server, maybe the endpoint aren't compatible. +To update them, got to `./src/environments/environment.ts` and change urls props into `ENDPOINT` prop from `http://localhost/...` to `http://10.0.2.2/...` + + +## 🧪 Testing + +We're using Jest to test our application. + +To run all tests, run `yarn run test` + + +### 🤝 Contribution + +Please refer to the [guide of the platform](https://github.com/ever-co/ever-demand/#contribute) diff --git a/packages/shop-mobile-expo/app.json b/packages/shop-mobile-expo/app.json new file mode 100644 index 000000000..9a95d0586 --- /dev/null +++ b/packages/shop-mobile-expo/app.json @@ -0,0 +1,38 @@ +{ + "expo": { + "name": "@ever-platform/shop-mobile-expo", + "slug": "shop-mobile-expo", + "description": "Ever Shop Mobile App build with expo", + "version": "0.4.3", + "orientation": "portrait", + "primaryColor": "#2A2C39", + "backgroundColor": "#2A2C39", + "icon": "./src/assets/img/appIcons/icon.png", + "splash": { + "image": "./src/assets/img/appIcons/splash.png", + "resizeMode": "cover", + "backgroundColor": "#2A2C39" + }, + "updates": { + "fallbackToCacheTimeout": 0 + }, + "assetBundlePatterns": ["**/*"], + "ios": { + "supportsTablet": true + }, + "androidStatusBar": { + "barStyle": "light-content", + "translucent": true, + "backgroundColor": "#00000000" + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./src/assets/img/appIcons/adaptive-icon.png", + "backgroundColor": "#2A2C39" + } + }, + "web": { + "favicon": "./src/assets/img/appIcons/favicon.png" + } + } +} diff --git a/packages/shop-mobile-expo/babel.config.js b/packages/shop-mobile-expo/babel.config.js new file mode 100644 index 000000000..7a7d30aac --- /dev/null +++ b/packages/shop-mobile-expo/babel.config.js @@ -0,0 +1,7 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: ['react-native-reanimated/plugin'], + }; +}; diff --git a/packages/shop-mobile-expo/metro.config.js b/packages/shop-mobile-expo/metro.config.js new file mode 100644 index 000000000..a83754444 --- /dev/null +++ b/packages/shop-mobile-expo/metro.config.js @@ -0,0 +1,29 @@ +/** + * ? Metro configuration for React Native + * https://github.com/facebook/react-native + * + * @format + */ +const defaultSourceExts = + require('metro-config/src/defaults/defaults').sourceExts; +module.exports = { + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, + resolver: { + // ? Add cjs files extention support + sourceExts: process.env.RN_SRC_EXT + ? [ + ...process.env.RN_SRC_EXT.split(',').concat( + defaultSourceExts, + ), + 'cjs', + ] + : [...defaultSourceExts, 'cjs'], + }, +}; diff --git a/packages/shop-mobile-expo/package.json b/packages/shop-mobile-expo/package.json new file mode 100644 index 000000000..5f73abcd4 --- /dev/null +++ b/packages/shop-mobile-expo/package.json @@ -0,0 +1,112 @@ +{ + "name": "@ever-platform/shop-mobile-expo", + "version": "0.4.3", + "description": "Ever Shop Mobile App built with Expo (ReactNative)", + "license": "GPL-3.0", + "homepage": "https://ever.co/", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": true, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "main": "App.tsx", + "scripts": { + "config": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn ts-node ./scripts/configure.ts", + "config:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=dev", + "config:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=prod", + "start": "expo start", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web", + "eject": "expo eject", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "test": "jest" + }, + "dependencies": { + "@apollo/client": "^3.5.8", + "@react-native-async-storage/async-storage": "~1.15.0", + "@react-native-community/netinfo": "7.1.3", + "@react-navigation/drawer": "^5.12.9", + "@react-navigation/native": "6.0.6", + "@react-navigation/native-stack": "6.2.5", + "@reduxjs/toolkit": "1.7.1", + "expo": "~44.0.0", + "expo-app-loading": "~1.3.0", + "expo-font": "~10.0.4", + "expo-location": "~14.0.1", + "expo-status-bar": "~1.2.0", + "expo-system-ui": "~1.1.0", + "graphql": "^16.3.0", + "lodash.debounce": "^4.0.8", + "react": "17.0.2", + "react-dom": "17.0.1", + "react-native": "0.64.3", + "react-native-flash-message": "^0.2.1", + "react-native-gesture-handler": "~2.1.0", + "react-native-global-props": "1.1.5", + "react-native-maps": "0.29.4", + "react-native-pager-view": "5.4.9", + "react-native-paper": "4.11.2", + "react-native-reanimated": "~2.3.1", + "react-native-safe-area-context": "3.3.2", + "react-native-screens": "~3.10.1", + "react-native-web": "0.17.1", + "react-redux": "7.2.6", + "redux-logger": "^3.0.6", + "validate.js": "^0.13.1" + }, + "devDependencies": { + "@babel/core": "^7.12.9", + "@babel/runtime": "^7.17.0", + "@react-native-community/eslint-config": "^2.0.0", + "@types/jest": "^26.0.23", + "@types/react-native": "^0.66.16", + "@types/react-native-global-props": "^1.1.1", + "@types/react-test-renderer": "^17.0.1", + "@types/redux-logger": "^3.0.9", + "@typescript-eslint/eslint-plugin": "^5.7.0", + "@typescript-eslint/parser": "^5.7.0", + "babel-jest": "^26.6.3", + "cross-env": "^7.0.3", + "dotenv": "^16.0.0", + "envalid": "^7.2.2", + "eslint": "^7.14.0", + "jest": "^26.6.3", + "jest-expo": "^44.0.1", + "metro-config": "^0.67.0", + "metro-react-native-babel-preset": "^0.66.2", + "react-test-renderer": "^17.0.2", + "ts-node": "^10.5.0", + "typescript": "^4.4.4", + "uuid": "^8.3.2", + "yargs": "^17.3.1" + }, + "resolutions": { + "@types/react": "^17" + }, + "jest": { + "preset": "jest-expo", + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ] + }, + "engines": { + "node": ">=14.4.0", + "yarn": ">=1.13.0" + }, + "production": false, + "snyk": false +} diff --git a/packages/shop-mobile-expo/scripts/configure.ts b/packages/shop-mobile-expo/scripts/configure.ts new file mode 100644 index 000000000..256776704 --- /dev/null +++ b/packages/shop-mobile-expo/scripts/configure.ts @@ -0,0 +1,192 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Expo app and all settings will be loaded into the client browser! + +import { writeFile, unlinkSync } from 'fs'; +import { env } from './env'; +import { argv } from 'yargs'; +import { production } from '../package.json'; + +const isProd: boolean = argv.environment === 'prod' || production; + +if (!env.GOOGLE_MAPS_API_KEY) { + console.warn( + 'WARNING: No Google Maps API Key defined in the .env. Google Maps may not be visible!', + ); +} + +if (!env.STRIPE_PUBLISHABLE_KEY) { + console.warn( + 'WARNING: No Stripe Publishable Key defined in the .env. Stripe payments may not be available!', + ); +} + +const envFileContent = `// NOTE: Auto-generated file +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses 'environment.ts', but if you do +// 'ng build --env=prod' then 'environment.prod.ts' will be used instead. +// The list of which env maps to which file can be found in '.angular-cli.json'. + +import type Environment from './model'; + +const environment: Environment = { + // TODO: Add more descriptive comments. + /** + * App default environment + * + * @type boolean + */ + PRODUCTION: false, + + /** + * App version + * + * @type string + */ + VERSION: '1.0.0', + + /** + * @type 'slides' | 'list' + */ + PRODUCTS_VIEW_TYPE: 'slides', + + /** + * Order info behavior type + * + * @type 'popup' | 'page' + */ + ORDER_INFO_TYPE: 'page', + + COMPANY_NAME: 'Ever Co. LTD', + + /** + * Contain images url + */ + IMAGE_URL: { + INVITE_BY_CODE_LOGO: 'assets/imgs/ever-logo.svg', + NO_INTERNET_LOGO: 'assets/imgs/logo.png', + MAP_MERCHANT_ICON: + 'http://maps.google.com/mapfiles/kml/pal3/icon21.png', + MAP_USER_ICON: 'http://maps.google.com/mapfiles/kml/pal3/icon48.png', + MAP_CARRIER_ICON: 'http://maps.google.com/mapfiles/kml/pal4/icon54.png', + }, + + GOOGLE: { + MAPS_API_KEY: '', + ANALYTICS_API_KEY: '', + }, + + FAKE_UUID: 'ceffd77c-8cc1-47ac-b9b8-889f057aba3d', + + // Not secret MixPanel Token + MIXPANEL_API_KEY: '', + + LANGUAGE: { + LANG: 'ENGLISH', + LOCALE: 'ENGLISH', + }, + + DELIVERY_TIME: { + MIN: 30, + MAX: 60, + }, + + SUPPORT_NUMBER: '0888888888', + + STRIP: { + PUBLISHABLE_KEY: '', + POP_UP_LOGO: + 'https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2016/Jan/30/1263967991-1-everbie-avatar.png', + }, + + COORDINATE: { + LATITUDE: 42.6459136, + LONGITUDE: 23.3332736, + }, + + /** + * Contain app endpoints + * + * _🚧 warning: if you're using a AVD (Android Virtual Device) and localhost don't works, replace it with **10.0.2.2**_ + */ + ENDPOINT: { + GQL: 'http://localhost:8443/graphql', + GQL_SUBSCRIPTIONS: 'ws://localhost:2086/subscriptions', + SERVICES: 'http://localhost:5500', + HTTPS_SERVICES: 'https://localhost:2087', + API_FILE_UPLOAD: 'https://api.cloudinary.com/v1_1/evereq/upload', + }, + + FAKE_INVITE: { + ID: '1ae9d04f9010d834f8906881', + CITY: 'Sofia', + POSTCODE: '1700', + ADDRESS: 'Simeonovsko shose', + HOUSE: '104', + CREATED_AT: '2018-05-02T14:50:55.658Z', + UPDATED_AT: '2018-05-02T14:50:55.658Z', + APARTMENT: '3', + CODE: 8321, + COUNTRY_ID: 21, + }, + + /** + * For maintenance micro service + */ + SETTINGS: { + APP_TYPE: 'shop-mobile', + MAINTENANCE_API_URL: '', + }, + + /** + * For "single" merchant (multiple branches) + */ + MERCHANT_IDS: [], + + SHOPPING_CART: false, +}; + +export default environment; + +/* + * In development mode, to ignore zone related error stack frames such as + * 'zone.run', 'zoneDelegate.invokeTask' for easier debugging, you can + * import the following file, but please comment it out in isProd mode + * because it will have performance impact when throw error + */ +`; + +const envFileDest: string = isProd ? 'environment.prod.ts' : 'environment.ts'; +const envFileDestOther: string = !isProd + ? 'environment.prod.ts' + : 'environment.ts'; + +// we always want first to remove old generated files (one of them is not needed for current build) +try { + unlinkSync('./src/environments/environment.ts'); +} catch {} +try { + unlinkSync('./src/environments/environment.prod.ts'); +} catch {} + +writeFile(`./src/environments/${envFileDest}`, envFileContent, function (err) { + if (err) { + console.log(err); + } else { + console.log(`Generated ts environment file: ${envFileDest}`); + } +}); + +writeFile( + `./src/environments/${envFileDestOther}`, + envFileContent, + function (err) { + if (err) { + console.log(err); + } else { + console.log( + `Generated Second environment file: ${envFileDestOther}`, + ); + } + }, +); diff --git a/packages/shop-mobile-expo/scripts/env.ts b/packages/shop-mobile-expo/scripts/env.ts new file mode 100644 index 000000000..4d7c3c2b9 --- /dev/null +++ b/packages/shop-mobile-expo/scripts/env.ts @@ -0,0 +1,212 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Expo app and all settings will be loaded into the client browser! + +require('dotenv').config(); + +import { cleanEnv, num, str, bool, makeValidator, CleanOptions } from 'envalid'; +import { v4 as uuid } from 'uuid'; + +export type Env = Readonly<{ + PRODUCTION: boolean; + + VERSION: string; + + // 'slides' | 'list' + PRODUCTS_VIEW_TYPE: string; + + // 'popup' or 'page' + ORDER_INFO_TYPE: string; + + API_FILE_UPLOAD_URL: string; + + INVITE_BY_CODE_LOGO: string; + + NO_INTERNET_LOGO: string; + + COMPANY_NAME: string; + + GOOGLE_MAPS_API_KEY: string; + + GOOGLE_ANALYTICS_API_KEY: string; + + FAKE_UUID: string; + + // Not secret MixPanel Token + MIXPANEL_API_KEY: string; + + DEFAULT_LANGUAGE: string; + + DEFAULT_LOCALE: string; + + DELIVERY_TIME_MIN: number; + + DELIVERY_TIME_MAX: number; + + SUPPORT_NUMBER: string; + + STRIPE_PUBLISHABLE_KEY: string; + + // TODO: replace logo with recent one! + STRIPE_POP_UP_LOGO: string; + + MAP_MERCHANT_ICON_LINK: string; + + MAP_USER_ICON_LINK: string; + + MAP_CARRIER_ICON_LINK: string; + + DEFAULT_LATITUDE: number; + DEFAULT_LONGITUDE: number; + + // Graphql endpoints for apollo services + GQL_ENDPOINT: string; + + GQL_SUBSCRIPTIONS_ENDPOINT: string; + + SERVICES_ENDPOINT: string; + + HTTPS_SERVICES_ENDPOINT: string; + + FAKE_INVITE_ID: string; + + FAKE_INVITE_CITY: string; + + FAKE_INVITE_POSTCODE: string; + + FAKE_INVITE_ADDRESS: string; + + FAKE_INVITE_HOUSE: string; + + FAKE_INVITE_CREATED_AT: string; + + FAKE_INVITE_UPDATED_AT: string; + + FAKE_INVITE_APARTMENT: string; + + FAKE_INVITE_CODE: number; + + FAKE_INVITE_COUNTRY_ID: number; + + // For maintenance micro service + SETTINGS_APP_TYPE?: string; + + SETTINGS_MAINTENANCE_API_URL?: string; + + // For "single" merchant (multiple branches) + MERCHANT_IDS?: string[]; + + WEB_CONCURRENCY: number; + + WEB_MEMORY: number; + + PORT: number; + + SHOPPING_CART: boolean; +}>; + +// TODO: validate better merchantIDs +const merchantIDs: any = makeValidator((x) => { + return x; +}); + +const opt: CleanOptions = {}; + +export const env = cleanEnv( + process.env, + { + PRODUCTION: bool({ default: false }), + + VERSION: str({ default: '1.0.0' }), + + // 'slides' | 'list' + PRODUCTS_VIEW_TYPE: str({ default: 'slides' }), + + // 'popup' or 'page' + ORDER_INFO_TYPE: str({ default: 'page' }), + + API_FILE_UPLOAD_URL: str({ + default: 'https://api.cloudinary.com/v1_1/evereq/upload', + }), + + INVITE_BY_CODE_LOGO: str({ default: 'assets/imgs/ever-logo.svg' }), + NO_INTERNET_LOGO: str({ default: 'assets/imgs/logo.png' }), + + COMPANY_NAME: str({ default: 'Ever Co. LTD' }), + + GOOGLE_MAPS_API_KEY: str({ default: '' }), + + GOOGLE_ANALYTICS_API_KEY: str({ default: '' }), + FAKE_UUID: str({ default: uuid() }), + + // Not secret MixPanel Token + MIXPANEL_API_KEY: str({ default: '' }), + + DEFAULT_LANGUAGE: str({ default: 'en-US' }), + + DEFAULT_LOCALE: str({ default: 'en-US' }), + + DELIVERY_TIME_MIN: num({ default: 30 }), + DELIVERY_TIME_MAX: num({ default: 60 }), + + SUPPORT_NUMBER: str({ default: '0888888888' }), + + STRIPE_PUBLISHABLE_KEY: str({ default: '' }), + + // TODO: replace logo with recent one! + STRIPE_POP_UP_LOGO: str({ + default: + 'https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2016/Jan/30/1263967991-1-everbie-avatar.png', + }), + + MAP_MERCHANT_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal3/icon21.png', + }), + MAP_USER_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal3/icon48.png', + }), + MAP_CARRIER_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal4/icon54.png', + }), + + DEFAULT_LATITUDE: num({ default: 42.6459136 }), + DEFAULT_LONGITUDE: num({ default: 23.3332736 }), + + // Graphql endpoints for apollo services + GQL_ENDPOINT: str({ default: 'http://localhost:8443/graphql' }), + GQL_SUBSCRIPTIONS_ENDPOINT: str({ + default: 'ws://localhost:2086/subscriptions', + }), + SERVICES_ENDPOINT: str({ default: 'http://localhost:5500' }), + HTTPS_SERVICES_ENDPOINT: str({ default: 'https://localhost:2087' }), + + FAKE_INVITE_ID: str({ default: '1ae9d04f9010d834f8906881' }), + FAKE_INVITE_CITY: str({ default: 'Sofia' }), + FAKE_INVITE_POSTCODE: str({ default: '1700' }), + FAKE_INVITE_ADDRESS: str({ default: 'Simeonovsko shose' }), + FAKE_INVITE_HOUSE: str({ default: '104' }), + FAKE_INVITE_CREATED_AT: str({ default: '2018-05-02T14:50:55.658Z' }), + FAKE_INVITE_UPDATED_AT: str({ default: '2018-05-02T14:50:55.658Z' }), + FAKE_INVITE_APARTMENT: str({ default: '3' }), + FAKE_INVITE_CODE: num({ default: 8321 }), + FAKE_INVITE_COUNTRY_ID: num({ default: 21 }), + + // For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status + SETTINGS_APP_TYPE: str({ default: 'shop-mobile' }), + SETTINGS_MAINTENANCE_API_URL: str({ + default: '', + }), + + // For "single" merchant (multiple branches) + MERCHANT_IDS: merchantIDs({ + default: [ + // Add existing merchant ids + ], + }), + WEB_CONCURRENCY: num({ default: 1 }), + WEB_MEMORY: num({ default: 2048 }), + PORT: num({ default: 4201 }), + SHOPPING_CART: bool({ default: false }), + }, + opt, +); diff --git a/packages/shop-mobile-expo/src/App.tsx b/packages/shop-mobile-expo/src/App.tsx new file mode 100644 index 000000000..deb02a614 --- /dev/null +++ b/packages/shop-mobile-expo/src/App.tsx @@ -0,0 +1,39 @@ +import 'react-native-gesture-handler'; +import React from 'react'; +import AppLoading from 'expo-app-loading'; +import { useFonts } from 'expo-font'; + +// ROUTER +import Router from './router'; + +// COMPONENTS +import ReduxProvider from './components/Providers/Redux'; +import ApolloProvider from './components/Providers/Apollo'; +import PaperProvider from './components/Providers/Paper'; +import AppProvider from './components/Providers/App'; + +export default function App() { + const [fontsLoaded] = useFonts({ + 'Nunito-ExtraLight': require('./assets/fonts/Nunito/Nunito-ExtraLight.ttf'), + 'Nunito-Light': require('./assets/fonts/Nunito/Nunito-Light.ttf'), + 'Nunito-Regular': require('./assets/fonts/Nunito/Nunito-Regular.ttf'), + 'Nunito-SemiBold': require('./assets/fonts/Nunito/Nunito-SemiBold.ttf'), + 'Nunito-Bold': require('./assets/fonts/Nunito/Nunito-Bold.ttf'), + 'Nunito-Black': require('./assets/fonts/Nunito/Nunito-Black.ttf'), + 'Lobster-Regular': require('./assets/fonts/Lobster/Lobster-Regular.ttf'), + }); + + return !fontsLoaded ? ( + + ) : ( + + + + + + + + + + ); +} diff --git a/packages/shop-mobile-expo/src/assets/fonts/Lobster/Lobster-Regular.ttf b/packages/shop-mobile-expo/src/assets/fonts/Lobster/Lobster-Regular.ttf new file mode 100644 index 000000000..21c0073fd Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Lobster/Lobster-Regular.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Lobster/OFL.txt b/packages/shop-mobile-expo/src/assets/fonts/Lobster/OFL.txt new file mode 100644 index 000000000..a41143009 --- /dev/null +++ b/packages/shop-mobile-expo/src/assets/fonts/Lobster/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2010 The Lobster Project Authors (https://github.com/impallari/The-Lobster-Font), with Reserved Font Name "Lobster". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Black.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Black.ttf new file mode 100644 index 000000000..e4fe5d0b0 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Black.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Bold.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Bold.ttf new file mode 100644 index 000000000..368468ce4 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Bold.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraBold.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraBold.ttf new file mode 100644 index 000000000..8f5e1f623 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraBold.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraLight.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraLight.ttf new file mode 100644 index 000000000..251c47119 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraLight.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Light.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Light.ttf new file mode 100644 index 000000000..ad56724f9 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Light.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Regular.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Regular.ttf new file mode 100644 index 000000000..c8c90b7c2 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Regular.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-SemiBold.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-SemiBold.ttf new file mode 100644 index 000000000..3a30359dc Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-SemiBold.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/img/appIcons/adaptive-icon.png b/packages/shop-mobile-expo/src/assets/img/appIcons/adaptive-icon.png new file mode 100644 index 000000000..971156639 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/appIcons/adaptive-icon.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/appIcons/favicon.png b/packages/shop-mobile-expo/src/assets/img/appIcons/favicon.png new file mode 100644 index 000000000..4abcb5dce Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/appIcons/favicon.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/appIcons/icon.png b/packages/shop-mobile-expo/src/assets/img/appIcons/icon.png new file mode 100644 index 000000000..7c1323ad3 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/appIcons/icon.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/appIcons/splash.png b/packages/shop-mobile-expo/src/assets/img/appIcons/splash.png new file mode 100644 index 000000000..f6128d9e9 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/appIcons/splash.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/ever-background-gradient.png b/packages/shop-mobile-expo/src/assets/img/ever/ever-background-gradient.png new file mode 100644 index 000000000..1fa95ff3b Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/ever-background-gradient.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/ever.png b/packages/shop-mobile-expo/src/assets/img/ever/ever.png new file mode 100644 index 000000000..51dfd562d Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/ever.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/image_placeholder.png b/packages/shop-mobile-expo/src/assets/img/ever/image_placeholder.png new file mode 100644 index 000000000..79a48dd5e Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/image_placeholder.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/login_back.png b/packages/shop-mobile-expo/src/assets/img/ever/login_back.png new file mode 100644 index 000000000..565cd8efe Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/login_back.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/logo.png b/packages/shop-mobile-expo/src/assets/img/ever/logo.png new file mode 100644 index 000000000..616fb2d3b Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/logo.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/reviews.png b/packages/shop-mobile-expo/src/assets/img/ever/reviews.png new file mode 100644 index 000000000..1d3e1d8a3 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/reviews.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/static.png b/packages/shop-mobile-expo/src/assets/img/ever/static.png new file mode 100644 index 000000000..c55ceeb1c Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/static.png differ diff --git a/packages/shop-mobile-expo/src/assets/ts/styles.ts b/packages/shop-mobile-expo/src/assets/ts/styles.ts new file mode 100644 index 000000000..68028acc6 --- /dev/null +++ b/packages/shop-mobile-expo/src/assets/ts/styles.ts @@ -0,0 +1,750 @@ +import { StyleSheet, Dimensions, Platform } from 'react-native'; + +export const isIOS = Platform.OS === 'ios'; +export const isAndroid = Platform.OS === 'android'; + +export const CONSTANT_SIZE = { + FONT_SIZE_SM: 11, + FONT_SIZE: 14, + FONT_SIZE_MD: 17, + FONT_SIZE_LG: 20, + FONT_SIZE_XLG: 23, + NO_SPACE: 0, + SPACE_SM: 8, + SPACE: 16, + SPACE_MD: 24, + SPACE_LG: 32, + SPACE_XLG: 40, + BOTTOM_NAVBAR_HEIGHT: 80, + STATUS_BAR_HEIGHT: 24, + SCREEN_HEIGHT: Dimensions.get('screen').height, + SCREEN_WIDTH: Dimensions.get('screen').width, + WINDOW_HEIGHT: Dimensions.get('window').height, + WINDOW_WIDTH: Dimensions.get('window').width, + DRAWER_HEADER_HEIGHT: 56, +}; + +export const CONSTANT_COLOR = { + primary: '#2A2C39', + primaryLight: '#444654', + primaryHightLight: '#676977', + light: '#f4f5f8', + dark: '#222428', + secondary: '#bd4742', + secondaryLight: '#d36b67', + secondaryHighLight: '#f27f7b', + gray: '#8f96a7', + grayLight: '#b3bbce', + grayHighLight: '#dce3f4', + + facebook: '#3B5998', + google: '#DD4B39', + tertiary: '#7044ff', + success: '#10dc60', + danger: '#f04141', + warning: '#ffce00', + white: '#FFF', + input: '#edeef2', +}; + +export const GLOBAL_STYLE = StyleSheet.create({ + FF_NunitoExtraLight: { + fontFamily: 'Nunito-ExtraLight', + }, + FF_NunitoLight: { + fontFamily: 'Nunito-Light', + }, + FF_Nunito: { + fontFamily: 'Nunito-Regular', + }, + FF_NunitoSemiBold: { + fontFamily: 'Nunito-SemiBold', + }, + FF_NunitoBold: { + fontFamily: 'Nunito-Bold', + }, + FF_NunitoBlack: { + fontFamily: 'Nunito-Black', + }, + FF_Lobster: { + fontFamily: 'Lobster-Regular', + }, + screen: { + flex: 1, + backgroundColor: CONSTANT_COLOR.primary, + }, + screenStatic: { + height: CONSTANT_SIZE.SCREEN_HEIGHT, + backgroundColor: CONSTANT_COLOR.primary, + }, + screenStaticNav: { + flex: 1, + marginBottom: CONSTANT_SIZE.BOTTOM_NAVBAR_HEIGHT, + backgroundColor: CONSTANT_COLOR.primary, + }, + bgPrimary: { + backgroundColor: CONSTANT_COLOR.primary, + }, + bgPrimaryLight: { + backgroundColor: CONSTANT_COLOR.primaryLight, + }, + bgSecondary: { + backgroundColor: CONSTANT_COLOR.secondary, + }, + bgSecondaryLight: { + backgroundColor: CONSTANT_COLOR.secondaryLight, + }, + bgLight: { + backgroundColor: CONSTANT_COLOR.light, + }, + bgDanger: { + backgroundColor: CONSTANT_COLOR.danger, + }, + bgSuccess: { + backgroundColor: CONSTANT_COLOR.success, + }, + bgTransparent: { + backgroundColor: 'transparent', + }, + txtPrimary: { + color: CONSTANT_COLOR.primary, + }, + txtPrimaryLight: { + color: CONSTANT_COLOR.primaryLight, + }, + txtSecondary: { + color: CONSTANT_COLOR.secondary, + }, + txtSecondaryLight: { + color: CONSTANT_COLOR.secondaryLight, + }, + txtSuccess: { + color: CONSTANT_COLOR.success, + }, + txtDanger: { + color: CONSTANT_COLOR.danger, + }, + txtCenter: { + textAlign: 'center', + }, + txtUpper: { + textTransform: 'uppercase', + }, + txtLower: { + textTransform: 'lowercase', + }, + txtCapitalize: { + textTransform: 'capitalize', + }, + fontBold: { + fontWeight: 'bold', + }, + fontItalic: { + fontStyle: 'italic', + }, + centered: { + justifyContent: 'center', + alignItems: 'center', + }, + container: { + marginHorizontal: 20, + paddingHorizontal: 10, + }, + content: {}, + dNone: { + display: 'none', + }, + section: { + backgroundColor: CONSTANT_COLOR.light, + padding: 10, + marginBottom: 10, + }, + titleSection: { + color: CONSTANT_COLOR.secondary, + marginBottom: 10, + textTransform: 'uppercase', + fontWeight: 'bold', + }, + w100: { + width: '100%', + }, + w75: { + width: '75%', + }, + w50: { + width: '50%', + }, + w25: { + width: '25%', + }, + h100: { + height: '100%', + }, + h75: { + height: '75%', + }, + h50: { + height: '50%', + }, + h25: { + height: '25%', + }, + m0: { + margin: 0, + }, + m1: { + margin: 5, + }, + m2: { + margin: 10, + }, + m3: { + margin: 15, + }, + m4: { + margin: 20, + }, + m5: { + margin: 25, + }, + mt0: { + marginTop: 0, + }, + mt1: { + marginTop: 5, + }, + mt2: { + marginTop: 10, + }, + mt3: { + marginTop: 15, + }, + mt4: { + marginTop: 20, + }, + mt5: { + marginTop: 25, + }, + mb0: { + marginBottom: 0, + }, + mb1: { + marginBottom: 5, + }, + mb2: { + marginBottom: 10, + }, + mb3: { + marginBottom: 15, + }, + mb4: { + marginBottom: 20, + }, + mb5: { + marginBottom: 25, + }, + ml0: { + marginLeft: 0, + }, + ml1: { + marginLeft: 5, + }, + ml2: { + marginLeft: 10, + }, + ml3: { + marginLeft: 15, + }, + ml4: { + marginLeft: 20, + }, + ml5: { + marginLeft: 25, + }, + mr0: { + marginRight: 0, + }, + mr1: { + marginRight: 5, + }, + mr2: { + marginRight: 10, + }, + mr3: { + marginRight: 15, + }, + mr4: { + marginRight: 20, + }, + mr5: { + marginRight: 25, + }, + my0: { + marginVertical: 0, + }, + my1: { + marginVertical: 5, + }, + my2: { + marginVertical: 10, + }, + my3: { + marginVertical: 15, + }, + my4: { + marginVertical: 20, + }, + my5: { + marginVertical: 25, + }, + mx0: { + marginHorizontal: 0, + }, + mx1: { + marginHorizontal: 5, + }, + mx2: { + marginHorizontal: 10, + }, + mx3: { + marginHorizontal: 15, + }, + mx4: { + marginHorizontal: 20, + }, + mx5: { + marginHorizontal: 25, + }, + p0: { + padding: 0, + }, + p1: { + padding: 5, + }, + p2: { + padding: 10, + }, + p3: { + padding: 15, + }, + p4: { + padding: 20, + }, + p5: { + padding: 25, + }, + pt0: { + paddingTop: 0, + }, + pt1: { + paddingTop: 5, + }, + pt2: { + paddingTop: 10, + }, + pt3: { + paddingTop: 15, + }, + pt4: { + paddingTop: 20, + }, + pt5: { + paddingTop: 25, + }, + pb0: { + paddingBottom: 0, + }, + pb1: { + paddingBottom: 5, + }, + pb2: { + paddingBottom: 10, + }, + pb3: { + paddingBottom: 15, + }, + pb4: { + paddingBottom: 20, + }, + pb5: { + paddingBottom: 25, + }, + pl0: { + paddingLeft: 0, + }, + pl1: { + paddingLeft: 5, + }, + pl2: { + paddingLeft: 10, + }, + pl3: { + paddingLeft: 15, + }, + pl4: { + paddingLeft: 20, + }, + pl5: { + paddingLeft: 25, + }, + pr0: { + paddingRight: 0, + }, + pr1: { + paddingRight: 5, + }, + pr2: { + paddingRight: 10, + }, + pr3: { + paddingRight: 15, + }, + pr4: { + paddingRight: 20, + }, + pr5: { + paddingRight: 25, + }, + py0: { + paddingVertical: 0, + }, + py1: { + paddingVertical: 5, + }, + py2: { + paddingVertical: 10, + }, + py3: { + paddingVertical: 15, + }, + py4: { + paddingVertical: 20, + }, + py5: { + paddingVertical: 25, + }, + px0: { + paddingHorizontal: 0, + }, + px1: { + paddingHorizontal: 5, + }, + px2: { + paddingHorizontal: 10, + }, + px3: { + paddingHorizontal: 15, + }, + px4: { + paddingHorizontal: 20, + }, + px5: { + paddingHorizontal: 25, + }, + imgCover: { + height: undefined, + width: undefined, + resizeMode: 'cover', + flex: 1, + }, + noShadow: { + shadowColor: 'transparent', + shadowOffset: { width: 0, height: 0 }, + shadowRadius: 0, + shadowOpacity: 0, + elevation: 0, + }, + shadowSm: { + shadowColor: CONSTANT_COLOR.dark, + shadowOffset: { width: 0, height: 1 }, + shadowRadius: 5, + shadowOpacity: 0.2, + elevation: 2, + }, + shadow: { + shadowColor: CONSTANT_COLOR.dark, + shadowOffset: { width: 1, height: 1.5 }, + shadowRadius: 5, + shadowOpacity: 0.5, + elevation: 5, + }, + shadowLg: { + shadowColor: isIOS ? '#000' : CONSTANT_COLOR.dark, + shadowOffset: { + width: isIOS ? 0 : 2, + height: 2, + }, + shadowOpacity: isIOS ? 0.25 : 0.7, + shadowRadius: isIOS ? 3.84 : 5, + + elevation: 8.5, + }, + roundedSm: { + borderRadius: 5, + }, + rounded: { + borderRadius: 10, + }, + roundedMd: { + borderRadius: 20, + }, + roundedLg: { + borderRadius: 30, + }, + noBorder: { + borderColor: 'transparent', + borderWidth: 0, + }, + borderSm: { + borderColor: CONSTANT_COLOR.gray, + borderWidth: 0.5, + }, + border: { + borderColor: CONSTANT_COLOR.gray, + borderWidth: 1, + }, + borderMd: { + borderColor: CONSTANT_COLOR.gray, + borderWidth: 2, + }, + borderLg: { + borderColor: CONSTANT_COLOR.gray, + borderWidth: 4, + }, + separator: { + borderTopColor: CONSTANT_COLOR.gray, + borderTopWidth: 1, + marginVertical: 10, + padding: 0, + }, + overlay: { + position: 'absolute', + flex: 1, + height: '100%', + width: '100%', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0,0,0,0.3)', + }, + card: { + position: 'relative', + padding: 15, + borderRadius: 10, + backgroundColor: 'white', + }, + cardBtnClose: { + position: 'absolute', + right: 10, + }, + tabsContainer: { + backgroundColor: CONSTANT_COLOR.light, + flexDirection: 'row', + alignItems: 'stretch', + }, + tab: { + paddingVertical: 12, + borderBottomWidth: 3, + borderBottomColor: CONSTANT_COLOR.light, + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + activeTab: { + borderBottomColor: CONSTANT_COLOR.primary, + }, + tabName: { + color: CONSTANT_COLOR.primary, + fontWeight: 'bold', + }, + tabIcon: { + marginRight: 4, + }, + selectContainer: { + borderBottomColor: CONSTANT_COLOR.primary, + borderBottomWidth: 1, + width: 130, + height: 35, + paddingHorizontal: 0, + alignItems: 'center', + }, + selectItem: { + width: '100%', + height: '100%', + color: CONSTANT_COLOR.gray, + fontSize: 15, + }, + statsContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginHorizontal: 30, + marginVertical: 10, + }, + stat: { + alignItems: 'center', + justifyContent: 'center', + flex: 1, + }, + statAmount: { + color: CONSTANT_COLOR.gray, + fontSize: 18, + fontWeight: 'bold', + }, + statTitle: { + color: CONSTANT_COLOR.gray, + fontSize: 12, + fontWeight: '500', + textTransform: 'capitalize', + marginTop: 4, + }, + form: { + marginBottom: 32, + }, + field: { + marginBottom: 20, + }, + inputTitle: { + color: CONSTANT_COLOR.gray, + fontSize: 10, + textTransform: 'uppercase', + marginBottom: 0, + }, + inputGroup: { + flexDirection: 'row', + alignItems: 'stretch', + }, + appendTag: {}, + input: { + height: 40, + borderBottomWidth: 1, + borderBottomColor: CONSTANT_COLOR.primary, + color: CONSTANT_COLOR.gray, + fontSize: 15, + paddingHorizontal: 10, + }, + th: { + height: 40, + backgroundColor: CONSTANT_COLOR.light, + }, + column: { + flexDirection: 'column', + }, + row: { + flexDirection: 'row', + }, + inlineItems: { + flexDirection: 'row', + alignItems: 'center', + }, + justifyContentBetween: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + tableBtn: { + padding: 5, + flexDirection: 'row', + justifyContent: 'center', + marginHorizontal: 5, + paddingHorizontal: 10, + borderRadius: 5, + }, + errorContainer: { + paddingVertical: 20, + alignItems: 'center', + justifyContent: 'center', + }, + errorMessage: { + color: CONSTANT_COLOR.primary, + fontSize: 13, + fontWeight: '400', + textAlign: 'center', + }, + btn: { + height: 45, + borderRadius: 5, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderWidth: 1, + borderColor: 'rgba(0,0,0,0)', + marginVertical: 5, + paddingHorizontal: 20, + }, + btnPrimary: { + backgroundColor: CONSTANT_COLOR.primary, + borderColor: CONSTANT_COLOR.primary, + }, + btnPrimaryLight: { + backgroundColor: CONSTANT_COLOR.primaryLight, + borderColor: CONSTANT_COLOR.primaryLight, + }, + btnSecondary: { + backgroundColor: CONSTANT_COLOR.secondary, + borderColor: CONSTANT_COLOR.secondary, + }, + btnTopLeft: { + position: 'absolute', + top: 30, + left: 20, + width: 40, + height: 40, + borderRadius: 40 / 2, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(22, 22, 43, 0.2)', + }, + btnBottomRight: { + position: 'absolute', + bottom: 20, + right: 20, + width: 40, + height: 40, + borderRadius: 40 / 2, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(22, 22, 43, 0.2)', + }, + accordionTitle: { + color: CONSTANT_COLOR.secondary, + }, + image: { + flex: 1, + height: undefined, + width: undefined, + resizeMode: 'cover', + }, + inputPicker: { + borderColor: CONSTANT_COLOR.input, + borderWidth: 1, + height: 30, + width: '100%', + color: CONSTANT_COLOR.gray, + borderRadius: 7, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 5, + }, + inputPickerLabel: { + fontSize: 12, + color: CONSTANT_COLOR.gray, + paddingRight: 0, + width: '85%', + }, + inputPickerIcon: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-end', + width: '15%', + }, + textarea: { + borderColor: CONSTANT_COLOR.grayLight, + borderWidth: 1, + borderRadius: 5, + overflow: 'hidden', + height: 100, + }, +}); diff --git a/packages/shop-mobile-expo/src/client/invite/argumentInterfaces.ts b/packages/shop-mobile-expo/src/client/invite/argumentInterfaces.ts new file mode 100644 index 000000000..671ca1761 --- /dev/null +++ b/packages/shop-mobile-expo/src/client/invite/argumentInterfaces.ts @@ -0,0 +1,60 @@ +// TYPES +import type { MaybeType, ScalarsInterface } from '../../types/index'; +import type { + GeoLocationInterface, + GeoLocationCreateInputInterface, + GeoLocationInputInterface, +} from '../types'; + +// TODO: Add descriptive comments for types/interfaces + +/** + * Invite structure + */ +export interface InviteInterface { + _id: ScalarsInterface['String']; + id: ScalarsInterface['String']; + code: ScalarsInterface['String']; + apartment: ScalarsInterface['String']; + geoLocation: GeoLocationInterface; +} + +export interface InviteByCodeInputInterface { + location: Location; + inviteCode: ScalarsInterface['String']; + firstName?: MaybeType; + lastName?: MaybeType; +} + +export interface InviteByLocationInputInterface { + countryId: ScalarsInterface['Int']; + city: ScalarsInterface['String']; + streetAddress: ScalarsInterface['String']; + house: ScalarsInterface['String']; + apartment: ScalarsInterface['String']; + postcode?: MaybeType; +} + +export interface QueryGetInviteByCodeArgsInterface { + info: InviteByCodeInputInterface; +} + +export interface QueryGetInviteByLocationArgsInterface { + info?: MaybeType; +} + +export interface CreateInviteByLocationMutationArgsInterface { + createInput: InviteCreateInputInterface; +} + +export interface InviteCreateInputInterface { + code?: MaybeType; + apartment: ScalarsInterface['String']; + geoLocation: GeoLocationCreateInputInterface; +} + +export interface InviteInputInterface { + code: ScalarsInterface['String']; + apartment: ScalarsInterface['String']; + geoLocation: GeoLocationInputInterface; +} diff --git a/packages/shop-mobile-expo/src/client/invite/mutations.ts b/packages/shop-mobile-expo/src/client/invite/mutations.ts new file mode 100644 index 000000000..0b56e7d2c --- /dev/null +++ b/packages/shop-mobile-expo/src/client/invite/mutations.ts @@ -0,0 +1,32 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +/** + * Mutation to add a new user + * + * @type TypedDocumentNode + */ +export const CREATE_INVITE_BY_LOCATION_MUTATION: TypedDocumentNode = gql` + mutation CreateInviteByLocationMutation($createInput: InviteCreateInput!) { + createInvite(createInput: $createInput) { + id + code + apartment + geoLocation { + id + createdAt + updatedAt + countryId + countryName + city + streetAddress + house + postcode + notes + coordinates { + lng + lat + } + } + } + } +`; diff --git a/packages/shop-mobile-expo/src/client/invite/queries.ts b/packages/shop-mobile-expo/src/client/invite/queries.ts new file mode 100644 index 000000000..95851dc6e --- /dev/null +++ b/packages/shop-mobile-expo/src/client/invite/queries.ts @@ -0,0 +1 @@ +// import { gql, TypedDocumentNode } from '@apollo/client'; diff --git a/packages/shop-mobile-expo/src/client/products/argumentInterfaces.ts b/packages/shop-mobile-expo/src/client/products/argumentInterfaces.ts new file mode 100644 index 000000000..9fbb8b50e --- /dev/null +++ b/packages/shop-mobile-expo/src/client/products/argumentInterfaces.ts @@ -0,0 +1,121 @@ +import type { + ImageInterface, + TranslateInterface, + PagingOptionsInputInterface, + GeoLocationInputInterface, +} from '../types'; +import type { MaybeType, ScalarsInterface } from '../../types'; + +// TODO: Add more comments + +export interface ProductInterface { + _id: ScalarsInterface['String']; + id: ScalarsInterface['String']; + title: TranslateInterface[]; + description: TranslateInterface[]; + details: TranslateInterface[]; + images: ImageInterface[]; + categories?: MaybeType[]>; + detailsHTML: TranslateInterface[]; + descriptionHTML: TranslateInterface[]; + _createdAt?: MaybeType; + _updatedAt?: MaybeType; +} + +export interface ProductCreateInputInterface { + title: TranslateInterface[]; + description: TranslateInterface[]; + details?: MaybeType; + images: ImageInterface[]; + categories?: MaybeType; + detailsHTML?: MaybeType; + descriptionHTML?: MaybeType; +} + +export interface ProductInfoInterface { + warehouseProduct: WarehouseProductInterface; + distance: ScalarsInterface['Float']; + warehouseId: ScalarsInterface['String']; + warehouseLogo: ScalarsInterface['String']; +} + +export interface ProductSaveInputInterface { + _id: ScalarsInterface['String']; + id?: MaybeType; + title: TranslateInterface[]; + description: TranslateInterface[]; + details?: MaybeType; + images: ImageInterface[]; + categories?: MaybeType; + detailsHTML?: MaybeType; + descriptionHTML?: MaybeType; +} + +export interface ProductsCategoriesCreateInputInterface { + name: TranslateInterface[]; + image?: MaybeType; +} + +export interface ProductsCategoriesFindInputInterface { + noop?: MaybeType; +} + +export interface ProductsCategoriesUpdatenputInterface { + name?: MaybeType; +} + +export interface ProductsCategoryInterface { + _id: ScalarsInterface['String']; + id: ScalarsInterface['String']; + name: TranslateInterface[]; + image?: MaybeType; + _createdAt?: MaybeType; + _updatedAt?: MaybeType; +} + +export interface ProductsCategoryInputInterface { + _id: ScalarsInterface['String']; + name?: MaybeType; +} + +export interface ProductsFindInputInterface { + title?: MaybeType; + description?: MaybeType; + details?: MaybeType; + image?: MaybeType; +} + +export interface WarehouseProductInterface { + id: ScalarsInterface['String']; + _id: ScalarsInterface['String']; + price: ScalarsInterface['Int']; + initialPrice: ScalarsInterface['Int']; + count?: MaybeType; + soldCount: ScalarsInterface['Int']; + product: ProductInterface; + isManufacturing?: MaybeType; + isCarrierRequired?: MaybeType; + isDeliveryRequired?: MaybeType; + isTakeaway?: MaybeType; + deliveryTimeMin?: MaybeType; + deliveryTimeMax?: MaybeType; +} + +export interface GetGeoLocationProductsOptions { + isDeliveryRequired?: MaybeType; + isTakeaway?: MaybeType; + merchantIds?: MaybeType[]>; + imageOrientation?: MaybeType; + locale?: MaybeType; + wihoutCount?: MaybeType; +} + +/** + * + */ +export interface ProductsQueryArgsInterface { + searchText?: MaybeType; + pagingOptions?: MaybeType; + options?: MaybeType; + geoLocation: GeoLocationInputInterface; +} diff --git a/packages/shop-mobile-expo/src/client/products/mutations.ts b/packages/shop-mobile-expo/src/client/products/mutations.ts new file mode 100644 index 000000000..95851dc6e --- /dev/null +++ b/packages/shop-mobile-expo/src/client/products/mutations.ts @@ -0,0 +1 @@ +// import { gql, TypedDocumentNode } from '@apollo/client'; diff --git a/packages/shop-mobile-expo/src/client/products/queries.ts b/packages/shop-mobile-expo/src/client/products/queries.ts new file mode 100644 index 000000000..94a42021f --- /dev/null +++ b/packages/shop-mobile-expo/src/client/products/queries.ts @@ -0,0 +1,95 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +/** + * Query to retrieve products + */ +export const PRODUCTS_QUERY: TypedDocumentNode = gql` + query ProductsQuery( + $existedProductsIds: [String] + $findInput: ProductsFindInput + $pagingOptions: PagingOptionsInput + ) { + products( + existedProductsIds: $existedProductsIds + findInput: $findInput + pagingOptions: $pagingOptions + ) { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + url + locale + } + categories + _createdAt + _updatedAt + } + } +`; + +export const GEO_LOCATION_PRODUCTS_BY_PAGING = gql` + query GeoLocationProductsByPaging( + $geoLocation: GeoLocationFindInput! + $options: GetGeoLocationProductsOptions + $pagingOptions: PagingOptionsInput + $searchText: String + ) { + geoLocationProductsByPaging( + geoLocation: $geoLocation + options: $options + pagingOptions: $pagingOptions + searchText: $searchText + ) { + distance + warehouseId + warehouseLogo + warehouseProduct { + id + price + initialPrice + count + soldCount + + product { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + } + isManufacturing + isCarrierRequired + isDeliveryRequired + isTakeaway + deliveryTimeMin + deliveryTimeMax + } + } + } +`; diff --git a/packages/shop-mobile-expo/src/client/types.ts b/packages/shop-mobile-expo/src/client/types.ts new file mode 100644 index 000000000..bd266e60f --- /dev/null +++ b/packages/shop-mobile-expo/src/client/types.ts @@ -0,0 +1,84 @@ +// This file will contain common types & interfaces that arguments will use +import type { MaybeType, ScalarsInterface } from '../types/index'; + +export interface GeoLocationInterface { + _id?: MaybeType; + id?: MaybeType; + _createdAt?: MaybeType; + createdAt?: MaybeType; + _updatedAt?: MaybeType; + updatedAt?: MaybeType; + countryId: ScalarsInterface['Int']; + countryName?: MaybeType; + city: ScalarsInterface['String']; + streetAddress: ScalarsInterface['String']; + house: ScalarsInterface['String']; + postcode?: MaybeType; + notes?: MaybeType; + loc: LocInterface; + coordinates?: GeoLocationCoordinatesInterface; +} + +export interface GeoLocationCoordinatesInterface { + lng: ScalarsInterface['Float']; + lat: ScalarsInterface['Float']; +} + +export interface LocInterface { + type: ScalarsInterface['String']; + coordinates: ScalarsInterface['Float'][]; +} + +export interface GeoLocationCreateInputInterface { + countryId: ScalarsInterface['Int']; + city: ScalarsInterface['String']; + streetAddress: ScalarsInterface['String']; + house: ScalarsInterface['String']; + postcode?: MaybeType; + notes: MaybeType; + loc: LocInputInterface; +} + +export interface LocInputInterface { + type: ScalarsInterface['String']; + coordinates: ScalarsInterface['Float'][]; +} + +export interface GeoLocationInputInterface { + countryId?: MaybeType; + countryName?: MaybeType; + city?: MaybeType; + streetAddress?: MaybeType; + house?: MaybeType; + postcode?: MaybeType; + loc: LocationInterface; +} + +export interface LocationInterface { + type: ScalarsInterface['String']; + coordinates: ScalarsInterface['Float'][]; +} + +export interface PagingOptionsInputInterface { + sort?: MaybeType; + limit?: MaybeType; + skip?: MaybeType; +} + +export interface PagingSortInputInterface { + field: ScalarsInterface['String']; + sortBy: ScalarsInterface['String']; +} + +export interface ImageInterface { + locale: ScalarsInterface['String']; + url: ScalarsInterface['String']; + width: ScalarsInterface['Int']; + height: ScalarsInterface['Int']; + orientation: ScalarsInterface['Int']; +} + +export interface TranslateInterface { + locale: ScalarsInterface['String']; + value: ScalarsInterface['String']; +} diff --git a/packages/shop-mobile-expo/src/client/user/argumentInterfaces.ts b/packages/shop-mobile-expo/src/client/user/argumentInterfaces.ts new file mode 100644 index 000000000..9346248de --- /dev/null +++ b/packages/shop-mobile-expo/src/client/user/argumentInterfaces.ts @@ -0,0 +1,24 @@ +// TODO: Add more comments + +/** + * Minimal user info to create a user. + */ +export interface RegisterUserArgsInterface { + registerInput: { + user: { + lastName: string; + firstName: string; + geoLocation: { + loc: { + coordinates: [number, number]; + type: string; + }; + house: string; + streetAddress: string; + city: string; + countryId: number; + }; + apartment: string; + }; + }; +} diff --git a/packages/shop-mobile-expo/src/client/user/mutations.ts b/packages/shop-mobile-expo/src/client/user/mutations.ts new file mode 100644 index 000000000..28197fdd7 --- /dev/null +++ b/packages/shop-mobile-expo/src/client/user/mutations.ts @@ -0,0 +1,28 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +/** + * Mutation to add a new user + * + * @type TypedDocumentNode + */ +export const REGISTER_USER_MUTATION: TypedDocumentNode = gql` + mutation RegisterUserMutation($registerInput: UserRegisterInput!) { + registerUser(registerInput: $registerInput) { + firstName + lastName + phone + id + apartment + image + createdAt + fullAddress + email + geoLocation { + city + countryName + streetAddress + house + } + } + } +`; diff --git a/packages/shop-mobile-expo/src/client/user/queries.ts b/packages/shop-mobile-expo/src/client/user/queries.ts new file mode 100644 index 000000000..95851dc6e --- /dev/null +++ b/packages/shop-mobile-expo/src/client/user/queries.ts @@ -0,0 +1 @@ +// import { gql, TypedDocumentNode } from '@apollo/client'; diff --git a/packages/shop-mobile-expo/src/components/Common/CustomScreenHeader.tsx b/packages/shop-mobile-expo/src/components/Common/CustomScreenHeader.tsx new file mode 100644 index 000000000..dadead2d0 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/CustomScreenHeader.tsx @@ -0,0 +1,289 @@ +import React, { useState } from 'react'; +import { View, TouchableOpacity, StyleSheet } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { IconButton, Title, Switch, TouchableRipple } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import MaterialIcons from '@expo/vector-icons/MaterialIcons'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getLanguage } from '../../store/features/translation'; + +// COMPONENTS +import Icon from './Icon'; +import PaperText from './PaperText'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +// LOCAL TYPES +export interface CustomScreenHeaderType { + title?: string; + children?: React.ReactChild; + showControls?: boolean; + controlOnPressSearch?: () => any; + controlOnPressStore?: () => any; + showHomeBtn?: boolean; + onPressHomeBtn?: () => any; + showBackBtn?: boolean; + onPressBackBtn?: () => any; +} + +const CustomScreenHeader: React.FC = ({ + title, + children, + showControls = false, + controlOnPressSearch, + controlOnPressStore = () => {}, + showHomeBtn = false, + onPressHomeBtn, + showBackBtn = false, + onPressBackBtn, +}) => { + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + + // HOOKS + const navigation = useNavigation(); + + // STATES + const [tmpSwitchValue, setTmpSwitchValue] = useState(false); + + // DATA + + const STYLES = StyleSheet.create({ + safeArea: { + ...GS.bgPrimaryLight, + ...GS.w100, + ...GS.shadowLg, + marginBottom: -CS.SPACE_SM, + borderBottomEndRadius: CS.SPACE_SM, + borderBottomStartRadius: CS.SPACE_SM, + }, + main: { + ...GS.row, + ...GS.px2, + alignItems: 'stretch', + height: CS.DRAWER_HEADER_HEIGHT, + }, + menuBtnContainer: { ...GS.centered, ...GS.mr1 }, + contentContainer: { + ...GS.inlineItems, + flex: 1, + }, + contentContainerChildren: { + ...GS.justifyContentBetween, + flex: 1, + }, + contentTitle: { + ...GS.mb1, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD - 1, + }, + productsStatusText: { + ...GS.txtCapitalize, + fontSize: CS.FONT_SIZE_SM, + opacity: 0.4, + }, + productsStatusTextFocused: { + opacity: 1, + }, + productsStatusSwitch: { + marginHorizontal: -3, + transform: [{ scale: 0.74 }], + }, + storeBtnContainer: { + ...GS.roundedLg, + overflow: 'hidden', + }, + }); + + return ( + + + {/* drawer menu btn */} + + + + + {/* header content */} + + {children ? ( + children + ) : ( + + {!!title && ( + + {title} + + )} + {!!showControls && ( + + + setTmpSwitchValue(!tmpSwitchValue) + } + style={{ + ...GS.inlineItems, + ...GS.mr2, + }}> + + {LANGUAGE.PRODUCTS_VIEW.TAKEAWAY} + + + setTmpSwitchValue( + !tmpSwitchValue, + ) + } + style={STYLES.productsStatusSwitch} + /> + + {LANGUAGE.PRODUCTS_VIEW.DELIVERY} + + + + + controlOnPressSearch + ? controlOnPressSearch() + : navigation.navigate( + 'DRAWER/MERCHANTS_SEARCH' as never, + ) + } + /> + + + + controlOnPressStore() + }> + + + + + )} + + {showBackBtn && ( + + + onPressBackBtn + ? onPressBackBtn() + : navigation.goBack() + }> + + + + { + LANGUAGE.PRODUCTS_VIEW + .DETAILS.BACK + } + + + + + )} + + {showHomeBtn && ( + + + onPressHomeBtn + ? onPressHomeBtn() + : navigation.navigate( + // @ts-ignore TODO: search to solve the next line issue + 'DRAWER/HOME', + ) + }> + + + + {LANGUAGE.PRODUCTS_VIEW.TITLE} + + + + + )} + + )} + + + + ); +}; + +export default CustomScreenHeader; diff --git a/packages/shop-mobile-expo/src/components/Common/Dialog.tsx b/packages/shop-mobile-expo/src/components/Common/Dialog.tsx new file mode 100644 index 000000000..8b024161c --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/Dialog.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { StyleSheet, ViewStyle } from 'react-native'; +import { + Dialog, + Portal, + IconButton, + Button, + Paragraph, +} from 'react-native-paper'; + +// HELPERS +import { getReactComponentProps } from '../../helpers/utils'; + +// STYLES +import { + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, + GLOBAL_STYLE as GS, +} from '../../assets/ts/styles'; + +export const PaperDialogTitleProps = getReactComponentProps(Dialog.Title); +export const PaperButtonProps = getReactComponentProps(Button); +export const PaperParagraphProps = getReactComponentProps(Paragraph); +export interface DialogType { + visible: boolean; + title?: string; + titleProps?: typeof PaperDialogTitleProps; + message?: React.ReactNode; + style?: ViewStyle; + contentStyle?: ViewStyle; + actionStyle?: ViewStyle; + children?: React.ReactNode; + showCloseBtn?: boolean; + actions?: typeof PaperButtonProps[]; + onDismiss: () => void; +} + +const CustomDialog: React.FC = ({ + visible = false, + title, + titleProps = {}, + message, + style = {}, + contentStyle = {}, + actionStyle = {}, + children, + showCloseBtn = true, + actions, + onDismiss = () => {}, +}) => { + // DATA + const STYLES = StyleSheet.create({ + style: { + ...GS.pt2, + borderRadius: CS.SPACE_SM, + overflow: 'hidden', + ...style, + }, + contentStyle: { + ...contentStyle, + }, + actionStyle: { + ...actionStyle, + }, + closeBtn: { + ...GS.mr2, + ...GS.mt2, + position: 'absolute', + top: 0, + right: 0, + }, + contentContainer: { width: '100%' }, + }); + + return ( + + + {showCloseBtn && ( + + )} + {title && titleProps && ( + + {title} + + )} + + + {!!children && children} + {!!message && !children && ( + + {message} + + )} + + + {actions && ( + + {actions.map((item, index) => ( + + + ); +}; + +export default CustomDialog; diff --git a/packages/shop-mobile-expo/src/components/Common/FocusAwareStatusBar.tsx b/packages/shop-mobile-expo/src/components/Common/FocusAwareStatusBar.tsx new file mode 100644 index 000000000..8d8ff64bf --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/FocusAwareStatusBar.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { StatusBar, StatusBarProps } from 'react-native'; +import { useIsFocused } from '@react-navigation/native'; + +const FocusAwareStatusBar: React.FC = (props) => { + const isFocused = useIsFocused(); + + return isFocused ? : null; +}; + +export default FocusAwareStatusBar; diff --git a/packages/shop-mobile-expo/src/components/Common/Icon.tsx b/packages/shop-mobile-expo/src/components/Common/Icon.tsx new file mode 100644 index 000000000..57ec4a099 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/Icon.tsx @@ -0,0 +1,14 @@ +import Icon_ from '@expo/vector-icons/Feather'; + +// HELPERS +import { getReactComponentProps } from '../../helpers/utils'; + +const IconProps = getReactComponentProps(Icon_); + +export type IconPropsType = typeof IconProps; +export type IconComponentType = typeof Icon_; +export type IconNameType = keyof typeof Icon_.glyphMap; + +const Icon: IconComponentType = Icon_; + +export default Icon; diff --git a/packages/shop-mobile-expo/src/components/Common/OrderHistoryItem.tsx b/packages/shop-mobile-expo/src/components/Common/OrderHistoryItem.tsx new file mode 100644 index 000000000..9858a43e1 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/OrderHistoryItem.tsx @@ -0,0 +1,195 @@ +import React from 'react'; +import { StyleSheet, Image, View } from 'react-native'; +import { Card, Avatar, Title } from 'react-native-paper'; + +// COMPONENTS +import { PaperText, TouchableCard } from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +const OrderHistoryItem = () => { + // DATA + const PRODUCT_HEIGHT = CS.SPACE_XLG * 2; + + const STYLES = StyleSheet.create({ + container: { + ...GS.shadow, + overflow: 'hidden', + }, + header: { + ...GS.inlineItems, + ...GS.p2, + }, + headerAvatarContainer: { + ...GS.shadow, + ...GS.mr5, + borderRadius: 100, + borderWidth: 2, + borderColor: CC.light, + }, + headerContent: { + flex: 1, + }, + headerContentTitle: { + ...GS.txtPrimaryLight, + fontSize: CS.FONT_SIZE_MD, + }, + headerContentDescription: { + fontSize: CS.FONT_SIZE_MD, + color: CC.gray, + }, + headerAmount: { + ...GS.centered, + width: CS.FONT_SIZE_XLG * 2.5, + }, + headerAmountText: { + ...GS.txtPrimaryLight, + ...GS.FF_NunitoSemiBold, + }, + separator: { + ...GS.w100, + ...GS.mt3, + borderWidth: 0.5, + borderColor: CC.primaryHightLight, + }, + productCard: { + ...GS.noShadow, + borderWidth: 0, + borderRadius: 0, + overflow: 'hidden', + }, + productCardContent: { + ...GS.px0, + }, + productContentContainer: { ...GS.w100, ...GS.pr2, ...GS.inlineItems }, + productContentImageContainer: { + overflow: 'hidden', + borderTopEndRadius: CS.SPACE_SM, + borderBottomEndRadius: CS.SPACE_SM, + }, + productContentImage: { + height: PRODUCT_HEIGHT, + width: PRODUCT_HEIGHT, + }, + productContent: { + ...GS.pl4, + flex: 1, + }, + productContentTitle: { ...GS.txtPrimary }, + productContentDescription: { + fontSize: CS.FONT_SIZE_MD, + color: CC.gray, + }, + productAmount: { + ...GS.centered, + width: CS.FONT_SIZE_XLG * 2.5, + }, + productAmountText: { + ...GS.txtPrimaryLight, + ...GS.FF_NunitoSemiBold, + }, + }); + + return ( + + + + + + + + + Title + + + Description + + + + + $00 + + + + + {}} + height={PRODUCT_HEIGHT} + cardStyle={STYLES.productCard} + cardStyleContent={STYLES.productCardContent} + rippleColor={CC.primary + '33'}> + + + + + + + Product title + + + Product description + + + + + + $00 + + + + + + {}} + height={PRODUCT_HEIGHT} + cardStyle={STYLES.productCard} + cardStyleContent={STYLES.productCardContent} + rippleColor={CC.primary + '33'}> + + + + + + + Product title + + + Product description + + + + + + $00 + + + + + + + ); +}; + +export default OrderHistoryItem; diff --git a/packages/shop-mobile-expo/src/components/Common/PaperText.tsx b/packages/shop-mobile-expo/src/components/Common/PaperText.tsx new file mode 100644 index 000000000..0530fc391 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/PaperText.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { TextProps } from 'react-native'; +import { Text } from 'react-native-paper'; + +type Props = TextProps; + +const PaperText: React.FC = (props) => ( + + {props?.children} + +); + +export default PaperText; diff --git a/packages/shop-mobile-expo/src/components/Common/ProductItem/List.tsx b/packages/shop-mobile-expo/src/components/Common/ProductItem/List.tsx new file mode 100644 index 000000000..20340564e --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/ProductItem/List.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { StyleSheet, View, Image, TouchableOpacity } from 'react-native'; +import { Avatar, Title, Text, Button } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import Ionicons from '@expo/vector-icons/Ionicons'; + +// TYPES +import type { ProductInfoInterface } from '../../../client/products/argumentInterfaces'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../../assets/ts/styles'; + +// LOCAL TYPES +export interface ProductItemType { + data: ProductInfoInterface; +} + +const ProductItemList: React.FC = (props: ProductItemType) => { + // LOCAL STYLES + const styles = StyleSheet.create({ + container: { + ...GS.w100, + ...GS.rounded, + backgroundColor: CC.primaryLight, + position: 'relative', + overflow: 'hidden', + }, + section: { + ...GS.p2, + ...GS.inlineItems, + }, + header: {}, + headerAvatarContainer: { + ...GS.shadow, + ...GS.mr5, + borderRadius: 100, + borderWidth: 2, + borderColor: CC.light, + }, + headerContent: { + flex: 1, + }, + headerContentTitle: { ...GS.fontBold, color: CC.light }, + headerContentDescription: { + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD, + color: CC.grayLight, + }, + headerAvailability: { + ...GS.centered, + width: CS.FONT_SIZE_XLG * 2.5, + }, + headerAvailabilityText: { + ...GS.FF_NunitoSemiBold, + fontSize: CS.FONT_SIZE_SM, + lineHeight: 12, + }, + prodImg: { + height: CS.SCREEN_HEIGHT / 3, + }, + footer: {}, + footerBtn: { + ...GS.py1, + minWidth: CS.FONT_SIZE_XLG * 6, + }, + footerBuyBtn: { + ...GS.bgSecondary, + ...GS.mr1, + flex: 1, + }, + footerDetailBtn: { + backgroundColor: CC.dark + '88', + }, + }); + + return ( + + + + + + + + { + props?.data?.warehouseProduct?.product?.title[0] + ?.value + } + + + { + props?.data?.warehouseProduct?.product + ?.description[0]?.value + } + + + + + + + Ready for takeaway + + + + + + + + + + ); +}; + +export default ProductItemList; diff --git a/packages/shop-mobile-expo/src/components/Common/ProductItem/Slide.tsx b/packages/shop-mobile-expo/src/components/Common/ProductItem/Slide.tsx new file mode 100644 index 000000000..920df91ef --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/ProductItem/Slide.tsx @@ -0,0 +1,211 @@ +import React from 'react'; +import { StyleSheet, View, Image, TouchableOpacity } from 'react-native'; +import { Avatar, Title, Text, Button } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import Ionicons from '@expo/vector-icons/Ionicons'; + +// TYPES +import type { ProductInfoInterface } from '../../../client/products/argumentInterfaces'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../../assets/ts/styles'; + +// LOCAL TYPES +export interface ProductItemType { + data: ProductInfoInterface; +} + +const ProductItemSlide: React.FC = ( + props: ProductItemType, +) => { + // DATA + const AVATAR_WAREHOUSE_SIZE = CS.FONT_SIZE_XLG * 2.5; + const STYLES = StyleSheet.create({ + container: { + ...GS.h100, + ...GS.w100, + flex: 1, + backgroundColor: CC.primary, + position: 'relative', + overflow: 'hidden', + }, + prodImg: { + flex: 1, + }, + containerAvailabilities: { + ...GS.mt4, + ...GS.mr2, + position: 'absolute', + top: 0, + right: 0, + }, + availabilitiesItem: { + ...GS.centered, + ...GS.rounded, + ...GS.mb2, + ...GS.p1, + backgroundColor: CC.primaryLight + 'cf', + width: CS.FONT_SIZE_XLG * 3.5, + }, + availabilitiesItemText: { + ...GS.FF_NunitoSemiBold, + fontSize: CS.FONT_SIZE_SM, + textAlign: 'center', + lineHeight: 12, + }, + availabilitiesItemTextLG: { + ...GS.FF_NunitoSemiBold, + fontSize: CS.FONT_SIZE_LG, + }, + availabilitiesItemTextPrice: { + textDecorationLine: 'line-through', + }, + section: { + ...GS.p2, + ...GS.inlineItems, + }, + header: { + ...GS.mb5, + position: 'relative', + }, + headerContent: { + flex: 1, + marginRight: AVATAR_WAREHOUSE_SIZE + CS.SPACE_SM, + }, + headerAvatarContainer: { + ...GS.shadow, + ...GS.mr2, + borderRadius: 100, + borderWidth: 2, + borderColor: CC.light, + position: 'absolute', + top: -(AVATAR_WAREHOUSE_SIZE / 2), + right: 0, + }, + headerContentTitle: { + ...GS.fontBold, + ...GS.mb1, + fontSize: CS.FONT_SIZE_XLG, + color: CC.light, + }, + headerContentDescription: { + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD, + color: CC.grayLight, + }, + footer: {}, + footerBtn: { + ...GS.py1, + minWidth: CS.FONT_SIZE_XLG * 6, + }, + footerBuyBtn: { + ...GS.bgSecondary, + ...GS.mr1, + flex: 1, + }, + footerDetailBtn: { + backgroundColor: CC.primaryLight, + }, + }); + + return ( + + + + + + + $139 + + + + + 1% + + + + + + + + Ready for takeaway + + + + + + + + { + props?.data?.warehouseProduct?.product?.title[0] + ?.value + } + + + { + props?.data?.warehouseProduct?.product + ?.description[0]?.value + } + + + + + + + + + + + + + + ); +}; + +export default ProductItemSlide; diff --git a/packages/shop-mobile-expo/src/components/Common/ProductItem/index.tsx b/packages/shop-mobile-expo/src/components/Common/ProductItem/index.tsx new file mode 100644 index 000000000..aa7398a54 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/ProductItem/index.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Text } from 'react-native'; + +// TYPES +import type ENV from '../../../environments/model'; +import type { ProductInfoInterface } from '../../../client/products/argumentInterfaces'; + +// COMPONENTS +import ProductItemVertical from './List'; +import ProductItemHorizontal from './Slide'; + +// LOCAL TYPES +export interface ProductItemType { + data: ProductInfoInterface; + type: ENV['PRODUCTS_VIEW_TYPE']; +} + +const ProductItem: React.FC = (props) => { + switch (props.type) { + case 'list': + return ; + case 'slides': + return ; + default: + return Type invalid; + } +}; + +export default ProductItem; diff --git a/packages/shop-mobile-expo/src/components/Common/TouchableCard.tsx b/packages/shop-mobile-expo/src/components/Common/TouchableCard.tsx new file mode 100644 index 000000000..da84947ea --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/TouchableCard.tsx @@ -0,0 +1,229 @@ +import React from 'react'; +import { + View, + Image, + ViewStyle, + TextStyle, + ImageStyle, + ActivityIndicator, + GestureResponderEvent, + StyleSheet, +} from 'react-native'; +import { Card, TouchableRipple } from 'react-native-paper'; + +// HELPERS +import { isEmpty } from '../../helpers/utils'; + +// COMPONENTS +import PaperText from './PaperText'; +import Icon, { IconPropsType } from './Icon'; + +// STYLES +import { + CONSTANT_COLOR as CC, + GLOBAL_STYLE as GS, +} from '../../assets/ts/styles'; + +export interface TouchableCardPropsType { + title?: null | React.ReactNode | string; + description?: null | string; + textOneLine?: boolean; + iconProps?: IconPropsType; + indicatorIconProps?: IconPropsType; + indicatorText?: null | string; + indicatorTextSize?: number; + indicatorTextColor?: null | string; + img?: string | object | undefined; + onPress?: (event: GestureResponderEvent) => any; + style?: ViewStyle; + cardStyle?: ViewStyle; + cardStyleContent?: ViewStyle; + titleStyle?: TextStyle; + descriptionStyle?: TextStyle; + imgStyle?: ImageStyle; + height?: number; + loading?: false; + loaderColor?: string; + disabled?: false; + rippleColor?: string; + children?: React.ReactNode; +} + +const STYLES = StyleSheet.create({ + main: { flex: 1 }, + card: { + ...GS.shadowSm, + borderRadius: 10, + overflow: 'hidden', + }, + cardContent: { + ...GS.row, + alignItems: 'center', + }, + loaderContainer: { ...GS.h100, ...GS.centered, flex: 1 }, + cardImg: { + ...GS.shadowSm, + width: 40, + height: 40, + borderRadius: 20, + resizeMode: 'contain', + }, + cardTextContent: { + flex: 1, + justifyContent: 'center', + }, + cardTextContentTitle: { fontSize: 18, paddingBottom: 2 }, +}); + +const TouchableCard: React.FC = ({ + title = null, + description = null, + textOneLine = true, + iconProps, + indicatorIconProps, + indicatorText = null, + indicatorTextSize = 10, + indicatorTextColor = null, + img, + onPress, + style = {}, + cardStyle = {}, + cardStyleContent = {}, + titleStyle = {}, + descriptionStyle = {}, + imgStyle = {}, + height = 75, + loading = false, + loaderColor = CC.gray, + disabled = false, + rippleColor = CC.secondary + '10', + children = null, +}) => { + return ( + + + + + {loading ? ( + + + + ) : ( + <> + {children + ? children + : (iconProps || img) && ( + + {iconProps && !img && ( + + )} + {!isEmpty(img) && ( + + )} + + )} + + + {!isEmpty(title) && ( + + {title} + + )} + {!isEmpty(description) && ( + + {description} + + )} + + + {!isEmpty(indicatorIconProps) && + !indicatorText && ( + + + + )} + + {!isEmpty(indicatorText) && ( + + {indicatorText} + + )} + + )} + + + + + ); +}; + +export default TouchableCard; diff --git a/packages/shop-mobile-expo/src/components/Common/index.ts b/packages/shop-mobile-expo/src/components/Common/index.ts new file mode 100644 index 000000000..f2b61007d --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/index.ts @@ -0,0 +1,33 @@ +// This file provide all custom commons components of the app +import CustomScreenHeader_ from './CustomScreenHeader'; +import FocusAwareStatusBar_ from './FocusAwareStatusBar'; +import Dialog_ from './Dialog'; +import Icon_ from './Icon'; +import OrderHistoryItem_ from './OrderHistoryItem'; +import PaperText_ from './PaperText'; +import ProductItem_ from './ProductItem'; +import TouchableCard_ from './TouchableCard'; + +// TODO: add type for components constant + +export const CustomScreenHeader = CustomScreenHeader_; +export const FocusAwareStatusBar = FocusAwareStatusBar_; +export const Dialog = Dialog_; +export const Icon = Icon_; +export const OrderHistoryItem = OrderHistoryItem_; +export const PaperText = PaperText_; +export const ProductItem = ProductItem_; +export const TouchableCard = TouchableCard_; + +const components = { + CustomScreenHeader, + FocusAwareStatusBar, + Dialog, + Icon, + OrderHistoryItem, + PaperText, + ProductItem, + TouchableCard, +}; + +export default components; diff --git a/packages/shop-mobile-expo/src/components/CustomDrawerContent/Header.tsx b/packages/shop-mobile-expo/src/components/CustomDrawerContent/Header.tsx new file mode 100644 index 000000000..72687b31b --- /dev/null +++ b/packages/shop-mobile-expo/src/components/CustomDrawerContent/Header.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +// COMPONENTS +import { PaperText } from '../Common'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getLang, getLanguage } from '../../store/features/translation'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +const DrawerHeader = () => { + // SELECTORS + const currentLanguage = useAppSelector(getLanguage); + const currentLang = useAppSelector(getLang); + + return ( + + + + {currentLanguage.SIDE_MENU.TITLE} + + + + ); +}; + +export default DrawerHeader; diff --git a/packages/shop-mobile-expo/src/components/CustomDrawerContent/Item.tsx b/packages/shop-mobile-expo/src/components/CustomDrawerContent/Item.tsx new file mode 100644 index 000000000..f0e8f11ed --- /dev/null +++ b/packages/shop-mobile-expo/src/components/CustomDrawerContent/Item.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import { View, Linking, StyleSheet } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; + +// TYPES +import { DrawerLinkItem } from '../../router/drawer.routes'; + +// COMPONENTS +import { TouchableCard } from '../Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +const Item: React.FC = ({ + label, + path, + icon, + external, + focused, +}) => { + const navigation = useNavigation(); + return ( + + { + if (external) { + return Linking.openURL(path); + } + + // @ts-ignore TODO: search to resolve this next line + navigation.navigate({ name: path }); + console.log(path); + + return; + }} + /> + + {focused && } + + ); +}; + +export default Item; + +const STYLES = StyleSheet.create({ + container: { position: 'relative', ...GS.w100, ...GS.mb1 }, + touchable: { ...GS.w100, ...GS.inlineItems, zIndex: 1 }, + touchableCard: { ...GS.w100, ...GS.px0, borderRadius: 0 }, + touchableCardContent: { + ...GS.px2, + }, + touchableCardContentFocused: { + backgroundColor: CC.primaryHightLight + '20', + }, + focusedIndicator: { + ...GS.h100, + ...GS.bgSecondary, + position: 'absolute', + top: 0, + left: 0, + width: 4, + borderTopEndRadius: 4, + borderBottomEndRadius: 4, + zIndex: 2, + }, +}); diff --git a/packages/shop-mobile-expo/src/components/CustomDrawerContent/index.tsx b/packages/shop-mobile-expo/src/components/CustomDrawerContent/index.tsx new file mode 100644 index 000000000..3f6a1db65 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/CustomDrawerContent/index.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { View, ScrollViewProps as ScrollViewProps_ } from 'react-native'; +import { Title } from 'react-native-paper'; +import { + DrawerContentScrollView, + DrawerContentComponentProps, +} from '@react-navigation/drawer'; + +// HELPERS +import { isEmpty } from '../../helpers/utils'; + +// CONSTANTS +import { DrawerRoutesGroupType } from '../../router/drawer.routes'; + +// COMPONENTS +import { Icon } from '../Common'; +import Header from './Header'; +import Item from './Item'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +// LOCAL TYPES +export type ContentProps = { + ScrollViewProps?: ScrollViewProps_; + drawerContentProps: DrawerContentComponentProps; + linksGroups: DrawerRoutesGroupType[]; +}; + +const CustomDrawer: React.FC = ({ + ScrollViewProps = {}, + drawerContentProps, + linksGroups = [], +}) => { + const navigationState = drawerContentProps.navigation.getState(); + const currentRouteName = navigationState.routeNames[navigationState.index]; + + return ( + +
+ + + + {linksGroups.map((linksGroup, linksGroup_id) => ( + + + {linksGroup?.icon && ( + + )} + {!isEmpty(linksGroup.title) && ( + + {linksGroup.title} + + )} + + {linksGroup?.linkItems && + linksGroup.linkItems.map( + (linkItem, linkItem_id) => ( + + ), + )} + + ))} + + + + ); +}; + +export default CustomDrawer; diff --git a/packages/shop-mobile-expo/src/components/Providers/Apollo.tsx b/packages/shop-mobile-expo/src/components/Providers/Apollo.tsx new file mode 100644 index 000000000..6303b4eac --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Providers/Apollo.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { + ApolloClient, + InMemoryCache, + ApolloProvider as Provider, +} from '@apollo/client'; + +// ENVIRONMENT +import ENV from '../../environments/environment'; + +// LOCAL TYPES +export interface Props { + children: React.ReactElement; +} + +const ApolloProvider: React.FC = (props) => { + // CONFIG + const APOLLO_CLIENT = new ApolloClient({ + uri: ENV.ENDPOINT.GQL, + cache: new InMemoryCache(), + defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network' } }, + }); + + return {props.children}; +}; + +export default ApolloProvider; diff --git a/packages/shop-mobile-expo/src/components/Providers/App.tsx b/packages/shop-mobile-expo/src/components/Providers/App.tsx new file mode 100644 index 000000000..c8c6e71f9 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Providers/App.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { setCustomTextInput, setCustomText } from 'react-native-global-props'; +import FlashMessage from 'react-native-flash-message'; + +// TYPES +import type { UserStateType } from '../../store/features/user/types'; +import type { TranslationStateType } from '../../store/features/translation/types'; + +// ACTIONS & SELECTORS +import { useAppDispatch } from '../../store/hooks'; +import { setUser } from '../../store/features/user'; +import { setLang, supportedLangs } from '../../store/features/translation'; +import { setGroup } from '../../store/features/navigation'; + +// CONSTANTS +import NAV_GROUPS from '../../router/groups.routes'; + +// HELPERS +import { isEmpty } from '../../helpers/utils'; + +// STYLES +import { + CONSTANT_COLOR as CC, + GLOBAL_STYLE as GS, +} from '../../assets/ts/styles'; + +// LOCAL TYPES +export interface Props { + children: React.ReactElement; +} + +const AppGuard: React.FC = (props) => { + // DISPATCHER + const dispatch = useAppDispatch(); + + // EFFECTS + // Set local user and default route group + React.useEffect(() => { + (async () => { + const LOCAL_USER_JSON = await AsyncStorage.getItem('user'); + + if (LOCAL_USER_JSON !== null && !isEmpty(LOCAL_USER_JSON)) { + const LOCAL_USER = JSON.parse(LOCAL_USER_JSON) as UserStateType; + + dispatch(setUser(LOCAL_USER)); + if (LOCAL_USER.isLoggedIn) { + dispatch(setGroup(NAV_GROUPS.APP)); + return; + } + } + + return dispatch(setGroup(NAV_GROUPS.REGISTRATION)); + })(); + + return () => {}; + }); + + // set default language + React.useEffect(() => { + (async () => { + const LOCAL_TRANSLATION_JSON = await AsyncStorage.getItem( + 'translation', + ); + + if ( + LOCAL_TRANSLATION_JSON !== null && + !isEmpty(LOCAL_TRANSLATION_JSON) + ) { + const LOCAL_TRANSLATION = JSON.parse( + LOCAL_TRANSLATION_JSON, + ) as TranslationStateType; + + if ( + LOCAL_TRANSLATION.lang && + typeof LOCAL_TRANSLATION.lang === 'string' && + Object.keys(supportedLangs).includes(LOCAL_TRANSLATION.lang) + ) { + dispatch(setLang(LOCAL_TRANSLATION.lang)); + } + } + })(); + + return () => {}; + }); + + // set default config for react-native components + React.useEffect(() => { + setCustomTextInput({ + style: { + ...GS.FF_Nunito, + color: CC.light, + }, + }); + + setCustomText({ + style: { + ...GS.FF_Nunito, + color: CC.light, + }, + }); + }, []); + + return ( + <> + {props.children} + + + ); +}; + +export default AppGuard; diff --git a/packages/shop-mobile-expo/src/components/Providers/Paper.tsx b/packages/shop-mobile-expo/src/components/Providers/Paper.tsx new file mode 100644 index 000000000..665db2493 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Providers/Paper.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { DefaultTheme, Provider } from 'react-native-paper'; + +// COMPONENTS +import { Icon } from '../Common'; + +// STYLES +import { + CONSTANT_COLOR as CC, + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +// LOCAL TYPES +export interface Props { + children: React.ReactElement; +} +export type PaperThemeType = typeof DefaultTheme; + +const PAPER_THEME: PaperThemeType = { + ...DefaultTheme, + dark: false, + mode: 'adaptive', + roundness: CS.SPACE_SM - 2, + colors: { + ...DefaultTheme.colors, + primary: CC.secondary, + accent: CC.secondaryLight, + background: CC.primary, + surface: CC.white, + onSurface: CC.grayHighLight, + disabled: CC.grayLight, + text: CC.light, + placeholder: CC.grayLight, + error: CC.danger, + }, + fonts: { + thin: GS.FF_NunitoExtraLight, + light: GS.FF_NunitoLight, + regular: GS.FF_Nunito, + medium: GS.FF_NunitoSemiBold, + }, +}; + +const PaperProvider: React.FC = (props) => { + return ( + }}> + {props.children} + + ); +}; + +export default PaperProvider; diff --git a/packages/shop-mobile-expo/src/components/Providers/Redux.tsx b/packages/shop-mobile-expo/src/components/Providers/Redux.tsx new file mode 100644 index 000000000..c75190ddb --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Providers/Redux.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Provider } from 'react-redux'; + +// STORE +import { store } from '../../store'; + +// LOCAL TYPES +export interface Props { + children: React.ReactElement; +} + +const ReduxProvider: React.FC = (props) => { + return {props.children}; +}; + +export default ReduxProvider; diff --git a/packages/shop-mobile-expo/src/constants/rules.validate.ts b/packages/shop-mobile-expo/src/constants/rules.validate.ts new file mode 100644 index 000000000..ef6e914ac --- /dev/null +++ b/packages/shop-mobile-expo/src/constants/rules.validate.ts @@ -0,0 +1,44 @@ +// This file contain rule validation constants for validate.js + +// import { validate } from 'validate.js'; + +// TODO: Type constants + +/** + * Require a non-empty field + */ +export const NOT_ALLOW_EMPTY_PRESENCE = { + allowEmpty: false, +}; + +// TODO: Add some useful comments +export const TEL_FORMAT = { + pattern: /^[0-9]?()[0-9](\s|\S)(\d[0-9]{9})$/, +}; + +export const NUMERIC_FORMAT = { + pattern: /^[0-9]+$/, +}; + +export const REQUIRE_NOT_EMPTY_PRESENCE = { + presence: NOT_ALLOW_EMPTY_PRESENCE, +}; + +export const REQUIRE_EMAIL = { + ...REQUIRE_NOT_EMPTY_PRESENCE, + email: true, +}; + +export const REQUIRE_NUMERIC = { + format: { + ...NUMERIC_FORMAT, + }, + strict: true, +}; + +export const REQUIRE_NUMERIC_NOT_STRICK = { + format: { + ...NUMERIC_FORMAT, + }, + strict: false, +}; diff --git a/packages/shop-mobile-expo/src/environments/model.ts b/packages/shop-mobile-expo/src/environments/model.ts new file mode 100644 index 000000000..4e2bdc022 --- /dev/null +++ b/packages/shop-mobile-expo/src/environments/model.ts @@ -0,0 +1,93 @@ +import type { supportedLangType } from '../store/features/translation/types'; + +export default interface Environment { + // TODO: Add more descriptive comments. + /** App environment */ + PRODUCTION: boolean; + + /** App version */ + VERSION: string; + + /** Default state of products view type' */ + PRODUCTS_VIEW_TYPE: 'slides' | 'list'; + + /** Default state of order info state */ + ORDER_INFO_TYPE: 'popup' | 'page'; + + COMPANY_NAME: string; + + /** Url of images */ + IMAGE_URL: { + INVITE_BY_CODE_LOGO: string; + NO_INTERNET_LOGO: string; + MAP_MERCHANT_ICON: string; + MAP_USER_ICON: string; + MAP_CARRIER_ICON: string; + }; + + GOOGLE: { + MAPS_API_KEY: string; + ANALYTICS_API_KEY: string; + }; + + FAKE_UUID: string; + + /** + * Not secret MixPanel Token + */ + MIXPANEL_API_KEY: string; + + LANGUAGE: { + LANG: supportedLangType; + LOCALE: supportedLangType; + }; + + DELIVERY_TIME: { + MIN: number; + MAX: number; + }; + + SUPPORT_NUMBER: string; + + STRIP: { + PUBLISHABLE_KEY: string; + POP_UP_LOGO: string; + }; + + COORDINATE: { + LATITUDE: number; + LONGITUDE: number; + }; + + ENDPOINT: { + GQL: string; + GQL_SUBSCRIPTIONS: string; + SERVICES: string; + HTTPS_SERVICES: string; + API_FILE_UPLOAD: string; + }; + + FAKE_INVITE: { + ID: string; + CITY: string; + POSTCODE: string; + ADDRESS: string; + HOUSE: string; + CREATED_AT: string; + UPDATED_AT: string; + APARTMENT: string; + CODE: number; + COUNTRY_ID: number; + }; + + /** For maintenance micro service */ + SETTINGS: { + APP_TYPE: string; + MAINTENANCE_API_URL: string; + }; + + /** For "single" merchant (multiple branches) */ + MERCHANT_IDS: string[]; + + SHOPPING_CART: boolean; +} diff --git a/packages/shop-mobile-expo/src/helpers/location.ts b/packages/shop-mobile-expo/src/helpers/location.ts new file mode 100644 index 000000000..268c6d473 --- /dev/null +++ b/packages/shop-mobile-expo/src/helpers/location.ts @@ -0,0 +1,43 @@ +import * as Location from 'expo-location'; + +export interface FormattedLocationInterface { + latitude: number; + longitude: number; + countryId: number; + country: string; + streetAddress: string; + city: string; +} + +/** + * This helper function return a formatted address using **expo-location** + * + * @param coords An object with **Location.LocationObjectCoord** type + * @returns FormattedLocationType + */ +export const getFormattedLocation: ( + coords: Location.LocationObjectCoords, +) => Promise = async (coords) => { + if (coords) { + const LocationGeoCoded = await Location.reverseGeocodeAsync({ + longitude: coords?.longitude, + latitude: coords?.latitude, + }); + + if (LocationGeoCoded.length) { + const firstLocationGeoCoded = LocationGeoCoded[0]; + const formattedLocation: FormattedLocationInterface = { + longitude: coords.longitude, + latitude: coords.latitude, + city: firstLocationGeoCoded.city as string, + country: firstLocationGeoCoded.country as string, + countryId: 0, + streetAddress: firstLocationGeoCoded.street as string, + }; + + return formattedLocation; + } + } + + return null; +}; diff --git a/packages/shop-mobile-expo/src/helpers/utils.ts b/packages/shop-mobile-expo/src/helpers/utils.ts new file mode 100644 index 000000000..bbb0f8d9e --- /dev/null +++ b/packages/shop-mobile-expo/src/helpers/utils.ts @@ -0,0 +1,92 @@ +import { ComponentType } from "react"; + +/** + * + * @param _MyComponent + * @returns + */ +export function getReactComponentProps( + _MyComponent: ComponentType +): Props { + return {} as Props; +} + +/** + * + * @param data + * @returns + */ +export function isEmpty(data: any) { + switch (typeof data) { + case "object": + for (let prop in data) { + if (data.hasOwnProperty(prop)) { + return false; + } + } + return JSON.stringify(data) === JSON.stringify({}) || data === null; + + case "string": + return !!!data && !!!data.trim().length && data != null; + + case "number": + return !!!data && !(data != NaN); + + case "boolean": + return !data; + + default: + return true; + } +} + +/** + * + * @param {object} object Object that will be tested + * @param {string[]} except fields that will be excepted + * @description Function that will test Object items one by one & return an object of empty fields + * @returns Array + */ +export function testObjectItem( + object: { [key: string]: any }, + except: string[] = [] +) { + if (typeof object != "object") + return console.warn("This function require a object"); + + let arrayKey = []; + + for (const key in object) { + if (Object.hasOwnProperty.call(object, key)) { + if (isEmpty(object[key]) && except.includes(key)) arrayKey.push(key); + } + } + return arrayKey; +} + +/** + * + * @param length + * @returns + */ +export function plural(length: number) { + if (length > 1) return "s"; + else return ""; +} + +/** + * + * @param date + * @returns + */ +export function formatNativeDate(date = "") { + let d = new Date(date), + month = "" + (d.getMonth() + 1), + day = "" + d.getDate(), + year = d.getFullYear(); + + if (month.length < 2) month = "0" + month; + if (day.length < 2) day = "0" + day; + + return [year, month, day].join("-"); +} diff --git a/packages/shop-mobile-expo/src/router/DrawerNavigator.tsx b/packages/shop-mobile-expo/src/router/DrawerNavigator.tsx new file mode 100644 index 000000000..409e38079 --- /dev/null +++ b/packages/shop-mobile-expo/src/router/DrawerNavigator.tsx @@ -0,0 +1,118 @@ +import * as React from 'react'; +import { useWindowDimensions } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; + +// TYPES +import DRAWER_ROUTES, { DrawerRoutesGroupType } from './drawer.routes'; + +// SELECTORS +import { useAppSelector } from '../store/hooks'; +import { getLanguage } from '../store/features/translation'; + +// COMPONENTS +import CustomDrawerContent from '../components/CustomDrawerContent'; + +const Drawer = createDrawerNavigator(); + +const DrawerNavigator: React.FC = () => { + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + + // DATA + const dimensions = useWindowDimensions(); + const ROUTES_GROUPS: DrawerRoutesGroupType[] = [ + { + title: LANGUAGE.SIDE_MENU.GROUPS.STORE.STORE_TITLE, + linkItems: [ + { + label: LANGUAGE.SIDE_MENU.GROUPS.NO_TITLE.ITEMS.PRODUCTS, + path: 'DRAWER/HOME', + icon: 'shopping-bag', + }, + { + label: LANGUAGE.SIDE_MENU.GROUPS.NO_TITLE.ITEMS + .ORDER_HISTORY, + path: 'DRAWER/ORDER_HISTORY', + icon: 'shopping-cart', + }, + ], + }, + + { + title: LANGUAGE.SIDE_MENU.GROUPS.SETTINGS.DIVER_TITLE, + linkItems: [ + { + label: 'Account & Preferences', + path: 'DRAWER/ACCOUNT', + icon: 'user', + }, + { + label: LANGUAGE.SIDE_MENU.GROUPS.SETTINGS.ITEMS.LANGUAGE, + path: 'DRAWER/TRANSLATION', + icon: 'align-center', + }, + ], + }, + + { + title: LANGUAGE.SIDE_MENU.GROUPS.INFO.DIVER_TITLE, + linkItems: [ + { + label: LANGUAGE.SIDE_MENU.GROUPS.INFO.ITEMS.FAQ, + path: 'https://google.com', + icon: 'help-circle', + external: true, + }, + { + label: LANGUAGE.SIDE_MENU.GROUPS.NO_TITLE.ITEMS.CALL_US, + path: 'https://google.com', + icon: 'phone', + external: true, + }, + { + label: LANGUAGE.SIDE_MENU.GROUPS.INFO.ITEMS.ABOUT_US, + path: 'https://google.com', + icon: 'info', + external: true, + }, + ], + }, + + { + title: LANGUAGE.SIDE_MENU.GROUPS.LEGALS.DIVER_TITLE, + linkItems: [ + { + label: LANGUAGE.SIDE_MENU.GROUPS.LEGALS.ITEMS.PRIVACY, + path: 'https://google.com', + icon: 'lock', + external: true, + }, + { + label: LANGUAGE.SIDE_MENU.GROUPS.LEGALS.ITEMS.TERMS_OF_USE, + path: 'https://google.com', + icon: 'list', + external: true, + }, + ], + }, + ]; + + return ( + = 768 ? 'permanent' : 'front'} + drawerContent={(props: any) => ( + + )}> + {DRAWER_ROUTES.map((stackScreenProps, id) => ( + + ))} + + ); +}; + +export default DrawerNavigator; diff --git a/packages/shop-mobile-expo/src/router/drawer.routes.ts b/packages/shop-mobile-expo/src/router/drawer.routes.ts new file mode 100644 index 000000000..93da84724 --- /dev/null +++ b/packages/shop-mobile-expo/src/router/drawer.routes.ts @@ -0,0 +1,53 @@ +import { createDrawerNavigator } from '@react-navigation/drawer'; + +// TYPES +import type { IconNameType } from '../components/Common/Icon'; + +// HELPERS +import { getReactComponentProps } from '../helpers/utils'; + +// SCREENS +import SCREENS from '../screens'; + +const DrawerScreen = createDrawerNavigator().Screen; +const DrawerScreenProps = getReactComponentProps(DrawerScreen); + +// LOCAL TYPES +export interface DrawerLinkItem { + label: string; + path: string; + icon?: IconNameType; + external?: boolean; + focused?: boolean; +} +export interface DrawerRoutesGroupType { + title: string; + icon?: IconNameType; + linkItems?: DrawerLinkItem[]; +} +export type DrawerScreenType = typeof DrawerScreenProps; + +const DRAWER_ROUTES: DrawerScreenType[] = [ + { + name: 'DRAWER/HOME', + component: SCREENS.APP.Home, + }, + { + name: 'DRAWER/ORDER_HISTORY', + component: SCREENS.APP.OrderHistory, + }, + { + name: 'DRAWER/ACCOUNT', + component: SCREENS.APP.Account, + }, + { + name: 'DRAWER/TRANSLATION', + component: SCREENS.APP.Translation, + }, + { + name: 'DRAWER/MERCHANTS_SEARCH', + component: SCREENS.APP.MerchantsSearch, + }, +]; + +export default DRAWER_ROUTES; diff --git a/packages/shop-mobile-expo/src/router/groups.routes.ts b/packages/shop-mobile-expo/src/router/groups.routes.ts new file mode 100644 index 000000000..8ca0d29d4 --- /dev/null +++ b/packages/shop-mobile-expo/src/router/groups.routes.ts @@ -0,0 +1,14 @@ +import { GroupNameType } from '../store/features/navigation/types'; + +export type RoutesGroupType = { + readonly [name in GroupNameType]: GroupNameType; +}; + +const ROUTE_GROUPS: RoutesGroupType = { + APP: 'APP', + LOADING: 'LOADING', + BLANK: 'BLANK', + REGISTRATION: 'REGISTRATION', +}; + +export default ROUTE_GROUPS; diff --git a/packages/shop-mobile-expo/src/router/index.tsx b/packages/shop-mobile-expo/src/router/index.tsx new file mode 100644 index 000000000..a05b9f01b --- /dev/null +++ b/packages/shop-mobile-expo/src/router/index.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +// HOOKS +import { useAppSelector } from '../store/hooks'; + +// ACTIONS & SELECTORS +import { getLang } from '../store/features/translation'; +import { getGroup } from '../store/features/navigation'; + +// ROUTING +import STACK_ROUTES from './stack.routes'; + +// STYLES +import { GLOBAL_STYLE as GS } from '../assets/ts/styles'; + +const Stack = createNativeStackNavigator(); + +// NAVIGATION COMPONENT +const Router = ({}) => { + // SELECTORS + const getCurrentNavGroup = useAppSelector(getGroup); + const getCurrentLang = useAppSelector(getLang); + + const Routes = () => { + const safeGroup = getCurrentNavGroup || 'BLANK'; + return ( + <> + {STACK_ROUTES[safeGroup].map((stackScreenProps, id) => ( + + ))} + + ); + }; + + return ( + + + + {Routes()} + + + + ); +}; + +export default Router; diff --git a/packages/shop-mobile-expo/src/router/stack.routes.ts b/packages/shop-mobile-expo/src/router/stack.routes.ts new file mode 100644 index 000000000..0dc0f43f3 --- /dev/null +++ b/packages/shop-mobile-expo/src/router/stack.routes.ts @@ -0,0 +1,53 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +// TYPES +import { GroupNameType } from '../store/features/navigation/types'; + +// UTILS +import { getReactComponentProps } from '../helpers/utils'; + +// SCREENS +import DrawerNavigator from './DrawerNavigator'; +import SCREENS from '../screens/index'; + +const STACK_SCREEN = createNativeStackNavigator().Screen; +const STACK_SCREEN_PROPS = getReactComponentProps(STACK_SCREEN); + +// LOCAL TYPES +export type StackScreenType = typeof STACK_SCREEN_PROPS; +export type StackScreenGroupType = { + [name in GroupNameType]: StackScreenType[]; +}; + +const STACK_ROUTES_GROUPS: StackScreenGroupType = { + APP: [ + { + name: 'STACK/HOME', + component: DrawerNavigator, + }, + ], + BLANK: [ + { + name: 'STACK/BLANK_', + component: SCREENS.Blank_, + }, + ], + LOADING: [ + { + name: 'STACK/LOADING', + component: SCREENS.Loading, + }, + ], + REGISTRATION: [ + { + name: 'STACK/SIGN_UP', + component: SCREENS.REGISTRATION.SIGN_UP, + }, + { + name: 'STACK/SIGN_UP_BY_ADDRESS', + component: SCREENS.REGISTRATION.SIGN_UP_BY_ADDRESS, + }, + ], +}; + +export default STACK_ROUTES_GROUPS; diff --git a/packages/shop-mobile-expo/src/screens/Blank_.screen.tsx b/packages/shop-mobile-expo/src/screens/Blank_.screen.tsx new file mode 100644 index 000000000..822a16ce3 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/Blank_.screen.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { View, BackHandler, StyleSheet } from 'react-native'; +import { Button } from 'react-native-paper'; +import { version, name } from '../../package.json'; + +// COMPONENTS +import { FocusAwareStatusBar, PaperText } from '../components/Common'; + +// STYLES +import { CONSTANT_COLOR as CC, GLOBAL_STYLE as GS } from '../assets/ts/styles'; + +export default function Blank_Screen() { + return ( + + + + + + Something wrong + + + + + {name} - v{version} + + + + ); +} + +const STYLES = StyleSheet.create({ + main: { + ...GS.centered, + ...GS.px4, + flex: 1, + }, + title: { ...GS.mb3, fontSize: 40 }, + subTitle: { + ...GS.txtCenter, + ...GS.mb2, + color: CC.gray, + fontSize: 16, + }, + footer: { ...GS.py2, ...GS.px4, alignItems: 'flex-end' }, + appInfo: { + ...GS.txtCenter, + color: CC.gray, + fontSize: 12, + }, +}); diff --git a/packages/shop-mobile-expo/src/screens/Loading.screen.tsx b/packages/shop-mobile-expo/src/screens/Loading.screen.tsx new file mode 100644 index 000000000..e0c3cae5a --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/Loading.screen.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { View, ActivityIndicator } from 'react-native'; + +// COMPONENTS +import { FocusAwareStatusBar } from '../components/Common'; + +// STYLES +import { GLOBAL_STYLE as GS, CONSTANT_COLOR as CC } from '../assets/ts/styles'; + +export default ({}) => { + // STATES + + return ( + + + + + + ); +}; diff --git a/packages/shop-mobile-expo/src/screens/app/Account.screen.tsx b/packages/shop-mobile-expo/src/screens/app/Account.screen.tsx new file mode 100644 index 000000000..d0cd6c365 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/Account.screen.tsx @@ -0,0 +1,350 @@ +import React from 'react'; +import { View, ScrollView, StyleSheet } from 'react-native'; +import { Card, Title, Text, RadioButton } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import MaterialIcon from '@expo/vector-icons/MaterialCommunityIcons'; + +// TYPES +import type ENV_TYPE from '../../environments/model'; + +// ACTIONS & SELECTORS +import { useAppSelector, useAppDispatch } from '../../store/hooks'; +import { + getUserData, + getProductViewType, + setProductViewType, +} from '../../store/features/user'; +// COMPONENTS +import { + TouchableCard, + FocusAwareStatusBar, + CustomScreenHeader, + Dialog, + Icon, +} from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +function AccountScreen({}) { + // ACTIONS + const dispatch = useAppDispatch(); + + // SELECTORS + const USER_DATA = useAppSelector(getUserData); + const PRODUCT_VIEW_TYPE = useAppSelector(getProductViewType); + // const CURRENT_LANG = useAppSelector(getLang); + + // STATES + const [productViewDialog, setProductViewDialog] = + React.useState(false); + const [logOutDialog, setLogOutDialog] = React.useState(false); + + // DATA + const IS_INVITE = USER_DATA.__typename === 'Invite'; + + const STYLES = StyleSheet.create({ + container: { ...GS.screen, ...GS.bgLight }, + dialogProductViewContent: { height: 180 }, + userInfoCard: { + ...GS.py5, + backgroundColor: CC.white, + borderBottomLeftRadius: CS.SPACE, + borderBottomRightRadius: CS.SPACE, + }, + userInfoCardContent: { + ...GS.centered, + justifyContent: 'space-between', + }, + userAvatarContainer: { + ...GS.centered, + ...GS.mb3, + borderColor: CC.gray, + borderWidth: 2, + borderRadius: 9999, + }, + avatarIcon: { + ...GS.m2, + }, + userInfoTitle: { + ...GS.mb4, + color: CC.primary, + textAlign: 'center', + }, + userInfoInfosContainer: { ...GS.inlineItems, width: '100%' }, + userInfoInfosItem: { ...GS.centered, flex: 1 }, + userInfoInfosItemTitle: { + color: CC.gray, + }, + userInfoInfosItemSubTitle: { + color: CC.primary, + }, + scrollViewOptions: { + ...GS.h100, + ...GS.pt4, + ...GS.px2, + }, + optionItem: { ...GS.inlineItems, ...GS.mb2 }, + optionItemCard: { ...GS.w100, borderRadius: 5 }, + optionItemCardContent: { borderRadius: 0 }, + optionItemCardContentContainerText: { + flex: 1, + }, + optionItemCardContentText: { + ...GS.txtCapitalize, + fontSize: CS.FONT_SIZE_MD, + color: CC.primary, + }, + optionItemCardContentSubText: { + ...GS.txtLower, + fontSize: CS.FONT_SIZE, + color: CC.primaryLight, + }, + dialogContent: { + ...GS.centered, + ...GS.w100, + }, + }); + + // FUNCTIONS + const onSelectProductView = (value: ENV_TYPE['PRODUCTS_VIEW_TYPE']) => { + dispatch(setProductViewType(value)); + setProductViewDialog(false); + }; + + return ( + <> + setLogOutDialog(false)} + title={'Log out?'} + message={'Do you really want to sign-out?'} + actions={[ + { + children: 'Cancel', + uppercase: false, + labelStyle: { color: CC.primary }, + onPress: () => setLogOutDialog(false), + }, + { + children: 'Sign out', + uppercase: false, + labelStyle: { color: CC.danger }, + onPress: () => {}, + }, + ]} + /> + + setProductViewDialog(false)} + title={'Product View'}> + + onSelectProductView('list')}> + + + + + List + + + Use list view (swipe by top or bottom) in + home + + + + onSelectProductView('list')} + /> + + + + onSelectProductView('slides')}> + + + + + + Slides + + + Use slide view (swipe by left or right) in + home + + + + onSelectProductView('slides')} + /> + + + + + + + + + + + + {IS_INVITE ? ( + <> + + + + + + {USER_DATA.__typename} + + + + + + Country + + + {USER_DATA.geoLocation.countryName} + + + + + + City + + + {USER_DATA.geoLocation.city} + + + + + + Apartment + + + {USER_DATA.apartment} + + + + + ) : ( + + )} + + + + + setLogOutDialog(true)} + /> + + setProductViewDialog(true)} + /> + + + + ); +} + +export default AccountScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/Home.screen.tsx b/packages/shop-mobile-expo/src/screens/app/Home.screen.tsx new file mode 100644 index 000000000..d91c0c5ac --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/Home.screen.tsx @@ -0,0 +1,140 @@ +import React from 'react'; +import { View, ActivityIndicator, FlatList, StyleSheet } from 'react-native'; +import { Title } from 'react-native-paper'; +import { useQuery } from '@apollo/client'; +import PagerView from 'react-native-pager-view'; + +// CONFIGS + +// TYPES/INTERFACES +import type { + ProductInfoInterface, + ProductsQueryArgsInterface, +} from '../../client/products/argumentInterfaces'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getUserData, getProductViewType } from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; + +// ACTIONS + +// QUERIES +import { GEO_LOCATION_PRODUCTS_BY_PAGING } from '../../client/products/queries'; + +// COMPONENTS +import { + FocusAwareStatusBar, + CustomScreenHeader, + ProductItem, +} from '../../components/Common'; + +// STYLES +import { GLOBAL_STYLE as GS } from '../../assets/ts/styles'; + +function HomeScreen({}) { + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + const USER_DATA = useAppSelector(getUserData); + const VIEW_TYPE = useAppSelector(getProductViewType); + + // STATES + + // DATA + const PRODUCTS_QUERY_ARGS_INTERFACE: ProductsQueryArgsInterface = { + geoLocation: { + loc: { + type: 'Point', + coordinates: [ + USER_DATA?.geoLocation + ? USER_DATA?.geoLocation?.coordinates?.lng + : 0, + USER_DATA?.geoLocation + ? USER_DATA?.geoLocation?.coordinates?.lat + : 0, + ], + }, + }, + }; + + // QUERIES + const PRODUCTS_QUERY_RESPONSE = useQuery(GEO_LOCATION_PRODUCTS_BY_PAGING, { + variables: { + ...PRODUCTS_QUERY_ARGS_INTERFACE, + }, + }); + + // STYLES + const styles = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + productItemContainer: { + ...GS.mx1, + ...GS.mt2, + ...GS.mb1, + }, + }); + + return ( + + + + + + {PRODUCTS_QUERY_RESPONSE.loading ? ( + + + + ) : PRODUCTS_QUERY_RESPONSE.data?.geoLocationProductsByPaging && + PRODUCTS_QUERY_RESPONSE.data?.geoLocationProductsByPaging + .length ? ( + VIEW_TYPE === 'list' ? ( + { + return ( + + + + ); + }} + keyExtractor={(_item, _index) => _index.toString()} + style={{ ...GS.h100 }} + /> + ) : ( + + {PRODUCTS_QUERY_RESPONSE.data?.geoLocationProductsByPaging?.map( + (item: ProductInfoInterface, index: number) => ( + + + + ), + )} + + ) + ) : ( + + Nothing to buy for now. + + )} + + ); +} + +export default HomeScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/MerchantsSearch.screen.tsx b/packages/shop-mobile-expo/src/screens/app/MerchantsSearch.screen.tsx new file mode 100644 index 000000000..03c7542a6 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/MerchantsSearch.screen.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { View, ActivityIndicator, StyleSheet, ScrollView } from 'react-native'; +import { Button, TextInput, Title } from 'react-native-paper'; +// import { useQuery } from '@apollo/client'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import MaterialIcon from '@expo/vector-icons/MaterialCommunityIcons'; + +// CONFIGS + +// TYPES/INTERFACES +import type {} from '../../client/products/argumentInterfaces'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +// import { getUserData } from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; + +// ACTIONS + +// QUERIES +// import { GEO_LOCATION_PRODUCTS_BY_PAGING } from '../../client/products/queries'; + +// COMPONENTS +import { + FocusAwareStatusBar, + CustomScreenHeader, + TouchableCard, +} from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +function MerchantsSearch({}) { + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + // const USER_DATA = useAppSelector(getUserData); + + // STATES + const [searchValue, setSearchValue] = React.useState(''); + // const [dataLoading, setDataLoading] = React.useState(false); + + // DATA + // const MERCHANTS_SEARCH_QUERY_ARGS: any = {}; + + // QUERIES + // const MERCHANTS_SEARCH_QUERY_RESPONSE = useQuery( + // GEO_LOCATION_PRODUCTS_BY_PAGING, + // { + // variables: { + // ...MERCHANTS_SEARCH_QUERY_ARGS, + // }, + // }, + // ); + + // STYLES + const STYLES = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + searchContainer: { + ...GS.px2, + ...GS.pt4, + ...GS.pb3, + ...GS.inlineItems, + backgroundColor: CC.dark, + }, + searchInput: { + ...GS.mr2, + ...GS.my0, + ...GS.bgLight, + flex: 1, + color: CC.dark, + height: 57, + }, + }); + + return ( + + + + + + + + } + onChangeText={(text) => setSearchValue(text)} + mode='outlined' + /> + + + + + {false ? ( + + + + ) : true ? ( + + {[''].map((_item, index) => ( + {}} + /> + ))} + + ) : ( + + Nothing found! + + )} + + ); +} + +export default MerchantsSearch; diff --git a/packages/shop-mobile-expo/src/screens/app/OrderHistory.screen.tsx b/packages/shop-mobile-expo/src/screens/app/OrderHistory.screen.tsx new file mode 100644 index 000000000..650e4761a --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/OrderHistory.screen.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { View, ActivityIndicator, FlatList, StyleSheet } from 'react-native'; +import { Title } from 'react-native-paper'; +import { gql, useQuery } from '@apollo/client'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getLanguage } from '../../store/features/translation'; + +// COMPONENTS +import { + CustomScreenHeader, + FocusAwareStatusBar, + OrderHistoryItem, +} from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + // CONSTANT_SIZE as CS, + // CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +const OrderHistoryScreen = () => { + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + + // QUERIES + const ORDERS_QUERY = gql` + query Orders { + generatePastOrdersPerCarrier + } + `; + const { data, loading, error } = useQuery(ORDERS_QUERY, { + variables: {}, + }); + + // STYLES + const STYLES = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + container: { + ...GS.screen, + ...GS.bgTransparent, + ...GS.px5, + ...GS.pb5, + ...GS.mx5, + zIndex: 1, + }, + orderHistoryItemContainer: { + ...GS.mt3, + ...GS.mb1, + ...GS.mx2, + }, + }); + + // EFFECT + React.useEffect(() => { + console.log('Orders ==>', data, loading, error); + }, [data, loading, error]); + + return ( + + + + + + {loading ? ( + + + + ) : [''] ? ( + { + return ( + + + + ); + }} + keyExtractor={(_item, _index) => _index.toString()} + style={{ ...GS.h100 }} + /> + ) : ( + + Nothing ordered. + + )} + + ); +}; + +export default OrderHistoryScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/Translation.screen.tsx b/packages/shop-mobile-expo/src/screens/app/Translation.screen.tsx new file mode 100644 index 000000000..333407c52 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/Translation.screen.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import { View, ScrollView, StyleSheet } from 'react-native'; +import { RadioButton } from 'react-native-paper'; + +// TYPES +import type { supportedLangType } from '../../store/features/translation/types'; + +// ACTIONS & SELECTORS +import { useAppSelector, useAppDispatch } from '../../store/hooks'; +import { + getLang, + getLanguage, + supportedLangs, + setLang, +} from '../../store/features/translation'; + +// COMPONENTS +import { + TouchableCard, + FocusAwareStatusBar, + PaperText, + CustomScreenHeader, +} from '../../components/Common'; +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +function TranslationScreen({}) { + // ACTIONS + const translate = useAppDispatch(); + + // SELECTORS + const currentLang = useAppSelector(getLang); + const languages = useAppSelector(getLanguage); + + const STYLES = StyleSheet.create({ + container: { ...GS.screen }, + scrollView: { + ...GS.h100, + ...GS.pt4, + ...GS.px2, + ...GS.bgLight, + }, + langItem: { ...GS.inlineItems, ...GS.mb2 }, + langItemCard: { ...GS.w100, borderRadius: 5 }, + langItemCardContent: { borderRadius: 0 }, + }); + + return ( + + + + + + {Object.keys(supportedLangs).map((lang: string, id) => { + const L = lang as supportedLangType; + + return ( + translate(setLang(L))}> + + + { + // @ts-ignore + languages[lang] + } + + + translate(setLang(L))} + /> + + + ); + })} + + + ); +} + +export default TranslationScreen; diff --git a/packages/shop-mobile-expo/src/screens/index.ts b/packages/shop-mobile-expo/src/screens/index.ts new file mode 100644 index 000000000..7d39191fc --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/index.ts @@ -0,0 +1,35 @@ +// +import LoadingScreen from './Loading.screen'; +import Blank_Screen from './Blank_.screen'; + +// REGISTRATION +import SignUpScreen from './registration/SignUp.screen'; +import SignUpByAddressScreen from './registration/SignUpByAddress.screen'; + +// APP +import HomeScreen from './app/Home.screen'; +import OrderHistoryScreen from './app/OrderHistory.screen'; +import AccountScreen from './app/Account.screen'; +import TranslationScreen from './app/Translation.screen'; +import MerchantsSearchScreen from './app/MerchantsSearch.screen'; + +// TODO: create a type for screens object + +const SCREENS = { + // SCREENS + Loading: LoadingScreen, + Blank_: Blank_Screen, + APP: { + Home: HomeScreen, + OrderHistory: OrderHistoryScreen, + Account: AccountScreen, + Translation: TranslationScreen, + MerchantsSearch: MerchantsSearchScreen, + }, + REGISTRATION: { + SIGN_UP: SignUpScreen, + SIGN_UP_BY_ADDRESS: SignUpByAddressScreen, + }, +}; + +export default SCREENS; diff --git a/packages/shop-mobile-expo/src/screens/registration/SignUp.screen.tsx b/packages/shop-mobile-expo/src/screens/registration/SignUp.screen.tsx new file mode 100644 index 000000000..704aa720e --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/registration/SignUp.screen.tsx @@ -0,0 +1,149 @@ +import React from 'react'; +import { Text, View, Image, StyleSheet } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { Button } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import AntDesignIcon from '@expo/vector-icons/AntDesign'; + +// ACTIONS & SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getLanguage } from '../../store/features/translation'; + +// COMPONENTS +import { FocusAwareStatusBar, PaperText } from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +const STYLES = StyleSheet.create({ + container: { + ...GS.screen, + ...GS.bgTransparent, + ...GS.px5, + ...GS.pb5, + ...GS.mx5, + zIndex: 1, + }, + titleLogoContainer: { + ...GS.centered, + flex: 1, + }, + logoImg: { ...GS.w100, height: 100, marginBottom: -20 }, + logoTitle: { + ...GS.txtCapitalize, + fontSize: CS.FONT_SIZE + 1, + opacity: 0.7, + }, + networkBtnFacebook: { flex: 1, backgroundColor: CC.facebook }, + networkBtnGoogle: { flex: 1, backgroundColor: CC.google }, +}); + +const SignUpScreen = () => { + // SELECTORS + const currentLanguage = useAppSelector(getLanguage); + + // NAVIGATION + const navigation = useNavigation(); + + // FUNCTIONS + const onPressSignUpByAddress = () => { + navigation.navigate('STACK/SIGN_UP_BY_ADDRESS' as never); + }; + + return ( + + + + + {/* title logo */} + + + + {currentLanguage.INVITE_VIEW.BY_CODE.LOGO.DETAILS} + + + + {/* Social Networks buttons */} + + + + + + + + + + + + + + + + {currentLanguage.OR}{' '} + + {currentLanguage.INVITE_VIEW.BY_CODE.OR_WHAT} + + + + + + + + + + + ); +}; + +export default SignUpScreen; diff --git a/packages/shop-mobile-expo/src/screens/registration/SignUpByAddress.screen.tsx b/packages/shop-mobile-expo/src/screens/registration/SignUpByAddress.screen.tsx new file mode 100644 index 000000000..0d2056335 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/registration/SignUpByAddress.screen.tsx @@ -0,0 +1,652 @@ +import React from 'react'; +import { + View, + StyleSheet, + ScrollView, + Alert, + TouchableOpacity, + // Button as NativeBtn, + TextInput as NativeTextInput, +} from 'react-native'; +import { + ActivityIndicator, + TextInput, + Button, + HelperText, + Checkbox, + Text, +} from 'react-native-paper'; +import { useNavigation } from '@react-navigation/native'; +import * as Location from 'expo-location'; +import { showMessage } from 'react-native-flash-message'; +import { validate } from 'validate.js'; +import { useMutation } from '@apollo/client'; + +// TYPES/INTERFACES +import { CreateInviteByLocationMutationArgsInterface } from '../../client/invite/argumentInterfaces'; + +// CONSTANTS +import GROUPS from '../../router/groups.routes'; +import { REQUIRE_NOT_EMPTY_PRESENCE } from '../../constants/rules.validate'; + +// MUTATIONS +import { CREATE_INVITE_BY_LOCATION_MUTATION } from '../../client/invite/mutations'; + +// ACTIONS & SELECTORS +import { useAppSelector, useAppDispatch } from '../../store/hooks'; +import { onUserSignUpByAddressSuccess } from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; +import { setGroup } from '../../store/features/navigation'; + +// HELPERS +import { + getFormattedLocation, + FormattedLocationInterface, +} from '../../helpers/location'; + +// COMPONENTS +import { FocusAwareStatusBar, PaperText } from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +// TYPE +export type FormInputNameType = 'city' | 'street' | 'house' | 'apartment'; +export type FormType = { + [name in FormInputNameType]: string; +}; +export type FormErrorsType = + | { + [name in FormInputNameType]: string[] | undefined; + // tslint:disable-next-line: indent + } + | { [name: string]: string[] | undefined }; + +const SignUpByAddressScreen = () => { + // SELECTORS + const CURRENT_LANGUAGE = useAppSelector(getLanguage); + + // ACTIONS + const reduxDispatch = useAppDispatch(); + + // NAVIGATION + const NAVIGATION = useNavigation(); + + // STATES + const [warningDialog, setWarningDialog] = React.useState(false); + const [form, setForm] = React.useState({ + city: '', + street: '', + house: '', + apartment: '', + }); + const [formApartmentCheckbox, setFormApartmentCheckbox] = + React.useState(true); + const [formErrors, setFormErrors] = React.useState({}); + const [canGoBack, setCanGoBack] = React.useState(false); + const [, /* preventBackCallBack */ setPreventBackCallBack] = React.useState< + () => any + >(() => {}); + const [currentPosition, setCurrentPosition] = + React.useState(null); + const [formattedLocation, setFormattedLocation] = + React.useState(null); + const [addressLoading, setAddressLoading] = React.useState(true); + const [submitFormLoading, setSubmitFormLoading] = + React.useState(false); + + // DATA + const STYLES = StyleSheet.create({ + screen: { + ...GS.screen, + ...GS.bgSuccess, + overflow: 'hidden', + }, + container: { + ...GS.screen, + ...GS.centered, + ...GS.bgTransparent, + ...GS.px5, + ...GS.pb5, + }, + section1: { + ...GS.centered, + ...GS.pt5, + ...GS.mt5, + ...GS.pb3, + ...GS.mb3, + marginTop: CS.FONT_SIZE_LG * 3, + }, + section1Title: { + ...GS.txtCenter, + ...GS.mb3, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_LG * 1.8, + }, + section1SubTitle: { + ...GS.txtCenter, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD, + opacity: 0.6, + }, + section2: { ...GS.py2, ...GS.w100, alignItems: 'center' }, + section2Title: { + ...GS.txtCenter, + ...GS.mb5, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_SM * 2, + }, + formContainer: { + ...GS.w100, + }, + formInputContainer: {}, + formInputContainerRow: { flex: 1 }, + formInput: { ...GS.bgTransparent, ...GS.mb0, textAlign: 'center' }, + formInputDisabled: { opacity: 0.4 }, + formBtn: { ...GS.mb2 }, + formBtnLabel: { + ...GS.py1, + ...GS.txtCapitalize, + color: CC.light, + fontSize: CS.FONT_SIZE + 3, + }, + formSubmitBtn: { + ...(submitFormLoading + ? { + backgroundColor: CC.secondaryLight, + // tslint:disable-next-line: indent + } + : GS.bgSecondary), + }, + formSkipBtn: { ...GS.bgLight }, + formErrorHelperText: { + textAlign: 'center', + }, + formErrorHelperTextApartment: { + ...GS.mb2, + marginTop: -(CS.SPACE - 5), + }, + }); + + // TODO: Add more constraints + const VALIDATION_CONSTRAINT: { [name in FormInputNameType]?: object } = { + city: REQUIRE_NOT_EMPTY_PRESENCE, + street: REQUIRE_NOT_EMPTY_PRESENCE, + house: REQUIRE_NOT_EMPTY_PRESENCE, + apartment: REQUIRE_NOT_EMPTY_PRESENCE, + }; + + // REFS + const SCREEN_SCROLL_VIEW_REF = React.useRef(null); + const CITY_INPUT_REF = React.useRef(null); + const STREET_INPUT_REF = React.useRef(null); + const HOUSE_INPUT_REF = React.useRef(null); + const APARTMENT_INPUT_REF = React.useRef(null); + + // MUTATIONS + const [handleCreatInviteByLocation] = useMutation( + CREATE_INVITE_BY_LOCATION_MUTATION, + ); + + // FUNCTIONS + const onSubmitForm = () => { + setFormErrors({}); + + const FORMATTED_FORM = { + ...form, + ...formattedLocation, + }; + + const FORMATTED_CONSTRAINTS = { + ...VALIDATION_CONSTRAINT, + }; + + if (!formApartmentCheckbox) { + delete FORMATTED_CONSTRAINTS.apartment; + } + + const VALIDATION_RESULT = validate( + FORMATTED_FORM, + FORMATTED_CONSTRAINTS, + ); + + if (VALIDATION_RESULT) { + setFormErrors(VALIDATION_RESULT); + SCREEN_SCROLL_VIEW_REF?.current?.scrollTo({ y: 0 }); + return; + } + + setSubmitFormLoading(true); + + const CREATE_INVITE_INPUT: CreateInviteByLocationMutationArgsInterface = + { + createInput: { + apartment: formApartmentCheckbox + ? FORMATTED_FORM.apartment + : '', + geoLocation: { + countryId: 0, + city: FORMATTED_FORM.city, + streetAddress: FORMATTED_FORM.streetAddress as string, + house: FORMATTED_FORM.house, + postcode: null, + notes: null, + loc: { + type: 'Point', + coordinates: [ + FORMATTED_FORM.longitude as number, + FORMATTED_FORM.latitude as number, + ], + }, + }, + }, + }; + + handleCreatInviteByLocation({ + variables: { + ...CREATE_INVITE_INPUT, + }, + onCompleted: (TData) => { + reduxDispatch(onUserSignUpByAddressSuccess(TData.createInvite)); + reduxDispatch(setGroup(GROUPS.APP)); + showMessage({ + message: "Great job 🎉, you're sign-up as invite", + type: 'success', + }); + setSubmitFormLoading(false); + }, + onError: (ApolloError) => { + console.log('ApolloError ==>', ApolloError); + showMessage({ + message: ApolloError.name, + description: ApolloError.message, + type: 'danger', + }); + setSubmitFormLoading(false); + }, + }); + }; + + // EFFECTS + React.useEffect(() => { + setAddressLoading(true); + (async () => { + const { status } = + await Location.requestForegroundPermissionsAsync(); + + if (status !== 'granted') { + const ERROR_MSG = 'Permission to access location was denied'; + showMessage({ message: ERROR_MSG }); + setCanGoBack(true); + setTimeout(() => { + NAVIGATION.goBack(); + }, 100); + return; + } + + const CURRENT_POSITION = await Location.getCurrentPositionAsync({}); + const FORMATTED_ADDRESS = await getFormattedLocation( + CURRENT_POSITION.coords, + ); + + setCurrentPosition(CURRENT_POSITION); + setFormattedLocation(FORMATTED_ADDRESS); + setAddressLoading(false); + })(); + }, [NAVIGATION]); + + React.useEffect(() => { + // setCanGoBack(true); + // reduxDispatch(setGroup(GROUPS.APP)); + }, [reduxDispatch]); + + React.useEffect(() => { + console.log( + '\nLocation error ===> ', + currentPosition, + formattedLocation, + ); + }, [currentPosition, formattedLocation]); + + React.useEffect(() => { + NAVIGATION.addListener('beforeRemove', (e) => { + if (canGoBack) { + return; + } + + // Prevent default behavior of leaving the screen + e.preventDefault(); + setWarningDialog(true); + + // Prompt the user before leaving the screen + setPreventBackCallBack(() => { + return () => { + setCanGoBack(true); + setWarningDialog(false); + NAVIGATION.dispatch(e.data.action); + }; + }); + + Alert.alert( + 'Leave sign-up?', + "Your account isn't yet created! Are you sure to leave the screen?", + [ + { text: "Don't leave", style: 'cancel', onPress: () => {} }, + { + text: 'leave', + style: 'destructive', + onPress: () => { + setCanGoBack(true); + NAVIGATION.dispatch(e.data.action); + }, + }, + ], + ); + }); + + return () => NAVIGATION.removeListener('beforeRemove', () => null); + }, [NAVIGATION, canGoBack, warningDialog]); + + return ( + + + + {/* Loading view */} + + {/* section1 */} + + + {CURRENT_LANGUAGE.INVITE_VIEW.YOUR_ADDRESS} + + + + {CURRENT_LANGUAGE.INVITE_VIEW.LAUNCH_NOTIFICATION} + + + + {/* section2 */} + + {addressLoading ? ( + <> + + { + CURRENT_LANGUAGE.INVITE_VIEW + .DETECTING_LOCATION + } + + + + ) : ( + + + + STREET_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + city: text, + })) + } + /> + + + {formErrors?.city ? formErrors.city[0] : ''} + + + + + + HOUSE_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + street: text, + })) + } + /> + + {formErrors.street + ? formErrors.street[0] + : ''} + + + + + + + APARTMENT_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + house: text, + })) + } + /> + + {formErrors.house + ? formErrors.house[0] + : ''} + + + + + + setForm((prevForm) => ({ + ...prevForm, + apartment: text, + })) + } + /> + + + setFormApartmentCheckbox( + !formApartmentCheckbox, + ) + } + style={{ + ...GS.justifyContentBetween, + ...GS.mb0, + }}> + + {CURRENT_LANGUAGE.APARTMENT} + + + + setFormApartmentCheckbox( + !formApartmentCheckbox, + ) + } + /> + + + + {formErrors.apartment + ? formErrors.apartment[0] + : ''} + + + + + + + + + + + Click here + {' '} + to skip this step and fill these fields + later + + + + + )} + + + {/* TODO: find how to use a custom alert (disable due to slowing virtual device) */} + {/* {warningDialog && ( + + + + Leave? + + + + preventBackCallBack()} + /> + + + setWarningDialog(false)} + /> + + + )} */} + + + ); +}; + +export default SignUpByAddressScreen; diff --git a/packages/shop-mobile-expo/src/store/features/navigation/index.ts b/packages/shop-mobile-expo/src/store/features/navigation/index.ts new file mode 100644 index 000000000..c71153407 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/navigation/index.ts @@ -0,0 +1,30 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +// TYPES +import type { RootState } from '../../index'; +import type { NavigationStateType, NavigationGroupType } from './types'; + +// CONSTANTS +import GROUPS from '../../../router/groups.routes'; + +const initialState: NavigationStateType = { + group: GROUPS.LOADING, +}; + +export const navigationSlice = createSlice({ + name: 'navigation', + initialState, + reducers: { + setGroup: (state, action: PayloadAction) => { + state.group = action.payload; + }, + }, +}); + +// ACTIONS +export const { setGroup } = navigationSlice.actions; + +// SELECTORS +export const getGroup = (state: RootState) => state.navigation.group; + +export default navigationSlice.reducer; diff --git a/packages/shop-mobile-expo/src/store/features/navigation/types.ts b/packages/shop-mobile-expo/src/store/features/navigation/types.ts new file mode 100644 index 000000000..e377a95eb --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/navigation/types.ts @@ -0,0 +1,5 @@ +export type GroupNameType = 'APP' | 'LOADING' | 'BLANK' | 'REGISTRATION'; +export type NavigationGroupType = GroupNameType | null; +export type NavigationStateType = { + group: GroupNameType | null; +}; diff --git a/packages/shop-mobile-expo/src/store/features/translation/index.ts b/packages/shop-mobile-expo/src/store/features/translation/index.ts new file mode 100644 index 000000000..de2a4d694 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/index.ts @@ -0,0 +1,47 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +// LANGUAGES +import LANGUAGES from './lang'; + +// TYPES +import { + supportedLangType, + supportedLangsType, + TranslationStateType, +} from './types'; +import type { RootState } from '../../index'; + +export const supportedLangs: supportedLangsType = { + BULGARIAN: 'BULGARIAN', + ENGLISH: 'ENGLISH', + HEBREW: 'HEBREW', + FRENCH: 'FRENCH', + RUSSIAN: 'RUSSIAN', + SPANISH: 'SPANISH', +}; +export const initialState: TranslationStateType = { + lang: supportedLangs.ENGLISH, +}; +export const translationSlice = createSlice({ + name: 'translation', + initialState, + reducers: { + setLang: (state, action: PayloadAction) => { + state.lang = action.payload; + + AsyncStorage.setItem('translation', JSON.stringify(state)); + }, + }, +}); + +// ACTIONS +export const { setLang } = translationSlice.actions; + +// SELECTORS +export const getLang = (state: RootState) => state.translation.lang; +export const getLanguage = (state: RootState) => + LANGUAGES[state.translation.lang]; + +// REDUCER +export default translationSlice.reducer; diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/bg-BG.json b/packages/shop-mobile-expo/src/store/features/translation/lang/bg-BG.json new file mode 100644 index 000000000..d671b69dc --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/bg-BG.json @@ -0,0 +1,264 @@ +{ + "LANGUAGE": { + "ID": "bg-BG", + "NAME": "Bulgarian" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "INVITE_VIEW": { + "WELCOME_TO": "Добре дошли", + "EVER": "Евър", + "INFO_MISSING": "Част от информацията липсва!", + "YOUR_INVITE_CODE": "Вашият поканен код", + "INVITED_TEXT": { + "TITLE": "Благодарим Ви, че се регистрирате!", + "DETAILS": "Ще ви изпратим известие с код за покана, когато стартираме:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "Грешен код!", + "DETAILS": "Моля, уверете се, че сте на мястото, където сте получили кода си." + }, + "CANT_ACCESS_LOCATION": "Няма достъп до местоположението ви.", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "Нямате достъп до местоположението си. Моля, опитайте да влезете по адрес.", + "GET_IN_BY_ADDRESS": "Регистрирайте се по адрес", + "GET_IN": "Влез вътре!", + "YOUR_ADDRESS": "Уведомете Вашия адрес", + "LAUNCH_NOTIFICATION": "Обещаваме да показваме само подходящи продукти според вашия адрес", + "BY_CODE": { + "OR_WHAT": "Регистрирайте се чрез покана", + "INVITED": "Влезте с Invite Code", + "LOGO": { + "DETAILS": "Доставка и вземане на храна" + }, + "INVITE_CODE": "Код за покана" + }, + "DETECTING_LOCATION": "Моля, изчакайте, опитваме се да открием настоящия си адрес ..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Продукти", + "BUY_BUTTON": { + "PRE": "Купи за", + "SUF": "" + }, + "NOT_AVAILABLE": "Продуктът не е наличен", + "MINUTES": "мин", + "DELIVERY": "Доставка", + "TAKEAWAY": "За вкъщи", + "READYFOR": "Готов за", + "DETAILS": { + "DETAILS": "Детайли", + "BACK": "Обратно", + "INCLUDES": "Включва", + "BUY_FOR": "Купи за" + } + }, + "HELP_VIEW": { + "TITLE": "Помощ" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "История на поръчките", + "DETAILS": "Детайли" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Последни покупки", + "NOTHING_ORDERED": "Все още няма поръчки", + "TO_PRODUCTS": "Към продукти" + }, + "ABOUT_VIEW": { + "TITLE": "За нас" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Условия за ползване" + }, + "PRIVACY_VIEW": { + "TITLE": "Поверителност" + }, + "LANGUAGE_VIEW": { + "TITLE": "Избери език" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "Виж още", + "EMPTY_LIST": "Няма намерени продукти или ресторанти ... ", + "SEARCH_PLACEHOLDER": "Продукт или ресторант", + "OPEN": "Отворено", + "CLOSED": "Затворено" + }, + "BUY_POPUP": { + "ORDER_PAID": "Поръчката е платена $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "Поръчката беше анулирана по време на подготовката на склада!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + "VIEW_ORDER_PRODUCTS": "Преглед на продукти за поръчка", + "STATUSES": [ + { + "TITLE": "Подготвяме поръчката!", + "DETAILS": "Ще я получите между %t минути.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Доставчика е в движение!", + "DETAILS": "Ще получите реда в %t min.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Проверете си вратата!", + "DETAILS": "Ще получите реда в секунди.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Поръчка завършена!", + "DETAILS": "Благодарим Ви, че използвате Ever", + "NOT_PAID_NOTE": "" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "Подготвяме поръчката!", + "DETAILS": "Можете да го получите между %t минути.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + "DELIVERY_STATUS": { + "WE": "Ние", + "CARRIER": "Доставчик", + "YOU": "Вие" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "Доставката не беше правилно!", + "PROCESSING_WRONG": "Обработката не беше правилно!", + "TRY_AGAIN": "Моля, опитайте отново.", + "CALL_FOR_DETAILS": "Обадете се за подробности" + }, + "ELAPSED_TIME": { + "TITLE": "Изминалото време" + }, + "BUTTONS": { + "UNDO": "Отмяна", + "PAY_NOW": "Плащане с карта", + "PAY_X": "Платете {{ сума }}", + "PAY_WITH_FIXED_CARD": "Плати с", + "PAID": "Платено", + "END": "Добър апетит!", + "CANCEL": "Отказ", + "GOT_IT": "Схванах го", + "IM_HERE": "Тук съм", + "SHOW_QR_CODE": "Показване на QR код", + "SHOW_PRODUCTS": "Показване на продукти" + }, + "UNDO_POPUP": { + "TITLE": "Сигурни ли сте?", + "DETAILS": [ + "Отмяната ще Ви върне парите, но вие ще", + "загубите", + "поръчката!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Продукти", + "LAST_PURCHASES": "Последни покупки", + "CALL_US": "Обадете ни се", + "ORDER_HISTORY": "История на поръчките" + } + }, + "STORE": { + "STORE_TITLE": "Магазин", + "ITEMS": { + "CALL_WAITER": "Обадете се на сервитьор", + "ABOUT": "Относно" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Настройки", + "ITEMS": { + "LANGUAGE": "Език" + } + }, + "INFO": { + "DIVER_TITLE": "Информация", + "ITEMS": { + "FAQ": "Помогне", + "ABOUT_US": "За нас", + "TERMS_OF_USE": "Условия за ползване", + "PRIVACY": "Поверителност" + }, + "CALL": { + "WE_APPOLOGISE": "Извиняваме се!", + "CALL_UNSUCCESSFULL": "Обаждането беше неуспешно!" + } + }, + "LEGALS": { + "DIVER_TITLE": "Юридически", + "ITEMS": { + "TERMS_OF_USE": "Условия за ползване", + "PRIVACY": "Поверителност" + } + } + } + }, + "TIMER": { + "MINUTES": "Минути", + "SECONDS": "Секунди" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Няма връзка с Ever", + "DESCRIPTION": [ + "Моля, уверете се, че сте", + "свързани с интернет", + "или опитайте по-късно." + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Търговци близо до вас", + "NAME": "Име на търговеца", + "WITH_NAME": "Търговци с име" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Няма връзка със сървъра" + }, + "OR": "Или", + "OR_LOWERCASE": "или", + "YES": "Да", + "NO": "Не", + "OK": "Добре", + "CITY": "Сити", + "STREET": "Улица", + "HOUSE": "Къща", + "APARTMENT": "Апартамент", + "BACK": "Обратно", + "MORE": "повече", + "TO": "До", + "STORE_INFO": "Информация за магазина", + "MAP": "Карта", + "ORDER_INFO": "Информация за поръчката", + "CLOSE": "Затворен", + "SCAN": "Търсене", + "MERCHANTS": "Купечество", + "NOT_FOUND": "Няма резултати!", + "IN_STORE": "В магазина", + "EXIT_STORE": "Излезте от магазина", + "FAILED": "Се провали", + "CANCELED": "Отменен", + "IN_DELIVERY": "При доставка", + "PENDING": "В очакване", + "COMPLETED": "Завършен", + "BROWSE": "РАЗГЛЕДАЙ", + "ENGLISH": "Английски", + "HEBREW": "Иврит", + "RUSSIAN": "Руски", + "BULGARIAN": "Български", + "SPANISH": "испански", + "FRENCH": "Френски", + "SELECT": "Изберете", + "LOCATION_NOTES": "Бележки за местоположението", + "ENTER_NOTES_HERE": "Въведете бележки тук" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/en-US.json b/packages/shop-mobile-expo/src/store/features/translation/lang/en-US.json new file mode 100644 index 000000000..22df627b2 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/en-US.json @@ -0,0 +1,265 @@ +{ + "LANGUAGE": { + "ID": "en-US", + "NAME": "English" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "INVITE_VIEW": { + "WELCOME_TO": "Welcome to", + "EVER": "Ever Shop", + "INFO_MISSING": "Some of the information missing!", + "YOUR_INVITE_CODE": "Your invite code", + "INVITED_TEXT": { + "TITLE": "Thank you for sign up!", + "DETAILS": "We will send you a notification with an invite code when we launch at:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "Wrong code!", + "DETAILS": "Please make sure you at the place that came with your code." + }, + "CANT_ACCESS_LOCATION": "Can't access your location.", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "Can't access your location, please try to get in by address.", + "GET_IN_BY_ADDRESS": "Sign up by Address", + "GET_IN": "Get inside!", + "YOUR_ADDRESS": "Let us know your Address", + "LAUNCH_NOTIFICATION": "We promise to show only relevant products according to your address", + "BY_CODE": { + "OR_WHAT": "sign up by Invite", + "INVITED": "Sign in with Invite Code", + "LOGO": { + "DETAILS": "Food Delivery & Takeout" + }, + "INVITE_CODE": "Invite Code" + }, + "DETECTING_LOCATION": "Please wait, we attempting to detect your current address..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Products", + "BUY_BUTTON": { + "PRE": "Buy for ", + "SUF": "" + }, + "NOT_AVAILABLE": "Product not available", + "MINUTES": "min", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeout", + "READYFOR": "Ready for", + "DETAILS": { + "DETAILS": "Details", + "BACK": "Back", + "INCLUDES": "Includes", + "BUY_FOR": "Buy for" + } + }, + "HELP_VIEW": { + "TITLE": "Help" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "Order history", + "DETAILS": "Details" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Last Purchases", + "NOTHING_ORDERED": "No orders yet", + "TO_PRODUCTS": "To Products" + }, + "ABOUT_VIEW": { + "TITLE": "About Us" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Terms of Use" + }, + "PRIVACY_VIEW": { + "TITLE": "Privacy" + }, + "LANGUAGE_VIEW": { + "TITLE": "Select Language" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "View More", + "EMPTY_LIST": "No Products Or Merchants Found ... ", + "SEARCH_PLACEHOLDER": "Product Or Restorant Name", + "OPEN": "Open", + "CLOSED": "Closed" + }, + "BUY_POPUP": { + "ORDER_PAID": "The order is paid $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "The order was cancel while Warehouse Preparation!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + + "VIEW_ORDER_PRODUCTS": "View Order Products", + "STATUSES": [ + { + "TITLE": "We're preparing the order!", + "DETAILS": "You will get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Carrier on the way!", + "DETAILS": "You will get the order in %t min.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Check your door!", + "DETAILS": "You will get the order in seconds.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Order Completed!", + "DETAILS": "Thanks for using Ever", + "NOT_PAID_NOTE": "" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "We're preparing the order!", + "DETAILS": "You can get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + "DELIVERY_STATUS": { + "WE": "We", + "CARRIER": "Carrier", + "YOU": "You" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "The Delivery Went Wrong!", + "PROCESSING_WRONG": "Processing Went Wrong!", + "TRY_AGAIN": "Please try again.", + "CALL_FOR_DETAILS": "Call for details" + }, + "ELAPSED_TIME": { + "TITLE": "Elapsed time" + }, + "BUTTONS": { + "UNDO": "Undo", + "PAY_NOW": "Pay with Card", + "PAY_X": "Pay {{amount}}", + "PAY_WITH_FIXED_CARD": "Pay with", + "PAID": "Paid", + "END": "Bon appetit!", + "CANCEL": "Cancel", + "GOT_IT": "Got It!", + "IM_HERE": "I'm here", + "SHOW_QR_CODE": "Show QR Code", + "SHOW_PRODUCTS": "Show Products" + }, + "UNDO_POPUP": { + "TITLE": "Are you sure?", + "DETAILS": [ + "Undo will return you the money, but you will", + "lose", + "the order!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Products", + "LAST_PURCHASES": "Last Purchases", + "CALL_US": "Call Us", + "ORDER_HISTORY": "Order history" + } + }, + "STORE": { + "STORE_TITLE": "Store", + "ITEMS": { + "CALL_WAITER": "Call Waiter", + "ABOUT": "About" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Settings", + "ITEMS": { + "LANGUAGE": "Language" + } + }, + "INFO": { + "DIVER_TITLE": "Information", + "ITEMS": { + "FAQ": "Help", + "ABOUT_US": "About Us", + "TERMS_OF_USE": "Terms of Use", + "PRIVACY": "Privacy" + }, + "CALL": { + "WE_APPOLOGISE": "We Apologise!", + "CALL_UNSUCCESSFULL": "Call Was Unsuccessful!" + } + }, + "LEGALS": { + "DIVER_TITLE": "LEGAL", + "ITEMS": { + "TERMS_OF_USE": "Terms of Use", + "PRIVACY": "Privacy" + } + } + } + }, + "TIMER": { + "MINUTES": "Minutes", + "SECONDS": "Seconds" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No connection to Ever", + "DESCRIPTION": [ + "Please make sure you are", + "connected to the internet", + "or try Ever app later." + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Merchants close to you", + "NAME": "Merchant name", + "WITH_NAME": "Merchants with name" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "OR": "Or", + "OR_LOWERCASE": "or", + "YES": "Yes", + "NO": "No", + "OK": "Ok", + "CITY": "City", + "STREET": "Street", + "HOUSE": "House", + "APARTMENT": "Apartment", + "BACK": "Back", + "MORE": "more", + "TO": "To", + "STORE_INFO": "Store Info", + "MAP": "Map", + "ORDER_INFO": "Order Info", + "CLOSE": "Close", + "SCAN": "Scan", + "MERCHANTS": "Merchants", + "NOT_FOUND": "Not Found!", + "IN_STORE": "In Store", + "EXIT_STORE": "Exit Store", + "FAILED": "Failed", + "CANCELED": "Canceled", + "IN_DELIVERY": "In Delivery", + "PENDING": "Pending", + "COMPLETED": "Completed", + "BROWSE": "Browse", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "SELECT": "Select", + "LOCATION_NOTES": "Location Notes", + "ENTER_NOTES_HERE": "Enter notes here" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/es-ES.json b/packages/shop-mobile-expo/src/store/features/translation/lang/es-ES.json new file mode 100644 index 000000000..b4bfd0712 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/es-ES.json @@ -0,0 +1,257 @@ +{ + "LANGUAGE": { + "ID": "es-ES", + "NAME": "Español" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "INVITE_VIEW": { + "WELCOME_TO": "Bienvenido a", + "EVER": "Tu-Pedido", + "INFO_MISSING": "¡Falta información!", + "YOUR_INVITE_CODE": "Su código de invitado", + "INVITED_TEXT": { + "TITLE": "¡Gracias por registrarte!", + "DETAILS": "Le enviaremos una notificación con un código de invitación cuando lancemos en:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "¡Código erroneo!", + "DETAILS": "Asegúrate de estar en el lugar que dice tu código" + }, + "CANT_ACCESS_LOCATION": "No se puede acceder a tu ubicación", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "No se puede acceder a tu ubicación, intenta ingresar por dirección", + "GET_IN_BY_ADDRESS": "Registrarse por dirección", + "GET_IN": "¡Entrar!", + "YOUR_ADDRESS": "Escribe tu dirección", + "LAUNCH_NOTIFICATION": "Prometemos mostrar solo productos relevantes de acuerdo con su dirección", + "BY_CODE": { + "OR_WHAT": "Entrar con invitación", + "INVITED": "Inicie sesión con el código de invitación", + "LOGO": { + "DETAILS": "Entrega de Comida y Comida para Llevar" + }, + "INVITE_CODE": "Código de invitación" + }, + "DETECTING_LOCATION": "Estamos intentando detectar tu dirección actual..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Productos", + "BUY_BUTTON": { + "PRE": "Comprar por ", + "SUF": "" + }, + "NOT_AVAILABLE": "Producto no disponible", + "MINUTES": "min", + "DELIVERY": "Delivery", + "TAKEAWAY": "Para retirar", + "READYFOR": "Listo para", + "DETAILS": { + "DETAILS": "Detalles", + "BACK": "Regresar", + "INCLUDES": "Incluye", + "BUY_FOR": "Comprar por " + } + }, + "HELP_VIEW": { + "TITLE": "Ayuda" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "Historial de Pedidos", + "DETAILS": "Detalles" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Ultimas Compras", + "NOTHING_ORDERED": "No existen pedidos todavía", + "TO_PRODUCTS": "A Prodcutos" + }, + "ABOUT_VIEW": { + "TITLE": "Acerca de" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Términos de uso" + }, + "PRIVACY_VIEW": { + "TITLE": "Privacidad" + }, + "LANGUAGE_VIEW": { + "TITLE": "Seleccionar Idioma" + }, + "BUY_POPUP": { + "ORDER_PAID": "El pedido cuesta $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "El pedido se canceló mientras se preparaba el almacén!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + "VIEW_ORDER_PRODUCTS": "Ver productos de pedido", + "STATUSES": [ + { + "TITLE": "¡Estamos preparando el pedido!", + "DETAILS": "Lo tendrás en %t minutos", + "NOT_PAID_NOTE": "Ten pronto %s en efectivo" + }, + { + "TITLE": "¡Delivery en camino!", + "DETAILS": "Recibirás el pedido en %t minutos", + "NOT_PAID_NOTE": "Ten pronto %s en efectivo" + }, + { + "TITLE": "¡Ya estamos en tu casa!", + "DETAILS": "Recibirás el pedido en segundos", + "NOT_PAID_NOTE": "Ten pronto %s en efectivo" + }, + { + "TITLE": "¡Pedido Completado!", + "DETAILS": "Gracias por usar nuestro servicio", + "NOT_PAID_NOTE": "" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "¡Estamos preparando el pedido!", + "DETAILS": "Lo tendrás en %t minutos", + "NOT_PAID_NOTE": "Ten pronto %s en efectivo" + }, + "DELIVERY_STATUS": { + "WE": "Nosotros", + "CARRIER": "Delivery", + "YOU": "Tú" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "¡La entrega salió mal!", + "PROCESSING_WRONG": "¡El procesamiento fue mal!", + "TRY_AGAIN": "Por favor, intenta de nuevo", + "CALL_FOR_DETAILS": "Llama por detalles" + }, + "ELAPSED_TIME": { + "TITLE": "Tiempo transcurrido" + }, + "BUTTONS": { + "UNDO": "Deshacer", + "PAY_NOW": "Pagar con Tarjeta", + "PAY_X": "Pagar {{amount}}", + "PAY_WITH_FIXED_CARD": "Pagar con", + "PAID": "Pago", + "END": "¡Buen apetito!", + "CANCEL": "Cancelar", + "GOT_IT": "¡Lo tengo!", + "IM_HERE": "Estamos aquí", + "SHOW_QR_CODE": "Mostrar código QR", + "SHOW_PRODUCTS": "Mostrar productos" + }, + "UNDO_POPUP": { + "TITLE": "Estás seguro?", + "DETAILS": [ + "¡Deshacer te devolverá el dinero, pero", + "perderás", + "el pedido!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Productos", + "LAST_PURCHASES": "Ultimas Compras", + "CALL_US": "Llámanos", + "ORDER_HISTORY": "Historial de pedidos" + } + }, + "STORE": { + "STORE_TITLE": "Comercio", + "ITEMS": { + "CALL_WAITER": "Llámanos", + "ABOUT": "Acerca de" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Configuraciones", + "ITEMS": { + "LANGUAGE": "Idioma" + } + }, + "INFO": { + "DIVER_TITLE": "Información", + "ITEMS": { + "FAQ": "Ayuda", + "ABOUT_US": "Acerca de", + "TERMS_OF_USE": "Condiciones de uso", + "PRIVACY": "Privacidad" + }, + "CALL": { + "WE_APPOLOGISE": "Pedimos perdón!", + "CALL_UNSUCCESSFULL": "La llamada no fue exitosa!" + } + }, + "LEGALS": { + "DIVER_TITLE": "LEGAL", + "ITEMS": { + "TERMS_OF_USE": "Condiciones de uso", + "PRIVACY": "Privacidad" + } + } + } + }, + "TIMER": { + "MINUTES": "Minutos", + "SECONDS": "Segundos" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No hay conexión al Servidor", + "DESCRIPTION": [ + "Por favor asegúrate de que estás", + "conectado a Internet", + "o inténtalo mas tarde" + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Comerciantes cerca de ti", + "NAME": "Nombre del Comercio", + "WITH_NAME": "Comercios" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Se perdió la conexión con el servidor" + }, + "OR": "O", + "OR_LOWERCASE": "o", + "YES": "Si", + "NO": "No", + "OK": "Ok", + "CITY": "Ciudad", + "STREET": "Calle", + "HOUSE": "Número de puerta", + "APARTMENT": "Apartmento", + "BACK": "Volver", + "MORE": "Más", + "TO": "Hacia", + "STORE_INFO": "Información del Comercio", + "MAP": "Mapa", + "ORDER_INFO": "Información del Pedido", + "CLOSE": "Cerrar", + "SCAN": "Escanear", + "MERCHANTS": "Comercios", + "NOT_FOUND": "¡No hay resultados!", + "IN_STORE": "En el comercio", + "EXIT_STORE": "Salir del comercio", + "FAILED": "Error", + "CANCELED": "Cancelado", + "IN_DELIVERY": "En viaje", + "PENDING": "Pendiente", + "COMPLETED": "Completado", + "BROWSE": "Vistazo", + "ENGLISH": "Inglés", + "HEBREW": "Hebreo", + "RUSSIAN": "Ruso", + "BULGARIAN": "Búlgaro", + "SPANISH": "Español", + "FRENCH": "Francés", + "SELECT": "Seleccione", + "LOCATION_NOTES": "Notas de ubicación", + "ENTER_NOTES_HERE": "Introduce notas aquí" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/fr-FR.json b/packages/shop-mobile-expo/src/store/features/translation/lang/fr-FR.json new file mode 100644 index 000000000..48fad121d --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/fr-FR.json @@ -0,0 +1,269 @@ +{ + "welcome": "Bienvenu(e) chez Ever", + "@welcome": { + "description": "Message de bienvenue" + }, + "LANGUAGE": { + "ID": "fr-FR", + "NAME": "Francais" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "gauche", + "INVITE_VIEW": { + "WELCOME_TO": "Bienvenu(e) à", + "EVER": "Ever", + "INFO_MISSING": "Certaines informations sont manquantes!", + "YOUR_INVITE_CODE": "Votre code d'invitation", + "INVITED_TEXT": { + "TITLE": "Merci de votre enregistrement!", + "DETAILS": "Nous allons vous enoyer une notification contenant votre code d'invitation dès que nous lançons à :" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "Le code est incorrect!", + "DETAILS": "Rassurez vous que vous êtes bien à l'endroit d'où a été reçu votre code" + }, + "CANT_ACCESS_LOCATION": "Nous n'arrivons à detecter votre emplacement", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "Nous n'arrivons pas à reconnaitre votre emplacement, veuillez essayer de rentrer les informations de votre adresse.", + "GET_IN_BY_ADDRESS": "S'enregistrement à partir d'une adresse", + "GET_IN": "On y va!", + "YOUR_ADDRESS": "Laissez nous detecter votre adresse", + "LAUNCH_NOTIFICATION": "Nous vous affichons que des produits bien spécifiques à votre adresse", + "BY_CODE": { + "OR_WHAT": "S'enregistrer avec un code d'invitation", + "INVITED": "Se connecter avec le code d'invitation", + "LOGO": { + "DETAILS": "Livraison express des repas & Take Away" + }, + "INVITE_CODE": "Code d'invitation" + }, + "DETECTING_LOCATION": "Veuillez patienter, le temps de detecter votre adresse actuelle..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Produits", + "BUY_BUTTON": { + "PRE": "Acheter ", + "SUF": "" + }, + "NOT_AVAILABLE": "Produit non disponible", + "MINUTES": "min", + "DELIVERY": "Livraisons", + "TAKEAWAY": "A emporter", + "READYFOR": "Prêt à", + "DETAILS": { + "DETAILS": "Details", + "BACK": "Retour", + "INCLUDES": "Inclus", + "BUY_FOR": "Acheter" + } + }, + "HELP_VIEW": { + "TITLE": "Aide" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "Historique de commandes", + "DETAILS": "Details" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Derniers achats", + "NOTHING_ORDERED": "Aucune commande", + "TO_PRODUCTS": "Aux produits" + }, + "ABOUT_VIEW": { + "TITLE": "A propos de nous" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Conditions d'utilisation" + }, + "PRIVACY_VIEW": { + "TITLE": "Politique de confidentialité" + }, + "LANGUAGE_VIEW": { + "TITLE": "Choisissez votre langue" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "Voir plus", + "EMPTY_LIST": "Aucun produit ou marchand trouvé ... ", + "SEARCH_PLACEHOLDER": "Produit ou Nom du restaurant", + "OPEN": "Ouvert", + "CLOSED": "Fermé" + }, + "BUY_POPUP": { + "ORDER_PAID": "La commande a été payée $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "La commande a été annulée lors de la synchronisation de l'entrepôt!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + + "VIEW_ORDER_PRODUCTS": "Voir les produits commandés", + "STATUSES": [ + { + "TITLE": "En préparation de votre commande!", + "DETAILS": "Nous y serons dans %t minutes.", + "NOT_PAID_NOTE": "Préparez votre portefeuille (%s en cash)." + }, + { + "TITLE": "Le livreur est en route!", + "DETAILS": "Vous allez recevoir votre commande dans %t min.", + "NOT_PAID_NOTE": "Préparez votre portefeuille (%s en cash).." + }, + { + "TITLE": "Checkez à votre porte!", + "DETAILS": "Votre commande sera la dans quelques seconds.", + "NOT_PAID_NOTE": "Préparez votre portefeuille (%s en cash)" + }, + { + "TITLE": "Commande complète!", + "DETAILS": "Merci d'utiliser Ever", + "NOT_PAID_NOTE": "" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "En préparation de votre commande!", + "DETAILS": "Nous y serons dans %t minutes.", + "NOT_PAID_NOTE": "Préparez votre portefeuille (%s en cash)." + }, + "DELIVERY_STATUS": { + "WE": "Nous", + "CARRIER": "Livreur", + "YOU": "Vous" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "La livraison n'a pas été correcte!", + "PROCESSING_WRONG": "La livraison n'a pas abouti!", + "TRY_AGAIN": "SVP Réessayez.", + "CALL_FOR_DETAILS": "Details" + }, + "ELAPSED_TIME": { + "TITLE": "Temps écoulé" + }, + "BUTTONS": { + "UNDO": "Annuler", + "PAY_NOW": "Payer avec une carte", + "PAY_X": "Paiement {{amount}}", + "PAY_WITH_FIXED_CARD": "Payer avec", + "PAID": "Payé", + "END": "Bon appetit!", + "CANCEL": "Annuler", + "GOT_IT": "J'ai compris!", + "IM_HERE": "Je suis ici", + "SHOW_QR_CODE": "Montrez votre QR Code", + "SHOW_PRODUCTS": "Voir les produits" + }, + "UNDO_POPUP": { + "TITLE": "Etes vous sure?", + "DETAILS": [ + "En cas d'annulation le montant payé vous est retourné, mais vous", + "perdez", + "votre commande!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Produits", + "LAST_PURCHASES": "Derniers achats", + "CALL_US": "Appelez-nous", + "ORDER_HISTORY": "Historique de commandes" + } + }, + "STORE": { + "STORE_TITLE": "Magasin", + "ITEMS": { + "CALL_WAITER": "Appelez le serveur", + "ABOUT": "A propos de nous" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Paramètres", + "ITEMS": { + "LANGUAGE": "Langue" + } + }, + "INFO": { + "DIVER_TITLE": "Information", + "ITEMS": { + "FAQ": "Aide", + "ABOUT_US": "A propos de nous", + "TERMS_OF_USE": "Conditions d'utilisation", + "PRIVACY": "Politique de confidentialité" + }, + "CALL": { + "WE_APPOLOGISE": "Nous nous excusons pour la gêne occasionnée.", + "CALL_UNSUCCESSFULL": "L'appel n'a pas abouti." + } + }, + "LEGALS": { + "DIVER_TITLE": "LEGAL", + "ITEMS": { + "TERMS_OF_USE": "Conditions d'utilisation", + "PRIVACY": "Politique de confidentialité" + } + } + } + }, + "TIMER": { + "MINUTES": "Minutes", + "SECONDS": "Seconds" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Aucune connexion à Ever", + "DESCRIPTION": [ + "Assurez vous que vous êtes", + "connecté à internet", + "ou essayer de relancer Ever app" + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Vendeurs proches de vous", + "NAME": "Nom du vendeur", + "WITH_NAME": "Vendeur avec noms" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "La connexion au serveur a été perdue" + }, + "OR": "Ou", + "OR_LOWERCASE": "ou", + "YES": "Oui", + "NO": "Non", + "OK": "Ok", + "CITY": "Ville ou Cité", + "STREET": "Avenue, Rue", + "HOUSE": "Numéro de maison", + "APARTMENT": "Numéro d'appartement", + "BACK": "Retour", + "MORE": "Plus", + "TO": "à", + "STORE_INFO": "Magasin", + "MAP": "Carte", + "ORDER_INFO": "Commande", + "CLOSE": "Fermer", + "SCAN": "Scanner", + "MERCHANTS": "Vendeurs", + "NOT_FOUND": "Non trouvé!", + "IN_STORE": "Dans le magasin", + "EXIT_STORE": "Sortir du magasin", + "FAILED": "Échec", + "CANCELED": "Annulé", + "IN_DELIVERY": "En livraison", + "PENDING": "En attente", + "COMPLETED": "Terminé", + "BROWSE": "Parcourir", + "ENGLISH": "Anglais", + "HEBREW": "Hébreu", + "RUSSIAN": "Russe", + "BULGARIAN": "bulgare", + "SPANISH": "Espagnol", + "FRENCH": "français", + "SELECT": "Sélectionner", + "LOCATION_NOTES": "Notes de localisation", + "ENTER_NOTES_HERE": "Entrez ici vos notes" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/he-IL.json b/packages/shop-mobile-expo/src/store/features/translation/lang/he-IL.json new file mode 100644 index 000000000..94eb854d0 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/he-IL.json @@ -0,0 +1,257 @@ +{ + "LANGUAGE": { + "ID": "he-IL", + "NAME": "עברית" + }, + "CURRENT_DIRECTION": "rtl", + "SIDEBAR_SIDE": "right", + "INVITE_VIEW": { + "WELCOME_TO": "ברוך הבא ל-", + "EVER": "Ever", + "INFO_MISSING": "אנא מלא את המידע החסר.", + "YOUR_INVITE_CODE": "קוד ההזמנה שלך", + "INVITED_TEXT": { + "TITLE": "תודה על ההרשמה!", + "DETAILS": "אנחנו נשלח לך הודעה ברגע שנתחיל לפעול בכתובת שלך:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "קוד שגוי!", + "DETAILS": "תוודא בבקשה שאתה במקום שמצורף לקוד שלך!" + }, + "CANT_ACCESS_LOCATION": "אין גישה למיקום שלך.", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "אין גישה למיקום שלך, אנא נסה להיכנס באמצעות הכתובת.", + "GET_IN_BY_ADDRESS": "הירשם עם כתובת", + "GET_IN": "כנס", + "YOUR_ADDRESS": "מה הכתובת שלך?", + "LAUNCH_NOTIFICATION": "אנחנו מבטיחים להראות רק מוצרים רלוונטיים בהתאם לכתובת שהוכנסה", + "BY_CODE": { + "OR_WHAT": "הירשם באמצעות הזמנה", + "INVITED": "היכנס עם הזמנה לאפליקציה", + "LOGO": { + "DETAILS": "משלוחי אוכל וטייק אווי" + }, + "INVITE_CODE": "קוד הזמנה" + }, + "DETECTING_LOCATION": "רק רגע... אנחנו מנסים לזהות את הכתובת שלך." + }, + "PRODUCTS_VIEW": { + "TITLE": "המוצרים שלנו", + "BUY_BUTTON": { + "PRE": "", + "SUF": "-קנה ב" + }, + "NOT_AVAILABLE": "המוצר אינו זמין", + "MINUTES": "דקות", + "DELIVERY": "משלוח", + "TAKEAWAY": "Takeaway", + "READYFOR": "מוכן ל-", + "DETAILS": { + "DETAILS": "פרטים", + "BACK": "חזרה", + "INCLUDES": "כולל", + "BUY_FOR": "קנה עבור" + } + }, + "HELP_VIEW": { + "TITLE": "עֶזרָה" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "היסטוריית הזמנות", + "DETAILS": "פרטים" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "רכישות אחרונות", + "NOTHING_ORDERED": "עדיין לא הזמנת שום דבר", + "TO_PRODUCTS": "למוצרים" + }, + "ABOUT_VIEW": { + "TITLE": "עלינו" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "תנאי שימוש" + }, + "PRIVACY_VIEW": { + "TITLE": "פרטיות" + }, + "LANGUAGE_VIEW": { + "TITLE": "בחירת שפה" + }, + "BUY_POPUP": { + "ORDER_PAID": "ההזמנה משולמת $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "ההזמנה בוטלה תוך כדי הכנת מחסן!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + "VIEW_ORDER_PRODUCTS": "צפו במוצרי הזמנה", + "STATUSES": [ + { + "TITLE": "אנחנו מכינים את ההזמנה!", + "DETAILS": "ההזמנה תהיה בידך בעוד %t דקות.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "השליח בדרך!", + "DETAILS": "ההזמנה תהיה בידך בעוד %t דקות.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "בדוק את הדלת שלך!", + "DETAILS": "ההזמנה תהיה בידך בעוד רגע.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "ההזמנה הושלמה!", + "DETAILS": "תודה על השימוש ב-Ever", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "אנחנו מכינים את ההזמנה!", + "DETAILS": "אתה יכול להכניס אותו %t דקות.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + "DELIVERY_STATUS": { + "WE": "אנחנו", + "CARRIER": "השליח", + "YOU": "אתה" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "המסירה השתבשה!", + "PROCESSING_WRONG": "עיבוד התבצעה טעות!", + "TRY_AGAIN": "בבקשה נסה שוב.", + "CALL_FOR_DETAILS": "התקשר לקבלת פרטים" + }, + "ELAPSED_TIME": { + "TITLE": "הזמן שחלף" + }, + "BUTTONS": { + "UNDO": "ביטול", + "PAY_NOW": "תשלום באשראי", + "PAY_WITH_FIXED_CARD": "שלם בעזרת", + "PAY_X": "לשלם {{amount}}", + "PAID": "שולם באשראי", + "END": "בתאבון!", + "CANCEL": "בטל", + "GOT_IT": "הבנתי", + "IM_HERE": "אני כאן", + "SHOW_QR_CODE": "הצג קוד QR", + "SHOW_PRODUCTS": "הצג מוצרים" + }, + "UNDO_POPUP": { + "TITLE": "אתה בטוח?", + "DETAILS": [ + "החזרת הכסף אולי תחזיר לך את כספך, אבל תנפץ את", + "ההזמנה שלך!", + "" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "מוצרים", + "LAST_PURCHASES": "רכישות אחרונות", + "CALL_US": "התקשר אלינו", + "ORDER_HISTORY": "היסטוריית הזמנות" + } + }, + "STORE": { + "STORE_TITLE": "חנות", + "ITEMS": { + "CALL_WAITER": "התקשר למלצר", + "ABOUT": "על אודות" + } + }, + "SETTINGS": { + "DIVER_TITLE": "הגדרות", + "ITEMS": { + "LANGUAGE": "(Language) שפה" + } + }, + "INFO": { + "DIVER_TITLE": "מידע", + "ITEMS": { + "FAQ": "עזרה", + "ABOUT_US": "עלינו", + "TERMS_OF_USE": "תנאי שימוש", + "PRIVACY": "פרטיות" + }, + "CALL": { + "WE_APPOLOGISE": "אנו מתנצלים", + "CALL_UNSUCCESSFULL": "השיחה לא הצליחה" + } + }, + "LEGALS": { + "DIVER_TITLE": "אלמוגים", + "ITEMS": { + "TERMS_OF_USE": "תנאי שימוש", + "PRIVACY": "פרטיות" + } + } + } + }, + "TIMER": { + "MINUTES": "דקות", + "SECONDS": "שניות" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "אין חיבור ל-Ever", + "DESCRIPTION": [ + "בבקשה תוודא שאתה", + "מחובר לאינטרנט או תנסה", + "את האפליקציה מאוחר יותר." + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "סוחרים קרובים אליך", + "NAME": "שם סוחר", + "WITH_NAME": "סוחרים עם שם" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "השרת נפל" + }, + "OR": "או", + "OR_LOWERCASE": "או", + "YES": "כן", + "NO": "לא", + "OK": "אוקי", + "CITY": "עיר", + "STREET": "רחוב", + "HOUSE": "בית", + "APARTMENT": "דירה", + "BACK": "לחזור", + "MORE": "יותר", + "TO": "ל", + "STORE_INFO": "מידע חנות", + "MAP": "מפה", + "ORDER_INFO": "פרטי הזמנה", + "CLOSE": "סגור", + "SCAN": "סרוק", + "MERCHANTS": "סוחרים", + "NOT_FOUND": "אין תוצאות!", + "IN_STORE": "בחנות", + "EXIT_STORE": "צא מהחנות", + "FAILED": "נכשל", + "CANCELED": "מבוטל", + "IN_DELIVERY": "במשלוח", + "PENDING": "ממתין ל", + "COMPLETED": "הושלם", + "BROWSE": "לְדַפדֵף", + "ENGLISH": "אנגלית", + "HEBREW": "עִברִית", + "RUSSIAN": "רוּסִי", + "BULGARIAN": "בולגרית", + "SPANISH": "ספרדית", + "FRENCH": "צָרְפָתִית", + "SELECT": "בחר", + "LOCATION_NOTES": "הערות מיקום", + "ENTER_NOTES_HERE": "הזן הערות כאן" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/index.ts b/packages/shop-mobile-expo/src/store/features/translation/lang/index.ts new file mode 100644 index 000000000..744677fcb --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/index.ts @@ -0,0 +1,29 @@ +// TYPES +import type { supportedLangType } from '../types'; + +// LANGUAGES +import bg_BG from './bg-BG.json'; +import en_US from './en-US.json'; +import es_ES from './es-ES.json'; +import fr_FR from './fr-FR.json'; +import he_IL from './he-IL.json'; +import ru_RU from './ru-RU.json'; + +const LANGUAGES: { + [name in supportedLangType]: + | typeof bg_BG + | typeof en_US + | typeof es_ES + | typeof fr_FR + | typeof he_IL + | typeof ru_RU; +} = { + BULGARIAN: bg_BG, + ENGLISH: en_US, + SPANISH: es_ES, + FRENCH: fr_FR, + HEBREW: he_IL, + RUSSIAN: ru_RU, +}; + +export default LANGUAGES; diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/ru-RU.json b/packages/shop-mobile-expo/src/store/features/translation/lang/ru-RU.json new file mode 100644 index 000000000..7f407e237 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/ru-RU.json @@ -0,0 +1,256 @@ +{ + "LANGUAGE": { + "ID": "ru-RU", + "NAME": "Русский" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "INVITE_VIEW": { + "WELCOME_TO": "Добро Пожаловать в", + "EVER": "Ever Shop", + "INFO_MISSING": "Некоторая информация отсутствует!", + "YOUR_INVITE_CODE": "Ваш код приглашения", + "INVITED_TEXT": { + "TITLE": "Спасибо за запрос на приглашение!", + "DETAILS": "Мы вышлем Вам уведомление когда мы будем обслуживать Ваш адрес:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "Неверный код!", + "DETAILS": "Пожулуйста убедитесь что Вы находитесь по адресу, который был использован для получения Вашего Кода приглашения в наш сервис." + }, + "CANT_ACCESS_LOCATION": "Не возможно получить Ваше местонахождение. Попробуйте зарегистрироваться используя Ваш адрес.", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "Не возможно получить Ваше местонахождение.", + "GET_IN_BY_ADDRESS": "Зарегистрироваться", + "GET_IN": "Зарегистрироваться", + "YOUR_ADDRESS": "Пожалуйста введите Адрес", + "LAUNCH_NOTIFICATION": "Мы обещаем предлагать Вам только продукты, доступные по указанному адресу", + "BY_CODE": { + "OR_WHAT": "войти по приглашению", + "INVITED": "Войти по приглашению", + "LOGO": { + "DETAILS": "Доставка Еды & Takeaway" + }, + "INVITE_CODE": "Код приглашения" + }, + "DETECTING_LOCATION": "Пожалуйста подождите, мы пытаемся определить Ваш текущий адрес..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Наши Продукты", + "BUY_BUTTON": { + "PRE": "Купить за ", + "SUF": "" + }, + "NOT_AVAILABLE": "Товар недоступен", + "MINUTES": "мин", + "DELIVERY": "Доставка", + "TAKEAWAY": "Takeaway", + "READYFOR": "Готово для", + "DETAILS": { + "DETAILS": "Детали", + "BACK": "Назад", + "INCLUDES": "Включает", + "BUY_FOR": "Купить для" + } + }, + "HELP_VIEW": { + "TITLE": "Помогите" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "История заказов", + "DETAILS": "Детали" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Недавние Покупки", + "NOTHING_ORDERED": "Вы еще ничего не заказывали", + "TO_PRODUCTS": "К Продуктам" + }, + "ABOUT_VIEW": { + "TITLE": "О Нас" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Правила Использования" + }, + "PRIVACY_VIEW": { + "TITLE": "Конфиденциальность" + }, + "LANGUAGE_VIEW": { + "TITLE": "Выбор Языка" + }, + "BUY_POPUP": { + "ORDER_PAID": "Заказ оплачен $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "Заказ был отменен при подготовке склада!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + "VIEW_ORDER_PRODUCTS": "Посмотреть продукты для заказа", + "STATUSES": [ + { + "TITLE": "Мы готовим Ваш заказ!", + "DETAILS": "Вы должны получить его в течении %t минут.", + "NOT_PAID_NOTE": "Вам будет необходимо оплатить %s (наличными курьеру)." + }, + { + "TITLE": "Курьер по дороге к Вам!", + "DETAILS": "Вы должны получить заказ в течении %t минут.", + "NOT_PAID_NOTE": "Вам будет необходимо оплатить %s (наличными курьеру)." + }, + { + "TITLE": "Курьер уже практически у Вас!", + "DETAILS": "Вы получите заказ через несколько секунд.", + "NOT_PAID_NOTE": "Вам будет необходимо оплатить %s (наличными курьеру)." + }, + { + "TITLE": "Заказ доставлен!", + "DETAILS": "Спасибо за использование Ever" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "Мы готовим Ваш заказ!", + "DETAILS": "Вы можете получить это в течении %t минут.", + "NOT_PAID_NOTE": "Вам будет необходимо оплатить %s (наличными курьеру)." + }, + "DELIVERY_STATUS": { + "WE": "Мы", + "CARRIER": "Курьер", + "YOU": "Вы" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "Доставка прошла неправильно!", + "PROCESSING_WRONG": "Произошла ошибка!", + "TRY_AGAIN": "Пожалуйста, попробуйте еще раз.", + "CALL_FOR_DETAILS": "Призыв к деталям" + }, + "ELAPSED_TIME": { + "TITLE": "Пройденное время" + }, + "BUTTONS": { + "UNDO": "Отменить", + "PAY_NOW": "Оплатить Кредиткой", + "PAY_X": "Оплатить {{amount}}", + "PAY_WITH_FIXED_CARD": "Оплатить с", + "PAID": "Оплачено", + "END": "Наслаждайтесь покупкой!", + "CANCEL": "Отменить", + "GOT_IT": "Понял", + "IM_HERE": "Я здесь", + "SHOW_QR_CODE": "Показать QR-код", + "SHOW_PRODUCTS": "Показать продукты" + }, + "UNDO_POPUP": { + "TITLE": "Вы уверены?", + "DETAILS": [ + "Отмена приведёт в возврату уплаченной суммы, но Вы", + "не получите", + "заказ!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Продукты", + "LAST_PURCHASES": "Недавние Покупки", + "CALL_US": "Позвонить Клиенту", + "ORDER_HISTORY": "История заказов" + } + }, + "STORE": { + "STORE_TITLE": "Хранить", + "ITEMS": { + "CALL_WAITER": "Вызов официанта", + "ABOUT": "Около" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Настройки", + "ITEMS": { + "LANGUAGE": "Язык" + } + }, + "INFO": { + "DIVER_TITLE": "Информация", + "ITEMS": { + "FAQ": "Помощь", + "ABOUT_US": "О Нас", + "TERMS_OF_USE": "Правила Использования", + "PRIVACY": "Конфиденциальность" + }, + "CALL": { + "WE_APPOLOGISE": "Мы приносим свои извинения!", + "CALL_UNSUCCESSFULL": "Вызов был неудачным!" + } + }, + "LEGALS": { + "DIVER_TITLE": "Юридические дела", + "ITEMS": { + "TERMS_OF_USE": "Правила Использования", + "PRIVACY": "Конфиденциальность" + } + } + } + }, + "TIMER": { + "MINUTES": "Минут", + "SECONDS": "Секунд" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Не удалось подключиться к Ever", + "DESCRIPTION": [ + "Проверьте подключение к", + "Интернету и повторите попытку.", + "" + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Торговцы рядом с вами", + "NAME": "Имя продавца", + "WITH_NAME": "Торговцы с именем" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Сервер упал" + }, + "OR": "Или", + "OR_LOWERCASE": "или", + "YES": "Да", + "NO": "Нет", + "OK": "Ok", + "CITY": "Город", + "STREET": "Улица", + "HOUSE": "Дом", + "APARTMENT": "Квартира", + "BACK": "Назад", + "MORE": "Больше", + "TO": "До", + "STORE_INFO": "Информация о магазине", + "MAP": "Карта", + "ORDER_INFO": "Информация о заказе", + "CLOSE": "заканчиваться", + "SCAN": "Поиск", + "MERCHANTS": "Купечество", + "NOT_FOUND": "Нет результатов!", + "IN_STORE": "В магазине", + "EXIT_STORE": "Выход из магазина", + "FAILED": "Не удалось", + "CANCELED": "Отменен", + "IN_DELIVERY": "В доставке", + "PENDING": "В ожидании", + "COMPLETED": "Завершенный", + "BROWSE": "ПРОСМАТРИВАТЬ", + "ENGLISH": "Английский", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "BULGARIAN": "Болгарский", + "SPANISH": "испанский язык", + "FRENCH": "Французский", + "SELECT": "Выбрать", + "LOCATION_NOTES": "Примечания о местонахождении", + "ENTER_NOTES_HERE": "Введите примечания здесь" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/types.ts b/packages/shop-mobile-expo/src/store/features/translation/types.ts new file mode 100644 index 000000000..fb10d2018 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/types.ts @@ -0,0 +1,20 @@ +// LANGUAGES +import LANGUAGES from './lang'; + +// TYPES +export type supportedLangType = + | 'BULGARIAN' + | 'ENGLISH' + | 'FRENCH' + | 'HEBREW' + | 'RUSSIAN' + | 'SPANISH'; +export type supportedLangsType = { + readonly [name in supportedLangType]: supportedLangType; +}; +export type supportedLangsObjectType = { + readonly [name in supportedLangType]: typeof LANGUAGES; +}; +export type TranslationStateType = { + lang: supportedLangType; +}; diff --git a/packages/shop-mobile-expo/src/store/features/user/index.ts b/packages/shop-mobile-expo/src/store/features/user/index.ts new file mode 100644 index 000000000..3144a3f8e --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/user/index.ts @@ -0,0 +1,61 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import asyncStorage from '@react-native-async-storage/async-storage'; + +// TYPES +import type ENV from '../../../environments/model'; +import type { UserStateType } from './types'; +import type { RootState } from '../../index'; + +const INITIAL_STATE: UserStateType = { + data: null, + isLoggedIn: false, + productViewType: 'list', +}; + +export const navigationSlice = createSlice({ + name: 'user', + initialState: INITIAL_STATE, + reducers: { + setUser: (state, cation: PayloadAction) => { + state.data = cation.payload.data; + state.isLoggedIn = cation.payload.isLoggedIn; + state.productViewType = cation.payload.productViewType; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + setData: (state, cation: PayloadAction) => { + state.data = cation.payload; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + onUserSignUpByAddressSuccess: (state, cation: PayloadAction) => { + state.data = cation.payload; + state.isLoggedIn = true; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + setProductViewType: ( + state, + cation: PayloadAction, + ) => { + state.productViewType = cation.payload; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + }, +}); + +// ACTIONS +export const setUser = navigationSlice.actions.setUser; +export const setUserData = navigationSlice.actions.setData; +export const onUserSignUpByAddressSuccess = + navigationSlice.actions.onUserSignUpByAddressSuccess; +export const setProductViewType = navigationSlice.actions.setProductViewType; + +// SELECTORS +export const getUserObject = (state: RootState) => state.user; +export const getUserData = (state: RootState) => state.user.data; +export const getProductViewType = (state: RootState) => + state.user.productViewType; + +export default navigationSlice.reducer; diff --git a/packages/shop-mobile-expo/src/store/features/user/types.ts b/packages/shop-mobile-expo/src/store/features/user/types.ts new file mode 100644 index 000000000..5eef18278 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/user/types.ts @@ -0,0 +1,8 @@ +// TYPES/INTERFACES +import type ENV from '../../../environments/model'; + +export interface UserStateType { + data: any; + isLoggedIn: boolean; + productViewType: ENV['PRODUCTS_VIEW_TYPE']; +} diff --git a/packages/shop-mobile-expo/src/store/hooks.ts b/packages/shop-mobile-expo/src/store/hooks.ts new file mode 100644 index 000000000..81bfc1768 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/hooks.ts @@ -0,0 +1,5 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import type { RootState, AppDispatch } from './index'; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/packages/shop-mobile-expo/src/store/index.ts b/packages/shop-mobile-expo/src/store/index.ts new file mode 100644 index 000000000..e9c04ae8c --- /dev/null +++ b/packages/shop-mobile-expo/src/store/index.ts @@ -0,0 +1,27 @@ +import { configureStore } from '@reduxjs/toolkit'; +import logger from 'redux-logger'; + +// CONSTANTS +import ENV from '../environments/environment'; + +// REDUCERS +import navigationReducer from './features/navigation'; +import translationReducer from './features/translation'; +import userReducer from './features/user'; + +export const store = configureStore({ + reducer: { + user: userReducer, + navigation: navigationReducer, + translation: translationReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: false, + }).concat(logger), + devTools: __DEV__ || !ENV.PRODUCTION, +}); + +// TYPES +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/packages/shop-mobile-expo/src/types/index.ts b/packages/shop-mobile-expo/src/types/index.ts new file mode 100644 index 000000000..916f8dfdb --- /dev/null +++ b/packages/shop-mobile-expo/src/types/index.ts @@ -0,0 +1,16 @@ +// This file will contain some global types +// that will be used in the react app + +// TODO: add more comments +export type MaybeType = T | null; + +export interface ScalarsInterface { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; + Date: any; + Any: any; + Void: any; +} diff --git a/packages/shop-mobile-expo/tsconfig.json b/packages/shop-mobile-expo/tsconfig.json new file mode 100644 index 000000000..44ba97b98 --- /dev/null +++ b/packages/shop-mobile-expo/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "jsx": "react-native", + "lib": ["dom", "esnext"], + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noEmit": true, + "noEmitHelpers": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext", + "allowJs": true, + "baseUrl": ".", + "paths": { + "*": ["src/*"], + "@assets": ["src/assets/*"], + "@environment": ["./src/environments/environment.ts"] + }, + "removeComments": true, + "typeRoots": ["node_modules/@types", "./src/@types"] + }, + "include": ["src"], + "exclude": [ + "node_modules", + "babel.config.js", + "metro.config.js", + "jest.config.js" + ], + "extends": "expo/tsconfig.base" +}