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 (
+
+
+
+ );
+};
+
+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 (
+ <>
+