├── .circleci └── config.yml ├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── README.md ├── apps ├── mobile │ ├── .gitignore │ ├── .watchmanconfig │ ├── App.tsx │ ├── __generated__ │ │ └── AppEntry.js │ ├── app.json │ ├── babel.config.js │ ├── configuration │ │ ├── env.dev.json │ │ ├── env.inmemory.json │ │ └── env.production.json │ ├── metro.config.js │ ├── package.json │ ├── src │ │ ├── assets │ │ │ ├── fonts │ │ │ │ └── OpenSans-Regular.ttf │ │ │ └── images │ │ │ │ ├── icon.png │ │ │ │ └── splash.png │ │ ├── common │ │ │ └── ui │ │ │ │ └── reactNativeUtils.ts │ │ ├── configuration │ │ │ ├── dependencies.ts │ │ │ ├── env.config.ts │ │ │ └── index.ts │ │ ├── index.tsx │ │ └── navcontext │ │ │ ├── adapter │ │ │ └── real │ │ │ │ └── ReactNavigationInteractor.ts │ │ │ ├── configuration │ │ │ ├── AppNavigationSingleton.ts │ │ │ ├── navigation.configuration.ts │ │ │ ├── navigationContextDependencies.ts │ │ │ └── navigationContextFactory.ts │ │ │ └── ui │ │ │ └── AppNavigation.tsx │ └── tsconfig.json └── web │ ├── configuration │ ├── env.dev.json │ ├── env.inmemory.json │ └── env.production.json │ ├── gatsby-config.js │ ├── gatsby-node.js │ ├── package.json │ ├── src │ ├── configuration │ │ ├── dependencies.ts │ │ ├── env.config.ts │ │ └── index.ts │ ├── navcontext │ │ ├── adapter │ │ │ └── real │ │ │ │ └── GatsbyNavigationInteractor.ts │ │ └── configuration │ │ │ ├── navigation.configuration.tsx │ │ │ ├── navigationContextDependencies.ts │ │ │ └── navigationContextFactory.ts │ └── pages │ │ ├── 404.tsx │ │ ├── app.tsx │ │ └── index.tsx │ ├── static │ ├── .well-known │ │ ├── apple-app-site-association │ │ └── assetlinks.json │ ├── base.css │ └── sitemap.xml │ ├── tslint.json │ └── yarn.lock ├── package.json ├── packages ├── common-context │ ├── common │ │ ├── adapter │ │ │ └── inmemory │ │ │ │ └── promise.helper.ts │ │ ├── domain │ │ │ └── entities │ │ │ │ └── createId.helper.ts │ │ └── usescases │ │ │ ├── actions.helper.ts │ │ │ ├── dispatch.helper.ts │ │ │ └── sagas.helper.ts │ ├── configuration │ │ ├── env.ts │ │ └── redux │ │ │ ├── dependencies.ts │ │ │ ├── middleware │ │ │ ├── actionListener.ts │ │ │ ├── logger.ts │ │ │ └── sagaMiddleware.ts │ │ │ ├── reducers.ts │ │ │ ├── sagas.ts │ │ │ └── store.ts │ ├── messagingcontext │ │ └── ui │ │ │ ├── Chat.tsx │ │ │ └── ChatList.tsx │ ├── navcontext │ │ ├── adapter │ │ │ └── inmemory │ │ │ │ └── InMemoryNavigationInteractor.ts │ │ ├── configuration │ │ │ ├── navigationContextDependencies.ts │ │ │ └── navigationContextFactory.ts │ │ ├── domain │ │ │ └── gateways │ │ │ │ └── Navigation.interactor.ts │ │ └── usecases │ │ │ ├── navigation.actions.ts │ │ │ ├── navigation.reducers.ts │ │ │ └── navigation.sagas.ts │ ├── package.json │ └── tslint.json └── common-ui │ ├── configuration │ ├── Provider.tsx │ └── Theme.tsx │ ├── package.json │ ├── primitives │ ├── AppAccessibilityRole.ts │ ├── AppColor.tsx │ ├── AppErrorBoundary.tsx │ ├── AppLoader.tsx │ ├── AppText.tsx │ └── AppView.tsx │ └── tslint.json ├── patches └── @types+reach__router+1.2.4.patch ├── tsconfig.json ├── tslint.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | ## Set Environment Variables 2 | # EXPO_CLI_PASSWORD 3 | # EXPO_USERNAME 4 | # NETLIFY_AUTH_TOKEN 5 | # NETLIFY_SITE_ID 6 | 7 | version: 2 8 | 9 | publish: &publish 10 | docker: 11 | - image: circleci/node:10.16.0 12 | steps: 13 | - checkout 14 | 15 | - restore_cache: 16 | keys: 17 | - v1-dependencies-{{ .Branch }}-{{ checksum "yarn.lock" }} 18 | - v1-dependencies- 19 | 20 | - run: 21 | name: Installing dependencies 22 | command: yarn install 23 | 24 | - run: 25 | name: Setup env 26 | command: yarn env:$ENV 27 | 28 | - run: 29 | name: Check typescript 30 | command: yarn compile 31 | 32 | # - run: 33 | # name: Check tests 34 | # command: yarn test 35 | 36 | - run: 37 | name: Install expo-cli 38 | command: sudo yarn global add expo-cli 39 | 40 | - run: 41 | name: Build 42 | command: yarn build 43 | 44 | - run: 45 | name: Login into Expo 46 | command: npx expo login --non-interactive -u $EXPO_USERNAME 47 | 48 | - run: 49 | name: Publish to Expo 50 | command: | 51 | expo_release_channel="" 52 | yarn env:$ENV 53 | case "$ENV" in 54 | "prod") expo_release_channel='default' 55 | ;; 56 | "dev") expo_release_channel=`echo $CIRCLE_BRANCH | sed 's/\//_/g'` 57 | ;; 58 | esac 59 | cd ./apps/mobile 60 | npx expo optimize 61 | npx expo publish --quiet --release-channel $expo_release_channel 62 | cd ../.. 63 | - run: 64 | name: Install netlify-cli 65 | command: sudo yarn global add netlify-cli 66 | 67 | - run: 68 | name: Deploy to Netlify 69 | command: npx netlify deploy --dir=./apps/web/public -p 70 | 71 | - save_cache: 72 | paths: 73 | - node_modules 74 | - ~/.cache/yarn 75 | key: v1-dependencies-{{ .Branch }}-{{ checksum "yarn.lock" }} 76 | 77 | checkdeps: &checkdeps 78 | docker: 79 | - image: circleci/node:10.16.0 80 | steps: 81 | - checkout 82 | - run: 83 | name: Install npm-check-updates 84 | command: sudo yarn global add npm-check-updates 85 | - run: 86 | name: Check deps 87 | command: ncu -e 2 --dep prod 88 | 89 | jobs: 90 | publish_dev: 91 | environment: 92 | ENV: dev 93 | <<: *publish 94 | 95 | publish_prod: 96 | environment: 97 | ENV: prod 98 | <<: *publish 99 | 100 | publish_gitflow: 101 | environment: 102 | ENV: dev 103 | <<: *publish 104 | 105 | check_dependencies: 106 | environment: 107 | ENV: dev 108 | <<: *checkdeps 109 | 110 | workflows: 111 | version: 2 112 | build_test_publish_wkfs: 113 | jobs: 114 | - publish_dev: 115 | filters: 116 | branches: 117 | only: develop 118 | - publish_prod: 119 | filters: 120 | branches: 121 | only: master 122 | - publish_gitflow: 123 | filters: 124 | branches: 125 | only: /^feature\/.*$|^release\/.*$|^hotfix\/.*$/ 126 | # nightly: 127 | # triggers: 128 | # - schedule: 129 | # cron: '0 0 * * *' 130 | # filters: 131 | # branches: 132 | # only: 133 | # - dev 134 | # jobs: 135 | # - check_dependencies 136 | 137 | # notify: 138 | # webhooks: 139 | # - url: https://xc-ci-webhook.now.sh/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: xcarpentier 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .history 3 | .jest 4 | .vscode 5 | .cache/ 6 | .expo/ 7 | .expo-shared/ 8 | public/ 9 | node_modules/ 10 | npm-debug.log 11 | yarn-error.log 12 | apps/mobile/configuration/env.json 13 | packages/common-*/lib/ 14 | env.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "typescript", 3 | "semi": false, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RN & Expo boilerplate 2 | 3 | # Stack 4 | 5 | - TypeScript 6 | - redux-saga 7 | - reselect 8 | - tslint 9 | - prettier 10 | - ramda 11 | - Storybook (ie. `yarn start:story`) 12 | - expo 13 | 14 | # Install 15 | 16 | 1. `yarn global add expo-cli` (ie. install expo tooling globally) 17 | 1. `yarn` (ie. install dependencies, do it from project root) 18 | 1. `yarn start:inmemory` or `yarn start:dev` or `yarn start:story` 19 | 20 | ## vscode 21 | 22 | - [prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 23 | 24 | # Git 25 | 26 | ## Branches 27 | 28 | ### fixed 29 | 30 | - dev (default) 31 | - master (prod) 32 | 33 | ### not fixed 34 | 35 | - feature/`feature name` 36 | - hotfix/`fix name` 37 | - release/`release name or version` 38 | 39 | ### Best practice 40 | 41 | - use rebase to update feature branch from dev: 42 | `git pull origin dev --rebase` 43 | 44 | # Clean Architecture 45 | 46 | - `__tests__` 47 | - `` 48 | - `unit` on actions and saga 49 | - `configuration` mainly snapshot 50 | - `e2e` detox tests 51 | - `components` snapshot and interactions with enzime 52 | - `src/common` 53 | - `ui/` UI kit 54 | - `src/` 55 | - `ui/` ui, used for navigation and its children component, redux connect, etc. 56 | - `index.js` just export 57 | - `.Smart.ts` redux maps and connect 58 | - `.Dumb.ts` dumb component 59 | - `components/` components used only for this context 60 | - `adapter/` Back-end api and mock (immemory) 61 | - `inmemory/` 62 | - `InMemory.loader.ts` or `InMemory.interactor.ts` 63 | - `real/` 64 | - `dto/` 65 | - `mapper/` 66 | - `.loader.ts` or `.interactor.ts` 67 | - `domain/` 68 | - `entities/` 69 | - `User.ts` entities used in this context, http://facebook.github.io/immutable-js/docs/#/Record.Factory 70 | - `gateways/` 71 | - Interface for `.loader.ts` or `.interactor.ts` 72 | - `usecases` 73 | - `/` store and actions definitions 74 | - `.actions.ts` 75 | - `.reducers.ts` 76 | - `.sagas.ts` 77 | - `.selectors.ts` 78 | 79 | # TODO 80 | 81 | ## 1. Initialize Git Flow 82 | > `git flow init` 83 | 84 | ## 2. Create A Netlify Website 85 | > [Netlify](https://app.netlify.com) 86 | 87 | ## 3. Set Environment Variables On CircleCi 88 | 89 | > [CircleCI](https://circleci.com/dashboard) 90 | - EXPO_CLI_PASSWORD 91 | - EXPO_USERNAME 92 | - NETLIFY_AUTH_TOKEN 93 | - NETLIFY_SITE_ID 94 | 95 | -------------------------------------------------------------------------------- /apps/mobile/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | -------------------------------------------------------------------------------- /apps/mobile/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /apps/mobile/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Logs } from 'expo' 3 | import { useScreens } from 'react-native-screens' 4 | 5 | useScreens() 6 | 7 | if (__DEV__) { 8 | const isRemoteDebuggingEnabled = typeof atob !== 'undefined' 9 | if (isRemoteDebuggingEnabled) { 10 | Logs.disableExpoCliLogging() 11 | } else { 12 | Logs.enableExpoCliLogging() 13 | } 14 | } 15 | 16 | console.disableYellowBox = true 17 | 18 | export default () => { 19 | const IndexComp = require('./src/index').default 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /apps/mobile/__generated__/AppEntry.js: -------------------------------------------------------------------------------- 1 | // @generated by expo-yarn-workspaces 2 | 3 | import { registerRootComponent } from 'expo'; 4 | import { activateKeepAwake } from 'expo-keep-awake'; 5 | 6 | import App from '../App'; 7 | 8 | if (__DEV__) { 9 | activateKeepAwake(); 10 | } 11 | 12 | registerRootComponent(App); 13 | -------------------------------------------------------------------------------- /apps/mobile/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "clean-archi-boilerplate", 4 | "slug": "clean-archi-boilerplate", 5 | "privacy": "public", 6 | "sdkVersion": "34.0.0", 7 | "platforms": [ 8 | "ios", 9 | "android", 10 | "web" 11 | ], 12 | "version": "1.0.0", 13 | "orientation": "portrait", 14 | "icon": "./src/assets/images/icon.png", 15 | "splash": { 16 | "image": "./src/assets/images/splash.png", 17 | "resizeMode": "contain", 18 | "backgroundColor": "#ffffff" 19 | }, 20 | "updates": { 21 | "checkAutomatically": "ON_LOAD", 22 | "fallbackToCacheTimeout": 10000 23 | }, 24 | "scheme": "xcapetir-demo", 25 | "ios": { 26 | "bundleIdentifier": "com.xaviercarpentier.demo", 27 | "supportsTablet": true, 28 | "buildNumber": "1", 29 | "config": { 30 | "usesNonExemptEncryption": false 31 | }, 32 | "icon": "./src/assets/images/icon.png", 33 | "infoPlist": { 34 | "NSCameraUsageDescription": "This app uses the camera to update your avatar picture or to send images through the chat", 35 | "NSPhotoLibraryUsageDescription": "This app uses photos to update your avatar picture or to send images through the chat" 36 | }, 37 | "associatedDomains": [ 38 | "applinks:demo.xaviercarpentier.com" 39 | ] 40 | }, 41 | "android": { 42 | "package": "com.xaviercarpentier.demo", 43 | "versionCode": 1, 44 | "intentFilters": [ 45 | { 46 | "action": "VIEW", 47 | "autoVerify": true, 48 | "data": [ 49 | { 50 | "scheme": "https", 51 | "host": "demo.xaviercarpentier.com" 52 | } 53 | ], 54 | "category": ["BROWSABLE", "DEFAULT"] 55 | } 56 | ] 57 | }, 58 | "androidShowExponentNotificationInShellApp": false 59 | }, 60 | "assetBundlePatterns": [ 61 | "src/assets/images/**", 62 | "src/assets/fonts/*.ttf", 63 | "node_modules/@expo/vector-icons/fonts/*.ttf", 64 | "node_modules/react-native-dropdownalert/assets/*.png" 65 | ], 66 | "packagerOpts": { 67 | "config": "metro.config.js" 68 | } 69 | } -------------------------------------------------------------------------------- /apps/mobile/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true) 3 | return { 4 | presets: ['babel-preset-expo'], 5 | env: { 6 | production: { 7 | plugins: ['react-native-paper/babel'], 8 | }, 9 | }, 10 | plugins: [ 11 | [ 12 | 'module-resolver', 13 | { 14 | root: ['./src'], 15 | extensions: [ 16 | '.js', 17 | '.jsx', 18 | '.ts', 19 | '.tsx', 20 | '.android.js', 21 | '.android.tsx', 22 | '.ios.js', 23 | '.ios.tsx', 24 | ], 25 | }, 26 | ], 27 | ], 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/mobile/configuration/env.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": "dev" 3 | } 4 | -------------------------------------------------------------------------------- /apps/mobile/configuration/env.inmemory.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": "inmemory" 3 | } -------------------------------------------------------------------------------- /apps/mobile/configuration/env.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": "production" 3 | } -------------------------------------------------------------------------------- /apps/mobile/metro.config.js: -------------------------------------------------------------------------------- 1 | const { createMetroConfiguration } = require('expo-yarn-workspaces') 2 | 3 | module.exports = createMetroConfiguration(__dirname) 4 | -------------------------------------------------------------------------------- /apps/mobile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apps/mobile", 3 | "version": "1.0.0", 4 | "main": "__generated__/AppEntry.js", 5 | "scripts": { 6 | "postinstall": "expo-yarn-workspaces postinstall", 7 | "env:inmemory": "cp -f ./configuration/env.inmemory.json ./env.json", 8 | "env:dev": "cp -f ./configuration/env.dev.json ./env.json", 9 | "env:prod": "cp -f ./configuration/env.prod.json ./env.json", 10 | "env:storybook:enable": "json -I -f env.json -e 'this.storybook=true'", 11 | "env:storybook:disable": "json -I -f env.json -e 'this.storybook=false'", 12 | "env:devtools:enable": "json -I -f env.json -e 'this.devtools=true'", 13 | "env:devtools:disable": "json -I -f env.json -e 'this.devtools=false'", 14 | "start:inmemory": "yarn env:inmemory && yarn start", 15 | "start:dev": "yarn env:dev && yarn start", 16 | "start:prod": "yarn env:prod && yarn start", 17 | "start": "expo start", 18 | "start:clean": "expo start -c", 19 | "compile": "tsc --noEmit", 20 | "compile:watch": "tsc --noEmit --watch", 21 | "cleaning": "watchman watch-del-all && rm -f yarn.lock && rm -rf node_modules && rm -rf $TMPDIR/react-* && yarn cache clean", 22 | "storybook": "yarn storybook:update && yarn env:devtools:enable && yarn env:storybook:enable && yarn start", 23 | "storybook:update": "rnstl --searchDir ./storybook/stories --pattern '**/*.stories.tsx'" 24 | }, 25 | "dependencies": { 26 | "@pack/common-context": "1.0.0", 27 | "@pack/common-ui": "1.0.0", 28 | "expo": "^34.0.1", 29 | "react": "16.8.3", 30 | "react-dom": "16.8.3", 31 | "react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz", 32 | "react-native-gesture-handler": "~1.3.0", 33 | "react-native-reanimated": "~1.1.0", 34 | "react-native-screens": "1.0.0-alpha.22", 35 | "react-native-web": "^0.11.4", 36 | "react-navigation": "4.0.3", 37 | "react-navigation-stack": "1.5.5" 38 | }, 39 | "devDependencies": { 40 | "@storybook/addon-actions": "^5.1.11", 41 | "@storybook/addon-info": "^5.1.11", 42 | "@storybook/addon-knobs": "^5.1.11", 43 | "@storybook/addon-links": "^5.1.11", 44 | "@storybook/addon-ondevice-backgrounds": "^5.1.11", 45 | "@storybook/addon-ondevice-knobs": "^5.1.11", 46 | "@storybook/addon-ondevice-notes": "^5.1.11", 47 | "@storybook/addons": "^5.1.11", 48 | "@storybook/react-native": "^5.1.11", 49 | "@storybook/theming": "^5.1.11", 50 | "@types/react": "^16.8.23", 51 | "@types/react-native": "^0.57.65", 52 | "babel-plugin-module-resolver": "3.2.0", 53 | "babel-plugin-redux-saga": "1.0.2", 54 | "babel-preset-expo": "^6.0.0", 55 | "expo-yarn-workspaces": "1.2.0", 56 | "react-native-storybook-loader": "1.8.0", 57 | "typescript": "^3.4.5" 58 | }, 59 | "private": true 60 | } 61 | -------------------------------------------------------------------------------- /apps/mobile/src/assets/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcarpentier/clean-archi-boilerplate/12763a100d04d23ed6d6a2aed7a8fefcc0a359a9/apps/mobile/src/assets/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /apps/mobile/src/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcarpentier/clean-archi-boilerplate/12763a100d04d23ed6d6a2aed7a8fefcc0a359a9/apps/mobile/src/assets/images/icon.png -------------------------------------------------------------------------------- /apps/mobile/src/assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcarpentier/clean-archi-boilerplate/12763a100d04d23ed6d6a2aed7a8fefcc0a359a9/apps/mobile/src/assets/images/splash.png -------------------------------------------------------------------------------- /apps/mobile/src/common/ui/reactNativeUtils.ts: -------------------------------------------------------------------------------- 1 | import { InteractionManager } from 'react-native' 2 | 3 | export const waitEndOfInteractions = (timeout: number = 5000) => { 4 | const waitPromise = new Promise(resolve => { 5 | InteractionManager.runAfterInteractions(resolve) 6 | }) 7 | 8 | const hasTimeout = timeout !== null && typeof timeout !== 'undefined' 9 | const timeoutPromise = new Promise(resolve => { 10 | if (hasTimeout) { 11 | setTimeout(resolve, timeout) 12 | } 13 | }) 14 | 15 | return Promise.race([waitPromise, timeoutPromise]) 16 | } 17 | -------------------------------------------------------------------------------- /apps/mobile/src/configuration/dependencies.ts: -------------------------------------------------------------------------------- 1 | import { navigationContextDependencies } from '../navcontext/configuration/navigationContextDependencies' 2 | 3 | export const dependencies = { ...navigationContextDependencies } 4 | -------------------------------------------------------------------------------- /apps/mobile/src/configuration/env.config.ts: -------------------------------------------------------------------------------- 1 | import { setEnv } from '@pack/common-context/configuration/env' 2 | import env from '../../env.json' 3 | setEnv(env) 4 | -------------------------------------------------------------------------------- /apps/mobile/src/configuration/index.ts: -------------------------------------------------------------------------------- 1 | import { env } from '../../env.json' 2 | export const ENV = env 3 | -------------------------------------------------------------------------------- /apps/mobile/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import './configuration/env.config' 3 | import { Updates, AppLoading } from 'expo' 4 | import { AppErrorBoundary } from '@pack/common-ui/primitives/AppErrorBoundary' 5 | import Provider from '@pack/common-ui/configuration/Provider' 6 | import theme from '@pack/common-ui/configuration/Theme' 7 | import { dependencies } from './configuration/dependencies' 8 | import createAppStore from '@pack/common-context/configuration/redux/store' 9 | import AppNavigation from './navcontext/ui/AppNavigation' 10 | import * as Font from 'expo-font' 11 | 12 | const store = createAppStore(dependencies) 13 | 14 | const bootstrapAsync = async () => { 15 | await Font.loadAsync({ 16 | 'Open Sans': require('./assets/fonts/OpenSans-Regular.ttf'), 17 | }) 18 | } 19 | 20 | export default function App() { 21 | const [isReady, setIsReady] = useState(false) 22 | 23 | useEffect(() => { 24 | bootstrapAsync().then(() => setIsReady(true)) 25 | }, []) 26 | 27 | return ( 28 | 29 | 30 | {isReady ? : } 31 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /apps/mobile/src/navcontext/adapter/real/ReactNavigationInteractor.ts: -------------------------------------------------------------------------------- 1 | import { getAppNavigation } from '../../configuration/AppNavigationSingleton' 2 | import { 3 | NavigationDispatch, 4 | NavigationActions, 5 | StackActions, 6 | } from 'react-navigation' 7 | import { InteractionManager } from 'react-native' 8 | import { waitEndOfInteractions } from '../../../common/ui/reactNativeUtils' 9 | import { NavigationInteractor } from '@pack/common-context/navcontext/domain/gateways/Navigation.interactor' 10 | 11 | const dispatchNavigation: NavigationDispatch = action => { 12 | const appNavigation = getAppNavigation() 13 | return appNavigation.dispatch(action) 14 | } 15 | 16 | export class ReactNavigationInteractor implements NavigationInteractor { 17 | popToTop(): void { 18 | InteractionManager.runAfterInteractions(() => { 19 | dispatchNavigation(StackActions.popToTop({})) 20 | }) 21 | } 22 | navigateTo(routeName: string, params?: any): void { 23 | InteractionManager.runAfterInteractions(() => { 24 | dispatchNavigation( 25 | NavigationActions.navigate({ 26 | routeName: routeName as string, 27 | params, 28 | }), 29 | ) 30 | }) 31 | } 32 | async navigateToAsync(routeName: string, params?: any): Promise { 33 | await waitEndOfInteractions() 34 | dispatchNavigation( 35 | NavigationActions.navigate({ 36 | routeName: routeName as string, 37 | params, 38 | }), 39 | ) 40 | await waitEndOfInteractions() 41 | } 42 | navigateBack(): void { 43 | InteractionManager.runAfterInteractions(() => { 44 | dispatchNavigation(NavigationActions.back()) 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /apps/mobile/src/navcontext/configuration/AppNavigationSingleton.ts: -------------------------------------------------------------------------------- 1 | import { NavigationContainerComponent } from 'react-navigation' 2 | 3 | let appNavigation: NavigationContainerComponent 4 | 5 | export const setAppNavigation = (ref: NavigationContainerComponent) => { 6 | if (ref) { 7 | appNavigation = ref 8 | } 9 | } 10 | 11 | export const getAppNavigation = (): NavigationContainerComponent => 12 | appNavigation 13 | -------------------------------------------------------------------------------- /apps/mobile/src/navcontext/configuration/navigation.configuration.ts: -------------------------------------------------------------------------------- 1 | import { createAppContainer } from 'react-navigation' 2 | import { createStackNavigator } from 'react-navigation-stack' 3 | import ChatList from '@pack/common-context/messagingcontext/ui/ChatList' 4 | import Chat from '@pack/common-context/messagingcontext/ui/Chat' 5 | 6 | // Root views 7 | export const AppNavigatorBase = createAppContainer( 8 | createStackNavigator( 9 | { 10 | Chat, 11 | ChatList, 12 | }, 13 | { 14 | initialRouteName: 'Chat', 15 | headerMode: 'none', 16 | }, 17 | ), 18 | ) 19 | -------------------------------------------------------------------------------- /apps/mobile/src/navcontext/configuration/navigationContextDependencies.ts: -------------------------------------------------------------------------------- 1 | import { NavigationContextFactory } from './navigationContextFactory' 2 | 3 | export const navigationContextDependencies = { 4 | navigationInteractor: NavigationContextFactory.navigationInteractor(), 5 | } 6 | -------------------------------------------------------------------------------- /apps/mobile/src/navcontext/configuration/navigationContextFactory.ts: -------------------------------------------------------------------------------- 1 | import { ENV } from '../../configuration' 2 | import { ReactNavigationInteractor } from '../adapter/real/ReactNavigationInteractor' 3 | import { NavigationInteractor } from '@pack/common-context/navcontext/domain/gateways/Navigation.interactor' 4 | import { InMemoryNavigationInteractor } from '@pack/common-context/navcontext/adapter/inmemory/InMemoryNavigationInteractor' 5 | 6 | export class NavigationContextFactory { 7 | static navigationInteractor(): NavigationInteractor { 8 | switch (ENV) { 9 | case 'production': 10 | return new ReactNavigationInteractor() 11 | case 'dev': 12 | return new ReactNavigationInteractor() 13 | 14 | default: 15 | return new InMemoryNavigationInteractor() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/mobile/src/navcontext/ui/AppNavigation.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { setAppNavigation } from '../configuration/AppNavigationSingleton' 3 | import { AppNavigatorBase } from '../configuration/navigation.configuration' 4 | 5 | export default function AppNavigation() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /apps/mobile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "noImplicitAny": true, 5 | "rootDir": "src", 6 | "baseUrl": "src", 7 | "jsx": "react-native", 8 | "lib": ["es2017", "es6", "dom"], 9 | "isolatedModules": true, 10 | "resolveJsonModule": true, 11 | "checkJs": true, 12 | "strictNullChecks": true, 13 | "noImplicitThis": true, 14 | "alwaysStrict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "allowSyntheticDefaultImports": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "paths": { 22 | "expo": ["../node_modules/@types/expo", "../node_modules/expo"], 23 | "*": ["*", "*.ios", "*.android"] 24 | } 25 | }, 26 | "include": ["App.tsx", "./src/**/*"], 27 | "exclude": [] 28 | } -------------------------------------------------------------------------------- /apps/web/configuration/env.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": "dev" 3 | } 4 | -------------------------------------------------------------------------------- /apps/web/configuration/env.inmemory.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": "inmemory" 3 | } -------------------------------------------------------------------------------- /apps/web/configuration/env.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": "production" 3 | } -------------------------------------------------------------------------------- /apps/web/gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: `clean-archi-boilerplate`, 4 | description: `clean-archi-boilerplate`, 5 | author: `@xcapetir`, 6 | }, 7 | plugins: [ 8 | { 9 | resolve: `gatsby-plugin-create-client-paths`, 10 | options: { prefixes: [`/app/*`] }, 11 | }, 12 | { 13 | resolve: `gatsby-source-filesystem`, 14 | options: { 15 | name: `images`, 16 | path: `${__dirname}/src/assets/images`, 17 | }, 18 | }, 19 | { 20 | resolve: 'gatsby-plugin-web-font-loader', 21 | options: { 22 | google: { 23 | families: ['Open Sans:400,600'], 24 | }, 25 | }, 26 | }, 27 | // { 28 | // resolve: `gatsby-plugin-google-analytics`, 29 | // options: { 30 | // trackingId: 'TBD', 31 | // }, 32 | // }, 33 | `gatsby-plugin-typescript`, 34 | `gatsby-plugin-offline`, 35 | `gatsby-plugin-sharp`, 36 | `gatsby-transformer-sharp`, 37 | `gatsby-plugin-react-helmet`, 38 | `gatsby-plugin-react-native-web`, 39 | ], 40 | } 41 | -------------------------------------------------------------------------------- /apps/web/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | 3 | const __DEV__ = process.env.NODE_ENV === 'development' 4 | 5 | exports.onCreateWebpackConfig = ({ actions, _loaders, getConfig }) => { 6 | const config = getConfig() 7 | config.module.rules.push({ 8 | test: /\.(js|tsx?)$/, 9 | include: /node_modules/, 10 | exclude: /node_modules[/\\](?!react-native-paper|react-native-vector-icons|react-native-safe-area-view|react-native-gifted-chat|react-native-lightbox|react-native-parsed-text|expo-av)/, 11 | use: { 12 | loader: 'babel-loader', 13 | options: { 14 | // Disable reading babel configuration 15 | babelrc: false, 16 | configFile: false, 17 | 18 | // The configuration for compilation 19 | env: { 20 | production: { 21 | plugins: ['react-native-paper/babel'], 22 | }, 23 | }, 24 | presets: [ 25 | ['@babel/preset-env', { useBuiltIns: 'usage' }], 26 | '@babel/preset-react', 27 | '@babel/preset-flow', 28 | ], 29 | plugins: [ 30 | '@babel/plugin-proposal-class-properties', 31 | '@babel/plugin-proposal-object-rest-spread', 32 | [ 33 | 'module-resolver', 34 | { 35 | root: ['./src'], 36 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 37 | }, 38 | ], 39 | ], 40 | }, 41 | }, 42 | }) 43 | config.plugins = [ 44 | ...config.plugins, 45 | new webpack.DefinePlugin({ 46 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 47 | __DEV__, 48 | }), 49 | ] 50 | actions.replaceWebpackConfig(config) 51 | } 52 | -------------------------------------------------------------------------------- /apps/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apps/web", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "vitrine: professional website", 6 | "scripts": { 7 | "env:inmemory": "cp -f ./configuration/env.inmemory.json ./env.json", 8 | "env:dev": "cp -f ./configuration/env.dev.json ./env.json", 9 | "env:prod": "cp -f ./configuration/env.prod.json ./env.json", 10 | "cleaning": "rm -rf ./dist/ && rm -rf ./public/ && rm -rf .cache/ rm -rf node_modules", 11 | "compile": "tsc --noEmit", 12 | "build": "gatsby build", 13 | "start": "gatsby develop", 14 | "start:inmemory": "yarn env:inmemory && yarn start", 15 | "start:dev": "yarn env:dev && yarn start", 16 | "start:prod": "yarn env:prod && yarn start", 17 | "serve": "gatsby serve", 18 | "test": "echo 'TODO: test'" 19 | }, 20 | "dependencies": { 21 | "@pack/common-context": "1.0.0", 22 | "@pack/common-ui": "1.0.0", 23 | "gatsby": "2.15.11", 24 | "gatsby-image": "2.2.17", 25 | "gatsby-plugin-create-client-paths": "2.1.7", 26 | "gatsby-plugin-google-analytics": "2.1.15", 27 | "gatsby-plugin-manifest": "2.2.15", 28 | "gatsby-plugin-offline": "3.0.4", 29 | "gatsby-plugin-react-helmet": "3.1.6", 30 | "gatsby-plugin-react-native-web": "2.0.0-beta.0", 31 | "gatsby-plugin-sharp": "2.2.20", 32 | "gatsby-plugin-typescript": "2.1.6", 33 | "gatsby-plugin-web-font-loader": "1.0.4", 34 | "gatsby-source-filesystem": "2.1.21", 35 | "gatsby-transformer-sharp": "2.2.13", 36 | "react": "16.8.3", 37 | "react-dom": "16.8.3", 38 | "react-helmet": "5.2.1", 39 | "react-native-vector-icons": "6.6.0", 40 | "react-native-web": "0.11.5" 41 | }, 42 | "devDependencies": { 43 | "@babel/preset-flow": "^7.0.0", 44 | "@types/node": "^10.12.11", 45 | "@types/react": "16.9.2", 46 | "@types/react-dom": "16.9.0", 47 | "@types/react-helmet": "5.0.9", 48 | "@types/react-native": "0.60.13", 49 | "@types/recompose": "0.30.7", 50 | "babel-preset-react-native": "4.0.1", 51 | "babel-plugin-module-resolver": "3.2.0", 52 | "babel-plugin-react-native-web": "0.11.5", 53 | "@babel/plugin-proposal-object-rest-spread": "7.5.5", 54 | "@babel/plugin-proposal-class-properties": "7.5.5", 55 | "prettier": "1.18.2", 56 | "tslint": "5.19.0", 57 | "tslint-config-prettier": "1.18.0", 58 | "tslint-react": "4.0.0", 59 | "typescript": "^3.4.5" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /apps/web/src/configuration/dependencies.ts: -------------------------------------------------------------------------------- 1 | import { navigationContextDependencies } from '../navcontext/configuration/navigationContextDependencies' 2 | 3 | export const dependencies = { ...navigationContextDependencies } 4 | -------------------------------------------------------------------------------- /apps/web/src/configuration/env.config.ts: -------------------------------------------------------------------------------- 1 | import { setEnv } from '@pack/common-context/configuration/env' 2 | import env from '../../env.json' 3 | setEnv(env) 4 | -------------------------------------------------------------------------------- /apps/web/src/configuration/index.ts: -------------------------------------------------------------------------------- 1 | import { env } from '../../env.json' 2 | export const ENV = env 3 | -------------------------------------------------------------------------------- /apps/web/src/navcontext/adapter/real/GatsbyNavigationInteractor.ts: -------------------------------------------------------------------------------- 1 | import { navigate } from 'gatsby' 2 | import { NavigationInteractor } from '@pack/common-context/navcontext/domain/gateways/Navigation.interactor' 3 | 4 | export class GatsbyNavigationInteractor implements NavigationInteractor { 5 | navigateTo(routeName: any, params?: any): void { 6 | navigate(routeName, { state: params }) 7 | } 8 | navigateToAsync(_routeName: string, _params?: any): Promise { 9 | throw new Error('Method not implemented.') 10 | } 11 | navigateBack(): void { 12 | navigate(-1) 13 | } 14 | popToTop(): void { 15 | throw new Error('Method not implemented.') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/src/navcontext/configuration/navigation.configuration.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Router } from '@reach/router' 3 | import ChatList from '@pack/common-context/messagingcontext/ui/ChatList' 4 | import Chat from '@pack/common-context/messagingcontext/ui/Chat' 5 | export default function AppNavigationBase() { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/navcontext/configuration/navigationContextDependencies.ts: -------------------------------------------------------------------------------- 1 | import { NavigationContextFactory } from './navigationContextFactory' 2 | 3 | export const navigationContextDependencies = { 4 | navigationInteractor: NavigationContextFactory.navigationInteractor(), 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/src/navcontext/configuration/navigationContextFactory.ts: -------------------------------------------------------------------------------- 1 | import { ENV } from '../../configuration' 2 | import { GatsbyNavigationInteractor } from '../adapter/real/GatsbyNavigationInteractor' 3 | import { NavigationInteractor } from '@pack/common-context/navcontext/domain/gateways/Navigation.interactor' 4 | import { InMemoryNavigationInteractor } from '@pack/common-context/navcontext/adapter/inmemory/InMemoryNavigationInteractor' 5 | 6 | export class NavigationContextFactory { 7 | static navigationInteractor(): NavigationInteractor { 8 | switch (ENV) { 9 | case 'production': 10 | return new GatsbyNavigationInteractor() 11 | case 'dev': 12 | return new GatsbyNavigationInteractor() 13 | 14 | default: 15 | return new InMemoryNavigationInteractor() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/web/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { View, Text } from 'react-native' 3 | import '../../static/base.css' 4 | 5 | export default function notFoundPage() { 6 | return ( 7 | 8 | 404 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/pages/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import '../configuration/env.config' 3 | import '../../static/base.css' 4 | import Provider from '@pack/common-ui/configuration/Provider' 5 | import createAppStore from '@pack/common-context/configuration/redux/store' 6 | import theme from '@pack/common-ui/configuration/Theme' 7 | import { dependencies } from '../configuration/dependencies' 8 | import { AppErrorBoundary } from '@pack/common-ui/primitives/AppErrorBoundary' 9 | import AppNavigationBase from '../navcontext/configuration/navigation.configuration' 10 | import { Centered } from '@pack/common-ui/primitives/AppView' 11 | import { Helmet } from 'react-helmet' 12 | import AppColor from '@pack/common-ui/primitives/AppColor' 13 | 14 | const store = createAppStore(dependencies) 15 | 16 | export default function App() { 17 | return ( 18 | console.log('todo reload')}> 19 | 20 | 21 | 22 | Clean Archi Boilerplate 23 | 24 | 25 | 29 | 33 | 34 | 40 | 41 | 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /apps/web/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { View, Text } from 'react-native' 3 | import '../../static/base.css' 4 | 5 | export default function IndexPage() { 6 | return ( 7 | 8 | Hello World! 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/static/.well-known/apple-app-site-association: -------------------------------------------------------------------------------- 1 | { 2 | "applinks": { 3 | "apps": [], 4 | "details": [{ 5 | "appID": "???.com.xaviercarpentier.demo", 6 | "paths": ["/*"] 7 | }] 8 | } 9 | } -------------------------------------------------------------------------------- /apps/web/static/.well-known/assetlinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "relation": ["delegate_permission/common.handle_all_urls"], 4 | "target": { 5 | "namespace": "android_app", 6 | "package_name": "com.xaviercarpentier.demo", 7 | "sha256_cert_fingerprints": [ 8 | "???" 9 | ] 10 | } 11 | } 12 | ] -------------------------------------------------------------------------------- /apps/web/static/base.css: -------------------------------------------------------------------------------- 1 | html > body { 2 | display: none; 3 | visibility: hidden; 4 | } 5 | html.wf-loading > body { 6 | visibility: hidden; 7 | display: none; 8 | } 9 | html.wf-inactive > body { 10 | visibility: hidden; 11 | display: none; 12 | } 13 | html.wf-active > body { 14 | visibility: visible; 15 | display: block; 16 | font-size: 21px; 17 | text-align: center; 18 | animation: fadein 3s; 19 | -moz-animation: fadein 3s; /* Firefox */ 20 | -webkit-animation: fadein 3s; /* Safari and Chrome */ 21 | -o-animation: fadein 3s; /* Opera */ 22 | } 23 | @keyframes fadein { 24 | from { 25 | opacity:0; 26 | } 27 | to { 28 | opacity:1; 29 | } 30 | } 31 | @-moz-keyframes fadein { /* Firefox */ 32 | from { 33 | opacity:0; 34 | } 35 | to { 36 | opacity:1; 37 | } 38 | } 39 | @-webkit-keyframes fadein { /* Safari and Chrome */ 40 | from { 41 | opacity:0; 42 | } 43 | to { 44 | opacity:1; 45 | } 46 | } 47 | @-o-keyframes fadein { /* Opera */ 48 | from { 49 | opacity:0; 50 | } 51 | to { 52 | opacity: 1; 53 | } 54 | } 55 | 56 | #visibleLayoutXS, .visibleLayoutXS { 57 | display: none; 58 | } 59 | 60 | @media (max-width: 376px) { 61 | #hiddenLayoutXS, .hiddenLayoutXS { 62 | display: none; 63 | width: 0px; 64 | } 65 | #visibleLayoutXS, .visibleLayoutXS { 66 | display: flex; 67 | width: 100%; 68 | } 69 | h1 { 70 | font-size: 28px !important; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /apps/web/static/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | TBD 9 | monthly 10 | 11 | 12 | TBD 13 | monthly 14 | 15 | -------------------------------------------------------------------------------- /apps/web/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tslint.json"] 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clean-archi-boilerplate", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "RN & Expo boilerplate", 6 | "main": "index.js", 7 | "author": "Xavier Carpentier (https://xaviercarpentier.com)", 8 | "license": "MIT", 9 | "workspaces": { 10 | "packages": [ 11 | "packages/*", 12 | "apps/*" 13 | ] 14 | }, 15 | "scripts": { 16 | "postinstall": "expo-yarn-workspaces check-workspace-dependencies", 17 | "cleaning": "yarn workspaces run cleaning && rm -f yarn.lock && yarn cache clean && rm -rf node_modules", 18 | "build": "yarn workspaces run build", 19 | "compile": "yarn workspaces run compile", 20 | "env:dev": "yarn workspace @apps/mobile env:dev && yarn workspace @apps/web env:dev", 21 | "env:prod": "yarn workspace @apps/mobile env:prod && yarn workspace @apps/web env:prod", 22 | "dev:mobile": "yarn workspace @apps/mobile env:dev && yarn workspace @apps/mobile compile && concurrently --kill-others \"yarn workspace @apps/mobile compile -w\" \"yarn workspace @apps/mobile start\"", 23 | "dev:mobile:clean": "yarn workspace @apps/mobile env:dev && yarn workspace @apps/mobile compile && concurrently --kill-others \"yarn workspace @apps/mobile compile -w\" \"yarn workspace @apps/mobile start:clean\"", 24 | "dev:web": "yarn workspace @apps/web env:dev && yarn workspace @apps/web compile && concurrently --kill-others \"yarn workspace @apps/web compile -w\" \"yarn workspace @apps/web start\"", 25 | "dev:web:inmemory": "yarn workspace @apps/web env:inmemory && yarn workspace @apps/web compile && concurrently --kill-others \"yarn workspace @apps/web compile -w\" \"yarn workspace @apps/web start\"", 26 | "build:web": "yarn workspace @apps/web build", 27 | "patch-package": "patch-package", 28 | "prepare": "yarn patch-package" 29 | }, 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "babel-core": "^7.0.0-bridge.0", 33 | "concurrently": "4.1.2", 34 | "expo-yarn-workspaces": "1.2.0", 35 | "husky": "3.0.5", 36 | "lint-staged-offline": "0.0.1", 37 | "patch-package": "6.1.4", 38 | "prettier": "1.18.2", 39 | "tslint": "5.19.0", 40 | "tslint-config-prettier": "1.18.0", 41 | "tslint-react": "4.0.0", 42 | "typescript": "^3.4.5" 43 | }, 44 | "husky": { 45 | "hooks": { 46 | "pre-commit": "lint-staged-offline" 47 | } 48 | }, 49 | "lint-staged": { 50 | "*.{ts,tsx}": [ 51 | "tslint --fix", 52 | "prettier --write", 53 | "git add" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/common-context/common/adapter/inmemory/promise.helper.ts: -------------------------------------------------------------------------------- 1 | export const delayPromise = (timer: number = 1000) => 2 | new Promise(resolve => setTimeout(() => resolve(), timer)) 3 | -------------------------------------------------------------------------------- /packages/common-context/common/domain/entities/createId.helper.ts: -------------------------------------------------------------------------------- 1 | export { v4 as createId } from 'uuid' 2 | -------------------------------------------------------------------------------- /packages/common-context/common/usescases/actions.helper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Action, 3 | ActionFunction1, 4 | createAction, 5 | handleAction, 6 | Reducer, 7 | } from 'redux-actions' 8 | 9 | export type TypedAction = ActionFunction1> 10 | 11 | interface AsyncTypedActions { 12 | request: TypedAction 13 | success: TypedAction 14 | failure: TypedAction 15 | } 16 | 17 | export function createTypedAction( 18 | actionType: string, 19 | ): TypedAction { 20 | return createAction(actionType) 21 | } 22 | 23 | export function createAsyncActions< 24 | RequestPayload, 25 | SuccessPayload, 26 | FailurePayload = Error 27 | >( 28 | actionType: string, 29 | ): AsyncTypedActions { 30 | return { 31 | request: createAction(`${actionType}_REQUEST`), 32 | success: createAction(`${actionType}_SUCCESS`), 33 | failure: createAction(`${actionType}_FAILURE`), 34 | } 35 | } 36 | 37 | interface TypedActionHandler { 38 | action: TypedAction 39 | reducer: Reducer 40 | } 41 | 42 | export function createTypedHandler( 43 | action: TypedAction, 44 | reducer: Reducer, 45 | ) { 46 | return { 47 | action, 48 | reducer, 49 | } 50 | } 51 | 52 | export function handleTypedAction( 53 | handler: TypedActionHandler, 54 | defaultState: State, 55 | ): Reducer { 56 | return handleAction( 57 | handler.action.toString(), 58 | handler.reducer, 59 | defaultState, 60 | ) 61 | } 62 | 63 | export function handleTypedActions( 64 | handlers: Array>, 65 | defaultState: State, 66 | ): Reducer> { 67 | // Create a reducer for each action handler 68 | const reducers: Array> = handlers.map(handler => { 69 | return handleTypedAction(handler, defaultState) 70 | }) 71 | // Combine all reducers to reduce the same (state/action) 72 | return (state: State, action: Action) => { 73 | return reducers.reduce((currentState, reducer) => { 74 | return reducer(currentState, action) 75 | }, state) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/common-context/common/usescases/dispatch.helper.ts: -------------------------------------------------------------------------------- 1 | import { Action } from 'redux' 2 | import { addActionListener } from '../../configuration/redux/middleware/actionListener' 3 | 4 | interface DispatchAsyncResultSuccess { 5 | success: true 6 | result: T 7 | } 8 | 9 | interface DispatchAsyncResultError { 10 | success: false 11 | error: Error 12 | } 13 | 14 | export type DispatchAsyncResult = 15 | | DispatchAsyncResultSuccess 16 | | DispatchAsyncResultError 17 | 18 | export function dispatchAsync( 19 | dispatch: (a: Action) => void, 20 | action: Action, 21 | ): Promise> { 22 | return new Promise(resolve => { 23 | const actionNameBase = action.type.replace('_REQUEST', '') 24 | const unsubscribe = addActionListener((resultAction: Action) => { 25 | if (resultAction.type === `${actionNameBase}_SUCCESS`) { 26 | resolve({ success: true, result: (resultAction as any).payload }) 27 | unsubscribe() 28 | } else if (resultAction.type === `${actionNameBase}_FAILURE`) { 29 | const error = 30 | (resultAction as any).payload instanceof Error 31 | ? (resultAction as any) 32 | : new Error(`Action failure: ${actionNameBase}`) 33 | resolve({ success: false, error }) 34 | unsubscribe() 35 | } 36 | }) 37 | dispatch(action) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /packages/common-context/common/usescases/sagas.helper.ts: -------------------------------------------------------------------------------- 1 | import { getContext, GetContextEffect } from 'redux-saga/effects' 2 | import { DependenciesType } from '../../configuration/redux/dependencies' 3 | 4 | type GetDependencies = GetContextEffect | DependenciesType 5 | 6 | export function* getDependencies(): IterableIterator { 7 | const dependencies = yield getContext('dependencies') 8 | return dependencies 9 | } 10 | -------------------------------------------------------------------------------- /packages/common-context/configuration/env.ts: -------------------------------------------------------------------------------- 1 | export let env: any 2 | export let ENV: any 3 | export const setEnv = (envToSet: any) => { 4 | env = envToSet 5 | ENV = envToSet.env 6 | } 7 | -------------------------------------------------------------------------------- /packages/common-context/configuration/redux/dependencies.ts: -------------------------------------------------------------------------------- 1 | import { navigationContextDependencies } from '../../navcontext/configuration/navigationContextDependencies' 2 | 3 | const dependencies = { 4 | ...navigationContextDependencies, 5 | } 6 | 7 | export type DependenciesType = typeof dependencies 8 | 9 | export default dependencies 10 | -------------------------------------------------------------------------------- /packages/common-context/configuration/redux/middleware/actionListener.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Action } from 'redux' 2 | import { createId } from '../../../common/domain/entities/createId.helper' 3 | 4 | export type Listener = (action: Action) => void 5 | 6 | const listeners: { [id: string]: Listener } = {} 7 | export const addActionListener = (newListener: Listener) => { 8 | const listenerId = createId() 9 | listeners[listenerId] = newListener 10 | return () => delete listeners[listenerId] 11 | } 12 | 13 | const isAsyncAction = (action: Action) => 14 | action.type.endsWith('_REQUEST') || 15 | action.type.endsWith('_SUCCESS') || 16 | action.type.endsWith('_FAILURE') 17 | 18 | export const listenAsyncAction: Middleware = () => next => action => { 19 | try { 20 | if (isAsyncAction(action) && Object.keys(listeners).length > 0) { 21 | for (const listener of Object.values(listeners)) { 22 | listener(action) 23 | } 24 | } 25 | } catch (error) { 26 | console.error(error) 27 | } 28 | return next(action) 29 | } 30 | -------------------------------------------------------------------------------- /packages/common-context/configuration/redux/middleware/logger.ts: -------------------------------------------------------------------------------- 1 | import { createLogger } from 'redux-logger' 2 | 3 | export const logger = createLogger({ 4 | predicate: () => __DEV__, 5 | collapsed: true, 6 | logErrors: true, 7 | diff: true, 8 | }) 9 | -------------------------------------------------------------------------------- /packages/common-context/configuration/redux/middleware/sagaMiddleware.ts: -------------------------------------------------------------------------------- 1 | import createSagaMiddleware from 'redux-saga' 2 | import dependencies from '../dependencies' 3 | export const sagaMiddleware = (overDependencies: { [key: string]: any }) => 4 | createSagaMiddleware({ 5 | context: { 6 | dependencies, 7 | ...overDependencies, 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /packages/common-context/configuration/redux/reducers.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { navigation } from '../../navcontext/usecases/navigation.reducers' 3 | export default combineReducers({ navigation }) 4 | -------------------------------------------------------------------------------- /packages/common-context/configuration/redux/sagas.ts: -------------------------------------------------------------------------------- 1 | import { all } from 'redux-saga/effects' 2 | import { SagaIterator } from 'redux-saga' 3 | import { navigationSagas } from '../../navcontext/usecases/navigation.sagas' 4 | 5 | export default function* sagas(): SagaIterator { 6 | yield all([navigationSagas()]) 7 | } 8 | -------------------------------------------------------------------------------- /packages/common-context/configuration/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import reducers from './reducers' 3 | import sagas from './sagas' 4 | import { sagaMiddleware } from './middleware/sagaMiddleware' 5 | import { DependenciesType } from './dependencies' 6 | import { logger } from './middleware/logger' 7 | import { listenAsyncAction } from './middleware/actionListener' 8 | 9 | export default function createAppStore( 10 | dependencies: Partial, 11 | ) { 12 | const sagasMid = sagaMiddleware(dependencies) 13 | const store = createStore( 14 | reducers, 15 | applyMiddleware(sagasMid, logger, listenAsyncAction), 16 | ) 17 | 18 | sagasMid.run(sagas) 19 | return store 20 | } 21 | -------------------------------------------------------------------------------- /packages/common-context/messagingcontext/ui/Chat.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { GiftedChat, IMessage, User } from 'react-native-gifted-chat' 3 | import { View, Dimensions } from 'react-native' 4 | 5 | interface ChatProps { 6 | path?: string 7 | } 8 | export default function Chat(_props: ChatProps) { 9 | const [messages, setMessages] = useState([ 10 | { 11 | _id: 123, 12 | text: 'Hello World', 13 | user: { _id: 2, name: 'you' }, 14 | createdAt: new Date(), 15 | }, 16 | ]) 17 | const onSend = (newMsg: IMessage[]) => setMessages([...messages, ...newMsg]) 18 | const user: User = { _id: 1, name: 'me' } 19 | const inverted = false 20 | const { height } = Dimensions.get('window') 21 | 22 | return ( 23 | 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /packages/common-context/messagingcontext/ui/ChatList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { MainContainer } from '@pack/common-ui/primitives/AppView' 3 | import { Heading1 } from '@pack/common-ui/primitives/AppText' 4 | 5 | interface ChatListProps { 6 | path?: string 7 | } 8 | export default function ChatList(_props: ChatListProps) { 9 | return ( 10 | 11 | ChatList 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/common-context/navcontext/adapter/inmemory/InMemoryNavigationInteractor.ts: -------------------------------------------------------------------------------- 1 | import { NavigationInteractor } from '../../domain/gateways/Navigation.interactor' 2 | 3 | export class InMemoryNavigationInteractor implements NavigationInteractor { 4 | navigateToAsync(routeName: string, params?: any): Promise { 5 | console.log('navigateToAsync', { routeName, params }) 6 | return Promise.resolve() 7 | } 8 | popToTop(): void { 9 | console.log('popToTop') 10 | } 11 | navigateTo(routeName: string, params?: any): void { 12 | console.log({ routeName, params }) 13 | } 14 | navigateBack(): void { 15 | console.log('navigateBack') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/common-context/navcontext/configuration/navigationContextDependencies.ts: -------------------------------------------------------------------------------- 1 | import { NavigationContextFactory } from './navigationContextFactory' 2 | 3 | export const navigationContextDependencies = { 4 | navigationInteractor: NavigationContextFactory.navigationInteractor(), 5 | } 6 | -------------------------------------------------------------------------------- /packages/common-context/navcontext/configuration/navigationContextFactory.ts: -------------------------------------------------------------------------------- 1 | import { NavigationInteractor } from '../domain/gateways/Navigation.interactor' 2 | import { InMemoryNavigationInteractor } from '../adapter/inmemory/InMemoryNavigationInteractor' 3 | 4 | export class NavigationContextFactory { 5 | static navigationInteractor(): NavigationInteractor { 6 | return new InMemoryNavigationInteractor() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/common-context/navcontext/domain/gateways/Navigation.interactor.ts: -------------------------------------------------------------------------------- 1 | export interface NavigationInteractor { 2 | navigateTo(routeName: string, params?: any): void 3 | navigateToAsync(routeName: string, params?: any): Promise 4 | navigateBack(): void 5 | popToTop(): void 6 | } 7 | -------------------------------------------------------------------------------- /packages/common-context/navcontext/usecases/navigation.actions.ts: -------------------------------------------------------------------------------- 1 | import { createTypedAction } from '../../common/usescases/actions.helper' 2 | export interface NavigationPayload { 3 | routeName: string 4 | params?: any 5 | } 6 | export const navigateTo = createTypedAction('NAVIGATE_TO') 7 | export const navigateBack = createTypedAction('NAVIGATE_BACK') 8 | export const popToTop = createTypedAction('POP_TO_TOP') 9 | -------------------------------------------------------------------------------- /packages/common-context/navcontext/usecases/navigation.reducers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createTypedHandler, 3 | handleTypedActions, 4 | } from '../../common/usescases/actions.helper' 5 | import { navigateTo } from './navigation.actions' 6 | 7 | function navigateToReducer() { 8 | return {} 9 | } 10 | 11 | export const navigation = handleTypedActions( 12 | [createTypedHandler(navigateTo, navigateToReducer)], 13 | {}, 14 | ) 15 | -------------------------------------------------------------------------------- /packages/common-context/navcontext/usecases/navigation.sagas.ts: -------------------------------------------------------------------------------- 1 | import { DependenciesType } from '../../configuration/redux/dependencies' 2 | import { all, takeLatest } from 'redux-saga/effects' 3 | import { 4 | NavigationPayload, 5 | navigateTo, 6 | navigateBack, 7 | popToTop, 8 | } from './navigation.actions' 9 | import { getDependencies } from '../../common/usescases/sagas.helper' 10 | import { Action } from 'redux-actions' 11 | import idx from 'idx' 12 | 13 | function* navigateToSaga(action: Action): any { 14 | const { navigationInteractor }: DependenciesType = yield getDependencies() 15 | const routeName = idx(action, _ => _.payload.routeName) 16 | const params = idx(action, _ => _.payload.params) 17 | if (routeName) { 18 | navigationInteractor.navigateTo(routeName, params) 19 | } 20 | } 21 | 22 | function* navigateBackSaga(): any { 23 | const { navigationInteractor }: DependenciesType = yield getDependencies() 24 | navigationInteractor.navigateBack() 25 | } 26 | 27 | function* popToTopSaga(): any { 28 | const { navigationInteractor }: DependenciesType = yield getDependencies() 29 | navigationInteractor.popToTop() 30 | } 31 | 32 | export const navigationSagas = function* navigationSagasFn(): any { 33 | yield all([ 34 | takeLatest(navigateTo, navigateToSaga), 35 | takeLatest(navigateBack, navigateBackSaga), 36 | takeLatest(popToTop, popToTopSaga), 37 | ]) 38 | } 39 | -------------------------------------------------------------------------------- /packages/common-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pack/common-context", 3 | "version": "1.0.0", 4 | "description": "common State package", 5 | "main": "index.js", 6 | "author": "Xavier Carpentier (https://xaviercarpentier.com/)", 7 | "license": "MIT", 8 | "scripts": { 9 | "cleaning": "rm -rf node_modules", 10 | "compile": "tsc --noEmit", 11 | "build": "yarn compile", 12 | "test": "echo 'TODO: test'" 13 | }, 14 | "dependencies": { 15 | "idx": "2.5.6", 16 | "ramda": "0.26.1", 17 | "react-native-gifted-chat": "0.10.0-beta.web.22", 18 | "redux": "4.0.4", 19 | "redux-actions": "2.6.5", 20 | "redux-logger": "3.0.6", 21 | "redux-saga": "1.0.5", 22 | "reselect": "4.0.0", 23 | "uuid": "3.3.3" 24 | }, 25 | "devDependencies": { 26 | "@types/ramda": "0.26.21", 27 | "@types/redux-actions": "2.6.1", 28 | "@types/redux-logger": "3.0.7", 29 | "@types/uuid": "3.4.5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/common-context/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tslint.json"] 3 | } -------------------------------------------------------------------------------- /packages/common-ui/configuration/Provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Provider as PaperProvider, Theme } from 'react-native-paper' 3 | import { Provider as StoreProvider } from 'react-redux' 4 | import { ActionSheetProvider } from '@expo/react-native-action-sheet' 5 | import { Store } from 'redux' 6 | 7 | interface AppProviderProps { 8 | theme: Theme 9 | store: Store 10 | children: React.ReactNode 11 | } 12 | export default function AppProvider({ 13 | theme, 14 | store, 15 | children, 16 | }: AppProviderProps) { 17 | return ( 18 | 19 | 20 | {children} 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /packages/common-ui/configuration/Theme.tsx: -------------------------------------------------------------------------------- 1 | import { DefaultTheme, Theme } from 'react-native-paper' 2 | import AppColor from '../primitives/AppColor' 3 | 4 | const theme: Theme = { 5 | ...DefaultTheme, 6 | colors: { 7 | ...DefaultTheme.colors, 8 | primary: AppColor.primary, 9 | }, 10 | fonts: { 11 | medium: 'Open Sans', 12 | light: 'Open Sans', 13 | thin: 'Open Sans', 14 | regular: 'Open Sans', 15 | }, 16 | } 17 | 18 | export default theme 19 | -------------------------------------------------------------------------------- /packages/common-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pack/common-ui", 3 | "version": "1.0.0", 4 | "description": "common UI package", 5 | "main": "index.js", 6 | "author": "Xavier Carpentier (https://xaviercarpentier.com/)", 7 | "license": "MIT", 8 | "scripts": { 9 | "cleaning": "rm -rf node_modules", 10 | "compile": "tsc --noEmit", 11 | "build": "yarn compile", 12 | "test": "echo 'TODO: test'" 13 | }, 14 | "dependencies": { 15 | "@expo/react-native-action-sheet": "3.0.3", 16 | "react-native-paper": "2.16.0", 17 | "react-redux": "7.1.1" 18 | }, 19 | "devDependencies": { 20 | "@types/react-redux": "7.1.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/common-ui/primitives/AppAccessibilityRole.ts: -------------------------------------------------------------------------------- 1 | export type AppAccessibilityRole = 2 | | 'none' 3 | | 'button' 4 | | 'link' 5 | | 'search' 6 | | 'image' 7 | | 'keyboardkey' 8 | | 'text' 9 | | 'adjustable' 10 | | 'header' 11 | | 'summary' 12 | | 'imagebutton' 13 | | 'heading' 14 | | 'navigation' 15 | | 'main' 16 | | 'article' 17 | | 'list' 18 | | 'listitem' 19 | -------------------------------------------------------------------------------- /packages/common-ui/primitives/AppColor.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Color name suffix convention: 3 | - T = more transparent 4 | - D = more dark 5 | - L = more light 6 | */ 7 | enum AppColor { 8 | transparent = 'transparent', 9 | 10 | primary = '#F9603C', 11 | primaryT = 'rgba(249, 96, 60, 0.8)', 12 | primaryTT = 'rgba(249, 96, 60, 0.5)', 13 | primaryTTT = 'rgba(249, 96, 60, 0.3)', 14 | primaryD = '#b96038', 15 | primaryDD = '#874229', 16 | 17 | black = '#181818', 18 | blackT = 'rgba(24, 24, 24, 0.8)', 19 | blackTT = 'rgba(24, 24, 24, 0.5)', 20 | blackTTT = 'rgba(24, 24, 24, 0.3)', 21 | blackInvisible = 'rgba(24, 24, 24, 0)', 22 | 23 | white = 'rgba(255, 255, 255, 1)', 24 | whiteT = 'rgba(255,255,255,.8)', 25 | whiteTT = 'rgba(255,255,255,.5)', 26 | whiteTTT = 'rgba(255,255,255,.2)', 27 | whiteTTTT = 'rgba(255,255,255,.03)', 28 | 29 | greyLLL = '#b9b9b9', 30 | greyLL = '#a5a5a5', 31 | greyL = '#919191', 32 | grey = '#7d7d7d', 33 | greyD = '#696969', 34 | greyDD = '#555555', 35 | greyDDD = '#414141', 36 | 37 | error = '#EC2434', 38 | success = '#27D9A1', 39 | 40 | shadow = 'rgba(30, 34, 45, 0.15)', 41 | } 42 | 43 | export default AppColor 44 | -------------------------------------------------------------------------------- /packages/common-ui/primitives/AppErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, View } from 'react-native' 3 | import { AppText } from './AppText' 4 | import AppColor from './AppColor' 5 | import { Button } from 'react-native-paper' 6 | 7 | interface Props { 8 | children: any 9 | hasError?: boolean 10 | reload?(): void 11 | } 12 | 13 | interface State { 14 | hasError?: boolean 15 | } 16 | 17 | const styles = StyleSheet.create({ 18 | container: { 19 | ...StyleSheet.absoluteFillObject, 20 | backgroundColor: '#fff', 21 | justifyContent: 'space-around', 22 | alignItems: 'center', 23 | padding: 15, 24 | }, 25 | }) 26 | 27 | export class AppErrorBoundary extends React.Component { 28 | static defaultProps = { 29 | hasError: false, 30 | } 31 | 32 | static getDerivedStateFromError(error: Error) { 33 | console.warn({ error }) 34 | 35 | return { hasError: true } 36 | } 37 | 38 | state = { hasError: this.props.hasError } 39 | 40 | render() { 41 | if (this.state.hasError) { 42 | const { reload } = this.props 43 | return ( 44 | 45 | 46 | Oups... 47 | 48 | {reload && ( 49 | 52 | )} 53 | 54 | ) 55 | } 56 | return this.props.children 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/common-ui/primitives/AppLoader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Centered } from './AppView' 3 | import { ActivityIndicator } from 'react-native-paper' 4 | 5 | export function AppLoader() { 6 | return ( 7 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /packages/common-ui/primitives/AppText.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { StyleSheet, Platform } from 'react-native' 3 | import { AppAccessibilityRole } from './AppAccessibilityRole' 4 | import AppColor from './AppColor' 5 | import { Text } from 'react-native-paper' 6 | 7 | type AppSize = 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl' 8 | type AppWeight = 'thin' | 'normal' 9 | 10 | type AppTextPropsBase = Omit & { 11 | accessibilityRole?: AppAccessibilityRole 12 | 'aria-level'?: string 13 | } 14 | 15 | export interface AppTextProps extends AppTextPropsBase { 16 | color?: AppColor 17 | backgroundColor?: AppColor 18 | size?: AppSize 19 | center?: boolean 20 | children: React.ReactNode 21 | weight?: AppWeight 22 | href?: string 23 | target?: HTMLAnchorElement['target'] 24 | } 25 | 26 | const TextBase = (Text as any) as React.ComponentType 27 | 28 | const BaseStyle = StyleSheet.create({ 29 | defaultStyle: { 30 | color: AppColor.black, 31 | fontWeight: '600', 32 | fontFamily: 'Open Sans', 33 | lineHeight: 20, 34 | ...Platform.select({ 35 | default: undefined, 36 | web: { 37 | WebkitFontSmoothing: 'antialiased', 38 | }, 39 | }), 40 | }, 41 | }) 42 | 43 | const calculateFontSize = (size?: AppSize): number => { 44 | switch (size) { 45 | case 'xs': { 46 | return 12 47 | } 48 | case 's': { 49 | return 14 50 | } 51 | case 'm': { 52 | return 16 53 | } 54 | case 'l': { 55 | return 20 56 | } 57 | case 'xl': { 58 | return 25 59 | } 60 | case 'xxl': { 61 | return 36 62 | } 63 | default: { 64 | return 16 65 | } 66 | } 67 | } 68 | 69 | export const AppText = ({ 70 | color, 71 | size, 72 | style, 73 | center, 74 | weight, 75 | backgroundColor, 76 | ...props 77 | }: AppTextProps) => ( 78 | 93 | ) 94 | 95 | export const Heading1 = (props: AppTextProps) => ( 96 | 97 | ) 98 | 99 | export const Heading2 = (props: AppTextProps) => ( 100 | 107 | ) 108 | 109 | export const Heading3 = (props: AppTextProps) => ( 110 | 117 | ) 118 | 119 | export const Heading4 = (props: AppTextProps) => ( 120 | 127 | ) 128 | 129 | export const Paragraph = (props: AppTextProps) => ( 130 | 131 | ) 132 | -------------------------------------------------------------------------------- /packages/common-ui/primitives/AppView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { FlexStyle, StyleSheet, View } from 'react-native' 3 | import { AppAccessibilityRole } from './AppAccessibilityRole' 4 | import AppColor from './AppColor' 5 | 6 | type LayerType = 'constrained' | undefined 7 | type LayerDirection = 'row' | 'column' | undefined 8 | 9 | const BaseStyle = StyleSheet.create({ 10 | defaultStyle: { 11 | position: 'relative', 12 | backgroundColor: AppColor.transparent, 13 | }, 14 | constrained: { 15 | paddingHorizontal: 20, 16 | }, 17 | mainHead: { 18 | maxWidth: 650, 19 | width: '100%', 20 | paddingBottom: 40, 21 | }, 22 | mainContainer: { 23 | width: '100%', 24 | paddingHorizontal: 20, 25 | }, 26 | main: { 27 | maxWidth: 800, 28 | width: '100%', 29 | }, 30 | box: { 31 | paddingVertical: 30, 32 | borderTopWidth: 1, 33 | borderTopColor: AppColor.greyLLL, 34 | }, 35 | grid: { 36 | flexDirection: 'row', 37 | flexWrap: 'wrap', 38 | justifyContent: 'center', 39 | marginTop: 20, 40 | }, 41 | gridItem: { 42 | height: 274, 43 | width: 395, 44 | paddingHorizontal: 15, 45 | }, 46 | borderBox: { 47 | flex: 1, 48 | padding: 45, 49 | borderColor: AppColor.primary, 50 | borderWidth: 2, 51 | borderRadius: 4, 52 | shadowColor: AppColor.blackT, 53 | shadowOffset: { width: 0, height: 1 }, 54 | shadowOpacity: 1, 55 | shadowRadius: 5, 56 | }, 57 | }) 58 | 59 | type AppViewPropsBase = Omit & { 60 | accessibilityRole?: AppAccessibilityRole 61 | className?: string 62 | ref?: (c: any) => void 63 | } 64 | 65 | const ViewBase = (View as any) as React.ComponentType 66 | 67 | export interface AppViewProps extends AppViewPropsBase { 68 | type?: LayerType 69 | direction?: LayerDirection 70 | justify?: FlexStyle['justifyContent'] 71 | align?: FlexStyle['alignItems'] 72 | wrap?: FlexStyle['flexWrap'] 73 | backgroundColor?: AppColor 74 | center?: boolean 75 | fullWidth?: boolean 76 | hiddenXS?: boolean 77 | visibleXS?: boolean 78 | children: React.ReactNode 79 | } 80 | 81 | export class AppView extends React.Component { 82 | root?: any = undefined 83 | 84 | render() { 85 | const { 86 | type, 87 | backgroundColor, 88 | center, 89 | direction, 90 | style, 91 | justify: justifyContent, 92 | align: alignItems, 93 | wrap: flexWrap, 94 | fullWidth, 95 | hiddenXS, 96 | ...otherProps 97 | }: AppViewProps = this.props 98 | return ( 99 | (this.root = component)} 101 | nativeID={hiddenXS ? 'hiddenLayoutXS' : undefined} 102 | style={[ 103 | BaseStyle.defaultStyle, 104 | backgroundColor && { backgroundColor }, 105 | center && { justifyContent: 'center', alignItems: 'center' }, 106 | type === 'constrained' && BaseStyle.constrained, 107 | direction && { flexDirection: direction }, 108 | justifyContent && { justifyContent }, 109 | alignItems && { alignItems }, 110 | flexWrap && { flexWrap }, 111 | fullWidth && { width: '100%' }, 112 | style, 113 | ]} 114 | {...otherProps} 115 | /> 116 | ) 117 | } 118 | } 119 | 120 | export const Centered = (props: AppViewProps) => 121 | 122 | export const Row = (props: AppViewProps) => ( 123 | 124 | ) 125 | 126 | export const MainContainer = (props: AppViewProps) => ( 127 | 132 | ) 133 | 134 | export const Main = (props: AppViewProps) => ( 135 | 136 | 137 | 138 | ) 139 | 140 | export const MainHead = (props: AppViewProps) => ( 141 | 142 | ) 143 | 144 | export const Box = (props: AppViewProps & { noBorder?: boolean }) => ( 145 | 154 | ) 155 | 156 | export const Grid = (props: AppViewProps) => ( 157 | 158 | ) 159 | 160 | export const GridItem = (props: AppViewProps) => ( 161 | 162 | ) 163 | 164 | export const BorderBox = (props: AppViewProps) => ( 165 | 166 | ) 167 | 168 | export const HiddenXS = (props: AppViewProps) => ( 169 | 170 | ) 171 | 172 | export const VisibleXS = (props: AppViewProps) => ( 173 | 174 | ) 175 | -------------------------------------------------------------------------------- /packages/common-ui/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tslint.json"] 3 | } -------------------------------------------------------------------------------- /patches/@types+reach__router+1.2.4.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@types/reach__router/index.d.ts b/node_modules/@types/reach__router/index.d.ts 2 | index 1397283..bcb316d 100644 3 | --- a/node_modules/@types/reach__router/index.d.ts 4 | +++ b/node_modules/@types/reach__router/index.d.ts 5 | @@ -4,24 +4,24 @@ 6 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 7 | // TypeScript Version: 2.8 8 | 9 | -import * as React from "react"; 10 | -import { Location as HLocation } from "history"; 11 | -export type WindowLocation = Window["location"] & HLocation; 12 | +import * as React from 'react' 13 | +import { Location as HLocation } from 'history' 14 | +export type WindowLocation = Window['location'] & HLocation 15 | 16 | -export type HistoryActionType = "PUSH" | "POP"; 17 | -export type HistoryLocation = WindowLocation & { state?: any }; 18 | +export type HistoryActionType = 'PUSH' | 'POP' 19 | +export type HistoryLocation = WindowLocation & { state?: any } 20 | export interface HistoryListenerParameter { 21 | - location: HistoryLocation; 22 | - action: HistoryActionType; 23 | + location: HistoryLocation 24 | + action: HistoryActionType 25 | } 26 | -export type HistoryListener = (parameter: HistoryListenerParameter) => void; 27 | -export type HistoryUnsubscribe = () => void; 28 | +export type HistoryListener = (parameter: HistoryListenerParameter) => void 29 | +export type HistoryUnsubscribe = () => void 30 | 31 | export interface History { 32 | - readonly location: HistoryLocation; 33 | - readonly transitioning: boolean; 34 | - listen: (listener: HistoryListener) => HistoryUnsubscribe; 35 | - navigate: NavigateFn; 36 | + readonly location: HistoryLocation 37 | + readonly transitioning: boolean 38 | + listen: (listener: HistoryListener) => HistoryUnsubscribe 39 | + navigate: NavigateFn 40 | } 41 | 42 | export class Router extends React.Component< 43 | @@ -29,131 +29,136 @@ export class Router extends React.Component< 44 | > {} 45 | 46 | export interface RouterProps { 47 | - basepath?: string; 48 | - primary?: boolean; 49 | - location?: WindowLocation; 50 | - component?: React.ComponentType | string; 51 | + basepath?: string 52 | + primary?: boolean 53 | + location?: WindowLocation 54 | + component?: React.ComponentType | string 55 | } 56 | 57 | export type RouteComponentProps = Partial & { 58 | - path?: string; 59 | - default?: boolean; 60 | - location?: WindowLocation; 61 | - navigate?: NavigateFn; 62 | - uri?: string; 63 | -}; 64 | + path?: string 65 | + default?: boolean 66 | + location?: WindowLocation 67 | + navigate?: NavigateFn 68 | + uri?: string 69 | +} 70 | 71 | -export type Omit = Pick>; 72 | +export type Omit = Pick> 73 | 74 | export type AnchorProps = Omit< 75 | - React.DetailedHTMLProps< 76 | + React.DetailedHTMLProps< 77 | React.AnchorHTMLAttributes, 78 | HTMLAnchorElement 79 | - >, 80 | - "href" // remove href, as it's ignored by the router 81 | - >; 82 | + >, 83 | + 'href' // remove href, as it's ignored by the router 84 | +> 85 | 86 | export interface LinkProps extends AnchorProps { 87 | - to?: string; 88 | - replace?: boolean; 89 | - getProps?: (props: LinkGetProps) => {}; 90 | - state?: TState; 91 | + to?: string 92 | + replace?: boolean 93 | + getProps?: (props: LinkGetProps) => {} 94 | + state?: TState 95 | } 96 | 97 | export interface LinkGetProps { 98 | - isCurrent: boolean; 99 | - isPartiallyCurrent: boolean; 100 | - href: string; 101 | - location: WindowLocation; 102 | + isCurrent: boolean 103 | + isPartiallyCurrent: boolean 104 | + href: string 105 | + location: WindowLocation 106 | } 107 | 108 | -export class Link extends React.Component> { } 109 | +export class Link extends React.Component> {} 110 | 111 | export interface RedirectProps { 112 | - from?: string; 113 | - to: string; 114 | - noThrow?: boolean; 115 | - state?: TState; 116 | - replace?: boolean; 117 | + from?: string 118 | + to: string 119 | + noThrow?: boolean 120 | + state?: TState 121 | + replace?: boolean 122 | } 123 | 124 | -export class Redirect extends React.Component>> { } 125 | +export class Redirect extends React.Component< 126 | + RouteComponentProps> 127 | +> {} 128 | 129 | export interface MatchProps { 130 | - path: string; 131 | - children: MatchRenderFn; 132 | + path: string 133 | + children: MatchRenderFn 134 | } 135 | 136 | export type MatchRenderFn = ( 137 | - props: MatchRenderProps 138 | -) => React.ReactNode; 139 | + props: MatchRenderProps, 140 | +) => React.ReactNode 141 | 142 | export interface MatchRenderProps { 143 | - match: null | { uri: string; path: string } & TParams; 144 | - location: WindowLocation; 145 | - navigate: NavigateFn; 146 | + match: null | { uri: string; path: string } & TParams 147 | + location: WindowLocation 148 | + navigate: NavigateFn 149 | } 150 | 151 | -export class Match extends React.Component> { } 152 | +export class Match extends React.Component> {} 153 | 154 | -export type NavigateFn = (to: string, options?: NavigateOptions<{}>) => void; 155 | +export type NavigateFn = ( 156 | + to: string | number, 157 | + options?: NavigateOptions<{}>, 158 | +) => void 159 | 160 | export interface NavigateOptions { 161 | - state?: TState; 162 | - replace?: boolean; 163 | + state?: TState 164 | + replace?: boolean 165 | } 166 | 167 | export interface LocationProps { 168 | - children: LocationProviderRenderFn; 169 | + children: LocationProviderRenderFn 170 | } 171 | 172 | -export class Location extends React.Component { } 173 | +export class Location extends React.Component {} 174 | 175 | export interface LocationProviderProps { 176 | - history?: History; 177 | - children?: React.ReactNode | LocationProviderRenderFn; 178 | + history?: History 179 | + children?: React.ReactNode | LocationProviderRenderFn 180 | } 181 | 182 | export type LocationProviderRenderFn = ( 183 | - context: LocationContext 184 | -) => React.ReactNode; 185 | + context: LocationContext, 186 | +) => React.ReactNode 187 | 188 | export interface LocationContext { 189 | - location: WindowLocation; 190 | - navigate: NavigateFn; 191 | + location: WindowLocation 192 | + navigate: NavigateFn 193 | } 194 | 195 | -export class LocationProvider extends React.Component { } 196 | +export class LocationProvider extends React.Component {} 197 | 198 | export interface ServerLocationProps { 199 | - url: string; 200 | + url: string 201 | } 202 | 203 | -export class ServerLocation extends React.Component { } 204 | +export class ServerLocation extends React.Component {} 205 | 206 | -export const navigate: NavigateFn; 207 | +export const navigate: NavigateFn 208 | 209 | export interface HistorySource { 210 | - readonly location: WindowLocation; 211 | - addEventListener(name: string, listener: (event: Event) => void): void; 212 | - removeEventListener(name: string, listener: (event: Event) => void): void; 213 | - history: { 214 | - readonly state: any; 215 | - pushState(state: any, title: string, uri: string): void; 216 | - replaceState(state: any, title: string, uri: string): void; 217 | - }; 218 | + readonly location: WindowLocation 219 | + addEventListener(name: string, listener: (event: Event) => void): void 220 | + removeEventListener(name: string, listener: (event: Event) => void): void 221 | + history: { 222 | + readonly state: any 223 | + pushState(state: any, title: string, uri: string): void 224 | + replaceState(state: any, title: string, uri: string): void 225 | + } 226 | } 227 | 228 | -export function createHistory(source: HistorySource): History; 229 | +export function createHistory(source: HistorySource): History 230 | 231 | -export function createMemorySource(initialPath: string): HistorySource; 232 | +export function createMemorySource(initialPath: string): HistorySource 233 | 234 | export interface RedirectRequest { 235 | - uri: string; 236 | + uri: string 237 | } 238 | 239 | -export function isRedirect(error: any): error is RedirectRequest; 240 | +export function isRedirect(error: any): error is RedirectRequest 241 | 242 | -export function redirectTo(uri: string): void; 243 | +export function redirectTo(uri: string): void 244 | 245 | -export const globalHistory: History; 246 | +export const globalHistory: History 247 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "esnext", 8 | "dom", 9 | "dom.iterable" 10 | ], 11 | "allowJs": true, 12 | "allowSyntheticDefaultImports": true, 13 | "esModuleInterop": true, 14 | "isolatedModules": false, 15 | "jsx": "preserve", 16 | "noEmit": true, 17 | "resolveJsonModule": true, 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strict": true 21 | } 22 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-config-prettier"], 4 | "jsRules": {}, 5 | "rules": { 6 | "object-literal-sort-keys": false, 7 | "interface-name": false, 8 | "ordered-imports": false, 9 | "member-access": false, 10 | "max-classes-per-file": false, 11 | "no-console": false, 12 | "variable-name": false 13 | }, 14 | "rulesDirectory": [] 15 | } 16 | --------------------------------------------------------------------------------