├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── client.code-workspace ├── package-lock.json ├── package.json ├── packages ├── appkit-client │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── src │ │ ├── components │ │ │ ├── AppBar.tsx │ │ │ ├── AppContainer.tsx │ │ │ ├── ButtonSimple.tsx │ │ │ ├── CheckboxSimple.tsx │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InformationModal.tsx │ │ │ ├── ListMenu.tsx │ │ │ ├── LoginModal.tsx │ │ │ ├── ModalSimple.tsx │ │ │ ├── SingleTextInputModal.tsx │ │ │ ├── SwitchSimple.tsx │ │ │ ├── TextFieldSimple.tsx │ │ │ ├── appContainer │ │ │ │ └── theme.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── model │ │ │ ├── config.ts │ │ │ ├── index.ts │ │ │ └── menus.ts │ │ ├── stores │ │ │ ├── InputStore.ts │ │ │ └── NotificationStore.ts │ │ └── util │ │ │ └── fetch.ts │ └── tsconfig.json ├── appkit-common │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── src │ │ └── model │ │ │ ├── auth.ts │ │ │ ├── index.ts │ │ │ └── misc.ts │ └── tsconfig.json ├── appkit-server │ ├── .vscode │ │ └── settings.json │ ├── src │ │ ├── model │ │ │ └── config.ts │ │ └── util │ │ │ ├── auth.ts │ │ │ ├── env.ts │ │ │ ├── file.ts │ │ │ ├── index.ts │ │ │ └── time.ts │ └── tsconfig.json ├── boilerplate-client │ ├── .gitignore │ ├── .vscode │ │ ├── settings.json │ │ └── tasks.json │ ├── README.md │ ├── jest.debug.config.json │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── components │ │ │ ├── App.tsx │ │ │ └── app │ │ │ │ └── ConditionalModals.tsx │ │ ├── config │ │ │ └── config.ts │ │ ├── index.tsx │ │ ├── model │ │ │ ├── index.ts │ │ │ └── serverApi.ts │ │ ├── pages │ │ │ ├── ComponentsDemo.tsx │ │ │ ├── Home.tsx │ │ │ ├── HooksDemo.tsx │ │ │ ├── ServerApiDemo.tsx │ │ │ ├── home │ │ │ │ └── home_content.md │ │ │ ├── hooksDemo │ │ │ │ ├── HooksTest.tsx │ │ │ │ └── useComputedValue.ts │ │ │ └── index.ts │ │ ├── stores │ │ │ ├── RootStore.ts │ │ │ ├── UserStore.ts │ │ │ └── ViewStore.ts │ │ └── util │ │ │ └── fetch.ts │ ├── tsconfig.json │ └── webpack.config.js └── boilerplate-server │ ├── .vscode │ ├── launch.json │ ├── settings.json │ └── tasks.json │ ├── Dockerfile │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── config │ │ ├── config.ts │ │ ├── middleware.ts │ │ └── version.ts │ ├── logic │ │ └── auth.ts │ ├── routes │ │ ├── auth.ts │ │ ├── index.ts │ │ └── test.ts │ ├── server.ts │ └── util │ │ └── env.ts │ ├── swaggerConfig.json │ ├── tsconfig.json │ └── webpack.config.js ├── server.code-workspace ├── tsconfig.base.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | dist -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}\\index.js", 12 | "outFiles": [ 13 | "${workspaceFolder}/**/*.js" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/*.js": true 4 | }, 5 | "prettier.semi": false, 6 | "prettier.singleQuote": true, 7 | "prettier.printWidth": 120 8 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "boilerplate-server-watch", 8 | "type": "npm", 9 | "script": "watch", 10 | "path": "packages/boilerplate-server/", 11 | "problemMatcher": [] 12 | }, 13 | { 14 | "label": "boilerplate-server-start", 15 | "type": "npm", 16 | "script": "start", 17 | "path": "packages/boilerplate-server/", 18 | "problemMatcher": [] 19 | }, 20 | { 21 | "label": "boilerplate-client-dev", 22 | "type": "npm", 23 | "script": "start", 24 | "path": "packages/boilerplate-client/", 25 | "problemMatcher": [] 26 | }, 27 | { 28 | "label": "boilerplate-server-all", 29 | "dependsOn": ["boilerplate-server-start", "boilerplate-server-watch"], 30 | "problemMatcher": [] 31 | }, 32 | { 33 | "label": "boilerplate-all", 34 | "dependsOn": ["boilerplate-server-all", "boilerplate-client-dev"], 35 | "problemMatcher": [] 36 | }, 37 | { 38 | "type": "npm", 39 | "script": "clean", 40 | "path": "packages/boilerplate-server/", 41 | "problemMatcher": [] 42 | }, 43 | { 44 | "type": "npm", 45 | "script": "watch", 46 | "path": "packages/boilerplate-server/", 47 | "problemMatcher": [] 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jeff Hull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typescript Fullstack Monorepo 2 | 3 | A fullstack boilerplate application using typescript, node, react, and mobx. 4 | 5 | # Quick Start 6 | 7 | ## Install dependencies 8 | 9 | ``` 10 | git clone https://github.com/jeffkhull/typescript-fullstack-monorepo.git 11 | cd typescript-fullstack-monorepo 12 | npm i 13 | cd packages/boilerplate-server && npm i 14 | ``` 15 | 16 | ## Start Processes 17 | 18 | ### If you are using vscode 19 | 20 | 1. From the project root, open client.code-workspaces and server.code-workspace in separate instances of vscode 21 | 2. In the client code workspace, run the vscode task "server all" 22 | 3. In the client code workspace, run the vscode task "client start" 23 | 24 | ### If you're not using vscode 25 | 26 | #### From a terminal in the project root 27 | 28 | 1. cd packages/boilerplate-server 29 | 2. npm run watch & npm run start 30 | 31 | #### From a second terminal in the project root 32 | 33 | 1. cd packages/boilerplate-client 34 | 2. npm run start 35 | 36 | # Dependencies 37 | 38 | 1. Node.js 8+ 39 | 40 | # Motivation 41 | 42 | The large number of language, framework, library, and toolchain choices in today's javascript ecosystem can make it difficult to get started in any javascript development, let alone develop a full-stack application. This repository is one such full-stack application meant as an example and easy starting point for those interested in building an application using typescript, node, react, and mobx. 43 | 44 | # Technology Choices 45 | 46 | This boilerplate uses my current preferred development stack. I've moved on from other technologies and will continue to do so as I learn. Here's the reasoning behind the technologies used thus far, and some explanation for why I've chosen some tech over others. 47 | 48 | ## Typescript 49 | 50 | I've been impressed at the active role Microsoft has taken in the open source community, in large part caused (as far as I can tell) by the leadership of [Satya Nadella](https://twitter.com/satyanadella). Typescript is one Microsoft project whose momentum, quality, and versatility are impressive. I started with vanilla ES6 and after some time in development, came to appreciate the power of strong typing and static analysis. Many popular open source projects have been adopting typescript. Angular was an early adopter, and recently, Vue.js [announced version 3.0 was being written in typescript](https://medium.com/the-vue-point/plans-for-the-next-iteration-of-vue-js-777ffea6fabf). My first exposure to strong typing in javascript was [this video](https://www.youtube.com/watch?v=Qiqsg02nXFE), and several talks by [Andrews Hejlsberg](https://twitter.com/ahejlsberg) shed light on the design and philosophy behind Typescript. The javascript community as a whole has seen a flurry of innovation in the past few years, being rekindled perhaps with ES6 (AKA ES2015) and the leadership of the folks on the [TC39](https://github.com/tc39/proposals). Typescript's commitment to the community and commitment to support all official javascript features, so that it is a true superset and maintains compatibility with javascript as a whole, coupled with the power of its associated tooling has made Typescript the current clear choice for my projects. 51 | 52 | ### Cons of Typescript 53 | 54 | I recently came across [this article](https://medium.com/javascript-scene/the-typescript-tax-132ff4cb175b) which discusses the cons of Typescript with respect to Javascript. One of my favorite quotes from this article is 55 | 56 | `"Interfaces are one of the best features of TypeScript, and I wish this feature was built into JavaScript."` 57 | 58 | This comment resonates with me, as interfaces are the one TypeScript feature I have a hard time doing without. They provide a way to explicitly define and enforce the shape of objects, which makes it easier to understand how those values are intended to be used within an application. I think if interfaces were part of standard Javascript, I would seriously consider going back to plain Javascript as my language of choice. 59 | 60 | ## Mobx 61 | 62 | When I started developing with React, I adopted Redux as my state management library of choice. After working with it for a year or so, I became tired of the amount of code I needed to maintain X piece of shared state in my React application. That's when I ran into the ideas of [Michel Westrate](https://twitter.com/mweststrate) and his React state management library, [Mobx](https://medium.com/@mweststrate/interactive-introduction-to-mobx-and-reactjs-1760e448103c). After putting it off for a while, I tried mobx out in one of my apps, and I have since used it as my global state management solution of choice. 63 | 64 | ### Cons of Mobx as of May 1, 2019 65 | 66 | My biggest issue with Mobx is that the observable pattern runs counter to the direction of React and the majority of the React ecosystem. Thus by choosing to use mobx, I am making my code less understandable to React developers whose mental models are adapted to using the flux pattern. In addition, I'm introducing a net new, and non-trivial, abstraction of observables into my code, thus increasing the conceptual overhead of my solution. 67 | 68 | ### Reasons I'm sticking with Mobx as of May 1, 2019 69 | 70 | I was about to switch back to redux, leveraging [redux-react-hook](https://github.com/facebookincubator/redux-react-hook) and [immer](https://github.com/immerjs/immer) as means of reducing boilerplate and using hooks as a natural way to use state and dispatch within function components. However, midway though this refactoring, I realized I felt I was moving in the wrong direction. My main gripe with the redux approach is that now, to maintain a piece of state, instead of needing a single ES6 observable class with state and methods colocated, I need a reducer, action model, and action creators. Immer and redux-react-hook help to make redux more ergonomic, but even with those improvements, the overhead and fragmented concepts needed to maintain redux state vs an observable class are still a high price to pay. By choosing Mobx, I incur the costs associated with the cons listed above... but as of right now, I find choosing Mobx to be a net positive for productivity and simplicity of my codebase. 71 | 72 | ## No React Router 73 | 74 | Like many starting out with react, I started with React Router as my routing solution. However, when I started migrating to react router v4 from v3, I developed the opinion that having my components control my routes is backward in an application where the state of my components should be driven by the contents of my stores, not the other way around. So once again Michel Westrate writes [an article](https://hackernoon.com/how-to-decouple-state-and-ui-a-k-a-you-dont-need-componentwillmount-cc90b787aa37) laying out the reasons to ditch route aware components for a store based approach using the simple [director](https://github.com/flatiron/director) routing library. 75 | 76 | ## Function Components 77 | 78 | The introduction of hooks has made it possible to create a react application without any class components. I'm a big fan of this approach, one reason being the conciseness and simplicity of just using functions everywhere, and another being not needing to deal with "this." in my components. 79 | 80 | ## Disclaimer 81 | 82 | This repository contains one set of technology choices and doesn't intend to claim the choices made are the best possible. I'm always looking to optimize my tooling, so if you have any interesting alternatives, please let me know! 83 | -------------------------------------------------------------------------------- /client.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "packages/appkit-client" 5 | }, 6 | { 7 | "path": "packages/appkit-common" 8 | }, 9 | { 10 | "path": "packages/boilerplate-client" 11 | } 12 | ], 13 | "settings": { 14 | "prettier.singleQuote": true, 15 | "prettier.semi": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-fullstack-monorepo", 3 | "version": "1.0.0", 4 | "description": "A fullstack example monorepo using typescript, node, react, and mobx.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://gitlab.com/jh23us-boilerplate/typescript-fullstack-boilerplate.git" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@material-ui/core": "^3.9.3", 14 | "@material-ui/icons": "^3.0.2", 15 | "@types/node": "^10.14.5", 16 | "@types/react": "^16.8.14", 17 | "cors": "^2.8.5", 18 | "director": "^1.2.8", 19 | "html-loader": "^0.5.5", 20 | "html-webpack-plugin": "^3.2.0", 21 | "jsonwebtoken": "^8.5.1", 22 | "jss-preset-default": "^4.5.0", 23 | "markdown-loader": "^4.0.0", 24 | "mobx": "^5.9.4", 25 | "mobx-react-lite": "^1.2.0", 26 | "nodemon": "^1.18.11", 27 | "prettier": "^1.17.0", 28 | "react": "^16.8.6", 29 | "react-dom": "^16.8.6", 30 | "rimraf": "^2.6.3", 31 | "ts-loader": "^5.4.3", 32 | "tsconfig-paths-webpack-plugin": "^3.2.0", 33 | "typescript": "^3.4.5", 34 | "webpack": "^4.30.0", 35 | "webpack-cli": "^3.3.1", 36 | "webpack-dev-server": "^3.3.1", 37 | "webpack-node-externals": "^1.7.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/appkit-client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.singleQuote": true, 3 | "prettier.semi": false 4 | } -------------------------------------------------------------------------------- /packages/appkit-client/README.md: -------------------------------------------------------------------------------- 1 | # Typescript Appkit Client 2 | 3 | A set of React components to enable rapid app development using React, Mobx, and Typescript 4 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/AppBar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import AccountCircle from '@material-ui/icons/AccountCircle' 3 | import IconButton from '@material-ui/core/IconButton' 4 | import Menu from '@material-ui/core/Menu' 5 | import MenuIcon from '@material-ui/icons/Menu' 6 | import MenuItem from '@material-ui/core/MenuItem' 7 | import Toolbar from '@material-ui/core/Toolbar' 8 | import Typography from '@material-ui/core/Typography' 9 | import { default as MuiAppbar } from '@material-ui/core/AppBar' 10 | 11 | export interface AppBarProps { 12 | leftMenuOnClick: () => void 13 | title: string 14 | loggedIn: boolean 15 | loginAction: () => void 16 | logoutAction: () => void 17 | } 18 | 19 | export const AppBar = (props: AppBarProps) => { 20 | const [anchorEl, setAnchorEl] = React.useState(null) 21 | // @observable anchorEl: any = null 22 | 23 | const handleMenu = event => { 24 | setAnchorEl(event.currentTarget) 25 | } 26 | 27 | const handleClose = () => { 28 | setAnchorEl(null) 29 | } 30 | 31 | return ( 32 |
36 | 37 | 38 | 59 |
60 | 66 | {' '} 67 | 68 | 69 | 83 | Profile 84 | My Account 85 | {props.loggedIn && ( 86 | { 88 | props.logoutAction() 89 | handleClose() 90 | }} 91 | > 92 | Log Out 93 | 94 | )} 95 | {!props.loggedIn && ( 96 | { 98 | props.loginAction() 99 | handleClose() 100 | }} 101 | > 102 | Log In 103 | 104 | )} 105 | 106 |
107 |
108 |
109 |
110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/AppContainer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Drawer from '@material-ui/core/Drawer' 3 | import { AppBar } from './AppBar' 4 | import { AppkitMenuItem } from '../model' 5 | import { ClientBaseConfig } from '@appkit-client/model/config' 6 | import { ListMenu } from './ListMenu' 7 | import { MuiThemeProvider } from '@material-ui/core/styles' 8 | 9 | export interface AppkitContainerProps { 10 | menuItems: AppkitMenuItem[] 11 | config: ClientBaseConfig 12 | loggedIn: boolean 13 | loginAction: () => void 14 | logoutAction: () => void 15 | children?: JSX.Element | JSX.Element[] 16 | } 17 | 18 | export const AppContainer = (props: AppkitContainerProps) => { 19 | const [drawerOpen, setDrawerOpen] = React.useState(false) 20 | 21 | return ( 22 |
{ 25 | setDrawerOpen(false) 26 | }} 27 | > 28 | 29 | { 33 | setDrawerOpen(!drawerOpen) 34 | }} 35 | title={props.config.appName + ' ' + (props.config.instanceName || '')} 36 | loggedIn={props.loggedIn} 37 | /> 38 | 39 | 40 | 41 |
53 | {props.children} 54 |
55 |
56 |
57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/ButtonSimple.tsx: -------------------------------------------------------------------------------- 1 | import { Button as MuiButton } from '@material-ui/core' 2 | import * as React from 'react' 3 | 4 | export interface ButtonSimpleProps { 5 | label: string 6 | onClick: () => void 7 | noPadding?: boolean 8 | } 9 | 10 | export const ButtonSimple = (props: ButtonSimpleProps) => { 11 | const paddingOverride = props.noPadding === true ? '0px' : undefined 12 | return ( 13 | 17 | {props.label} 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/CheckboxSimple.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphercider/typescript-fullstack-monorepo/8875cf48b95e392bfa85f6af9125cfe4dfe8b273/packages/appkit-client/src/components/CheckboxSimple.tsx -------------------------------------------------------------------------------- /packages/appkit-client/src/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { observable, runInAction } from 'mobx' 3 | 4 | export class ErrorBoundary extends React.Component { 5 | @observable hasError: boolean = false 6 | @observable errorMessage: string = '' 7 | 8 | constructor(props) { 9 | super(props) 10 | this.state = { hasError: false } 11 | } 12 | 13 | static getDerivedStateFromError(error) { 14 | return { hasError: true } 15 | } 16 | 17 | componentDidCatch(error, info) { 18 | runInAction(() => { 19 | this.hasError = true 20 | this.errorMessage = `React encountered an error 21 | ${error.toString()} 22 | ${info.componentStack} 23 | ` 24 | }) 25 | } 26 | 27 | render() { 28 | if (this.hasError) { 29 | return

{this.errorMessage}

30 | } 31 | 32 | return this.props.children 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/InformationModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { ModalSimple } from '@appkit-client/index' 3 | import { Typography } from '@material-ui/core' 4 | 5 | interface Props { 6 | title: string 7 | message: string 8 | onClose: () => void 9 | } 10 | 11 | export const InformationModal = (props: Props) => { 12 | return ( 13 | 14 | {props.message} 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/ListMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { AppkitMenuItem } from '../model/menus' 3 | import { List, ListItem, ListItemText } from '@material-ui/core' 4 | 5 | interface ListMenuProps { 6 | menuItems: AppkitMenuItem[] 7 | } 8 | 9 | export const ListMenu = (props: ListMenuProps) => { 10 | return ( 11 | 12 | {props.menuItems.map(item => { 13 | return ( 14 | 15 | {item.name} 16 | 17 | ) 18 | })} 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/LoginModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { ModalSimple } from './ModalSimple' 3 | import { TextFieldSimple } from './TextFieldSimple' 4 | import { Credentials } from '@appkit-common/model/auth' 5 | 6 | interface LoginModalProps { 7 | callbackWithCredentials: (cred: Credentials) => void 8 | closeModal: () => void 9 | } 10 | 11 | export const LoginModal = (props: LoginModalProps) => { 12 | const [userName, setUserName] = React.useState('') 13 | const [password, setPassword] = React.useState('') 14 | 15 | const doCommit = () => { 16 | props.callbackWithCredentials({ 17 | userName: userName, 18 | password: password 19 | }) 20 | props.closeModal() 21 | } 22 | 23 | return ( 24 | { 27 | props.closeModal() 28 | }} 29 | onCommit={() => { 30 | doCommit() 31 | }} 32 | > 33 |
34 | 41 | 48 |
49 |
50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/ModalSimple.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { 3 | Dialog, 4 | DialogActions, 5 | DialogTitle, 6 | DialogContent 7 | } from '@material-ui/core' 8 | import { ButtonSimple } from './ButtonSimple' 9 | 10 | interface Props { 11 | title: string 12 | onClose: () => void 13 | /** 14 | * To display OK button separate from close button, supply a commit function 15 | */ 16 | onCommit?: () => void 17 | children?: JSX.Element 18 | } 19 | 20 | export const ModalSimple = (props: Props) => { 21 | return ( 22 | 31 | {props.title} 32 | {props.children} 33 | 34 | {props.onCommit && } 35 | 39 | 40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/SingleTextInputModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { ModalSimple } from '@appkit-client/index' 3 | import { TextFieldSimple } from '@appkit-client/components/index' 4 | 5 | interface Props { 6 | title: string 7 | message: string 8 | returnWithTextValue: (val: string) => void 9 | inputLabel: string 10 | style?: React.CSSProperties 11 | } 12 | 13 | export const SingleTextInputModal = (props: Props) => { 14 | const [inputText, setText] = React.useState('') 15 | return ( 16 | { 19 | props.returnWithTextValue(inputText) 20 | }} 21 | > 22 | { 25 | props.returnWithTextValue(inputText) 26 | }} 27 | id="single-text-modal-input" 28 | label={props.inputLabel} 29 | updateValue={(val: string) => { 30 | setText(val) 31 | }} 32 | currentValue={inputText} 33 | style={props.style} 34 | /> 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/SwitchSimple.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphercider/typescript-fullstack-monorepo/8875cf48b95e392bfa85f6af9125cfe4dfe8b273/packages/appkit-client/src/components/SwitchSimple.tsx -------------------------------------------------------------------------------- /packages/appkit-client/src/components/TextFieldSimple.tsx: -------------------------------------------------------------------------------- 1 | import { TextField } from '@material-ui/core' 2 | import * as React from 'react' 3 | 4 | export interface TextFieldSimpleProps { 5 | label: string 6 | updateValue: (val: string) => void 7 | currentValue: string 8 | passwordField?: boolean 9 | onCtrlEnter?: () => void 10 | style?: React.CSSProperties 11 | id?: string 12 | autofocus?: boolean 13 | } 14 | 15 | export const TextFieldSimple = (props: TextFieldSimpleProps) => { 16 | return ( 17 | { 19 | if (event.ctrlKey && event.keyCode === 13) { 20 | if (props.onCtrlEnter != null) { 21 | props.onCtrlEnter() 22 | } 23 | } 24 | }} 25 | autoFocus={props.autofocus} 26 | type={props.passwordField ? 'password' : undefined} 27 | id={props.id} 28 | label={props.label} 29 | value={props.currentValue} 30 | onChange={(event: any) => { 31 | props.updateValue(event.target.value) 32 | }} 33 | style={{ width: '100%', ...props.style }} 34 | > 35 | {props.label} 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/appContainer/theme.ts: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | import { Theme as MaterialUITheme } from '@material-ui/core' 3 | import blue from '@material-ui/core/colors/blue' 4 | import green from '@material-ui/core/colors/green' 5 | import { MarkdownTheme } from '../../model/config' 6 | 7 | export const blueMuiTheme: MaterialUITheme = createMuiTheme({ 8 | typography: { 9 | useNextVariants: true 10 | }, 11 | palette: { 12 | primary: blue, 13 | secondary: green 14 | }, 15 | overrides: { 16 | MuiPaper: { 17 | root: { 18 | backgroundColor: 'rgb(240,240,240)' 19 | } 20 | }, 21 | MuiToolbar: { 22 | dense: { 23 | minHeight: '50px' 24 | }, 25 | root: { 26 | minHeight: '50px' 27 | }, 28 | regular: { 29 | height: '50px', 30 | minHeight: '50px' 31 | } 32 | }, 33 | MuiTypography: { 34 | root: { margin: undefined }, 35 | body1: { fontSize: '14px' }, 36 | h1: { 37 | marginBlockEnd: '10px', 38 | marginBlockStart: '10px', 39 | fontSize: '64px' 40 | }, 41 | h2: { marginBlockEnd: '10px', marginBlockStart: '10px', fontSize: '46px' } 42 | } 43 | } 44 | }) 45 | 46 | export const blueMarkdownTheme: MarkdownTheme = { 47 | h1: { 48 | ...blueMuiTheme.typography.h2, 49 | marginTop: '25px', 50 | marginBottom: '25px' 51 | }, 52 | h2: { 53 | ...blueMuiTheme.typography.h3, 54 | marginTop: '13px', 55 | marginBottom: '13px', 56 | fontWeight: 300 57 | }, 58 | h3: { ...blueMuiTheme.typography.h5, marginTop: '7px', marginBottom: '7px' }, 59 | h4: undefined, 60 | h5: undefined, 61 | h6: undefined, 62 | p: { ...blueMuiTheme.typography.body1, fontSize: '1rem' }, 63 | li: { ...blueMuiTheme.typography.body1, fontSize: '1rem' } 64 | } 65 | -------------------------------------------------------------------------------- /packages/appkit-client/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ButtonSimple' 2 | export * from './AppContainer' 3 | export * from './AppBar' 4 | export * from './ModalSimple' 5 | export * from './ErrorBoundary' 6 | export * from './appContainer/theme' 7 | export * from './TextFieldSimple' 8 | export * from './LoginModal' 9 | -------------------------------------------------------------------------------- /packages/appkit-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/index' 2 | export * from './model/index' 3 | -------------------------------------------------------------------------------- /packages/appkit-client/src/model/config.ts: -------------------------------------------------------------------------------- 1 | import { User } from '@appkit-common/model/auth' 2 | import { Theme } from '@material-ui/core' 3 | import * as React from 'react' 4 | 5 | export interface MarkdownTheme { 6 | h1?: React.CSSProperties 7 | h2?: React.CSSProperties 8 | h3?: React.CSSProperties 9 | h4?: React.CSSProperties 10 | h5?: React.CSSProperties 11 | h6?: React.CSSProperties 12 | p?: React.CSSProperties 13 | li?: React.CSSProperties 14 | } 15 | 16 | export interface ClientBaseConfig { 17 | /** 18 | * This is the name of the app - e.g. to be displayed in the app title bar 19 | */ 20 | appName: string 21 | /** 22 | * This is the fully qualified server host for server API calls- i.e. protocol://host:port 23 | */ 24 | host: string 25 | /** 26 | * If you have multiple application instances - this can keep track of the name of the instance you are in 27 | */ 28 | instanceName: string 29 | /** 30 | * The client build version 31 | */ 32 | clientVersion: string 33 | /** 34 | * The server build version. Will be received from server upon login 35 | */ 36 | serverVersion: string | null 37 | /** 38 | * The current logged in user. Useful for displaying profile information to the user. 39 | */ 40 | user: User | null 41 | /** 42 | * Whether or not our app is running in development mode 43 | */ 44 | inDevelopment: boolean 45 | muiTheme: Theme 46 | markdownTheme: MarkdownTheme 47 | } 48 | -------------------------------------------------------------------------------- /packages/appkit-client/src/model/index.ts: -------------------------------------------------------------------------------- 1 | // export client specific models 2 | export * from './config' 3 | export * from './menus' 4 | 5 | // export common models 6 | export * from '@appkit-common/model/index' 7 | -------------------------------------------------------------------------------- /packages/appkit-client/src/model/menus.ts: -------------------------------------------------------------------------------- 1 | // list item / handler ifc here 2 | 3 | export interface AppkitMenuItem { 4 | name: string, 5 | onClick: () => void 6 | } -------------------------------------------------------------------------------- /packages/appkit-client/src/stores/InputStore.ts: -------------------------------------------------------------------------------- 1 | import { runInAction, observable } from 'mobx' 2 | 3 | export default class InputStore { 4 | rootStore: any 5 | @observable inputTitle: string = '' 6 | singleTextValueReturnCallback: (strVal: string) => void 7 | @observable singleTextInputOpen: boolean = false 8 | @observable singleTextInputValue: string = '' 9 | 10 | constructor(rootStore: any) { 11 | this.rootStore = rootStore 12 | } 13 | 14 | openSingleTextInputModal = ( 15 | title: string, 16 | callbackWithFinalValue: (val: string) => void 17 | ) => { 18 | runInAction(() => { 19 | this.inputTitle = title 20 | this.singleTextValueReturnCallback = callbackWithFinalValue 21 | this.singleTextInputOpen = true 22 | }) 23 | } 24 | 25 | closeSingleTextInputModal = () => { 26 | runInAction(() => { 27 | this.singleTextInputOpen = false 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/appkit-client/src/stores/NotificationStore.ts: -------------------------------------------------------------------------------- 1 | import { action, observable } from 'mobx' 2 | 3 | export default class NotificationStore { 4 | rootStore: any 5 | @observable infoModalOpen: boolean = false 6 | @observable infoModalMessage: string = '' 7 | @observable infoModalTitle: string = '' 8 | 9 | constructor(rootStore: any) { 10 | this.rootStore = rootStore 11 | } 12 | 13 | @action 14 | displayInfoModal = (title: string, message: string) => { 15 | console.log(`DISPLAYING INFO MODAL`) 16 | this.infoModalMessage = message 17 | this.infoModalTitle = title 18 | this.infoModalOpen = true 19 | } 20 | 21 | @action 22 | closeInfoModal = () => { 23 | this.infoModalOpen = false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/appkit-client/src/util/fetch.ts: -------------------------------------------------------------------------------- 1 | export async function fetchBodyPost( 2 | url: string, 3 | reqBodyObject: Object, 4 | authorizationHeaderValue: string | null = null 5 | ) { 6 | try { 7 | let params: RequestInit = { 8 | method: 'POST', 9 | headers: { 10 | 'Content-Type': 'application/json; charset=utf-8' 11 | }, 12 | body: JSON.stringify(reqBodyObject) 13 | } 14 | 15 | if (authorizationHeaderValue != null && params.headers != null) { 16 | params.headers['Authorization'] = authorizationHeaderValue 17 | } 18 | 19 | const res = await fetch(url, params) 20 | return await res.json() 21 | } catch (err) { 22 | console.error(`Problem calling fetch post from gui utils`) 23 | throw err 24 | } 25 | } 26 | 27 | export async function fetchBodyGet( 28 | url: string, 29 | authorizationHeaderValue: string | null = null 30 | ) { 31 | try { 32 | let params: RequestInit = { 33 | method: 'GET', 34 | mode: 'cors', 35 | headers: {} 36 | } 37 | 38 | if (authorizationHeaderValue != null && params.headers != null) { 39 | params.headers['Authorization'] = authorizationHeaderValue 40 | } 41 | 42 | const resRaw = await fetch(url, params) 43 | const res = await resRaw.json() 44 | // console.log(`returning`) 45 | // console.log(JSON.stringify(res)) 46 | return res 47 | } catch (err) { 48 | console.error(`Problem calling fetch post from gui utils`) 49 | throw err 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/appkit-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "jsx": "react", 6 | "baseUrl": "..", 7 | "lib": ["dom", "es2018"] 8 | }, 9 | "include": ["./src/**/*", "../appkit-server/src/model/auth.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/appkit-common/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}\\index.js" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /packages/appkit-common/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.singleQuote": true, 3 | "prettier.semi": false 4 | } -------------------------------------------------------------------------------- /packages/appkit-common/src/model/auth.ts: -------------------------------------------------------------------------------- 1 | export interface Credentials { 2 | userName: string 3 | password: string 4 | } 5 | 6 | export interface User { 7 | userName: string 8 | email: string 9 | firstName: string 10 | lastName: string 11 | } 12 | 13 | export interface LoginRequest { 14 | credentials: Credentials 15 | } 16 | 17 | export interface LoginResponse { 18 | succeeded: boolean 19 | token: string 20 | user: User 21 | serverVersion: string 22 | } 23 | 24 | export interface CheckTokenRequest { 25 | token: string 26 | } 27 | 28 | export interface CheckTokenResponse { 29 | succeeded: boolean 30 | user: User 31 | serverVersion: string 32 | } 33 | -------------------------------------------------------------------------------- /packages/appkit-common/src/model/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth' 2 | export * from './misc' 3 | -------------------------------------------------------------------------------- /packages/appkit-common/src/model/misc.ts: -------------------------------------------------------------------------------- 1 | export interface StringContainer { 2 | string: string 3 | } 4 | -------------------------------------------------------------------------------- /packages/appkit-common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "jsx": "react", 6 | "baseUrl": ".." 7 | }, 8 | "include": ["./src/**/*"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/appkit-server/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.semi": false, 3 | "prettier.singleQuote": true 4 | } -------------------------------------------------------------------------------- /packages/appkit-server/src/model/config.ts: -------------------------------------------------------------------------------- 1 | export interface BaseServerConfig { 2 | port: number 3 | version: string 4 | authSecretKey: string 5 | configEnvironmentVariable: string 6 | rootDir: string 7 | nodeEnvironment: 'production' | 'development' 8 | } 9 | -------------------------------------------------------------------------------- /packages/appkit-server/src/util/auth.ts: -------------------------------------------------------------------------------- 1 | import { sign, verify } from 'jsonwebtoken' 2 | 3 | /** 4 | * @returns {string} token 5 | */ 6 | export function signJwt(payload: any, secretKey: string): string { 7 | return sign(payload, secretKey) 8 | } 9 | 10 | /** 11 | * @returns {string} decoded token 12 | */ 13 | export function verifyJwt(jwt: string, secretKey: string): string { 14 | return verify(jwt, secretKey) 15 | } 16 | -------------------------------------------------------------------------------- /packages/appkit-server/src/util/env.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * retrieve a JSON config environment variable, parse it, and return it 3 | */ 4 | export function getServerConfigEnvVar(envVarName: string): Object { 5 | try { 6 | const varJson = process.env[envVarName] 7 | if (varJson == null) { 8 | throw new Error(`environment variable name ${envVarName} doesn't exist`) 9 | } 10 | 11 | try { 12 | const obj = JSON.parse(varJson) as Object 13 | return obj 14 | } catch (err) { 15 | throw new Error(`Error parsing environment variable JSON ${varJson} `) 16 | } 17 | } catch (err) { 18 | throw new Error(`Error getting environment variable ${envVarName} `) 19 | } 20 | } 21 | 22 | export function inProduction(): boolean { 23 | const env = process.env.NODE_ENV 24 | if (env == null) { 25 | return false 26 | } 27 | 28 | return env.toLowerCase().substr(0, 3) === 'dev' 29 | } 30 | 31 | /** 32 | * returns the location on disk of the location of node.exe which is running 33 | */ 34 | export function getWorkingDirectory(): string { 35 | return process.cwd() 36 | } 37 | -------------------------------------------------------------------------------- /packages/appkit-server/src/util/file.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | 4 | /** 5 | * Get an array of filenames at a provided directory 6 | * 7 | * IF the name returned 8 | */ 9 | export async function getFileList(directoryPath: string): Promise { 10 | return new Promise((resolve: (res: string[]) => void, reject) => { 11 | const fileNames: string[] = [] 12 | try { 13 | const files = fs.readdirSync(directoryPath) 14 | 15 | files.forEach(file => { 16 | const stat = fs.statSync(directoryPath) 17 | if (stat.isDirectory()) { 18 | const joinedName = path.join(directoryPath, file) 19 | const moreFiles = fs.readdirSync(joinedName) 20 | moreFiles.forEach(otherfile => { 21 | const nestedFile = path.join(joinedName, otherfile) 22 | if (fs.statSync(nestedFile).isDirectory() === false) { 23 | fileNames.push(nestedFile) 24 | } 25 | }) 26 | // const recursed = await getFileList() 27 | fileNames.push() 28 | } else { 29 | fileNames.push(file) 30 | } 31 | }) 32 | resolve(fileNames) 33 | } catch (err) { 34 | reject(err) 35 | } 36 | }) 37 | } 38 | 39 | export async function getFileContentsString(filePath: string): Promise { 40 | return new Promise((resolve, reject) => { 41 | try { 42 | fs.readFile(filePath, { encoding: 'utf8' }, (err, data) => { 43 | if (err) { 44 | reject(err) 45 | } 46 | resolve(data) 47 | }) 48 | } catch (err) { 49 | reject(err) 50 | } 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /packages/appkit-server/src/util/index.ts: -------------------------------------------------------------------------------- 1 | // export server specific utils 2 | export * from './auth' 3 | export * from './env' 4 | export * from './file' 5 | export * from './time' 6 | 7 | // export common utils once available 8 | -------------------------------------------------------------------------------- /packages/appkit-server/src/util/time.ts: -------------------------------------------------------------------------------- 1 | export async function delay(msDelay: number): Promise { 2 | return new Promise(resolve => { 3 | setTimeout(() => { 4 | resolve() 5 | }, msDelay) 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /packages/appkit-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "baseUrl": "..", 6 | "lib": ["es2018"] 7 | }, 8 | "include": ["./src/**/*", "../appkit-server/src/**/*"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/boilerplate-client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /packages/boilerplate-client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.singleQuote": true, 3 | "prettier.semi": false 4 | } -------------------------------------------------------------------------------- /packages/boilerplate-client/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "client start", 8 | "type": "npm", 9 | "script": "start", 10 | "problemMatcher": [] 11 | }, 12 | { 13 | "type": "npm", 14 | "script": "pack", 15 | "problemMatcher": [] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/boilerplate-client/README.md: -------------------------------------------------------------------------------- 1 | # File structure 2 | 3 | [Fractal](https://hackernoon.com/fractal-a-react-app-structure-for-infinite-scale-4dab943092af) 4 | -------------------------------------------------------------------------------- /packages/boilerplate-client/jest.debug.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest" 4 | }, 5 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 6 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 7 | "watchPathIgnorePatterns": ["/node_modules/"], 8 | "testPathIgnorePatterns": ["/node_modules/", "/src/"], 9 | "moduleNameMapper": { 10 | "^@root(.*)$": "/dist$1", 11 | "^@utils(.*)$": "/dist/utils$1", 12 | "^@components(.*)$": "/dist/components/generic$1", 13 | "^@stores(.*)$": "/dist/stores$1", 14 | "^@styles(.*)$": "/dist/styles$1", 15 | "^@model(.*)$": "/dist/model$1", 16 | "\\.(css|less)$": "/__mocks__/styleMock.js", 17 | "\\.(gif|ttf|eot|svg)$": "/__mocks__/fileMock.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/boilerplate-client/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/runtime": { 8 | "version": "7.1.2", 9 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz", 10 | "integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==", 11 | "requires": { 12 | "regenerator-runtime": "^0.12.0" 13 | } 14 | }, 15 | "@material-ui/core": { 16 | "version": "3.6.0", 17 | "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.6.0.tgz", 18 | "integrity": "sha512-Erg9PI1xYa8pQFZf6tvWMGyoKCup+P3HJ6phagKGGPzNQ8dswpgW/qNkTOJNG2DaTtXDmkOswpjWcceHqURwYw==", 19 | "requires": { 20 | "@babel/runtime": "7.1.2", 21 | "@material-ui/utils": "^3.0.0-alpha.0", 22 | "@types/jss": "^9.5.6", 23 | "@types/react-transition-group": "^2.0.8", 24 | "brcast": "^3.0.1", 25 | "classnames": "^2.2.5", 26 | "csstype": "^2.5.2", 27 | "debounce": "^1.1.0", 28 | "deepmerge": "^2.0.1", 29 | "dom-helpers": "^3.2.1", 30 | "hoist-non-react-statics": "^3.0.0", 31 | "is-plain-object": "^2.0.4", 32 | "jss": "^9.3.3", 33 | "jss-camel-case": "^6.0.0", 34 | "jss-default-unit": "^8.0.2", 35 | "jss-global": "^3.0.0", 36 | "jss-nested": "^6.0.1", 37 | "jss-props-sort": "^6.0.0", 38 | "jss-vendor-prefixer": "^7.0.0", 39 | "keycode": "^2.1.9", 40 | "normalize-scroll-left": "^0.1.2", 41 | "popper.js": "^1.14.1", 42 | "prop-types": "^15.6.0", 43 | "react-event-listener": "^0.6.2", 44 | "react-transition-group": "^2.2.1", 45 | "recompose": "0.28.0 - 0.30.0", 46 | "warning": "^4.0.1" 47 | }, 48 | "dependencies": { 49 | "hoist-non-react-statics": { 50 | "version": "3.2.0", 51 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.2.0.tgz", 52 | "integrity": "sha512-3IascCRfaEkbmHjJnUxWSspIUE1okLPjGTMVXW8zraUo1t3yg1BadKAxAGILHwgoBzmMnzrgeeaDGBvpuPz6dA==", 53 | "requires": { 54 | "react-is": "^16.3.2" 55 | } 56 | } 57 | } 58 | }, 59 | "@material-ui/icons": { 60 | "version": "3.0.1", 61 | "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-3.0.1.tgz", 62 | "integrity": "sha512-1kNcxYiIT1x8iDPEAlgmKrfRTIV8UyK6fLVcZ9kMHIKGWft9I451V5mvSrbCjbf7MX1TbLWzZjph0aVCRf9MqQ==", 63 | "requires": { 64 | "@babel/runtime": "7.0.0", 65 | "recompose": "^0.29.0" 66 | }, 67 | "dependencies": { 68 | "@babel/runtime": { 69 | "version": "7.0.0", 70 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", 71 | "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", 72 | "requires": { 73 | "regenerator-runtime": "^0.12.0" 74 | } 75 | }, 76 | "recompose": { 77 | "version": "0.29.0", 78 | "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.29.0.tgz", 79 | "integrity": "sha512-J/qLXNU4W+AeHCDR70ajW8eMd1uroqZaECTj6qqDLPMILz3y0EzpYlvrnxKB9DnqcngWrtGwjXY9JeXaW9kS1A==", 80 | "requires": { 81 | "@babel/runtime": "^7.0.0", 82 | "change-emitter": "^0.1.2", 83 | "fbjs": "^0.8.1", 84 | "hoist-non-react-statics": "^2.3.1", 85 | "react-lifecycles-compat": "^3.0.2", 86 | "symbol-observable": "^1.0.4" 87 | } 88 | }, 89 | "regenerator-runtime": { 90 | "version": "0.12.1", 91 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", 92 | "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" 93 | } 94 | } 95 | }, 96 | "@material-ui/utils": { 97 | "version": "3.0.0-alpha.0", 98 | "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.0.tgz", 99 | "integrity": "sha512-HwC/pBcPG81UP4Jh0BaH2i4wAY9aDOrvL5+B/7J6cyFlG/Wip5Li+qI+GDlk9MR3kpG8v0sxfpWA0IlQYcHK6g==", 100 | "requires": { 101 | "@babel/runtime": "7.1.2" 102 | } 103 | }, 104 | "@types/jss": { 105 | "version": "9.5.7", 106 | "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.7.tgz", 107 | "integrity": "sha512-OZimStu2QdDMtZ0h72JXqvLVbWUjXd5ZLk8vxLmfuC/nM1AabRyyGoxSufnzixrbpEcVcyy/JV5qeQu2JnjVZw==", 108 | "requires": { 109 | "csstype": "^2.0.0", 110 | "indefinite-observable": "^1.0.1" 111 | } 112 | }, 113 | "@types/prop-types": { 114 | "version": "15.5.6", 115 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.6.tgz", 116 | "integrity": "sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ==" 117 | }, 118 | "@types/react": { 119 | "version": "16.7.8", 120 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.7.8.tgz", 121 | "integrity": "sha512-7Vua2IYokMiPBk0WWXaDt4Cg3XVcwU6HpkuPhvwhBrVEy4SafpcvEfGYByoY9jxyFMiegYQPaSOT+H+AY00CPw==", 122 | "requires": { 123 | "@types/prop-types": "*", 124 | "csstype": "^2.2.0" 125 | } 126 | }, 127 | "@types/react-transition-group": { 128 | "version": "2.0.14", 129 | "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.14.tgz", 130 | "integrity": "sha512-pa7qB0/mkhwWMBFoXhX8BcntK8G4eQl4sIfSrJCxnivTYRQWjOWf2ClR9bWdm0EUFBDHzMbKYS+QYfDtBzkY4w==", 131 | "requires": { 132 | "@types/react": "*" 133 | } 134 | }, 135 | "ansi-regex": { 136 | "version": "2.1.1", 137 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 138 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 139 | "dev": true 140 | }, 141 | "asap": { 142 | "version": "2.0.6", 143 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 144 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 145 | }, 146 | "big.js": { 147 | "version": "3.2.0", 148 | "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", 149 | "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", 150 | "dev": true 151 | }, 152 | "boolbase": { 153 | "version": "1.0.0", 154 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 155 | "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", 156 | "dev": true 157 | }, 158 | "brcast": { 159 | "version": "3.0.1", 160 | "resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.1.tgz", 161 | "integrity": "sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg==" 162 | }, 163 | "camel-case": { 164 | "version": "3.0.0", 165 | "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", 166 | "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", 167 | "dev": true, 168 | "requires": { 169 | "no-case": "^2.2.0", 170 | "upper-case": "^1.1.1" 171 | } 172 | }, 173 | "change-emitter": { 174 | "version": "0.1.6", 175 | "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", 176 | "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" 177 | }, 178 | "classnames": { 179 | "version": "2.2.6", 180 | "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", 181 | "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" 182 | }, 183 | "clean-css": { 184 | "version": "4.2.1", 185 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", 186 | "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", 187 | "dev": true, 188 | "requires": { 189 | "source-map": "~0.6.0" 190 | }, 191 | "dependencies": { 192 | "source-map": { 193 | "version": "0.6.1", 194 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 195 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 196 | "dev": true 197 | } 198 | } 199 | }, 200 | "core-js": { 201 | "version": "1.2.7", 202 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 203 | "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" 204 | }, 205 | "core-util-is": { 206 | "version": "1.0.2", 207 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 208 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 209 | "dev": true 210 | }, 211 | "css-select": { 212 | "version": "1.2.0", 213 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", 214 | "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", 215 | "dev": true, 216 | "requires": { 217 | "boolbase": "~1.0.0", 218 | "css-what": "2.1", 219 | "domutils": "1.5.1", 220 | "nth-check": "~1.0.1" 221 | } 222 | }, 223 | "css-vendor": { 224 | "version": "0.3.8", 225 | "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz", 226 | "integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=", 227 | "requires": { 228 | "is-in-browser": "^1.0.2" 229 | } 230 | }, 231 | "css-what": { 232 | "version": "2.1.0", 233 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", 234 | "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", 235 | "dev": true 236 | }, 237 | "csstype": { 238 | "version": "2.5.7", 239 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.7.tgz", 240 | "integrity": "sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw==" 241 | }, 242 | "debounce": { 243 | "version": "1.2.0", 244 | "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", 245 | "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==" 246 | }, 247 | "deepmerge": { 248 | "version": "2.1.1", 249 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.1.tgz", 250 | "integrity": "sha512-urQxA1smbLZ2cBbXbaYObM1dJ82aJ2H57A1C/Kklfh/ZN1bgH4G/n5KWhdNfOK11W98gqZfyYj7W4frJJRwA2w==" 251 | }, 252 | "define-properties": { 253 | "version": "1.1.3", 254 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 255 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 256 | "dev": true, 257 | "requires": { 258 | "object-keys": "^1.0.12" 259 | } 260 | }, 261 | "director": { 262 | "version": "1.2.8", 263 | "resolved": "https://registry.npmjs.org/director/-/director-1.2.8.tgz", 264 | "integrity": "sha1-xtm03YkOmv9TZRg/6cyOc5lM8tU=" 265 | }, 266 | "dom-converter": { 267 | "version": "0.1.4", 268 | "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", 269 | "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", 270 | "dev": true, 271 | "requires": { 272 | "utila": "~0.3" 273 | }, 274 | "dependencies": { 275 | "utila": { 276 | "version": "0.3.3", 277 | "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", 278 | "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", 279 | "dev": true 280 | } 281 | } 282 | }, 283 | "dom-helpers": { 284 | "version": "3.4.0", 285 | "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", 286 | "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", 287 | "requires": { 288 | "@babel/runtime": "^7.1.2" 289 | } 290 | }, 291 | "dom-serializer": { 292 | "version": "0.1.0", 293 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", 294 | "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", 295 | "dev": true, 296 | "requires": { 297 | "domelementtype": "~1.1.1", 298 | "entities": "~1.1.1" 299 | }, 300 | "dependencies": { 301 | "domelementtype": { 302 | "version": "1.1.3", 303 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", 304 | "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", 305 | "dev": true 306 | } 307 | } 308 | }, 309 | "domelementtype": { 310 | "version": "1.3.0", 311 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", 312 | "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", 313 | "dev": true 314 | }, 315 | "domhandler": { 316 | "version": "2.1.0", 317 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", 318 | "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", 319 | "dev": true, 320 | "requires": { 321 | "domelementtype": "1" 322 | } 323 | }, 324 | "domutils": { 325 | "version": "1.5.1", 326 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 327 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", 328 | "dev": true, 329 | "requires": { 330 | "dom-serializer": "0", 331 | "domelementtype": "1" 332 | } 333 | }, 334 | "emojis-list": { 335 | "version": "2.1.0", 336 | "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", 337 | "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", 338 | "dev": true 339 | }, 340 | "encoding": { 341 | "version": "0.1.12", 342 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 343 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", 344 | "requires": { 345 | "iconv-lite": "~0.4.13" 346 | } 347 | }, 348 | "entities": { 349 | "version": "1.1.1", 350 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", 351 | "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", 352 | "dev": true 353 | }, 354 | "es-abstract": { 355 | "version": "1.12.0", 356 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", 357 | "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", 358 | "dev": true, 359 | "requires": { 360 | "es-to-primitive": "^1.1.1", 361 | "function-bind": "^1.1.1", 362 | "has": "^1.0.1", 363 | "is-callable": "^1.1.3", 364 | "is-regex": "^1.0.4" 365 | } 366 | }, 367 | "es-to-primitive": { 368 | "version": "1.1.1", 369 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 370 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 371 | "dev": true, 372 | "requires": { 373 | "is-callable": "^1.1.1", 374 | "is-date-object": "^1.0.1", 375 | "is-symbol": "^1.0.1" 376 | } 377 | }, 378 | "fbjs": { 379 | "version": "0.8.17", 380 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", 381 | "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", 382 | "requires": { 383 | "core-js": "^1.0.0", 384 | "isomorphic-fetch": "^2.1.1", 385 | "loose-envify": "^1.0.0", 386 | "object-assign": "^4.1.0", 387 | "promise": "^7.1.1", 388 | "setimmediate": "^1.0.5", 389 | "ua-parser-js": "^0.7.18" 390 | } 391 | }, 392 | "function-bind": { 393 | "version": "1.1.1", 394 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 395 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 396 | "dev": true 397 | }, 398 | "has": { 399 | "version": "1.0.3", 400 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 401 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 402 | "dev": true, 403 | "requires": { 404 | "function-bind": "^1.1.1" 405 | } 406 | }, 407 | "he": { 408 | "version": "1.1.1", 409 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 410 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 411 | "dev": true 412 | }, 413 | "hoist-non-react-statics": { 414 | "version": "2.5.5", 415 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", 416 | "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" 417 | }, 418 | "html-minifier": { 419 | "version": "3.5.20", 420 | "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", 421 | "integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", 422 | "dev": true, 423 | "requires": { 424 | "camel-case": "3.0.x", 425 | "clean-css": "4.2.x", 426 | "commander": "2.17.x", 427 | "he": "1.1.x", 428 | "param-case": "2.1.x", 429 | "relateurl": "0.2.x", 430 | "uglify-js": "3.4.x" 431 | }, 432 | "dependencies": { 433 | "commander": { 434 | "version": "2.17.1", 435 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", 436 | "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", 437 | "dev": true 438 | }, 439 | "source-map": { 440 | "version": "0.6.1", 441 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 442 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 443 | "dev": true 444 | }, 445 | "uglify-js": { 446 | "version": "3.4.9", 447 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", 448 | "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", 449 | "dev": true, 450 | "requires": { 451 | "commander": "~2.17.1", 452 | "source-map": "~0.6.1" 453 | } 454 | } 455 | } 456 | }, 457 | "html-webpack-plugin": { 458 | "version": "3.2.0", 459 | "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", 460 | "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", 461 | "dev": true, 462 | "requires": { 463 | "html-minifier": "^3.2.3", 464 | "loader-utils": "^0.2.16", 465 | "lodash": "^4.17.3", 466 | "pretty-error": "^2.0.2", 467 | "tapable": "^1.0.0", 468 | "toposort": "^1.0.0", 469 | "util.promisify": "1.0.0" 470 | }, 471 | "dependencies": { 472 | "loader-utils": { 473 | "version": "0.2.17", 474 | "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", 475 | "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", 476 | "dev": true, 477 | "requires": { 478 | "big.js": "^3.1.3", 479 | "emojis-list": "^2.0.0", 480 | "json5": "^0.5.0", 481 | "object-assign": "^4.0.1" 482 | } 483 | } 484 | } 485 | }, 486 | "htmlparser2": { 487 | "version": "3.3.0", 488 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", 489 | "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", 490 | "dev": true, 491 | "requires": { 492 | "domelementtype": "1", 493 | "domhandler": "2.1", 494 | "domutils": "1.1", 495 | "readable-stream": "1.0" 496 | }, 497 | "dependencies": { 498 | "domutils": { 499 | "version": "1.1.6", 500 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", 501 | "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", 502 | "dev": true, 503 | "requires": { 504 | "domelementtype": "1" 505 | } 506 | }, 507 | "readable-stream": { 508 | "version": "1.0.34", 509 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", 510 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", 511 | "dev": true, 512 | "requires": { 513 | "core-util-is": "~1.0.0", 514 | "inherits": "~2.0.1", 515 | "isarray": "0.0.1", 516 | "string_decoder": "~0.10.x" 517 | } 518 | }, 519 | "string_decoder": { 520 | "version": "0.10.31", 521 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 522 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 523 | "dev": true 524 | } 525 | } 526 | }, 527 | "hyphenate-style-name": { 528 | "version": "1.0.2", 529 | "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz", 530 | "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=" 531 | }, 532 | "iconv-lite": { 533 | "version": "0.4.24", 534 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 535 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 536 | "requires": { 537 | "safer-buffer": ">= 2.1.2 < 3" 538 | } 539 | }, 540 | "indefinite-observable": { 541 | "version": "1.0.2", 542 | "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.2.tgz", 543 | "integrity": "sha512-Mps0898zEduHyPhb7UCgNmfzlqNZknVmaFz5qzr0mm04YQ5FGLhAyK/dJ+NaRxGyR6juQXIxh5Ev0xx+qq0nYA==", 544 | "requires": { 545 | "symbol-observable": "1.2.0" 546 | } 547 | }, 548 | "inherits": { 549 | "version": "2.0.3", 550 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 551 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 552 | "dev": true 553 | }, 554 | "is-callable": { 555 | "version": "1.1.4", 556 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 557 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 558 | "dev": true 559 | }, 560 | "is-date-object": { 561 | "version": "1.0.1", 562 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 563 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 564 | "dev": true 565 | }, 566 | "is-in-browser": { 567 | "version": "1.1.3", 568 | "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", 569 | "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" 570 | }, 571 | "is-plain-object": { 572 | "version": "2.0.4", 573 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 574 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 575 | "requires": { 576 | "isobject": "^3.0.1" 577 | } 578 | }, 579 | "is-regex": { 580 | "version": "1.0.4", 581 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 582 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 583 | "dev": true, 584 | "requires": { 585 | "has": "^1.0.1" 586 | } 587 | }, 588 | "is-stream": { 589 | "version": "1.1.0", 590 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 591 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 592 | }, 593 | "is-symbol": { 594 | "version": "1.0.1", 595 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 596 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", 597 | "dev": true 598 | }, 599 | "isarray": { 600 | "version": "0.0.1", 601 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 602 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 603 | "dev": true 604 | }, 605 | "isobject": { 606 | "version": "3.0.1", 607 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 608 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" 609 | }, 610 | "isomorphic-fetch": { 611 | "version": "2.2.1", 612 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 613 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 614 | "requires": { 615 | "node-fetch": "^1.0.1", 616 | "whatwg-fetch": ">=0.10.0" 617 | } 618 | }, 619 | "js-tokens": { 620 | "version": "4.0.0", 621 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 622 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 623 | }, 624 | "json5": { 625 | "version": "0.5.1", 626 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", 627 | "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", 628 | "dev": true 629 | }, 630 | "jss": { 631 | "version": "9.8.7", 632 | "resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz", 633 | "integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==", 634 | "requires": { 635 | "is-in-browser": "^1.1.3", 636 | "symbol-observable": "^1.1.0", 637 | "warning": "^3.0.0" 638 | }, 639 | "dependencies": { 640 | "warning": { 641 | "version": "3.0.0", 642 | "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", 643 | "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", 644 | "requires": { 645 | "loose-envify": "^1.0.0" 646 | } 647 | } 648 | } 649 | }, 650 | "jss-camel-case": { 651 | "version": "6.1.0", 652 | "resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz", 653 | "integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==", 654 | "requires": { 655 | "hyphenate-style-name": "^1.0.2" 656 | } 657 | }, 658 | "jss-default-unit": { 659 | "version": "8.0.2", 660 | "resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz", 661 | "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg==" 662 | }, 663 | "jss-global": { 664 | "version": "3.0.0", 665 | "resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz", 666 | "integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q==" 667 | }, 668 | "jss-nested": { 669 | "version": "6.0.1", 670 | "resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz", 671 | "integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==", 672 | "requires": { 673 | "warning": "^3.0.0" 674 | }, 675 | "dependencies": { 676 | "warning": { 677 | "version": "3.0.0", 678 | "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", 679 | "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", 680 | "requires": { 681 | "loose-envify": "^1.0.0" 682 | } 683 | } 684 | } 685 | }, 686 | "jss-props-sort": { 687 | "version": "6.0.0", 688 | "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz", 689 | "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g==" 690 | }, 691 | "jss-vendor-prefixer": { 692 | "version": "7.0.0", 693 | "resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz", 694 | "integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==", 695 | "requires": { 696 | "css-vendor": "^0.3.8" 697 | } 698 | }, 699 | "keycode": { 700 | "version": "2.2.0", 701 | "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", 702 | "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" 703 | }, 704 | "lodash": { 705 | "version": "4.17.10", 706 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 707 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", 708 | "dev": true 709 | }, 710 | "loose-envify": { 711 | "version": "1.4.0", 712 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 713 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 714 | "requires": { 715 | "js-tokens": "^3.0.0 || ^4.0.0" 716 | } 717 | }, 718 | "lower-case": { 719 | "version": "1.1.4", 720 | "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", 721 | "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", 722 | "dev": true 723 | }, 724 | "mobx": { 725 | "version": "5.6.0", 726 | "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.6.0.tgz", 727 | "integrity": "sha512-xrA0tBnSMANXCDjS2W/6YTasesA8mkg9o+v/Bw/OcbXaRVE6/soVwDMWIh7A6TwMDY4tYQprQJZ5WQN1TRSb3A==" 728 | }, 729 | "mobx-react": { 730 | "version": "5.4.2", 731 | "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.2.tgz", 732 | "integrity": "sha512-alSN0KDAAOb1OkYujfoJjjk0JWxWRKO4sLGB4hN2CuvaJMrlj7bhGQe7CBMJvEFNjtJRbhJcquYVjQ3rrH2zQQ==", 733 | "requires": { 734 | "hoist-non-react-statics": "^3.0.0", 735 | "react-lifecycles-compat": "^3.0.2" 736 | }, 737 | "dependencies": { 738 | "hoist-non-react-statics": { 739 | "version": "3.2.0", 740 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.2.0.tgz", 741 | "integrity": "sha512-3IascCRfaEkbmHjJnUxWSspIUE1okLPjGTMVXW8zraUo1t3yg1BadKAxAGILHwgoBzmMnzrgeeaDGBvpuPz6dA==", 742 | "requires": { 743 | "react-is": "^16.3.2" 744 | } 745 | } 746 | } 747 | }, 748 | "no-case": { 749 | "version": "2.3.2", 750 | "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", 751 | "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", 752 | "dev": true, 753 | "requires": { 754 | "lower-case": "^1.1.1" 755 | } 756 | }, 757 | "node-fetch": { 758 | "version": "1.7.3", 759 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", 760 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", 761 | "requires": { 762 | "encoding": "^0.1.11", 763 | "is-stream": "^1.0.1" 764 | } 765 | }, 766 | "normalize-scroll-left": { 767 | "version": "0.1.2", 768 | "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz", 769 | "integrity": "sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg==" 770 | }, 771 | "nth-check": { 772 | "version": "1.0.1", 773 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", 774 | "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", 775 | "dev": true, 776 | "requires": { 777 | "boolbase": "~1.0.0" 778 | } 779 | }, 780 | "object-assign": { 781 | "version": "4.1.1", 782 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 783 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 784 | }, 785 | "object-keys": { 786 | "version": "1.0.12", 787 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", 788 | "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", 789 | "dev": true 790 | }, 791 | "object.getownpropertydescriptors": { 792 | "version": "2.0.3", 793 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", 794 | "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", 795 | "dev": true, 796 | "requires": { 797 | "define-properties": "^1.1.2", 798 | "es-abstract": "^1.5.1" 799 | } 800 | }, 801 | "param-case": { 802 | "version": "2.1.1", 803 | "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", 804 | "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", 805 | "dev": true, 806 | "requires": { 807 | "no-case": "^2.2.0" 808 | } 809 | }, 810 | "popper.js": { 811 | "version": "1.14.6", 812 | "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", 813 | "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" 814 | }, 815 | "prettier": { 816 | "version": "1.15.2", 817 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.15.2.tgz", 818 | "integrity": "sha512-YgPLFFA0CdKL4Eg2IHtUSjzj/BWgszDHiNQAe0VAIBse34148whfdzLagRL+QiKS+YfK5ftB6X4v/MBw8yCoug==", 819 | "dev": true 820 | }, 821 | "pretty-error": { 822 | "version": "2.1.1", 823 | "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", 824 | "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", 825 | "dev": true, 826 | "requires": { 827 | "renderkid": "^2.0.1", 828 | "utila": "~0.4" 829 | } 830 | }, 831 | "promise": { 832 | "version": "7.3.1", 833 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 834 | "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 835 | "requires": { 836 | "asap": "~2.0.3" 837 | } 838 | }, 839 | "prop-types": { 840 | "version": "15.6.2", 841 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", 842 | "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", 843 | "requires": { 844 | "loose-envify": "^1.3.1", 845 | "object-assign": "^4.1.1" 846 | } 847 | }, 848 | "react": { 849 | "version": "16.7.0-alpha.2", 850 | "resolved": "https://registry.npmjs.org/react/-/react-16.7.0-alpha.2.tgz", 851 | "integrity": "sha512-Xh1CC8KkqIojhC+LFXd21jxlVtzoVYdGnQAi/I2+dxbmos9ghbx5TQf9/nDxc4WxaFfUQJkya0w1k6rMeyIaxQ==", 852 | "requires": { 853 | "loose-envify": "^1.1.0", 854 | "object-assign": "^4.1.1", 855 | "prop-types": "^15.6.2", 856 | "scheduler": "^0.12.0-alpha.2" 857 | } 858 | }, 859 | "react-dom": { 860 | "version": "16.7.0-alpha.2", 861 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0-alpha.2.tgz", 862 | "integrity": "sha512-o0mMw8jBlwHjGZEy/vvKd/6giAX0+skREMOTs3/QHmgi+yAhUClp4My4Z9lsKy3SXV+03uPdm1l/QM7NTcGuMw==", 863 | "requires": { 864 | "loose-envify": "^1.1.0", 865 | "object-assign": "^4.1.1", 866 | "prop-types": "^15.6.2", 867 | "scheduler": "^0.12.0-alpha.2" 868 | } 869 | }, 870 | "react-event-listener": { 871 | "version": "0.6.4", 872 | "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.4.tgz", 873 | "integrity": "sha512-t7VSjIuUFmN+GeyKb+wm025YLeojVB85kJL6sSs0wEBJddfmKBEQz+CNBZ2zBLKVWkPy/fZXM6U5yvojjYBVYQ==", 874 | "requires": { 875 | "@babel/runtime": "7.0.0", 876 | "prop-types": "^15.6.0", 877 | "warning": "^4.0.1" 878 | }, 879 | "dependencies": { 880 | "@babel/runtime": { 881 | "version": "7.0.0", 882 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", 883 | "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", 884 | "requires": { 885 | "regenerator-runtime": "^0.12.0" 886 | } 887 | } 888 | } 889 | }, 890 | "react-is": { 891 | "version": "16.6.3", 892 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", 893 | "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==" 894 | }, 895 | "react-lifecycles-compat": { 896 | "version": "3.0.4", 897 | "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", 898 | "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" 899 | }, 900 | "react-transition-group": { 901 | "version": "2.5.0", 902 | "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz", 903 | "integrity": "sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw==", 904 | "requires": { 905 | "dom-helpers": "^3.3.1", 906 | "loose-envify": "^1.4.0", 907 | "prop-types": "^15.6.2", 908 | "react-lifecycles-compat": "^3.0.4" 909 | } 910 | }, 911 | "recompose": { 912 | "version": "0.30.0", 913 | "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", 914 | "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", 915 | "requires": { 916 | "@babel/runtime": "^7.0.0", 917 | "change-emitter": "^0.1.2", 918 | "fbjs": "^0.8.1", 919 | "hoist-non-react-statics": "^2.3.1", 920 | "react-lifecycles-compat": "^3.0.2", 921 | "symbol-observable": "^1.0.4" 922 | } 923 | }, 924 | "regenerator-runtime": { 925 | "version": "0.12.1", 926 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", 927 | "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" 928 | }, 929 | "relateurl": { 930 | "version": "0.2.7", 931 | "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", 932 | "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", 933 | "dev": true 934 | }, 935 | "renderkid": { 936 | "version": "2.0.1", 937 | "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz", 938 | "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", 939 | "dev": true, 940 | "requires": { 941 | "css-select": "^1.1.0", 942 | "dom-converter": "~0.1", 943 | "htmlparser2": "~3.3.0", 944 | "strip-ansi": "^3.0.0", 945 | "utila": "~0.3" 946 | }, 947 | "dependencies": { 948 | "utila": { 949 | "version": "0.3.3", 950 | "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", 951 | "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", 952 | "dev": true 953 | } 954 | } 955 | }, 956 | "safer-buffer": { 957 | "version": "2.1.2", 958 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 959 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 960 | }, 961 | "scheduler": { 962 | "version": "0.12.0-alpha.2", 963 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0-alpha.2.tgz", 964 | "integrity": "sha512-bfqFzGH18MjjhePIzYQNR0uGQ1wMCX6Q83c2s+3fzyuqKT6zBI2wNQTpq01q72C7QItAp8if5w2LfMiXnI2SYw==", 965 | "requires": { 966 | "loose-envify": "^1.1.0", 967 | "object-assign": "^4.1.1" 968 | } 969 | }, 970 | "setimmediate": { 971 | "version": "1.0.5", 972 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 973 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 974 | }, 975 | "strip-ansi": { 976 | "version": "3.0.1", 977 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 978 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 979 | "dev": true, 980 | "requires": { 981 | "ansi-regex": "^2.0.0" 982 | } 983 | }, 984 | "symbol-observable": { 985 | "version": "1.2.0", 986 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", 987 | "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" 988 | }, 989 | "tapable": { 990 | "version": "1.0.0", 991 | "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", 992 | "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", 993 | "dev": true 994 | }, 995 | "toposort": { 996 | "version": "1.0.7", 997 | "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", 998 | "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", 999 | "dev": true 1000 | }, 1001 | "ua-parser-js": { 1002 | "version": "0.7.18", 1003 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", 1004 | "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" 1005 | }, 1006 | "upper-case": { 1007 | "version": "1.1.3", 1008 | "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", 1009 | "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", 1010 | "dev": true 1011 | }, 1012 | "util.promisify": { 1013 | "version": "1.0.0", 1014 | "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", 1015 | "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", 1016 | "dev": true, 1017 | "requires": { 1018 | "define-properties": "^1.1.2", 1019 | "object.getownpropertydescriptors": "^2.0.3" 1020 | } 1021 | }, 1022 | "utila": { 1023 | "version": "0.4.0", 1024 | "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", 1025 | "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", 1026 | "dev": true 1027 | }, 1028 | "warning": { 1029 | "version": "4.0.2", 1030 | "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", 1031 | "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", 1032 | "requires": { 1033 | "loose-envify": "^1.0.0" 1034 | } 1035 | }, 1036 | "whatwg-fetch": { 1037 | "version": "3.0.0", 1038 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", 1039 | "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" 1040 | } 1041 | } 1042 | } 1043 | -------------------------------------------------------------------------------- /packages/boilerplate-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --progress --inline", 8 | "pack": "NODE_ENV=production webpack", 9 | "packWindows": "set NODE_ENV=production && webpack" 10 | }, 11 | "author": "Jeff Hull", 12 | "license": "MIT", 13 | "dependencies": {}, 14 | "devDependencies": {} 15 | } 16 | -------------------------------------------------------------------------------- /packages/boilerplate-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphercider/typescript-fullstack-monorepo/8875cf48b95e392bfa85f6af9125cfe4dfe8b273/packages/boilerplate-client/public/favicon.ico -------------------------------------------------------------------------------- /packages/boilerplate-client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Boilerplate Client 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { runInAction } from 'mobx' 3 | import { 4 | Home, 5 | HooksDemo, 6 | ComponentsDemo, 7 | ServerApiDemo 8 | } from '@client/pages/index' 9 | import { AppContainer } from '@appkit-client/index' 10 | import { AppkitMenuItem } from '@appkit-client/index' 11 | import { AppPage } from '@client/stores/ViewStore' 12 | import { Paper } from '@material-ui/core' 13 | import { config } from '../config/config' 14 | import { ConditionalModals } from './app/ConditionalModals' 15 | import jss from 'jss' 16 | import jssPreset from 'jss-preset-default' 17 | import { useComputed, observer } from 'mobx-react-lite' 18 | import { StoreContext } from '@client/index' 19 | 20 | const styles = { 21 | '@global': { 22 | h1: config.markdownTheme.h1, 23 | h2: config.markdownTheme.h2, 24 | h3: config.markdownTheme.h3, 25 | p: config.markdownTheme.p, 26 | li: config.markdownTheme.li 27 | } 28 | } 29 | 30 | jss.setup(jssPreset()) 31 | jss.createStyleSheet(styles as any).attach() 32 | 33 | export const App = observer(() => { 34 | const store = React.useContext(StoreContext) 35 | const currentPage: JSX.Element = useComputed(() => { 36 | switch (store.viewStore.currentPage) { 37 | case AppPage.Home: 38 | return 39 | case AppPage.ComponentsDemos: 40 | return 41 | case AppPage.ServerApiDemo: 42 | return 43 | case AppPage.HooksDemo: 44 | return 45 | default: 46 | throw new Error(`Unknown page ${store.viewStore.currentPage}`) 47 | } 48 | }) 49 | 50 | const menuItems: AppkitMenuItem[] = useComputed(() => { 51 | return [ 52 | { 53 | name: 'Home', 54 | onClick: () => { 55 | store.viewStore.setPage(AppPage.Home) 56 | } 57 | }, 58 | { 59 | name: 'Components Demo', 60 | onClick: () => { 61 | store.viewStore.setPage(AppPage.ComponentsDemos) 62 | } 63 | }, 64 | { 65 | name: 'Server API Demo', 66 | onClick: () => { 67 | store.viewStore.setPage(AppPage.ServerApiDemo) 68 | } 69 | }, 70 | { 71 | name: 'Hooks Demo', 72 | onClick: () => { 73 | store.viewStore.setPage(AppPage.HooksDemo) 74 | } 75 | } 76 | ] 77 | }) 78 | 79 | return ( 80 | { 85 | runInAction(() => { 86 | store.userStore.loginModalOpen = true 87 | }) 88 | }} 89 | logoutAction={() => { 90 | store.userStore.logout() 91 | }} 92 | > 93 | 94 | 106 | {currentPage} 107 | 108 | 109 | ) 110 | }) 111 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/components/app/ConditionalModals.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { runInAction } from 'mobx' 3 | import { InformationModal } from '@appkit-client/components/InformationModal' 4 | import { SingleTextInputModal } from '@appkit-client/components/SingleTextInputModal' 5 | import { LoginModal } from '@appkit-client/index' 6 | import * as authModel from '@appkit-common/model/auth' 7 | import { StoreContext } from '@client/index' 8 | import { observer } from 'mobx-react-lite' 9 | 10 | export const ConditionalModals = observer(() => { 11 | const store = React.useContext(StoreContext) 12 | return ( 13 |
14 | {[ 15 | store.userStore.loginModalOpen && ( 16 | { 19 | runInAction(() => { 20 | store.userStore.loginModalOpen = false 21 | }) 22 | }} 23 | callbackWithCredentials={(cred: authModel.Credentials) => { 24 | runInAction(() => { 25 | store.userStore.login({ credentials: cred }) 26 | }) 27 | }} 28 | /> 29 | ), 30 | store.notificationStore.infoModalOpen && ( 31 | 37 | ), 38 | store.inputStore.singleTextInputOpen && ( 39 | { 44 | store.inputStore.singleTextValueReturnCallback(val) 45 | store.inputStore.closeSingleTextInputModal() 46 | }} 47 | inputLabel="input label" 48 | /> 49 | ) 50 | ]} 51 |
52 | ) 53 | }) 54 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import { ClientBaseConfig } from '@appkit-client/model/config' 2 | import { User } from '@appkit-common/model/auth' 3 | import { blueMarkdownTheme, blueMuiTheme } from '@appkit-client/index' 4 | 5 | declare var process 6 | 7 | export const config: ClientBaseConfig = { 8 | appName: 'Boilerplate Client', 9 | host: window.location.origin, 10 | instanceName: 'Development', 11 | clientVersion: '1.0.0', 12 | serverVersion: null, 13 | user: null, 14 | inDevelopment: process.env.NODE_ENV === 'development', 15 | muiTheme: blueMuiTheme, 16 | markdownTheme: blueMarkdownTheme 17 | } 18 | 19 | if (config.inDevelopment === true) { 20 | config.host = 'http://localhost:3000' 21 | } 22 | 23 | console.log(`config object: ${JSON.stringify(config)}`) 24 | console.log(`process object: ${JSON.stringify(process)}`) 25 | 26 | export function setServerVersion(version: string) { 27 | config.serverVersion = version 28 | } 29 | 30 | export function setUser(user: User) { 31 | config.user = user 32 | } 33 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as ReactDOM from 'react-dom' 3 | import { ErrorBoundary } from '@appkit-client/index' 4 | import RootStore from '@client/stores/RootStore' 5 | import { configure } from 'mobx' 6 | import { App } from './components/App' 7 | 8 | configure({ enforceActions: 'observed' }) 9 | 10 | const store = new RootStore() 11 | 12 | console.log(`created root store ${typeof store}`) 13 | export const StoreContext: React.Context = React.createContext(store) 14 | console.log(`created store context ${typeof StoreContext}`) 15 | 16 | async function renderView() { 17 | await store.initAllStores() 18 | ReactDOM.render( 19 | 20 | 21 | , 22 | document.getElementById('root') 23 | ) 24 | } 25 | 26 | renderView() 27 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/model/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphercider/typescript-fullstack-monorepo/8875cf48b95e392bfa85f6af9125cfe4dfe8b273/packages/boilerplate-client/src/model/index.ts -------------------------------------------------------------------------------- /packages/boilerplate-client/src/model/serverApi.ts: -------------------------------------------------------------------------------- 1 | import * as fetchUtil from '@client/util/fetch' 2 | import * as authModel from '@appkit-common/model/auth' 3 | 4 | const endpoints = { 5 | getHello: '/test/hello', 6 | login: '/auth/login' 7 | } 8 | 9 | export async function login( 10 | req: authModel.LoginRequest 11 | ): Promise { 12 | return await fetchUtil.postToServerNow(endpoints.login, req) 13 | } 14 | 15 | export async function getHello(): Promise { 16 | return await fetchUtil.getFromServerNow(endpoints.getHello) 17 | } 18 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/pages/ComponentsDemo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Typography } from '@material-ui/core' 3 | import { ButtonSimple } from '@appkit-client/index' 4 | import { StoreContext } from '@client/index' 5 | 6 | export const ComponentsDemo = () => { 7 | const store = React.useContext(StoreContext) 8 | return ( 9 |
10 | Simple Components 11 | 12 | A showcase of simple wrapper components, which exposure minimal API 13 | surface area for just the component features you need. 14 | 15 | { 19 | store.inputStore.openSingleTextInputModal( 20 | `test input`, 21 | (val: string) => { 22 | console.log(`got value ${val} from input`) 23 | } 24 | ) 25 | }} 26 | /> 27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Divider } from '@material-ui/core' 3 | const markdownContent = require('./home/home_content.md') 4 | 5 | export const Home = () => { 6 | return ( 7 |
8 |
9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/pages/HooksDemo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Typography } from '@material-ui/core' 3 | import { HooksTest } from './hooksDemo/HooksTest' 4 | 5 | export const HooksDemo = () => { 6 | return ( 7 |
8 | Hooks Demo 9 | 10 | A simple example of usage of React 16.7.alpha hooks 11 | 12 | Hooks test 13 | 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/pages/ServerApiDemo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Typography } from '@material-ui/core' 3 | import { ButtonSimple } from '@appkit-client/index' 4 | import * as serverApi from '@client/model/serverApi' 5 | import { StoreContext } from '@client/index' 6 | import { observer } from 'mobx-react-lite' 7 | 8 | export const ServerApiDemo = observer(() => { 9 | const store = React.useContext(StoreContext) 10 | return ( 11 |
12 | Server Api Demo 13 | 14 | A demo of some basic server API calls 15 | 16 | { 20 | const res = await serverApi.getHello() 21 | store.notificationStore.displayInfoModal( 22 | 'Server Response', 23 | JSON.stringify(res) 24 | ) 25 | console.log(`Got response from server ${JSON.stringify(res)}`) 26 | }} 27 | /> 28 |
29 | ) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/pages/home/home_content.md: -------------------------------------------------------------------------------- 1 | # Typescript Fullstack Boilerplate 2 | 3 | A starter fullstack typescript application with the following features 4 | 5 | ## Frontend 6 | 7 | 1. UI components with React 8 | 2. State management with Mobx 9 | 3. Bundling with webpack 10 | 1. Markdown content display with [markdown-loader](https://www.npmjs.com/package/markdown-loader) 11 | 2. Absolute path mapping, using mappings from tsconfig.json, with [tsconfig-paths-webpack-plugin](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin) 12 | 13 | ## Backend 14 | 15 | 1. Node.js 16 | 2. Bundling with webpack 17 | 1. Absolute path mapping, using mappings from tsconfig.json, with [tsconfig-paths-webpack-plugin](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin) 18 | 19 | ## Repository-wide 20 | 21 | 1. [Monorepository](https://en.wikipedia.org/wiki/Monorepo) 22 | 1. Multiple applications (in this case, node server and browser client) sharing some common dependencies 23 | 1. Note: applications depend directly on .ts source - not on a package as when using lerna 24 | 2. [tsconfig.json](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) inheritance using "extends" 25 | 2. Vscode [multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces) for client and server 26 | 3. A dockerfile for containerizing the fullstack app for deployment 27 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/pages/hooksDemo/HooksTest.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef, useMemo } from 'react' 2 | import * as React from 'react' 3 | // import { useComputedValue } from './useComputedValue' 4 | 5 | // set timeout not working - see https://github.com/facebook/react/issues/14010 6 | 7 | export function HooksTest() { 8 | // Declare a new state variable, which we'll call "count" 9 | const [clickCount, setClickCount] = useState(0) 10 | const [timer, setTimer] = useState(0) 11 | 12 | const countRef = useRef(timer) 13 | countRef.current = timer 14 | 15 | const startTimer = () => { 16 | return setInterval(() => { 17 | setTimer(countRef.current + 1) 18 | }, 3000) 19 | } 20 | 21 | useEffect(() => { 22 | const intervalId = startTimer() 23 | return () => { 24 | clearInterval(intervalId) 25 | } 26 | }, []) 27 | 28 | const computedValue = useMemo( 29 | () => { 30 | return timer * 2 31 | }, 32 | [timer] 33 | ) 34 | 35 | const computedValueFromClick = useMemo( 36 | () => { 37 | return clickCount * 2 38 | }, 39 | [clickCount] 40 | ) 41 | 42 | return ( 43 |
44 |

You clicked {clickCount} times

45 | 46 |

Your timer is at {timer}

47 |

Your timer computed value is {computedValue}

48 |

Your click computed value is {computedValueFromClick}

49 |
50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/pages/hooksDemo/useComputedValue.ts: -------------------------------------------------------------------------------- 1 | // https://reactjs.org/docs/hooks-faq.html 2 | import { useMemo } from 'react' 3 | 4 | export function useComputedValue(fn: () => any, watchedValue: any[]) { 5 | return useMemo(fn, watchedValue) 6 | } 7 | 8 | // export const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]) 9 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/pages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Home' 2 | export * from './HooksDemo' 3 | export * from './ComponentsDemo' 4 | export * from './ServerApiDemo' 5 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/stores/RootStore.ts: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | import ViewStore from '@client/stores/ViewStore' 3 | import NotificationStore from '@appkit-client/stores/NotificationStore' 4 | import UserStore from './UserStore' 5 | import InputStore from '@appkit-client/stores/InputStore' 6 | 7 | export default class RootStore { 8 | static rootStore: RootStore 9 | @observable serverVersion: string = 'not updated' 10 | @observable clientVersion: string = 'not updated' 11 | viewStore: ViewStore 12 | notificationStore: NotificationStore 13 | inputStore: InputStore 14 | userStore: UserStore 15 | 16 | constructor() { 17 | RootStore.rootStore = this 18 | this.viewStore = new ViewStore(this) 19 | this.notificationStore = new NotificationStore(this) 20 | this.inputStore = new InputStore(this) 21 | this.userStore = new UserStore(this) 22 | // init all stores 23 | // this.initAllStores(callbackOnInit) 24 | } 25 | 26 | initAllStores = async () => { 27 | console.log(`init all stores`) 28 | // yield this.setVersionInfo(() => {}) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/stores/UserStore.ts: -------------------------------------------------------------------------------- 1 | import RootStore from './RootStore' 2 | import { observable, runInAction } from 'mobx' 3 | import { LoginRequest, User } from '@appkit-common/model/auth' 4 | import { login } from '@client/model/serverApi' 5 | 6 | export default class UserStore { 7 | rootStore: RootStore 8 | @observable loggedIn: boolean = false 9 | @observable user: User 10 | @observable loginModalOpen: boolean = false 11 | 12 | logout = async () => { 13 | console.log(`do logout`) 14 | runInAction(() => { 15 | this.loggedIn = false 16 | }) 17 | } 18 | 19 | login = async (req: LoginRequest) => { 20 | const res = await login(req) 21 | console.log(`got res ${JSON.stringify(res)}`) 22 | runInAction(() => { 23 | this.user = res.user 24 | this.loggedIn = true 25 | }) 26 | } 27 | 28 | constructor(rootStore: RootStore) { 29 | this.rootStore = rootStore 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/stores/ViewStore.ts: -------------------------------------------------------------------------------- 1 | import { observable, action, computed, autorun } from 'mobx' 2 | import RootStore from '@client/stores/RootStore' 3 | import { Router } from 'director/build/director' 4 | 5 | export enum AppPage { 6 | Home = 'Home', 7 | HooksDemo = 'HooksDemo', 8 | ComponentsDemos = 'ComponentsDemos', 9 | ServerApiDemo = 'ServerApiDemo' 10 | } 11 | 12 | export default class ViewStore { 13 | rootStore: RootStore 14 | @observable serverVersion: string = 'not updated' 15 | @observable clientVersion: string = 'not updated' 16 | @observable currentPage: AppPage = AppPage.Home 17 | 18 | constructor(rootStore: RootStore) { 19 | this.rootStore = rootStore 20 | this.startRouter() 21 | } 22 | 23 | @action.bound 24 | setPage(page: AppPage) { 25 | if (page !== this.currentPage) { 26 | this.currentPage = page 27 | } 28 | } 29 | 30 | @computed 31 | get currentRoute() { 32 | switch (this.currentPage) { 33 | case AppPage.Home: 34 | return '/#/home' 35 | case AppPage.HooksDemo: 36 | return '/#/hooks' 37 | case AppPage.ServerApiDemo: 38 | return '/#/serverApi' 39 | case AppPage.ComponentsDemos: 40 | return '/#/components' 41 | default: 42 | throw new Error(`page not recognized ${this.currentPage}`) 43 | } 44 | } 45 | 46 | startRouter = () => { 47 | const router = new Router({ 48 | '/': () => this.setPage(AppPage.Home), 49 | '/home': () => this.setPage(AppPage.Home), 50 | '/hooks': () => this.setPage(AppPage.HooksDemo), 51 | '/serverApi': () => this.setPage(AppPage.ServerApiDemo), 52 | '/components': () => this.setPage(AppPage.ComponentsDemos) 53 | }) 54 | router.configure({ 55 | notfound: () => this.setPage(AppPage.Home), 56 | html5history: true 57 | }) 58 | router.init() 59 | autorun(() => { 60 | const path = this.currentRoute 61 | if (path !== window.location.pathname) 62 | window.history.pushState(null, '', path) 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/boilerplate-client/src/util/fetch.ts: -------------------------------------------------------------------------------- 1 | import * as fetchUtil from '@appkit-client/util/fetch' 2 | import { config } from '@client/config/config' 3 | 4 | export async function postToServerNow( 5 | endpoint: string, 6 | body: Object 7 | ): Promise { 8 | return await fetchUtil.fetchBodyPost(config.host + endpoint, body) 9 | } 10 | 11 | export async function getFromServerNow(endpoint: string): Promise { 12 | return await fetchUtil.fetchBodyGet(config.host + endpoint) 13 | } 14 | -------------------------------------------------------------------------------- /packages/boilerplate-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "jsx": "react", 6 | "baseUrl": "..", 7 | "lib": ["es2018", "dom"] 8 | }, 9 | "include": ["./src/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/boilerplate-client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const webpack = require("webpack") 3 | const HtmlWebpackPlugin = require("html-webpack-plugin") 4 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin") 5 | // const marked = require('marked') 6 | // const markdownRenderer = new marked.Renderer() 7 | 8 | let devMode = false 9 | if ( 10 | process.env.NODE_ENV == null || 11 | process.env.NODE_ENV.substr(0, 3).toLowerCase() === "dev" 12 | ) { 13 | devMode = true 14 | console.log(`Webpack is running in development mode`) 15 | } 16 | 17 | module.exports = { 18 | target: "web", 19 | mode: "development", 20 | entry: ["./src/index.tsx"], 21 | devtool: "source-map", 22 | resolve: { 23 | extensions: [".ts", ".tsx", ".js", ".json"], 24 | plugins: [ 25 | new TsconfigPathsPlugin({ 26 | configFile: "./tsconfig.json", 27 | logLevel: "info", 28 | logInfoToStdOut: true, 29 | extensions: [".ts", ".tsx"] 30 | }) 31 | ] 32 | }, 33 | module: { 34 | rules: [ 35 | { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ }, 36 | { 37 | test: /\.md$/, 38 | use: [{ loader: "html-loader" }, { loader: "markdown-loader" }] 39 | }, 40 | { 41 | test: /\.css$/, 42 | exclude: /\.useable\.css$/, 43 | loader: "style-loader!raw-loader" 44 | }, 45 | { test: /\.useable\.css$/, loader: "style-loader/useable!raw-loader" }, 46 | { 47 | test: /\.(png|ico|gif)?$/, 48 | loaders: ["file"], 49 | include: __dirname 50 | } 51 | ] 52 | }, 53 | plugins: [ 54 | new webpack.DefinePlugin({ 55 | "process.env.NODE_ENV": devMode ? '"development"' : '"production"' 56 | }), 57 | new HtmlWebpackPlugin({ 58 | template: "./public/index.html", 59 | favicon: "./public/favicon.ico", 60 | filename: "index.html" 61 | }), 62 | ...(devMode ? [new webpack.HotModuleReplacementPlugin()] : []) 63 | ], 64 | devServer: { 65 | host: "0.0.0.0", 66 | disableHostCheck: true, 67 | contentBase: path.join(__dirname, "build"), 68 | port: 8000, 69 | compress: true 70 | }, 71 | output: { 72 | path: path.join(__dirname, "dist"), 73 | filename: "bundle.js", 74 | publicPath: "/" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/boilerplate-server/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug Boilerplate Server", 11 | "program": "${workspaceFolder}\\src\\server.ts", 12 | "env": { 13 | "NODE_ENV": "DEV" 14 | }, 15 | "smartStep": true, 16 | "restart": true, 17 | "console": "integratedTerminal", 18 | "internalConsoleOptions": "neverOpen", 19 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 20 | "sourceMaps": true 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/boilerplate-server/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.semi": false, 3 | "prettier.singleQuote": true 4 | } -------------------------------------------------------------------------------- /packages/boilerplate-server/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "server watch", 8 | "type": "npm", 9 | "script": "watch", 10 | "problemMatcher": [] 11 | }, 12 | { 13 | "label": "server clean", 14 | "type": "npm", 15 | "script": "clean", 16 | "problemMatcher": [] 17 | }, 18 | { 19 | "label": "server start", 20 | "type": "npm", 21 | "script": "start", 22 | "problemMatcher": [] 23 | }, 24 | { 25 | "label": "server all", 26 | "dependsOn": ["server watch", "server start"], 27 | "problemMatcher": [] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/boilerplate-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | WORKDIR /usr/src/app 3 | COPY dist ./dist 4 | COPY public ./public 5 | COPY package.json . 6 | RUN npm install --only=production 7 | 8 | EXPOSE 3000 9 | 10 | CMD [ "npm", "run", "startServer" ] -------------------------------------------------------------------------------- /packages/boilerplate-server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/body-parser": { 8 | "version": "1.17.0", 9 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", 10 | "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", 11 | "requires": { 12 | "@types/connect": "*", 13 | "@types/node": "*" 14 | } 15 | }, 16 | "@types/connect": { 17 | "version": "3.4.32", 18 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", 19 | "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", 20 | "requires": { 21 | "@types/node": "*" 22 | } 23 | }, 24 | "@types/express": { 25 | "version": "4.16.1", 26 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz", 27 | "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==", 28 | "requires": { 29 | "@types/body-parser": "*", 30 | "@types/express-serve-static-core": "*", 31 | "@types/serve-static": "*" 32 | } 33 | }, 34 | "@types/express-serve-static-core": { 35 | "version": "4.16.1", 36 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.1.tgz", 37 | "integrity": "sha512-QgbIMRU1EVRry5cIu1ORCQP4flSYqLM1lS5LYyGWfKnFT3E58f0gKto7BR13clBFVrVZ0G0rbLZ1hUpSkgQQOA==", 38 | "requires": { 39 | "@types/node": "*", 40 | "@types/range-parser": "*" 41 | } 42 | }, 43 | "@types/mime": { 44 | "version": "2.0.1", 45 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", 46 | "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" 47 | }, 48 | "@types/node": { 49 | "version": "10.14.5", 50 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.5.tgz", 51 | "integrity": "sha512-Ja7d4s0qyGFxjGeDq5S7Si25OFibSAHUi6i17UWnwNnpitADN7hah9q0Tl25gxuV5R1u2Bx+np6w4LHXfHyj/g==" 52 | }, 53 | "@types/range-parser": { 54 | "version": "1.2.3", 55 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", 56 | "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" 57 | }, 58 | "@types/serve-static": { 59 | "version": "1.13.2", 60 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", 61 | "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", 62 | "requires": { 63 | "@types/express-serve-static-core": "*", 64 | "@types/mime": "*" 65 | } 66 | }, 67 | "accepts": { 68 | "version": "1.3.5", 69 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 70 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 71 | "requires": { 72 | "mime-types": "~2.1.18", 73 | "negotiator": "0.6.1" 74 | } 75 | }, 76 | "array-flatten": { 77 | "version": "1.1.1", 78 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 79 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 80 | }, 81 | "async": { 82 | "version": "1.5.2", 83 | "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", 84 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 85 | }, 86 | "body-parser": { 87 | "version": "1.18.3", 88 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 89 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 90 | "requires": { 91 | "bytes": "3.0.0", 92 | "content-type": "~1.0.4", 93 | "debug": "2.6.9", 94 | "depd": "~1.1.2", 95 | "http-errors": "~1.6.3", 96 | "iconv-lite": "0.4.23", 97 | "on-finished": "~2.3.0", 98 | "qs": "6.5.2", 99 | "raw-body": "2.3.3", 100 | "type-is": "~1.6.16" 101 | } 102 | }, 103 | "buffer-equal-constant-time": { 104 | "version": "1.0.1", 105 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 106 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 107 | }, 108 | "buffer-from": { 109 | "version": "1.1.1", 110 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 111 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 112 | "dev": true 113 | }, 114 | "bytes": { 115 | "version": "3.0.0", 116 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 117 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 118 | }, 119 | "content-disposition": { 120 | "version": "0.5.2", 121 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 122 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 123 | }, 124 | "content-type": { 125 | "version": "1.0.4", 126 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 127 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 128 | }, 129 | "cookie": { 130 | "version": "0.3.1", 131 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 132 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 133 | }, 134 | "cookie-signature": { 135 | "version": "1.0.6", 136 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 137 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 138 | }, 139 | "cors": { 140 | "version": "2.8.5", 141 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 142 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 143 | "requires": { 144 | "object-assign": "^4", 145 | "vary": "^1" 146 | } 147 | }, 148 | "debug": { 149 | "version": "2.6.9", 150 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 151 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 152 | "requires": { 153 | "ms": "2.0.0" 154 | } 155 | }, 156 | "depd": { 157 | "version": "1.1.2", 158 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 159 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 160 | }, 161 | "destroy": { 162 | "version": "1.0.4", 163 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 164 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 165 | }, 166 | "ecdsa-sig-formatter": { 167 | "version": "1.0.11", 168 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 169 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 170 | "requires": { 171 | "safe-buffer": "^5.0.1" 172 | } 173 | }, 174 | "ee-first": { 175 | "version": "1.1.1", 176 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 177 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 178 | }, 179 | "encodeurl": { 180 | "version": "1.0.2", 181 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 182 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 183 | }, 184 | "escape-html": { 185 | "version": "1.0.3", 186 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 187 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 188 | }, 189 | "etag": { 190 | "version": "1.8.1", 191 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 192 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 193 | }, 194 | "express": { 195 | "version": "4.16.4", 196 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", 197 | "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", 198 | "requires": { 199 | "accepts": "~1.3.5", 200 | "array-flatten": "1.1.1", 201 | "body-parser": "1.18.3", 202 | "content-disposition": "0.5.2", 203 | "content-type": "~1.0.4", 204 | "cookie": "0.3.1", 205 | "cookie-signature": "1.0.6", 206 | "debug": "2.6.9", 207 | "depd": "~1.1.2", 208 | "encodeurl": "~1.0.2", 209 | "escape-html": "~1.0.3", 210 | "etag": "~1.8.1", 211 | "finalhandler": "1.1.1", 212 | "fresh": "0.5.2", 213 | "merge-descriptors": "1.0.1", 214 | "methods": "~1.1.2", 215 | "on-finished": "~2.3.0", 216 | "parseurl": "~1.3.2", 217 | "path-to-regexp": "0.1.7", 218 | "proxy-addr": "~2.0.4", 219 | "qs": "6.5.2", 220 | "range-parser": "~1.2.0", 221 | "safe-buffer": "5.1.2", 222 | "send": "0.16.2", 223 | "serve-static": "1.13.2", 224 | "setprototypeof": "1.1.0", 225 | "statuses": "~1.4.0", 226 | "type-is": "~1.6.16", 227 | "utils-merge": "1.0.1", 228 | "vary": "~1.1.2" 229 | } 230 | }, 231 | "express-jwt": { 232 | "version": "5.3.1", 233 | "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", 234 | "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", 235 | "requires": { 236 | "async": "^1.5.0", 237 | "express-unless": "^0.3.0", 238 | "jsonwebtoken": "^8.1.0", 239 | "lodash.set": "^4.0.0" 240 | } 241 | }, 242 | "express-unless": { 243 | "version": "0.3.1", 244 | "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", 245 | "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" 246 | }, 247 | "finalhandler": { 248 | "version": "1.1.1", 249 | "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 250 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 251 | "requires": { 252 | "debug": "2.6.9", 253 | "encodeurl": "~1.0.2", 254 | "escape-html": "~1.0.3", 255 | "on-finished": "~2.3.0", 256 | "parseurl": "~1.3.2", 257 | "statuses": "~1.4.0", 258 | "unpipe": "~1.0.0" 259 | } 260 | }, 261 | "forwarded": { 262 | "version": "0.1.2", 263 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 264 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 265 | }, 266 | "fresh": { 267 | "version": "0.5.2", 268 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 269 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 270 | }, 271 | "http-errors": { 272 | "version": "1.6.3", 273 | "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 274 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 275 | "requires": { 276 | "depd": "~1.1.2", 277 | "inherits": "2.0.3", 278 | "setprototypeof": "1.1.0", 279 | "statuses": ">= 1.4.0 < 2" 280 | } 281 | }, 282 | "iconv-lite": { 283 | "version": "0.4.23", 284 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 285 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 286 | "requires": { 287 | "safer-buffer": ">= 2.1.2 < 3" 288 | } 289 | }, 290 | "inherits": { 291 | "version": "2.0.3", 292 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 293 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 294 | }, 295 | "ipaddr.js": { 296 | "version": "1.8.0", 297 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 298 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" 299 | }, 300 | "jsonwebtoken": { 301 | "version": "8.5.1", 302 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 303 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 304 | "requires": { 305 | "jws": "^3.2.2", 306 | "lodash.includes": "^4.3.0", 307 | "lodash.isboolean": "^3.0.3", 308 | "lodash.isinteger": "^4.0.4", 309 | "lodash.isnumber": "^3.0.3", 310 | "lodash.isplainobject": "^4.0.6", 311 | "lodash.isstring": "^4.0.1", 312 | "lodash.once": "^4.0.0", 313 | "ms": "^2.1.1", 314 | "semver": "^5.6.0" 315 | }, 316 | "dependencies": { 317 | "ms": { 318 | "version": "2.1.1", 319 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 320 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 321 | } 322 | } 323 | }, 324 | "jwa": { 325 | "version": "1.4.1", 326 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 327 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 328 | "requires": { 329 | "buffer-equal-constant-time": "1.0.1", 330 | "ecdsa-sig-formatter": "1.0.11", 331 | "safe-buffer": "^5.0.1" 332 | } 333 | }, 334 | "jws": { 335 | "version": "3.2.2", 336 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 337 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 338 | "requires": { 339 | "jwa": "^1.4.1", 340 | "safe-buffer": "^5.0.1" 341 | } 342 | }, 343 | "lodash.includes": { 344 | "version": "4.3.0", 345 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 346 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 347 | }, 348 | "lodash.isboolean": { 349 | "version": "3.0.3", 350 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 351 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 352 | }, 353 | "lodash.isinteger": { 354 | "version": "4.0.4", 355 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 356 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 357 | }, 358 | "lodash.isnumber": { 359 | "version": "3.0.3", 360 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 361 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 362 | }, 363 | "lodash.isplainobject": { 364 | "version": "4.0.6", 365 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 366 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 367 | }, 368 | "lodash.isstring": { 369 | "version": "4.0.1", 370 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 371 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 372 | }, 373 | "lodash.once": { 374 | "version": "4.1.1", 375 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 376 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 377 | }, 378 | "lodash.set": { 379 | "version": "4.3.2", 380 | "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", 381 | "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" 382 | }, 383 | "media-typer": { 384 | "version": "0.3.0", 385 | "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 386 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 387 | }, 388 | "merge-descriptors": { 389 | "version": "1.0.1", 390 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 391 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 392 | }, 393 | "methods": { 394 | "version": "1.1.2", 395 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 396 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 397 | }, 398 | "mime": { 399 | "version": "1.4.1", 400 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 401 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 402 | }, 403 | "mime-db": { 404 | "version": "1.37.0", 405 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", 406 | "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" 407 | }, 408 | "mime-types": { 409 | "version": "2.1.21", 410 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", 411 | "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", 412 | "requires": { 413 | "mime-db": "~1.37.0" 414 | } 415 | }, 416 | "ms": { 417 | "version": "2.0.0", 418 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 419 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 420 | }, 421 | "negotiator": { 422 | "version": "0.6.1", 423 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 424 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 425 | }, 426 | "object-assign": { 427 | "version": "4.1.1", 428 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 429 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 430 | }, 431 | "on-finished": { 432 | "version": "2.3.0", 433 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 434 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 435 | "requires": { 436 | "ee-first": "1.1.1" 437 | } 438 | }, 439 | "parseurl": { 440 | "version": "1.3.2", 441 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 442 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 443 | }, 444 | "path-to-regexp": { 445 | "version": "0.1.7", 446 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 447 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 448 | }, 449 | "prettier": { 450 | "version": "1.17.0", 451 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", 452 | "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", 453 | "dev": true 454 | }, 455 | "proxy-addr": { 456 | "version": "2.0.4", 457 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", 458 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", 459 | "requires": { 460 | "forwarded": "~0.1.2", 461 | "ipaddr.js": "1.8.0" 462 | } 463 | }, 464 | "qs": { 465 | "version": "6.5.2", 466 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 467 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 468 | }, 469 | "range-parser": { 470 | "version": "1.2.0", 471 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 472 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 473 | }, 474 | "raw-body": { 475 | "version": "2.3.3", 476 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 477 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 478 | "requires": { 479 | "bytes": "3.0.0", 480 | "http-errors": "1.6.3", 481 | "iconv-lite": "0.4.23", 482 | "unpipe": "1.0.0" 483 | } 484 | }, 485 | "safe-buffer": { 486 | "version": "5.1.2", 487 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 488 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 489 | }, 490 | "safer-buffer": { 491 | "version": "2.1.2", 492 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 493 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 494 | }, 495 | "semver": { 496 | "version": "5.7.0", 497 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 498 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" 499 | }, 500 | "send": { 501 | "version": "0.16.2", 502 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 503 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 504 | "requires": { 505 | "debug": "2.6.9", 506 | "depd": "~1.1.2", 507 | "destroy": "~1.0.4", 508 | "encodeurl": "~1.0.2", 509 | "escape-html": "~1.0.3", 510 | "etag": "~1.8.1", 511 | "fresh": "0.5.2", 512 | "http-errors": "~1.6.2", 513 | "mime": "1.4.1", 514 | "ms": "2.0.0", 515 | "on-finished": "~2.3.0", 516 | "range-parser": "~1.2.0", 517 | "statuses": "~1.4.0" 518 | } 519 | }, 520 | "serve-static": { 521 | "version": "1.13.2", 522 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 523 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 524 | "requires": { 525 | "encodeurl": "~1.0.2", 526 | "escape-html": "~1.0.3", 527 | "parseurl": "~1.3.2", 528 | "send": "0.16.2" 529 | } 530 | }, 531 | "setprototypeof": { 532 | "version": "1.1.0", 533 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 534 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 535 | }, 536 | "source-map": { 537 | "version": "0.6.1", 538 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 539 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 540 | "dev": true 541 | }, 542 | "source-map-support": { 543 | "version": "0.5.12", 544 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", 545 | "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", 546 | "dev": true, 547 | "requires": { 548 | "buffer-from": "^1.0.0", 549 | "source-map": "^0.6.0" 550 | } 551 | }, 552 | "statuses": { 553 | "version": "1.4.0", 554 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 555 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 556 | }, 557 | "type-is": { 558 | "version": "1.6.16", 559 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 560 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 561 | "requires": { 562 | "media-typer": "0.3.0", 563 | "mime-types": "~2.1.18" 564 | } 565 | }, 566 | "unpipe": { 567 | "version": "1.0.0", 568 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 569 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 570 | }, 571 | "utils-merge": { 572 | "version": "1.0.1", 573 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 574 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 575 | }, 576 | "vary": { 577 | "version": "1.1.2", 578 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 579 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 580 | } 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /packages/boilerplate-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "A boilerplate node.js server using typescript", 5 | "scripts": { 6 | "watch": "webpack", 7 | "pack": "NODE_ENV=production webpack", 8 | "packWindows": "set NODE_ENV=production && webpack", 9 | "start": "nodemon --delay 3000ms ./dist/server.js", 10 | "clean": "rimraf dist", 11 | "startServer": "node ./dist/server.js" 12 | }, 13 | "author": "Jeff Hull", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@types/express": "^4.16.1", 17 | "cors": "^2.8.5", 18 | "express": "^4.16.4", 19 | "express-jwt": "^5.3.1" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^10.14.5", 23 | "jsonwebtoken": "^8.5.1", 24 | "prettier": "^1.17.0", 25 | "source-map-support": "^0.5.12" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import { version } from './version' 2 | import { BaseServerConfig } from '@appkit-server/model/config' 3 | import { getServerConfigEnvVar } from '@appkit-server/util/env' 4 | import { User } from '@appkit-common/model/auth' 5 | 6 | interface Config extends BaseServerConfig { 7 | publicPath: string 8 | userDocsPath: string 9 | defaultUser: User 10 | } 11 | 12 | let initConfig: Config = { 13 | publicPath: '/public', 14 | userDocsPath: '/docs', 15 | authSecretKey: 'secretkey', 16 | port: 3000, 17 | version: version, 18 | rootDir: process.cwd(), 19 | configEnvironmentVariable: 'BOILERPLATE_SERVER_CONFIG', 20 | nodeEnvironment: 'development', 21 | defaultUser: { 22 | userName: 'admin', 23 | firstName: 'admin', 24 | lastName: 'admin', 25 | email: '' 26 | } 27 | } 28 | 29 | const nodeEnv = process.env.NODE_ENV 30 | 31 | if ( 32 | nodeEnv != null && 33 | nodeEnv 34 | .trim() 35 | .toLowerCase() 36 | .substr(0, 3) === 'dev' 37 | ) { 38 | initConfig.nodeEnvironment = 'development' 39 | } else { 40 | initConfig.nodeEnvironment = 'production' 41 | } 42 | 43 | if (initConfig.nodeEnvironment === 'production') { 44 | initConfig = { 45 | ...initConfig, 46 | ...(getServerConfigEnvVar(initConfig.configEnvironmentVariable) as any) 47 | } 48 | } 49 | 50 | export const config = initConfig 51 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/config/middleware.ts: -------------------------------------------------------------------------------- 1 | import * as bodyParser from 'body-parser' 2 | import * as cors from 'cors' 3 | import * as express from 'express' 4 | import { config } from './config' 5 | // import * as jwtExpress from 'express-jwt' 6 | 7 | export function mountMiddleware(app: express.Express) { 8 | app.use('/', express.static(config.publicPath)) 9 | app.use('/docs', express.static(config.userDocsPath)) 10 | app.use(bodyParser.json({ limit: '10mb' })) 11 | if (config.nodeEnvironment === 'development') { 12 | // If we're in development, we need to enable cors so our webpack dev server can talk to our node server 13 | app.use(cors()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/config/version.ts: -------------------------------------------------------------------------------- 1 | export const version = '2018.09.08.01' 2 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/logic/auth.ts: -------------------------------------------------------------------------------- 1 | import * as authUtil from '@appkit-server/util/auth' 2 | import { 3 | LoginRequest, 4 | LoginResponse, 5 | CheckTokenRequest, 6 | CheckTokenResponse 7 | } from '@appkit-common/model/auth' 8 | import { config } from '@server/config/config' 9 | 10 | export async function login(req: LoginRequest): Promise { 11 | if ( 12 | req.credentials.userName === 'admin' && 13 | req.credentials.password === 'admin' 14 | ) { 15 | const jwt = authUtil.signJwt( 16 | { string: req.credentials.userName }, 17 | config.authSecretKey 18 | ) 19 | return { 20 | succeeded: true, 21 | token: jwt, 22 | user: config.defaultUser, 23 | serverVersion: config.version 24 | } 25 | } else { 26 | throw new Error(`Login failed`) 27 | } 28 | } 29 | 30 | export async function checkToken( 31 | req: CheckTokenRequest 32 | ): Promise { 33 | try { 34 | authUtil.verifyJwt(req.token, config.authSecretKey) 35 | return { 36 | succeeded: true, 37 | serverVersion: config.version, 38 | user: config.defaultUser 39 | } 40 | } catch (err) { 41 | throw new Error(`Error validating token! ${err} `) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/routes/auth.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LoginRequest, 3 | LoginResponse 4 | // LoginResponse, 5 | // CheckTokenRequest, 6 | // CheckTokenResponse 7 | } from '@appkit-common/model/auth' 8 | import * as authUtil from '@appkit-server/util/auth' 9 | import { config } from '@server/config/config' 10 | 11 | import { Express } from 'express' 12 | 13 | export function makeRoutes(app: Express) { 14 | // app.get('/test/hello', async (req, res) => { 15 | // res.send('Hello') 16 | // }) 17 | 18 | app.post('/auth/login', async (req, res) => { 19 | const reqBody = req.body as LoginRequest 20 | // first check password. if OK, then provide a token based on the user name 21 | const token = authUtil.signJwt( 22 | { userName: reqBody.credentials.userName }, 23 | config.authSecretKey 24 | ) 25 | const loginResponse: LoginResponse = { 26 | succeeded: true, 27 | token: token, 28 | user: { 29 | userName: reqBody.credentials.userName, 30 | email: '', 31 | firstName: '', 32 | lastName: '' 33 | }, 34 | serverVersion: config.version 35 | } 36 | res.json(loginResponse) 37 | }) 38 | } 39 | 40 | // @Path('/auth') 41 | // export class AuthService { 42 | // @Path('/login') 43 | // @POST 44 | // async login( 45 | // req: LoginRequest, 46 | // @HeaderParam('authorization') authToken: string 47 | // ): Promise { 48 | // return { 49 | // succeeded: false, 50 | // token: '', 51 | // user: null, 52 | // serverVersion: null 53 | // } 54 | // } 55 | 56 | // @Path('/checkToken') 57 | // @POST 58 | // async checkToken( 59 | // req: CheckTokenRequest, 60 | // @HeaderParam('authorization') authToken: string 61 | // ): Promise { 62 | // return { 63 | // succeeded: false, 64 | // user: null, 65 | // serverVersion: null 66 | // } 67 | // } 68 | // } 69 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Express } from 'express' 2 | import { makeRoutes as makeTestRoutes } from './test' 3 | import { makeRoutes as makeAuthRoutes } from './auth' 4 | 5 | /** 6 | * How can we build a route creator helper function? 7 | * One for get and one for post (prefer over just 'use') 8 | * Would be nice if we could pass an async function into it... so like 9 | * 10 | * makeroute(async (R) => T) 11 | * where R is the type of the body expected by the route, and T is the return type of the route. 12 | */ 13 | 14 | export { makeRoutes } from './test' 15 | 16 | export function createRoutes(app: Express) { 17 | // Server.buildServices(app) 18 | makeTestRoutes(app) 19 | makeAuthRoutes(app) 20 | } 21 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/routes/test.ts: -------------------------------------------------------------------------------- 1 | import { Express } from 'express' 2 | 3 | export function makeRoutes(app: Express) { 4 | app.get('/test/hello', async (req, res) => { 5 | console.log(`got request...sending response`) 6 | res.json({ msg: 'hello' }) 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/server.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express' 2 | import * as http from 'http' 3 | import * as timeUtils from '@appkit-server/util/time' 4 | import { mountMiddleware } from '@server/config/middleware' 5 | import { createRoutes } from '@server/routes/index' 6 | import { config } from '@server/config/config' 7 | 8 | if (config.nodeEnvironment === 'development') { 9 | require('source-map-support').install() 10 | } 11 | 12 | let expressApp: express.Express = express() 13 | 14 | mountMiddleware(expressApp) 15 | createRoutes(expressApp) 16 | 17 | let server = http.createServer(expressApp) 18 | 19 | async function doInitTasks() { 20 | console.log(`init tasks here`) 21 | } 22 | 23 | server.listen(config.port, '0.0.0.0', async function() { 24 | console.log(`Server started on port ${config.port}`) 25 | const MAX_INIT_RETRIES = 50 26 | const INIT_FAIL_DELAY_MS = 5000 27 | for (let i = 0; i < MAX_INIT_RETRIES; i++) { 28 | try { 29 | await doInitTasks() 30 | break 31 | } catch (e) { 32 | console.log(`Error starting server`) 33 | await timeUtils.delay(INIT_FAIL_DELAY_MS) 34 | } 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /packages/boilerplate-server/src/util/env.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphercider/typescript-fullstack-monorepo/8875cf48b95e392bfa85f6af9125cfe4dfe8b273/packages/boilerplate-server/src/util/env.ts -------------------------------------------------------------------------------- /packages/boilerplate-server/swaggerConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": { 3 | "outputDirectory": "./dist", 4 | "entryFile": "./src/routes/index.ts", 5 | "host": "", 6 | "version": "", 7 | "name": "Kolbot Dashboard", 8 | "description": "A web client for managing Kolbot", 9 | "license": "ISC", 10 | "basePath": "" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/boilerplate-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compileOnSave": true, 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | "baseUrl": ".." 7 | }, 8 | "include": [ 9 | "../appkit-server/src/**/*.ts", 10 | "./src/**/*.ts", 11 | "../kolbot-pickit/src/**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/boilerplate-server/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const webpack = require("webpack") 3 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin") 4 | const nodeExternals = require("webpack-node-externals") 5 | 6 | let devMode = false 7 | if ( 8 | process.env.NODE_ENV == null || 9 | process.env.NODE_ENV.substr(0, 3).toLowerCase() === "dev" 10 | ) { 11 | devMode = true 12 | console.log(`Webpack is running in development mode`) 13 | } 14 | 15 | module.exports = { 16 | target: "node", 17 | mode: devMode ? "development" : "production", 18 | entry: ["./src/server.ts"], 19 | watch: devMode, 20 | devtool: devMode ? "source-map" : undefined, 21 | externals: [nodeExternals()], 22 | resolve: { 23 | extensions: [".ts", ".js", ".json"], 24 | plugins: [ 25 | new TsconfigPathsPlugin({ 26 | configFile: "./tsconfig.json", 27 | logLevel: "info", 28 | logInfoToStdOut: true, 29 | extensions: [".ts"] 30 | }) 31 | ] 32 | }, 33 | module: { 34 | rules: [ 35 | { 36 | test: /.tsx?$/, 37 | use: "ts-loader" 38 | } 39 | ] 40 | }, 41 | plugins: [], 42 | output: { 43 | path: path.join(__dirname, "dist"), 44 | filename: "server.js", 45 | publicPath: "/" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "packages/appkit-common" 5 | }, 6 | { 7 | "path": "packages/boilerplate-server" 8 | }, 9 | { 10 | "path": "packages/appkit-server" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "packages", 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "lib": ["es2018"], 7 | "module": "commonjs", 8 | "noUnusedLocals": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "target": "ES2016", 12 | "strictNullChecks": true, 13 | "paths": { 14 | "@appkit-client/*": ["appkit-client/src/*"], 15 | "@appkit-server/*": ["appkit-server/src/*"], 16 | "@appkit-common/*": ["appkit-common/src/*"], 17 | "@server/*": ["boilerplate-server/src/*"], 18 | "@client/*": ["boilerplate-client/src/*"] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base" 3 | } 4 | --------------------------------------------------------------------------------