├── .gitignore ├── src ├── flow-types.js ├── worker.js ├── Todo │ ├── services │ │ ├── ErrorService.js │ │ ├── BaseService.js │ │ ├── __test__ │ │ │ └── ErrorService.test.js │ │ ├── index.js │ │ ├── ModalService.js │ │ ├── ValidationService.js │ │ └── TodoService.js │ ├── components │ │ ├── index.js │ │ ├── TodoListComponent.js │ │ ├── TodoComponent.js │ │ └── TodoFormComponent.js │ ├── containers │ │ ├── index.js │ │ ├── TodoFormContainer.js │ │ ├── TodoListContainer.js │ │ └── TodoContainer.js │ ├── reducers │ │ ├── index.js │ │ ├── usersReducer.js │ │ ├── todoListsReducer.js │ │ ├── __test__ │ │ │ ├── usersReducer.test.js │ │ │ └── todosReducer.test.js │ │ └── todosReducer.js │ ├── actions │ │ ├── index.js │ │ ├── userActions.js │ │ ├── todoListActions.js │ │ └── todoActions.js │ ├── index.js │ ├── models │ │ ├── UserModel.js │ │ ├── index.js │ │ ├── UserCollection.js │ │ ├── TodoListModel.js │ │ ├── TodoModel.js │ │ ├── TodoListCollection.js │ │ └── TodoCollection.js │ ├── usecases.js │ ├── __test__ │ │ ├── actions.test.js │ │ └── usecases.test.js │ ├── selectors.js │ └── sdk.js ├── clean-architecture-utils │ └── index.js ├── template.html ├── style.css ├── usecases.js ├── services.js ├── index.js ├── immutable-perf.babel.js ├── ReverseLookup.js ├── app.js └── store.js ├── .flowconfig ├── test ├── example.test.js └── ReverseLookup.test.js ├── flow-typed └── npm │ ├── flow-bin_v0.x.x.js │ ├── shortid_v2.2.x.js │ ├── uuid_v3.x.x.js │ ├── font-awesome_vx.x.x.js │ ├── url-loader_vx.x.x.js │ ├── file-loader_vx.x.x.js │ ├── babel-preset-react_vx.x.x.js │ ├── babel-preset-es2015_vx.x.x.js │ ├── babel-preset-es2017_vx.x.x.js │ ├── react-addons-perf_vx.x.x.js │ ├── babel-preset-stage-1_vx.x.x.js │ ├── babel-preset-power-assert_vx.x.x.js │ ├── eslint-config-airbnb-flow_vx.x.x.js │ ├── babel-plugin-syntax-dynamic-import_vx.x.x.js │ ├── babel-plugin-transform-regenerator_vx.x.x.js │ ├── babel-plugin-syntax-async-functions_vx.x.x.js │ ├── babel-plugin-transform-async-to-generator_vx.x.x.js │ ├── uglifyjs-webpack-plugin_vx.x.x.js │ ├── babel-plugin-transform-runtime_vx.x.x.js │ ├── re-reselect_vx.x.x.js │ ├── eslint-plugin-flow-vars_vx.x.x.js │ ├── eslint-plugin-class-property_vx.x.x.js │ ├── style-loader_vx.x.x.js │ ├── redux-thunk_vx.x.x.js │ ├── html-webpack-plugin_vx.x.x.js │ ├── babel-loader_vx.x.x.js │ ├── babel-polyfill_vx.x.x.js │ ├── sass-loader_vx.x.x.js │ ├── redux_v3.x.x.js │ ├── eslint-config-airbnb_vx.x.x.js │ ├── babel-preset-env_vx.x.x.js │ ├── css-loader_vx.x.x.js │ ├── babel-eslint_vx.x.x.js │ ├── react-redux_v5.x.x.js │ ├── babel-plugin-espower_vx.x.x.js │ ├── redux-logger_vx.x.x.js │ ├── babel-cli_vx.x.x.js │ ├── uglify-js_vx.x.x.js │ ├── offline-plugin_vx.x.x.js │ ├── react-hot-loader_vx.x.x.js │ ├── webpack-dev-server_vx.x.x.js │ ├── worker-loader_vx.x.x.js │ ├── flow-typed_vx.x.x.js │ ├── eslint-plugin-ava_vx.x.x.js │ ├── babel-core_vx.x.x.js │ ├── node-sass_vx.x.x.js │ ├── eslint-plugin-import_vx.x.x.js │ ├── sinon_vx.x.x.js │ └── eslint-plugin-flow_vx.x.x.js ├── README.md ├── clean-architecture-demo.sublime-project ├── .babelrc ├── wallaby.js ├── .eslintrc ├── webpack.config.babel.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /src/flow-types.js: -------------------------------------------------------------------------------- 1 | declare type ReduxAction

= { 2 | type: string, 3 | payload: P, 4 | }; 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | src/flow-types.js 7 | 8 | [options] 9 | -------------------------------------------------------------------------------- /test/example.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | 3 | 4 | test("example test", t => { 5 | t.truthy(true); 6 | }); 7 | -------------------------------------------------------------------------------- /src/worker.js: -------------------------------------------------------------------------------- 1 | // silly little experiment 2 | 3 | import "babel-polyfill"; 4 | import * as usecases from "Todo/usecases"; 5 | 6 | onmessage = e => { 7 | console.log(e); 8 | postMessage("Hello World"); 9 | }; 10 | -------------------------------------------------------------------------------- /src/Todo/services/ErrorService.js: -------------------------------------------------------------------------------- 1 | import BaseService from "./BaseService"; 2 | 3 | export default class ErrorService extends BaseService { 4 | async reportError(err: Error) { 5 | console.error(err.message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /flow-typed/npm/flow-bin_v0.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583 2 | // flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x 3 | 4 | declare module "flow-bin" { 5 | declare module.exports: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/Todo/services/BaseService.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from "redux"; 2 | 3 | export default class BaseService { 4 | constructor(dispatch, getState) { 5 | this.dispatch = dispatch; 6 | this.getState = getState; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Todo/components/index.js: -------------------------------------------------------------------------------- 1 | import TodoComponent from "./TodoComponent"; 2 | import TodoListComponent from "./TodoListComponent"; 3 | import TodoFormComponent from "./TodoFormComponent"; 4 | 5 | export { TodoComponent, TodoListComponent, TodoFormComponent }; -------------------------------------------------------------------------------- /src/Todo/services/__test__/ErrorService.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import ErrorService from "../ErrorService"; 3 | 4 | test(t => { 5 | t.truthy(ErrorService); 6 | const errorService = new ErrorService(); 7 | t.truthy(errorService.reportError); 8 | }); 9 | -------------------------------------------------------------------------------- /src/Todo/containers/index.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import TodoContainer from "./TodoContainer"; 4 | import TodoListContainer from "./TodoListContainer"; 5 | import TodoFormContainer from "./TodoFormContainer"; 6 | 7 | export { TodoContainer, TodoListContainer, TodoFormContainer }; -------------------------------------------------------------------------------- /src/Todo/reducers/index.js: -------------------------------------------------------------------------------- 1 | import todos from "./todosReducer"; 2 | import todoLists from "./todoListsReducer"; 3 | import users from "./usersReducer"; 4 | 5 | const reducers = { 6 | todos, 7 | todoLists, 8 | users, 9 | }; 10 | 11 | export default reducers; 12 | -------------------------------------------------------------------------------- /src/Todo/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as todo from "./todoActions"; 2 | import * as todoList from "./todoListActions"; 3 | import * as users from "./userActions"; 4 | 5 | const actions = { 6 | todo, 7 | todoList, 8 | users, 9 | }; 10 | 11 | export default actions; 12 | -------------------------------------------------------------------------------- /src/Todo/services/index.js: -------------------------------------------------------------------------------- 1 | // import ModalService from "./ModalService"; 2 | import ErrorService from "./ErrorService"; 3 | import TodoService from "./TodoService"; 4 | import ValidationService from "./ValidationService"; 5 | 6 | 7 | export { 8 | // ModalService, 9 | ErrorService, 10 | TodoService, 11 | ValidationService, 12 | }; 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Architecture Demo 2 | 3 | This project is to illustrate the structure of a React + Redux web application using Clean Architecture. 4 | 5 | # Setup 6 | 7 | ``` 8 | yarn install 9 | ``` 10 | 11 | or, if you like npm just install using that method. 12 | 13 | 14 | # Running 15 | 16 | ``` 17 | NODE_ENV=test webpack-dev-server 18 | ``` 19 | -------------------------------------------------------------------------------- /src/clean-architecture-utils/index.js: -------------------------------------------------------------------------------- 1 | export const UsecaseInteractor = usecase => services => (...args) => usecase(services)(...args); 2 | 3 | export const ServiceInitializer = (dispatch, getState) => (services) => { 4 | let _services = {}; 5 | for (let p in services) { 6 | _services[p] = new services[p](dispatch, getState); 7 | } 8 | return _services; 9 | } 10 | -------------------------------------------------------------------------------- /src/Todo/index.js: -------------------------------------------------------------------------------- 1 | import actions from "./actions"; 2 | import reducers from "./reducers"; 3 | import containers from "./containers"; 4 | import models from "./models"; 5 | import * as services from "./services"; 6 | import usecases from "./usecases"; 7 | 8 | export { 9 | actions, 10 | containers, 11 | models, 12 | reducers, 13 | services, 14 | usecases, 15 | }; 16 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 |

15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Todo/models/UserModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { Record } from "immutable"; 4 | 5 | const _shape = { 6 | id: undefined, 7 | name: "", 8 | }; 9 | 10 | export class UserModel extends Record(_shape) {} 11 | 12 | export const create = (payload) => new UserModel(payload); 13 | 14 | export const getID = (userModel) => userModel.id; 15 | 16 | export const getName = (userModel) => userModel.name; 17 | -------------------------------------------------------------------------------- /clean-architecture-demo.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "config_file": "/home/meyerrd/Documents/source/clean-architecture-demo/wallaby.js", 3 | "folders": 4 | [ 5 | { 6 | "folder_exclude_patterns": 7 | [ 8 | "node_modules", 9 | "dist" 10 | ], 11 | "name": "Clean Architecture Demo", 12 | "path": "." 13 | } 14 | ], 15 | "settings": 16 | { 17 | "tab_size": 2 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Todo/services/ModalService.js: -------------------------------------------------------------------------------- 1 | import BaseService from "./BaseService"; 2 | import { open } from "../actions/modalActions"; 3 | 4 | 5 | export default class ModalService extends BaseService { 6 | async openModal(modalID: string) { 7 | this.dispatch(open(modalID)); 8 | // this needs to return a promise that resolves to the submitted form value 9 | return Promise.resolve("debug"); 10 | } 11 | 12 | closeModal() { 13 | this.dispatch(actions.modals.close()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | @import "~@blueprintjs/core/dist/blueprint.css"; 2 | @import "~@blueprintjs/datetime/dist/blueprint-datetime.css"; 3 | 4 | body { 5 | background-color: #efefef; 6 | margin: 15px; 7 | padding-top: 50px; 8 | } 9 | 10 | .TodoList { 11 | margin-top: 15px; 12 | } 13 | 14 | .TodoForm__label { 15 | display: inline-block; 16 | padding-right: 20px; 17 | width: 80px; 18 | } 19 | 20 | .TodoForm__date-group .pt-popover-target { 21 | width: 100%; 22 | } 23 | 24 | .TodoNavbar__link { 25 | color: #106ba3; 26 | } 27 | -------------------------------------------------------------------------------- /src/Todo/models/index.js: -------------------------------------------------------------------------------- 1 | import * as TodoModel from "./TodoModel"; 2 | import * as TodoCollection from "./TodoCollection"; 3 | import * as TodoListModel from "./TodoListModel"; 4 | import * as TodoListCollection from "./TodoListCollection"; 5 | import * as UserModel from "./UserModel"; 6 | import * as UserCollection from "./UserCollection"; 7 | 8 | const models = { 9 | TodoModel, 10 | TodoCollection, 11 | TodoListModel, 12 | TodoListCollection, 13 | UserModel, 14 | UserCollection, 15 | }; 16 | 17 | export default models; 18 | -------------------------------------------------------------------------------- /flow-typed/npm/shortid_v2.2.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: b156a2435634461a06e78c26c06b3e55 2 | // flow-typed version: 4037d522d2/shortid_v2.2.x/flow_>=v0.27.x 3 | 4 | type ShortIdModule = { 5 | (): string, 6 | generate(): string, 7 | seed(seed: number): ShortIdModule, 8 | worker(workerId: number): ShortIdModule, 9 | characters(characters: string): string, 10 | decode(id: string): { version: number, worker: number }, 11 | isValid(id: mixed): boolean, 12 | }; 13 | 14 | declare module 'shortid' { 15 | declare module.exports: ShortIdModule; 16 | }; 17 | -------------------------------------------------------------------------------- /flow-typed/npm/uuid_v3.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: c07f382c8238bb78e545b60dd4f097a6 2 | // flow-typed version: 27f92307d3/uuid_v3.x.x/flow_>=v0.33.x 3 | 4 | declare module 'uuid' { 5 | declare function v1(options?: {| 6 | node?: number[], 7 | clockseq?: number, 8 | msecs?: number | Date, 9 | nsecs?: number, 10 | |}, buffer?: number[] | Buffer, offset?: number): string; 11 | declare function v4(options?: {| 12 | random?: number[], 13 | rng?: () => number[] | Buffer, 14 | |}, buffer?: number[] | Buffer, offset?: number): string; 15 | } 16 | -------------------------------------------------------------------------------- /src/Todo/containers/TodoFormContainer.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import { connect } from "react-redux"; 4 | import TodoFormComponent from "../components/TodoFormComponent"; 5 | import { addTodoUsecase } from "usecases"; 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => { 8 | return { 9 | addTodo: (name: string, description: string, date: string) => addTodoUsecase(ownProps.userID, name, description, date), 10 | }; 11 | }; 12 | 13 | const TodoFormContainer = connect(undefined, mapDispatchToProps)(TodoFormComponent); 14 | 15 | export default TodoFormContainer; 16 | -------------------------------------------------------------------------------- /flow-typed/npm/font-awesome_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: f8be86a6060f971e78b251ec2163941b 2 | // flow-typed version: <>/font-awesome_v^4.7.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'font-awesome' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'font-awesome' { 17 | declare module.exports: any; 18 | } 19 | -------------------------------------------------------------------------------- /src/Todo/components/TodoListComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import TodoContainer from "../containers/TodoContainer"; 4 | 5 | 6 | export default class TodoListComponent extends Component { 7 | static propTypes = { 8 | todoIDs: PropTypes.array.isRequired, 9 | }; 10 | 11 | render() { 12 | return (
13 |
Todo List
14 | { this.props.todoIDs ? this.props.todoIDs.map(todoID => ) : null } 15 |
); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Todo/actions/userActions.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | "use strict"; 4 | 5 | export const ADD: string = "@user/ADD"; 6 | export const REMOVE: string = "@user/REMOVE"; 7 | 8 | type t_Action

= { 9 | type: string, 10 | payload: P, 11 | }; 12 | 13 | type t_AddPayload = { 14 | id: string, 15 | name: string, 16 | }; 17 | 18 | type t_RemovePayload = { 19 | id: string, 20 | }; 21 | 22 | export const add = (id: string, name: string): t_Action => ({ 23 | type: ADD, 24 | payload: { id, name }, 25 | }); 26 | 27 | export const remove = (id: string): t_Action => ({ 28 | type: REMOVE, 29 | payload: { id }, 30 | }); 31 | -------------------------------------------------------------------------------- /src/Todo/services/ValidationService.js: -------------------------------------------------------------------------------- 1 | import BaseService from "./BaseService"; 2 | import ErrorService from "./ErrorService"; 3 | import TodoSDK from "../sdk"; 4 | 5 | export default class ValidationService extends BaseService { 6 | async validateTodo(description: string, name: string, creatorID: string, date: string) { 7 | const validation = await TodoSDK.validateTodo(description, name, creatorID, date) 8 | .catch(console.error) 9 | .then(data => data.json()); 10 | 11 | if (validation && validation.valid) { 12 | return true; 13 | } else { 14 | return new Error("Validation service failed"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/usecases.js: -------------------------------------------------------------------------------- 1 | import { UsecaseInteractor } from "clean-architecture-utils"; 2 | import { AddTodoUsecase, LoadTodosUsecase } from "Todo/usecases"; 3 | import { addTodoServices, loadTodoServices } from "./services"; 4 | 5 | // essentially, stores the usecase and services so that when we actually go to call the usecase in the component, 6 | // we don't need to pass in dispatch or getState. createInteractor, when passed in the usecase and services, then calls 7 | // the usecase with those services, then spreads args on addTodoUsecase 8 | export const addTodoUsecase = UsecaseInteractor(AddTodoUsecase)(addTodoServices); 9 | export const loadTodosUsecase = UsecaseInteractor(LoadTodosUsecase)(loadTodoServices); 10 | -------------------------------------------------------------------------------- /src/Todo/actions/todoListActions.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | export const ADD: string = "@todoList/ADD"; 4 | export const UPDATE: string = "@todoList/UPDATE"; 5 | 6 | type Action

= { 7 | type: string, 8 | payload: P, 9 | }; 10 | 11 | type AddPayload = { 12 | id: string, 13 | name: string, 14 | todoIDs: string[], 15 | }; 16 | 17 | type UpdatePayload = { 18 | id: string, 19 | name: string, 20 | todoID: string, 21 | }; 22 | 23 | export const add = (id: string, name: string, todoIDs: string[]): Action => ({ 24 | type: ADD, 25 | payload: { id, name, todoIDs }, 26 | }); 27 | 28 | export const update = (id: string, name: string, todoID: string): Action => ({ 29 | type: UPDATE, 30 | payload: { id, name, todoID }, 31 | }); 32 | -------------------------------------------------------------------------------- /src/services.js: -------------------------------------------------------------------------------- 1 | import { ServiceInitializer } from "clean-architecture-utils"; 2 | import { ValidationService, TodoService, ErrorService } from "Todo/services"; 3 | import { dispatch, getState } from "./store"; 4 | 5 | // This creates a function that passes in dispatch and getState to all the uninitialized services. 6 | // 7 | // 8 | const serviceInitializer = ServiceInitializer(dispatch, getState); 9 | 10 | // You could use this to initialize all the services in the application, then pull out the ones you need for each usecase. 11 | export const addTodoServices = serviceInitializer({ 12 | ValidationService, 13 | TodoService, 14 | ErrorService, 15 | }); 16 | 17 | export const loadTodoServices = serviceInitializer({ 18 | TodoService, 19 | ErrorService, 20 | }) 21 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | [ 5 | "env", 6 | { 7 | "targets": { 8 | "browsers": [ 9 | "last 2 versions", 10 | "ie >= 10" 11 | ] 12 | }, 13 | "modules": false, 14 | "loose": true 15 | } 16 | ], 17 | "stage-1" 18 | ], 19 | "plugins": [ 20 | "react-hot-loader/babel", 21 | "transform-flow-strip-types" 22 | ], 23 | "retainLines": true, 24 | "env": { 25 | "test": { 26 | "presets": [ 27 | "react", 28 | [ 29 | "env", 30 | { 31 | "targets": { 32 | "node": true 33 | } 34 | } 35 | ], 36 | "stage-1" 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Todo/models/UserCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { Record, List } from "immutable"; 4 | import { getID } from "./UserModel"; 5 | 6 | const _shape = { 7 | users: List(), 8 | }; 9 | 10 | export class UserCollection extends Record(_shape) {} 11 | 12 | export const size = (collection: UserCollection): number => collection.users.size; 13 | 14 | export const empty = new UserCollection(); 15 | 16 | export const add = (userCollection, userModel) => userCollection.update("users", users => users.push(userModel)); 17 | 18 | export const remove = (userCollection, id) => userCollection.update("users", 19 | users => users.filterNot(user => getID(user) === id) 20 | ); 21 | 22 | export const get = (userCollection, id) => userCollection.users.find(user => getID(user) === id); 23 | -------------------------------------------------------------------------------- /src/Todo/components/TodoComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | 5 | export default class TodoComponent extends Component { 6 | static propTypes = { 7 | description: PropTypes.string.isRequired, 8 | date: PropTypes.string.isRequired, 9 | name: PropTypes.string.isRequired, 10 | userName: PropTypes.string.isRequired, 11 | }; 12 | 13 | render() { 14 | const { description, name, userName, date } = this.props; 15 | return (

16 |
{name} - {userName} - {date}
17 |
18 | {description} 19 |
20 |
); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Todo/reducers/usersReducer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { ADD, REMOVE } from "../actions/userActions"; 4 | import { create } from "../models/UserModel"; 5 | import { add, remove, empty } from "../models/UserCollection"; 6 | 7 | 8 | const initialState = empty; 9 | 10 | 11 | const user = (state, action) => { 12 | switch (action.type) { 13 | case ADD: 14 | return create(action.payload); 15 | default: 16 | return state; 17 | } 18 | }; 19 | 20 | const users = (state=initialState, action) => { 21 | switch (action.type) { 22 | case ADD: 23 | return add(state, user(undefined, action)); 24 | case REMOVE: 25 | return remove(state, action.payload.id); 26 | default: 27 | return state; 28 | } 29 | }; 30 | 31 | export default users; 32 | -------------------------------------------------------------------------------- /src/Todo/containers/TodoListContainer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createSelector } from "reselect"; 3 | import { connect } from "react-redux"; 4 | import TodoListComponent from "../components/TodoListComponent"; 5 | import models from "../models"; 6 | const { TodoListModel, TodoListCollection } = models; 7 | import { selectTodoListByID } from "../selectors"; 8 | 9 | const mapStateToProps = (state, ownProps) => { 10 | const todoList = selectTodoListByID(state, ownProps); 11 | const todoIDs = TodoListModel.getTodoIDs(todoList); 12 | 13 | return { 14 | todoIDs, 15 | }; 16 | }; 17 | 18 | // TodoListContainer takes in an ID for a TodoList 19 | // todoLists: { id: TodoIDs[] } 20 | const TodoListContainer = connect(mapStateToProps)(TodoListComponent); 21 | 22 | export default TodoListContainer; 23 | -------------------------------------------------------------------------------- /src/Todo/containers/TodoContainer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react"; 3 | import { createSelector } from "reselect"; 4 | import { connect } from "react-redux"; 5 | import TodoComponent from "../components/TodoComponent"; 6 | import models from "../models"; 7 | import { selectTodoByIDWithUser } from "../selectors"; 8 | 9 | 10 | const { TodoModel, TodoCollection, UserCollection, UserModel } = models; 11 | 12 | const mapStateToProps = (state, ownProps) => { 13 | const { todo, user } = selectTodoByIDWithUser(state, ownProps); 14 | 15 | return { 16 | date: TodoModel.getDate(todo), 17 | description: TodoModel.getDescription(todo), 18 | name: TodoModel.getName(todo), 19 | userName: UserModel.getName(user), 20 | }; 21 | }; 22 | 23 | 24 | const TodoContainer = connect(mapStateToProps)(TodoComponent); 25 | 26 | export default TodoContainer; 27 | -------------------------------------------------------------------------------- /src/Todo/models/TodoListModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { Record, List } from "immutable"; 4 | 5 | const _shape = { 6 | id: undefined, 7 | name: "", 8 | todoIDs: List(), 9 | }; 10 | 11 | export class TodoListModel extends Record(_shape) { 12 | } 13 | 14 | export const create = (payload) => { 15 | const { id, name } = payload; 16 | const todoIDs = List.of(...payload.todoIDs); 17 | return new TodoListModel({ id, name, todoIDs }); 18 | }; 19 | 20 | export const getID = (todoListModel) => todoListModel.id; 21 | 22 | export const add = (todoListModel, id) => todoListModel.update("todoIDs", todoIDs => todoIDs.push(id)); 23 | 24 | export const getTodoIDs = (todoListModel) => { 25 | return todoListModel.todoIDs.toArray(); 26 | }; 27 | 28 | export const remove = (todoListModel, id) => todoListModel.update("todoIDs", todoIDs => todoIDs.remove(id)); 29 | -------------------------------------------------------------------------------- /src/Todo/usecases.js: -------------------------------------------------------------------------------- 1 | export const DeleteTodoUsecase = ({ TodoService }) => async (todoID) => { 2 | await TodoService.deleteTodo(todoID); 3 | }; 4 | 5 | export const AddTodoUsecase = ({ ValidationService, TodoService, ErrorService }) => { 6 | return async (creatorID, name, description, date) => { 7 | let maybeError = await ValidationService.validateTodo(description, name, creatorID, date); 8 | 9 | if (maybeError instanceof Error) { 10 | ErrorService.reportError(maybeError); 11 | } else { 12 | await TodoService.createTodo(description, name, creatorID, date); 13 | } 14 | }; 15 | }; 16 | 17 | export const LoadTodosUsecase = ({ TodoService, ErrorService }) => { 18 | return async () => { 19 | try { 20 | await TodoService.loadTodos(); 21 | } catch (e) { 22 | ErrorService.reportError(e); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/Todo/models/TodoModel.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | "use strict"; 4 | 5 | import { Record } from "immutable"; 6 | import type { AddPayload } from "../actions/todoActions"; 7 | 8 | type T_ID = number | string; 9 | 10 | export class TodoModel extends Record({ 11 | description: "", 12 | id: undefined, 13 | name: "", 14 | creatorID: undefined, 15 | date: undefined, 16 | }) {} 17 | 18 | //TODO: flow type payload 19 | export const create = (payload: AddPayload): TodoModel => new TodoModel(payload); 20 | 21 | export const getID = (todoModel: TodoModel): T_ID => todoModel.id; 22 | 23 | export const getName = (todoModel: TodoModel): string => todoModel.name; 24 | 25 | export const getCreatorID = (todoModel: TodoModel): T_ID => todoModel.creatorID; 26 | 27 | export const getDescription = (todoModel: TodoModel): string => todoModel.description; 28 | 29 | export const getDate = (todoModel: TodoModel): string => todoModel.date; 30 | -------------------------------------------------------------------------------- /src/Todo/reducers/todoListsReducer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { ADD, UPDATE } from "../actions/todoListActions"; 4 | import * as TodoListModel from "../models/TodoListModel"; 5 | import { add, remove, empty, get, update } from "../models/TodoListCollection"; 6 | 7 | 8 | const initialState = empty; 9 | 10 | const todoList = (state, action) => { 11 | switch (action.type) { 12 | case ADD: 13 | return TodoListModel.create(action.payload); 14 | case UPDATE: 15 | return TodoListModel.add(state, action.payload.todoID); 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | const todoLists = (state=initialState, action) => { 22 | switch (action.type) { 23 | case ADD: 24 | return add(state, todoList(undefined, action)); 25 | case UPDATE: 26 | return update(state, action.payload.id, todoList(get(state, action.payload.id), action)); 27 | default: 28 | return state; 29 | } 30 | }; 31 | 32 | export default todoLists; 33 | -------------------------------------------------------------------------------- /flow-typed/npm/url-loader_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: bcbed12c56c453cd1be6e3a1882f9cd7 2 | // flow-typed version: <>/url-loader_v^0.5.8/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'url-loader' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'url-loader' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'url-loader/index' { 29 | declare module.exports: $Exports<'url-loader'>; 30 | } 31 | declare module 'url-loader/index.js' { 32 | declare module.exports: $Exports<'url-loader'>; 33 | } 34 | -------------------------------------------------------------------------------- /flow-typed/npm/file-loader_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: b3e8928e7262efdbd7562018b0a16547 2 | // flow-typed version: <>/file-loader_v^0.11.1/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'file-loader' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'file-loader' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'file-loader/index' { 29 | declare module.exports: $Exports<'file-loader'>; 30 | } 31 | declare module 'file-loader/index.js' { 32 | declare module.exports: $Exports<'file-loader'>; 33 | } 34 | -------------------------------------------------------------------------------- /src/Todo/actions/todoActions.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | "use strict"; 4 | 5 | export const ADD = "@todo/ADD"; 6 | export const ADD_ALL = "@todo/ADD_ALL"; 7 | export const REMOVE = "@todo/REMOVE"; 8 | 9 | export type AddPayload = {| 10 | creatorID: string, 11 | date: string, 12 | description: string, 13 | id: string, 14 | name: string, 15 | |}; 16 | 17 | export type RemovePayload = {| 18 | id: string, 19 | |}; 20 | 21 | export const add = (id: string, creatorID: string, name: string, description: string, date: string): ReduxAction => { 22 | return { 23 | type: ADD, 24 | payload: { id, creatorID, name, description, date }, 25 | }; 26 | }; 27 | 28 | export const addAll = (todos: AddPayload[]): ReduxAction<{ todos: AddPayload[] }> => { 29 | return { 30 | type: ADD_ALL, 31 | payload: { 32 | todos, 33 | }, 34 | }; 35 | }; 36 | 37 | export const remove = (id: string): ReduxAction => { 38 | return { 39 | type: REMOVE, 40 | payload: { id }, 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | process.env.BABEL_ENV = 'test'; 2 | 3 | module.exports = wallaby => { 4 | return { 5 | files: [ 6 | { 7 | pattern: "node_modules/react/dist/react-with-addons.js", 8 | instrument: false, 9 | }, 10 | { 11 | pattern: "node_modules/immutable/dist/immutable.js", 12 | instrument: false, 13 | }, 14 | { 15 | pattern: "src/**/__test__/*.test.js", 16 | ignore: true, 17 | }, 18 | "src/**/*.js", 19 | ], 20 | 21 | tests: [ "src/**/__test__/*.test.js" ], 22 | 23 | delays: { 24 | run: 500, 25 | }, 26 | 27 | compilers: { 28 | "**/*.js": wallaby.compilers.babel(), 29 | }, 30 | 31 | debug: true, 32 | 33 | setup: function (wallaby) { 34 | require("babel-polyfill"); 35 | }, 36 | 37 | testFramework: "ava", 38 | 39 | reportConsoleErrorAsError: true, 40 | 41 | env: { 42 | type: "node", 43 | params: { 44 | env: "NODE_PATH=./src", 45 | }, 46 | }, 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/Todo/__test__/actions.test.js: -------------------------------------------------------------------------------- 1 | /* eslint no-magic-numbers: "off" */ 2 | 3 | import test from "ava"; 4 | import { ADD, REMOVE, add, remove } from "Todo/actions/todoActions"; 5 | 6 | 7 | test("ADD", t => { 8 | t.truthy(ADD); 9 | t.is(ADD, "@todo/ADD"); 10 | }); 11 | 12 | test("REMOVE", t => { 13 | t.truthy(REMOVE); 14 | t.is(REMOVE, "@todo/REMOVE"); 15 | }); 16 | 17 | test("add(id, creatorID, name, description, date)", t => { 18 | t.truthy(add); 19 | 20 | const id = 0, 21 | name = "test name", 22 | creatorID = 0, 23 | description = "test description", 24 | date = "2017-11-10"; 25 | 26 | const expected = { 27 | type: ADD, 28 | payload: { id, creatorID, name, description, date }, 29 | }; 30 | 31 | t.deepEqual(add(id, creatorID, name, description, date), expected); 32 | }); 33 | 34 | test("remove(id)", t => { 35 | t.truthy(remove); 36 | const id = 0; 37 | const expected = { 38 | type: REMOVE, 39 | payload: { id }, 40 | }; 41 | 42 | t.deepEqual(remove(id), expected); 43 | }); 44 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-preset-react_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: c200c0210ef5b1aec3b906881036d4af 2 | // flow-typed version: <>/babel-preset-react_v^6.22.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-preset-react' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-preset-react' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-preset-react/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-preset-react/lib/index.js' { 31 | declare module.exports: $Exports<'babel-preset-react/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-preset-es2015_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 7ec00ef61c5ef865263e150f80a7592c 2 | // flow-typed version: <>/babel-preset-es2015_v^6.22.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-preset-es2015' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-preset-es2015' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-preset-es2015/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-preset-es2015/lib/index.js' { 31 | declare module.exports: $Exports<'babel-preset-es2015/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-preset-es2017_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 3144d986be34a879222bfcfed5c8ae07 2 | // flow-typed version: <>/babel-preset-es2017_v^6.22.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-preset-es2017' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-preset-es2017' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-preset-es2017/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-preset-es2017/lib/index.js' { 31 | declare module.exports: $Exports<'babel-preset-es2017/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/react-addons-perf_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 207c7aa80351103c2a1b7d5daba48654 2 | // flow-typed version: <>/react-addons-perf_v^15.4.2/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'react-addons-perf' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'react-addons-perf' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'react-addons-perf/index' { 29 | declare module.exports: $Exports<'react-addons-perf'>; 30 | } 31 | declare module 'react-addons-perf/index.js' { 32 | declare module.exports: $Exports<'react-addons-perf'>; 33 | } 34 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-preset-stage-1_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: c94a6efd0f2b388db784eab679da55f6 2 | // flow-typed version: <>/babel-preset-stage-1_v^6.22.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-preset-stage-1' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-preset-stage-1' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-preset-stage-1/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-preset-stage-1/lib/index.js' { 31 | declare module.exports: $Exports<'babel-preset-stage-1/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /src/Todo/reducers/__test__/usersReducer.test.js: -------------------------------------------------------------------------------- 1 | /* eslint no-magic-numbers: "off" */ 2 | 3 | import test from "ava"; 4 | import actions from "Todo/actions"; 5 | import reducers from "Todo/reducers"; 6 | import models from "Todo/models"; 7 | 8 | const { users } = reducers; 9 | const { empty, add, remove, size } = models.UserCollection; 10 | const { create } = models.UserModel; 11 | 12 | test("users", t => { 13 | t.truthy(users); 14 | 15 | let state = users(undefined, {}); 16 | t.is(size(state), 0, "the users reducer should handle initial state"); 17 | 18 | let addUserAction = actions.users.add(0, "test name"); 19 | t.is(size(state), 0, "The state is empty"); 20 | state = users(state, addUserAction); 21 | t.is(size(state), 1, "A user has been added"); 22 | 23 | let removeUserAction = actions.users.remove(1); 24 | state = users(state, removeUserAction); 25 | t.is(size(state), 1, "A remove action with a non-existant id does nothing"); 26 | 27 | removeUserAction = actions.users.remove(0); 28 | state = users(state, removeUserAction); 29 | t.is(size(state), 0, "A user has been removed"); 30 | }); 31 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-preset-power-assert_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 6094e018eb8a5d953200b99904fbaae3 2 | // flow-typed version: <>/babel-preset-power-assert_v^1.0.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-preset-power-assert' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-preset-power-assert' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'babel-preset-power-assert/index' { 29 | declare module.exports: $Exports<'babel-preset-power-assert'>; 30 | } 31 | declare module 'babel-preset-power-assert/index.js' { 32 | declare module.exports: $Exports<'babel-preset-power-assert'>; 33 | } 34 | -------------------------------------------------------------------------------- /flow-typed/npm/eslint-config-airbnb-flow_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 9fd132cefb0098072882f1651550be12 2 | // flow-typed version: <>/eslint-config-airbnb-flow_v^1.0.2/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'eslint-config-airbnb-flow' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'eslint-config-airbnb-flow' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'eslint-config-airbnb-flow/index' { 29 | declare module.exports: $Exports<'eslint-config-airbnb-flow'>; 30 | } 31 | declare module 'eslint-config-airbnb-flow/index.js' { 32 | declare module.exports: $Exports<'eslint-config-airbnb-flow'>; 33 | } 34 | -------------------------------------------------------------------------------- /src/Todo/services/TodoService.js: -------------------------------------------------------------------------------- 1 | import BaseService from "./BaseService"; 2 | import TodoSDK from "../sdk"; 3 | import { bindActionCreators } from "redux"; 4 | import { add, addAll } from "../actions/todoActions"; 5 | import { update } from "../actions/todoListActions"; 6 | 7 | export default class TodoService extends BaseService { 8 | async createTodo(description, name, creatorID, date) { 9 | const payload = await TodoSDK.createTodo(description, name, creatorID, date) 10 | .catch(console.error) 11 | .then(data => data.json()); 12 | 13 | let { id } = payload; 14 | 15 | this.dispatch(add(id, creatorID, name, description, date)); 16 | // temporary hack 17 | this.dispatch(update("all", "All Todos", payload.id)); 18 | 19 | return payload; 20 | } 21 | 22 | async loadTodos() { 23 | const payload = await TodoSDK.loadTodos() 24 | .catch(console.error) 25 | .then(data => data.json()); 26 | 27 | const todos = Object.values(payload); 28 | 29 | this.dispatch(addAll(todos)); 30 | todos.forEach(({ id }) => { 31 | this.dispatch(update("all", "All Todos", id)); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-plugin-syntax-dynamic-import_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: abd0b4a1787467c0df51ce9bec778a2c 2 | // flow-typed version: <>/babel-plugin-syntax-dynamic-import_v^6.18.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-plugin-syntax-dynamic-import' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-plugin-syntax-dynamic-import' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-plugin-syntax-dynamic-import/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-plugin-syntax-dynamic-import/lib/index.js' { 31 | declare module.exports: $Exports<'babel-plugin-syntax-dynamic-import/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-plugin-transform-regenerator_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 73a3d2b02e57e9c4dfc24de3cfb91b03 2 | // flow-typed version: <>/babel-plugin-transform-regenerator_v^6.22.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-plugin-transform-regenerator' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-plugin-transform-regenerator' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-plugin-transform-regenerator/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-plugin-transform-regenerator/lib/index.js' { 31 | declare module.exports: $Exports<'babel-plugin-transform-regenerator/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-plugin-syntax-async-functions_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: be9dc346b90957af74e6b174db140cf8 2 | // flow-typed version: <>/babel-plugin-syntax-async-functions_v^6.13.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-plugin-syntax-async-functions' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-plugin-syntax-async-functions' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-plugin-syntax-async-functions/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-plugin-syntax-async-functions/lib/index.js' { 31 | declare module.exports: $Exports<'babel-plugin-syntax-async-functions/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-plugin-transform-async-to-generator_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 611852678f2e926e87b8fd9bfd8cf926 2 | // flow-typed version: <>/babel-plugin-transform-async-to-generator_v^6.22.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-plugin-transform-async-to-generator' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-plugin-transform-async-to-generator' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-plugin-transform-async-to-generator/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-plugin-transform-async-to-generator/lib/index.js' { 31 | declare module.exports: $Exports<'babel-plugin-transform-async-to-generator/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import "babel-polyfill"; 3 | import React from "react"; 4 | import { render } from "react-dom"; 5 | import store from "./store"; 6 | import { addTodoUsecase } from "./usecases"; 7 | import Perf from "react-addons-perf"; 8 | import shortid from "shortid"; 9 | 10 | import "./style.css"; 11 | import "font-awesome/scss/font-awesome.scss"; 12 | 13 | import App from "./app"; 14 | 15 | import { actions } from "Todo"; 16 | 17 | // const MyWorker = require("worker-loader!./worker.js"); 18 | 19 | // let myWorker = new MyWorker(); 20 | 21 | // myWorker.postMessage({ 22 | // foo: "bar", 23 | // }); 24 | 25 | // myWorker.onmessage = e => { 26 | // console.log(e); 27 | // }; 28 | 29 | 30 | // Perf.start(); 31 | // 32 | store.dispatch(actions.users.add("user-1", "Test User")); 33 | store.dispatch(actions.todoList.add("all", "All Todos", [])); 34 | 35 | // addTodoUsecase("user-1", "name", "Test description", "1920-10-10"); 36 | 37 | render(, document.getElementById("react-mount")); 38 | 39 | // Perf.stop(); 40 | 41 | // let measurements = Perf.getLastMeasurements(); 42 | 43 | // Perf.printInclusive(measurements); 44 | // Perf.printExclusive(measurements); 45 | // Perf.printOperations(measurements); 46 | -------------------------------------------------------------------------------- /src/Todo/reducers/todosReducer.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | "use strict"; 4 | 5 | import { ADD, ADD_ALL, REMOVE } from "../actions/todoActions"; 6 | import type { AddPayload, RemovePayload } from "../actions/todoActions"; 7 | import { create } from "../models/TodoModel"; 8 | import type { TodoModel } from "../models/TodoModel"; 9 | import { add, addAll, remove, empty } from "../models/TodoCollection"; 10 | import type { TodoCollection } from "../models/TodoCollection"; 11 | 12 | 13 | const initialState = empty(); 14 | 15 | const todo = (state: ?TodoModel, action: ReduxAction) => { 16 | switch (action.type) { 17 | case ADD: 18 | return create(action.payload); 19 | default: 20 | return state; 21 | } 22 | }; 23 | 24 | const todos = (state: TodoCollection = initialState, action: ReduxAction) => { 25 | switch (action.type) { 26 | case ADD: 27 | return add(state, todo(undefined, action)); 28 | case ADD_ALL: { 29 | const { payload: { todos } } = action; 30 | return addAll(state, todos.map(create)); 31 | } 32 | 33 | case REMOVE: 34 | return remove(state, action.payload.id); 35 | default: 36 | return state; 37 | } 38 | }; 39 | 40 | export default todos; 41 | -------------------------------------------------------------------------------- /src/Todo/reducers/__test__/todosReducer.test.js: -------------------------------------------------------------------------------- 1 | /* eslint no-magic-numbers: "off" */ 2 | 3 | import test from "ava"; 4 | import reducers from "Todo/reducers"; 5 | import actions from "Todo/actions"; 6 | import models from "Todo/models"; 7 | import { is } from "immutable"; 8 | const { todo, todos } = reducers; 9 | const { empty, add, remove } = models.TodoCollection; 10 | const { create } = models.TodoModel; 11 | 12 | 13 | test("todos", t => { 14 | t.truthy(todos); 15 | let expected = empty(); 16 | let state = todos(undefined, {}); 17 | t.truthy(is(expected, state), "todos handles initial state"); 18 | }); 19 | 20 | test("addTodo", t => { 21 | let state = empty(); 22 | let addTodoAction = actions.todo.add(0, "test name", "test description"); 23 | state = todos(state, addTodoAction); 24 | let expected = add(empty(), create(addTodoAction.payload)); 25 | 26 | t.truthy(is(expected, state), "add a todo to the state"); 27 | 28 | }); 29 | 30 | test(t => { 31 | let state = empty(); 32 | let addTodoAction = actions.todo.add(0, "test name", "test description"); 33 | state = todos(state, addTodoAction); 34 | t.is(state.todos.size, 1); 35 | state = todos(state, actions.todo.remove(0)); 36 | t.truthy(is(empty(), state)); 37 | t.is(state.todos.size, 0); 38 | }); 39 | -------------------------------------------------------------------------------- /src/Todo/__test__/usecases.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import sinon from "sinon"; 3 | import { AddTodoUsecase } from "Todo/usecases"; 4 | 5 | 6 | test("AddTodoUsecase", t => { 7 | const services = { 8 | ValidationService: { 9 | validateTodo: sinon.spy(() => Promise.resolve(true)), 10 | }, 11 | TodoService: { 12 | createTodo: sinon.spy((description, name, creatorID, date) => ({ id: "foo", description, name, creatorID, date })), 13 | }, 14 | ErrorService: { 15 | reportError: sinon.spy(err => console.log(err)), 16 | }, 17 | }; 18 | 19 | t.truthy(AddTodoUsecase, "AddTodoUsecase is defined"); 20 | const addTodoUsecase = AddTodoUsecase(services); 21 | t.truthy(addTodoUsecase, "the curried usecase is defined"); 22 | 23 | // Here we're just checking to make sure the services provided were called. We don't care about if the services *did* 24 | // anything because they'll have their own tests 25 | addTodoUsecase("bar", "name", "description", "2017-01-01").then(data => { 26 | t.true(services.ValidationService.validateTodo.calledOnce); 27 | t.false(services.ErrorService.reportError.calledOnce); 28 | t.is(services.ErrorService.reportError.called, 0); 29 | t.true(services.TodoService.createTodo.calledOnce); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /flow-typed/npm/uglifyjs-webpack-plugin_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 4a5159ddd53a6b2107aab3c5c1fb1c96 2 | // flow-typed version: <>/uglifyjs-webpack-plugin_v^0.2.1/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'uglifyjs-webpack-plugin' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'uglifyjs-webpack-plugin' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'uglifyjs-webpack-plugin/dist/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'uglifyjs-webpack-plugin/lib/post_install' { 30 | declare module.exports: any; 31 | } 32 | 33 | // Filename aliases 34 | declare module 'uglifyjs-webpack-plugin/dist/index.js' { 35 | declare module.exports: $Exports<'uglifyjs-webpack-plugin/dist/index'>; 36 | } 37 | declare module 'uglifyjs-webpack-plugin/lib/post_install.js' { 38 | declare module.exports: $Exports<'uglifyjs-webpack-plugin/lib/post_install'>; 39 | } 40 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-plugin-transform-runtime_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: ac2cc34046f94d04260b66a018ddaa2b 2 | // flow-typed version: <>/babel-plugin-transform-runtime_v^6.23.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-plugin-transform-runtime' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-plugin-transform-runtime' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-plugin-transform-runtime/lib/definitions' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'babel-plugin-transform-runtime/lib/index' { 30 | declare module.exports: any; 31 | } 32 | 33 | // Filename aliases 34 | declare module 'babel-plugin-transform-runtime/lib/definitions.js' { 35 | declare module.exports: $Exports<'babel-plugin-transform-runtime/lib/definitions'>; 36 | } 37 | declare module 'babel-plugin-transform-runtime/lib/index.js' { 38 | declare module.exports: $Exports<'babel-plugin-transform-runtime/lib/index'>; 39 | } 40 | -------------------------------------------------------------------------------- /flow-typed/npm/re-reselect_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 0de5cc4e585357b847469b70a1a0240d 2 | // flow-typed version: <>/re-reselect_v^0.4.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 're-reselect' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 're-reselect' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 're-reselect/dist/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 're-reselect/es/index' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 're-reselect/lib/index' { 34 | declare module.exports: any; 35 | } 36 | 37 | // Filename aliases 38 | declare module 're-reselect/dist/index.js' { 39 | declare module.exports: $Exports<'re-reselect/dist/index'>; 40 | } 41 | declare module 're-reselect/es/index.js' { 42 | declare module.exports: $Exports<'re-reselect/es/index'>; 43 | } 44 | declare module 're-reselect/lib/index.js' { 45 | declare module.exports: $Exports<'re-reselect/lib/index'>; 46 | } 47 | -------------------------------------------------------------------------------- /src/immutable-perf.babel.js: -------------------------------------------------------------------------------- 1 | import { Map, List } from "immutable"; 2 | 3 | class Obj { 4 | constructor(id) { 5 | this.id = id; 6 | } 7 | 8 | // get id() { 9 | // return this.id; 10 | // } 11 | 12 | // set id(newID) { 13 | // this.id = newID; 14 | // } 15 | } 16 | 17 | 18 | let map = Map(); 19 | 20 | console.log("Map:\n----"); 21 | 22 | console.time("set"); 23 | for (let i = 0; i < 10000; i++) { 24 | map = map.set(i, new Obj(i)); 25 | } 26 | console.timeEnd("set"); 27 | 28 | console.time("5000 random finds"); 29 | for (let i = 0; i < 5000; i++) { 30 | let id = Math.floor(Math.random() * 10000); 31 | map.find(item => item.id === id); 32 | } 33 | console.timeEnd("5000 random finds"); 34 | 35 | console.time("remove"); 36 | for (let i = 10000; i > 0; i--) { 37 | map = map.remove(i); 38 | } 39 | console.timeEnd("remove"); 40 | 41 | console.log("\n"); 42 | 43 | 44 | let list = List(); 45 | 46 | console.log("List:\n-----"); 47 | console.time("push"); 48 | for (let i = 0; i < 10000; i++) { 49 | list = list.push(new Obj(i)); 50 | } 51 | console.timeEnd("push"); 52 | 53 | console.time("5000 random finds"); 54 | for (let i = 0; i < 5000; i++) { 55 | let id = Math.floor(Math.random() * 10000); 56 | list.find(item => item.id === id); 57 | } 58 | console.timeEnd("5000 random finds"); 59 | 60 | console.time("remove"); 61 | for (let i = 10000; i > 0; i--) { 62 | list = list.remove(i); 63 | } 64 | console.timeEnd("remove"); 65 | -------------------------------------------------------------------------------- /src/ReverseLookup.js: -------------------------------------------------------------------------------- 1 | import { Record, Set, Map } from "immutable"; 2 | 3 | 4 | // export default class ReverseLookup extends Record({ map: Map() }) { 5 | // add = (k, v) => { 6 | // return this.map.set(k, v); 7 | // } 8 | 9 | // remove = (key, value) => { 10 | // if (!this.map.has(value)) { 11 | // return this.map; 12 | // } 13 | // return this.map.update(key, v => v.remove(value) ); 14 | // } 15 | // } 16 | 17 | // const add = (table, k, v) => { 18 | // if (!table.has(v)) { 19 | // return table.set(k, v); 20 | // } 21 | // }; 22 | 23 | // export class IndexedLookupTable extends Record({ map: Map() }) { 24 | // } 25 | 26 | /** holds a map of Key : Set */ 27 | export const empty = Map(); 28 | 29 | const ensureSet = values => Set.isSet(values) ? values : Set.of(values); 30 | 31 | // If the lookupTable already contains an index, merge with the existing values. 32 | export const add = (lookupTable, index, values) => { 33 | let valueSet = ensureSet(values); 34 | return !lookupTable.has(index) ? 35 | lookupTable.set(index, valueSet) : 36 | lookupTable.update(index, set => set.union(valueSet)); 37 | }; 38 | 39 | export const remove = (lookupTable, index, values) => { 40 | if (!lookupTable.has(index)) { 41 | return lookupTable; 42 | } 43 | 44 | if (values) { 45 | let valueSet = ensureSet(values); 46 | return lookupTable.update(index, set => set.subtract(valueSet)); 47 | } 48 | 49 | return lookupTable.remove(index); 50 | }; 51 | 52 | export const get = (lookupTable, index) => lookupTable.get(index); 53 | -------------------------------------------------------------------------------- /src/Todo/selectors.js: -------------------------------------------------------------------------------- 1 | import createCachedSelector from "re-reselect"; 2 | import { getCreatorID } from "./models/TodoModel"; 3 | import { get as getUser } from "./models/UserCollection"; 4 | import { get as getTodoList } from "./models/TodoListCollection"; 5 | import { get as getTodo } from "./models/TodoCollection"; 6 | 7 | 8 | export const usersSelector = state => state.users; 9 | export const userIDSelector = (state, props) => props.userID; 10 | 11 | export const selectUserByID = createCachedSelector( 12 | [usersSelector, userIDSelector], 13 | (users, userID) => getUser(users, userID) 14 | )((state, props) => props.userID); 15 | 16 | export const todosSelector = state => state.todos; 17 | export const propTodoIDSelector = (state, props) => props.todoID; 18 | 19 | export const selectTodoByID = createCachedSelector( 20 | [todosSelector, propTodoIDSelector], 21 | (todos, todoID) => getTodo(todos, todoID) 22 | )((state, props) => props.todoID); 23 | 24 | export const selectTodoByIDWithUser = createCachedSelector( 25 | [selectTodoByID, usersSelector], 26 | (todo, users) => { 27 | return { 28 | user: getUser(users, getCreatorID(todo)), 29 | todo, 30 | }; 31 | } 32 | )((state, props) => props.todoID); 33 | 34 | export const todoListsSelector = state => state.todoLists; 35 | export const todoListIDSelector = (state, props) => props.todoListID; 36 | 37 | export const selectTodoListByID = createCachedSelector( 38 | [todoListsSelector, todoListIDSelector], 39 | (todoLists, todoListID) => getTodoList(todoLists, todoListID) 40 | )((state, props) => props.todoListID); 41 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import store from "./store"; 3 | import { Provider } from "react-redux"; 4 | import { BrowserRouter as Router, Route, Link } from "react-router-dom"; 5 | import { TodoListContainer, TodoFormContainer } from "Todo/containers"; 6 | import { loadTodosUsecase } from './usecases'; 7 | 8 | export default class App extends React.Component { 9 | componentDidMount() { 10 | loadTodosUsecase(); 11 | } 12 | 13 | render() { 14 | return ( 15 | 16 | 17 |
18 | 31 | 32 | } /> 33 | } /> 34 |
35 |
36 |
37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /flow-typed/npm/eslint-plugin-flow-vars_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: b5ec82c18e1659b3d634dd2e44982196 2 | // flow-typed version: <>/eslint-plugin-flow-vars_v^0.5.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'eslint-plugin-flow-vars' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'eslint-plugin-flow-vars' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'eslint-plugin-flow-vars/define-flow-type' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'eslint-plugin-flow-vars/use-flow-type' { 30 | declare module.exports: any; 31 | } 32 | 33 | // Filename aliases 34 | declare module 'eslint-plugin-flow-vars/define-flow-type.js' { 35 | declare module.exports: $Exports<'eslint-plugin-flow-vars/define-flow-type'>; 36 | } 37 | declare module 'eslint-plugin-flow-vars/index' { 38 | declare module.exports: $Exports<'eslint-plugin-flow-vars'>; 39 | } 40 | declare module 'eslint-plugin-flow-vars/index.js' { 41 | declare module.exports: $Exports<'eslint-plugin-flow-vars'>; 42 | } 43 | declare module 'eslint-plugin-flow-vars/use-flow-type.js' { 44 | declare module.exports: $Exports<'eslint-plugin-flow-vars/use-flow-type'>; 45 | } 46 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import reducers from "Todo/reducers"; 2 | // import actions from "Todo/actions"; 3 | // import models from "Todo/modles"; 4 | import { applyMiddleware, createStore, combineReducers } from "redux"; 5 | import Perf from "react-addons-perf"; 6 | import shortid from "shortid"; 7 | 8 | const storeCreator = typeof window !== "undefined" && window.devToolsExtension ? window.devToolsExtension()(createStore) : createStore; 9 | 10 | const store = storeCreator( 11 | combineReducers({ 12 | todoLists: reducers.todoLists, 13 | todos: reducers.todos, 14 | users: reducers.users, 15 | }), 16 | {} 17 | ); 18 | 19 | // store.subscribe(() => { 20 | // console.log(store.getState()); 21 | // }); 22 | 23 | // let todoID = 0; 24 | 25 | // local store hydration 26 | 27 | // let userIDs = []; 28 | 29 | // for (let i = 0; i < 5; i++) { 30 | // userIDs[i] = shortid.generate(); 31 | 32 | // store.dispatch(actions.users.add(userIDs[i], `user ${userIDs[i]}`)); 33 | 34 | // for (let j = 0; j < 10; j++) { 35 | // let todoID = shortid.generate(); 36 | // store.dispatch(addTodoUsecase(userIDs[i], `Hello World - ${todoID}`, "Description")); 37 | // } 38 | // } 39 | 40 | // const getTodos = state => state.todos; 41 | 42 | // let todosBy0 = models.TodoCollection.getIDsByCreatorID(getTodos(store.getState()), userIDs[0]); 43 | // let todosBy1 = models.TodoCollection.getIDsByCreatorID(getTodos(store.getState()), userIDs[1]); 44 | 45 | //TODO: add user buckets to TodoCollection and query 46 | // store.dispatch(actions.todoList.add(0, todosBy0)); 47 | // store.dispatch(actions.todoList.add(1, todosBy1)); 48 | 49 | export const { dispatch, getState } = store; 50 | export default store; 51 | -------------------------------------------------------------------------------- /flow-typed/npm/eslint-plugin-class-property_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: e4b691f268f03c8ece81682c098a4a98 2 | // flow-typed version: <>/eslint-plugin-class-property_v^1.0.3/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'eslint-plugin-class-property' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'eslint-plugin-class-property' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'eslint-plugin-class-property/lib/rules/class-property-semicolon' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'eslint-plugin-class-property/test/lib/rules/class-property-semicolon' { 30 | declare module.exports: any; 31 | } 32 | 33 | // Filename aliases 34 | declare module 'eslint-plugin-class-property/index' { 35 | declare module.exports: $Exports<'eslint-plugin-class-property'>; 36 | } 37 | declare module 'eslint-plugin-class-property/index.js' { 38 | declare module.exports: $Exports<'eslint-plugin-class-property'>; 39 | } 40 | declare module 'eslint-plugin-class-property/lib/rules/class-property-semicolon.js' { 41 | declare module.exports: $Exports<'eslint-plugin-class-property/lib/rules/class-property-semicolon'>; 42 | } 43 | declare module 'eslint-plugin-class-property/test/lib/rules/class-property-semicolon.js' { 44 | declare module.exports: $Exports<'eslint-plugin-class-property/test/lib/rules/class-property-semicolon'>; 45 | } 46 | -------------------------------------------------------------------------------- /flow-typed/npm/style-loader_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 21fe15ca75071ead74f6f26669281f3d 2 | // flow-typed version: <>/style-loader_v^0.13.1/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'style-loader' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'style-loader' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'style-loader/addStyles' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'style-loader/addStyleUrl' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'style-loader/url' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'style-loader/useable' { 38 | declare module.exports: any; 39 | } 40 | 41 | // Filename aliases 42 | declare module 'style-loader/addStyles.js' { 43 | declare module.exports: $Exports<'style-loader/addStyles'>; 44 | } 45 | declare module 'style-loader/addStyleUrl.js' { 46 | declare module.exports: $Exports<'style-loader/addStyleUrl'>; 47 | } 48 | declare module 'style-loader/index' { 49 | declare module.exports: $Exports<'style-loader'>; 50 | } 51 | declare module 'style-loader/index.js' { 52 | declare module.exports: $Exports<'style-loader'>; 53 | } 54 | declare module 'style-loader/url.js' { 55 | declare module.exports: $Exports<'style-loader/url'>; 56 | } 57 | declare module 'style-loader/useable.js' { 58 | declare module.exports: $Exports<'style-loader/useable'>; 59 | } 60 | -------------------------------------------------------------------------------- /src/Todo/sdk.js: -------------------------------------------------------------------------------- 1 | import shortid from "shortid"; 2 | 3 | const LOCALSTORAGE_KEY = 'todos'; 4 | 5 | const mockFetchResponse = (res) => { 6 | return { json: () => res }; 7 | }; 8 | 9 | // ** MOCK API ** // 10 | const TodoSDK = { 11 | validateTodo: (description, name, creatorID, date) => { 12 | return new Promise((resolve, reject) => { 13 | let response = { 14 | valid: !!(description && name && creatorID), 15 | }; 16 | resolve(mockFetchResponse(response)); 17 | }); 18 | }, 19 | 20 | createTodo: async (description, name, creatorID, date) => { 21 | return new Promise((resolve, reject) => { 22 | const id = shortid.generate(); 23 | const response = { 24 | id, 25 | description, 26 | name, 27 | creatorID, 28 | date, 29 | }; 30 | 31 | const oldTodos = JSON.parse(localStorage.getItem(LOCALSTORAGE_KEY) || "{}"); 32 | localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify({ 33 | ...oldTodos, 34 | [id]: response, 35 | })); 36 | 37 | resolve(mockFetchResponse(response)); 38 | }); 39 | }, 40 | 41 | loadTodo: async (todoID) => { 42 | return new Promise((resolve, reject) => { 43 | const todos = JSON.parse(localStorage.getItem(LOCALSTORAGE_KEY)); 44 | 45 | if (todos.hasOwnProperty(todoID)) { 46 | resolve(mockFetchResponse(todos[todoID])); 47 | } 48 | 49 | reject(new Error(`Could not find todo of id: ${todoID}`)); 50 | }); 51 | }, 52 | 53 | loadTodos: async () => { 54 | return new Promise((resolve, reject) => { 55 | const todos = JSON.parse(localStorage.getItem(LOCALSTORAGE_KEY)); 56 | 57 | if (todos) { 58 | resolve(mockFetchResponse(todos)); 59 | } 60 | 61 | reject(new Error('Could not load todos')); 62 | }); 63 | }, 64 | }; 65 | 66 | export default TodoSDK; 67 | -------------------------------------------------------------------------------- /flow-typed/npm/redux-thunk_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: b1d9306bf13a939b0234075a7ac14f93 2 | // flow-typed version: <>/redux-thunk_v^2.2.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'redux-thunk' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'redux-thunk' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'redux-thunk/dist/redux-thunk' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'redux-thunk/dist/redux-thunk.min' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'redux-thunk/es/index' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'redux-thunk/lib/index' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'redux-thunk/src/index' { 42 | declare module.exports: any; 43 | } 44 | 45 | // Filename aliases 46 | declare module 'redux-thunk/dist/redux-thunk.js' { 47 | declare module.exports: $Exports<'redux-thunk/dist/redux-thunk'>; 48 | } 49 | declare module 'redux-thunk/dist/redux-thunk.min.js' { 50 | declare module.exports: $Exports<'redux-thunk/dist/redux-thunk.min'>; 51 | } 52 | declare module 'redux-thunk/es/index.js' { 53 | declare module.exports: $Exports<'redux-thunk/es/index'>; 54 | } 55 | declare module 'redux-thunk/lib/index.js' { 56 | declare module.exports: $Exports<'redux-thunk/lib/index'>; 57 | } 58 | declare module 'redux-thunk/src/index.js' { 59 | declare module.exports: $Exports<'redux-thunk/src/index'>; 60 | } 61 | -------------------------------------------------------------------------------- /flow-typed/npm/html-webpack-plugin_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: a5e9410d101ff3d0bbe2afb4b3f85122 2 | // flow-typed version: <>/html-webpack-plugin_v^2.28.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'html-webpack-plugin' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'html-webpack-plugin' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'html-webpack-plugin/lib/chunksorter' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'html-webpack-plugin/lib/compiler' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'html-webpack-plugin/lib/errors' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'html-webpack-plugin/lib/loader' { 38 | declare module.exports: any; 39 | } 40 | 41 | // Filename aliases 42 | declare module 'html-webpack-plugin/index' { 43 | declare module.exports: $Exports<'html-webpack-plugin'>; 44 | } 45 | declare module 'html-webpack-plugin/index.js' { 46 | declare module.exports: $Exports<'html-webpack-plugin'>; 47 | } 48 | declare module 'html-webpack-plugin/lib/chunksorter.js' { 49 | declare module.exports: $Exports<'html-webpack-plugin/lib/chunksorter'>; 50 | } 51 | declare module 'html-webpack-plugin/lib/compiler.js' { 52 | declare module.exports: $Exports<'html-webpack-plugin/lib/compiler'>; 53 | } 54 | declare module 'html-webpack-plugin/lib/errors.js' { 55 | declare module.exports: $Exports<'html-webpack-plugin/lib/errors'>; 56 | } 57 | declare module 'html-webpack-plugin/lib/loader.js' { 58 | declare module.exports: $Exports<'html-webpack-plugin/lib/loader'>; 59 | } 60 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-loader_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 20db5f65135d6e9717d94e9ceb3dd337 2 | // flow-typed version: <>/babel-loader_v^6.2.10/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-loader' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-loader' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-loader/lib/fs-cache' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'babel-loader/lib/index' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'babel-loader/lib/resolve-rc' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'babel-loader/lib/utils/exists' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'babel-loader/lib/utils/read' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'babel-loader/lib/utils/relative' { 46 | declare module.exports: any; 47 | } 48 | 49 | // Filename aliases 50 | declare module 'babel-loader/lib/fs-cache.js' { 51 | declare module.exports: $Exports<'babel-loader/lib/fs-cache'>; 52 | } 53 | declare module 'babel-loader/lib/index.js' { 54 | declare module.exports: $Exports<'babel-loader/lib/index'>; 55 | } 56 | declare module 'babel-loader/lib/resolve-rc.js' { 57 | declare module.exports: $Exports<'babel-loader/lib/resolve-rc'>; 58 | } 59 | declare module 'babel-loader/lib/utils/exists.js' { 60 | declare module.exports: $Exports<'babel-loader/lib/utils/exists'>; 61 | } 62 | declare module 'babel-loader/lib/utils/read.js' { 63 | declare module.exports: $Exports<'babel-loader/lib/utils/read'>; 64 | } 65 | declare module 'babel-loader/lib/utils/relative.js' { 66 | declare module.exports: $Exports<'babel-loader/lib/utils/relative'>; 67 | } 68 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-polyfill_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: f026917ba583b62e74f0198b3ecee7e1 2 | // flow-typed version: <>/babel-polyfill_v^6.23.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-polyfill' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-polyfill' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-polyfill/browser' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'babel-polyfill/dist/polyfill' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'babel-polyfill/dist/polyfill.min' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'babel-polyfill/lib/index' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'babel-polyfill/scripts/postpublish' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'babel-polyfill/scripts/prepublish' { 46 | declare module.exports: any; 47 | } 48 | 49 | // Filename aliases 50 | declare module 'babel-polyfill/browser.js' { 51 | declare module.exports: $Exports<'babel-polyfill/browser'>; 52 | } 53 | declare module 'babel-polyfill/dist/polyfill.js' { 54 | declare module.exports: $Exports<'babel-polyfill/dist/polyfill'>; 55 | } 56 | declare module 'babel-polyfill/dist/polyfill.min.js' { 57 | declare module.exports: $Exports<'babel-polyfill/dist/polyfill.min'>; 58 | } 59 | declare module 'babel-polyfill/lib/index.js' { 60 | declare module.exports: $Exports<'babel-polyfill/lib/index'>; 61 | } 62 | declare module 'babel-polyfill/scripts/postpublish.js' { 63 | declare module.exports: $Exports<'babel-polyfill/scripts/postpublish'>; 64 | } 65 | declare module 'babel-polyfill/scripts/prepublish.js' { 66 | declare module.exports: $Exports<'babel-polyfill/scripts/prepublish'>; 67 | } 68 | -------------------------------------------------------------------------------- /flow-typed/npm/sass-loader_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 7a8bd535af6ef09a631dbef31ba10f69 2 | // flow-typed version: <>/sass-loader_v^6.0.3/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'sass-loader' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'sass-loader' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'sass-loader/lib/formatSassError' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'sass-loader/lib/importsToResolve' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'sass-loader/lib/loader' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'sass-loader/lib/normalizeOptions' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'sass-loader/lib/proxyCustomImporters' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'sass-loader/lib/webpackImporter' { 46 | declare module.exports: any; 47 | } 48 | 49 | // Filename aliases 50 | declare module 'sass-loader/lib/formatSassError.js' { 51 | declare module.exports: $Exports<'sass-loader/lib/formatSassError'>; 52 | } 53 | declare module 'sass-loader/lib/importsToResolve.js' { 54 | declare module.exports: $Exports<'sass-loader/lib/importsToResolve'>; 55 | } 56 | declare module 'sass-loader/lib/loader.js' { 57 | declare module.exports: $Exports<'sass-loader/lib/loader'>; 58 | } 59 | declare module 'sass-loader/lib/normalizeOptions.js' { 60 | declare module.exports: $Exports<'sass-loader/lib/normalizeOptions'>; 61 | } 62 | declare module 'sass-loader/lib/proxyCustomImporters.js' { 63 | declare module.exports: $Exports<'sass-loader/lib/proxyCustomImporters'>; 64 | } 65 | declare module 'sass-loader/lib/webpackImporter.js' { 66 | declare module.exports: $Exports<'sass-loader/lib/webpackImporter'>; 67 | } 68 | -------------------------------------------------------------------------------- /src/Todo/models/TodoListCollection.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { List, Record } from "immutable"; 4 | import { getID } from "./TodoListModel"; 5 | import * as ReverseLookup from "../../ReverseLookup"; 6 | import type { TodoCollection } from "./TodoCollection"; 7 | 8 | 9 | const todoListCollectionShape = { 10 | todoLists: List(), 11 | indexTable: ReverseLookup.empty, 12 | }; 13 | 14 | export class TodoListCollection extends Record(todoListCollectionShape) {} 15 | 16 | export const empty = new TodoListCollection(); 17 | 18 | export const add = (todoListCollection: TodoListCollection, todoList: TodoCollection): TodoListCollection => { 19 | return todoListCollection.withMutations((state) => { 20 | const i = state.todoLists.size; 21 | return state 22 | .update("todoLists", todoLists => todoLists.push(todoList)) 23 | .update("indexTable", indexTable => ReverseLookup.add(indexTable, getID(todoList), i)); 24 | }); 25 | }; 26 | 27 | export const remove = (todoListCollection: TodoListCollection, id: string): TodoListCollection => { 28 | const set = ReverseLookup.get(todoListCollection.indexTable, id); 29 | 30 | if (set) { 31 | return todoListCollection.withMutations((state) => { 32 | const i = set.first(); 33 | state 34 | .update("todoList", todoList => todoList.remove(i)) 35 | .update("indexTable", indexTable => ReverseLookup.remove(indexTable, id)); 36 | }); 37 | } 38 | 39 | return todoListCollection; 40 | }; 41 | 42 | export const update = (todoListCollection: TodoListCollection, id: string, todoList: TodoCollection): TodoListCollection => { 43 | const set = ReverseLookup.get(todoListCollection.indexTable, id); 44 | 45 | if (set) { 46 | const i = set.first(); // one-to-one mapping for indexTable 47 | return todoListCollection.update("todoLists", todoLists => todoLists.set(i, todoList)); 48 | } 49 | return todoListCollection; 50 | }; 51 | 52 | export const get = (todoListCollection: TodoListCollection, id: string): ?TodoCollection => { 53 | const set = ReverseLookup.get(todoListCollection.indexTable, id); 54 | return (set) ? todoListCollection.todoLists.get(set.first()) : null; 55 | }; 56 | 57 | export const size = (collection: TodoListCollection): number => collection.todoLists.size; 58 | -------------------------------------------------------------------------------- /flow-typed/npm/redux_v3.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 7f1a115f75043c44385071ea3f33c586 2 | // flow-typed version: 358375125e/redux_v3.x.x/flow_>=v0.33.x 3 | 4 | declare module 'redux' { 5 | 6 | /* 7 | 8 | S = State 9 | A = Action 10 | 11 | */ 12 | 13 | declare type Dispatch }> = (action: A) => A; 14 | 15 | declare type MiddlewareAPI = { 16 | dispatch: Dispatch; 17 | getState(): S; 18 | }; 19 | 20 | declare type Store = { 21 | // rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages) 22 | dispatch: Dispatch; 23 | getState(): S; 24 | subscribe(listener: () => void): () => void; 25 | replaceReducer(nextReducer: Reducer): void 26 | }; 27 | 28 | declare type Reducer = (state: S, action: A) => S; 29 | 30 | declare type CombinedReducer = (state: $Shape & {} | void, action: A) => S; 31 | 32 | declare type Middleware = 33 | (api: MiddlewareAPI) => 34 | (next: Dispatch) => Dispatch; 35 | 36 | declare type StoreCreator = { 37 | (reducer: Reducer, enhancer?: StoreEnhancer): Store; 38 | (reducer: Reducer, preloadedState: S, enhancer?: StoreEnhancer): Store; 39 | }; 40 | 41 | declare type StoreEnhancer = (next: StoreCreator) => StoreCreator; 42 | 43 | declare function createStore(reducer: Reducer, enhancer?: StoreEnhancer): Store; 44 | declare function createStore(reducer: Reducer, preloadedState: S, enhancer?: StoreEnhancer): Store; 45 | 46 | declare function applyMiddleware(...middlewares: Array>): StoreEnhancer; 47 | 48 | declare type ActionCreator = (...args: Array) => A; 49 | declare type ActionCreators = { [key: K]: ActionCreator }; 50 | 51 | declare function bindActionCreators>(actionCreator: C, dispatch: Dispatch): C; 52 | declare function bindActionCreators>(actionCreators: C, dispatch: Dispatch): C; 53 | 54 | declare function combineReducers(reducers: O): CombinedReducer<$ObjMap(r: Reducer) => S>, A>; 55 | 56 | declare function compose(...fns: Array>): Function; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true, 8 | "modules": true 9 | } 10 | }, 11 | "plugins": [ 12 | "class-property", 13 | "react", 14 | "ava", 15 | "flowtype", 16 | "flow-vars" 17 | ], 18 | "extends": [ 19 | "airbnb", 20 | "airbnb-flow", 21 | "plugin:ava/recommended" 22 | ], 23 | "rules": { 24 | "ava/assertion-arguments": "error", 25 | "ava/no-async-fn-without-await": "error", 26 | "ava/no-duplicate-modifiers": "error", 27 | "ava/no-identical-title": "error", 28 | "ava/no-statement-after-end": "error", 29 | "ava/no-todo-implementation": "error", 30 | "ava/no-unknown-modifiers": "error", 31 | "ava/prefer-async-await": "error", 32 | "ava/test-ended": "error", 33 | "ava/test-title": ["error", "if-multiple"], 34 | "ava/use-t": "error", 35 | "ava/use-test": "error", 36 | "ava/use-true-false": "error", 37 | "comma-dangle": [ "error", "always-multiline" ], 38 | "curly": [ "error", "multi-line" ], 39 | "dot-notation": [ "error", { "allowKeywords": true } ], 40 | "eqeqeq": [ "error", "smart" ], 41 | "flow-vars/define-flow-type": 1, 42 | "flow-vars/use-flow-type": 1, 43 | "no-alert": "error", 44 | "no-cond-assign": [ "error", "always" ], 45 | "no-constant-condition": "error", 46 | "no-debugger": "error", 47 | "no-dupe-args": "error", 48 | "no-dupe-keys": "error", 49 | "no-extend-native": "error", 50 | "no-extra-bind": "error", 51 | "no-floating-decimal": "error", 52 | "no-lonely-if": "error", 53 | "no-magic-numbers": [ "error", { "ignoreArrayIndexes": true } ], 54 | "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 1 }], 55 | "no-path-concat": "error", 56 | "no-spaced-func": "error", 57 | "no-trailing-spaces": "error", 58 | "no-unexpected-multiline": "error", 59 | "no-unneeded-ternary": "error", 60 | "no-whitespace-before-property": "error", 61 | "quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }], 62 | "semi": [ "error", "always", { "omitLastInOneLineBlock": true } ], 63 | "valid-typeof": "error", 64 | "vars-on-top": "error" 65 | }, 66 | "env": { 67 | "browser": true, 68 | "es6": true 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/ReverseLookup.test.js: -------------------------------------------------------------------------------- 1 | /* eslint no-magic-numbers: "off" */ 2 | 3 | import test from "ava"; 4 | import { Map, Set } from "immutable"; 5 | import { empty, add, get, remove } from "ReverseLookup"; 6 | 7 | 8 | test("empty", t => { 9 | t.truthy(empty, "empty is defined"); 10 | t.truthy(Map.isMap(empty), "empty is an Immutable.js Map"); 11 | t.is(empty.size, 0, "empty has a size of 0"); 12 | }); 13 | 14 | test("get", t => { 15 | t.truthy(get, "get is defined"); 16 | let table = empty; 17 | const key = 1; 18 | 19 | t.falsy( get(table, 1), "getting a key that does not exist is falsy" ); 20 | 21 | table = add(table, 1, 1); 22 | t.truthy( Set.isSet( get(table, 1) ), "get returns a Set at the given key" ); 23 | }); 24 | 25 | test("add", t => { 26 | t.truthy(add, "add is defined"); 27 | let table = empty; 28 | const key = 1; 29 | 30 | table = add(table, key, 0); 31 | t.is(table.size, 1, "add increases the size of the table when given an Array"); 32 | t.is(table.get(key).size, 1, "add increases the size of the set at the key"); 33 | t.truthy(Set.isSet(table.get(key)), "the object at the key is an Immutable Set"); 34 | 35 | table = add(table, key, 1); 36 | t.is(table.size, 1, "consequtive adds to the same key do not add to the size of the map"); 37 | t.is(table.get(key).size, 2, "add increases the size of the Set at the key given"); 38 | 39 | table = add(table, key, Set.of(1)); 40 | t.is(table.get(key).size, 2, "attempting to add an already existing value does not work"); 41 | 42 | table = add(table, 2, 1); 43 | t.is(table.size, 2, "add to a different key increase the size of the map"); 44 | t.is(table.get(2).size, 1, "add increases the size of the Set at the key given"); 45 | }); 46 | 47 | test("remove", t => { 48 | t.truthy(remove, "remove is defined"); 49 | let table = empty; 50 | const key = 1; 51 | table = add(table, key, 0); 52 | table = add(table, key, 1); 53 | table = add(table, key, 2); 54 | 55 | t.is(table.get(key).size, 3); 56 | table = remove(table, key, 1); 57 | t.is(table.get(key).size, 2, "remove reduces the amount of values in the set at the given key"); 58 | 59 | table = remove(table, 2, 1); 60 | t.is(table.size, 1, "remove at an undefined key does not affect the map"); 61 | t.is(table.get(key).size, 2, "remove at an undefined key does not affect the map"); 62 | 63 | table = remove(table, 1); 64 | t.is(table.size, 0, "remove without a values argument removes all values at the specified key"); 65 | }); 66 | -------------------------------------------------------------------------------- /flow-typed/npm/eslint-config-airbnb_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 17665f7dfb20054779e6e9fb1979b845 2 | // flow-typed version: <>/eslint-config-airbnb_v^14.1.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'eslint-config-airbnb' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'eslint-config-airbnb' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'eslint-config-airbnb/base' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'eslint-config-airbnb/legacy' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'eslint-config-airbnb/rules/react-a11y' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'eslint-config-airbnb/rules/react' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'eslint-config-airbnb/test/test-base' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'eslint-config-airbnb/test/test-react-order' { 46 | declare module.exports: any; 47 | } 48 | 49 | // Filename aliases 50 | declare module 'eslint-config-airbnb/base.js' { 51 | declare module.exports: $Exports<'eslint-config-airbnb/base'>; 52 | } 53 | declare module 'eslint-config-airbnb/index' { 54 | declare module.exports: $Exports<'eslint-config-airbnb'>; 55 | } 56 | declare module 'eslint-config-airbnb/index.js' { 57 | declare module.exports: $Exports<'eslint-config-airbnb'>; 58 | } 59 | declare module 'eslint-config-airbnb/legacy.js' { 60 | declare module.exports: $Exports<'eslint-config-airbnb/legacy'>; 61 | } 62 | declare module 'eslint-config-airbnb/rules/react-a11y.js' { 63 | declare module.exports: $Exports<'eslint-config-airbnb/rules/react-a11y'>; 64 | } 65 | declare module 'eslint-config-airbnb/rules/react.js' { 66 | declare module.exports: $Exports<'eslint-config-airbnb/rules/react'>; 67 | } 68 | declare module 'eslint-config-airbnb/test/test-base.js' { 69 | declare module.exports: $Exports<'eslint-config-airbnb/test/test-base'>; 70 | } 71 | declare module 'eslint-config-airbnb/test/test-react-order.js' { 72 | declare module.exports: $Exports<'eslint-config-airbnb/test/test-react-order'>; 73 | } 74 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-preset-env_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 8b3dfef4b9ea1a72a7f6559fae3dfd40 2 | // flow-typed version: <>/babel-preset-env_v^1.4.0/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-preset-env' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-preset-env' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-preset-env/data/built-in-features' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'babel-preset-env/data/plugin-features' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'babel-preset-env/lib/default-includes' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'babel-preset-env/lib/index' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'babel-preset-env/lib/module-transformations' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'babel-preset-env/lib/normalize-options' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module 'babel-preset-env/lib/transform-polyfill-require-plugin' { 50 | declare module.exports: any; 51 | } 52 | 53 | // Filename aliases 54 | declare module 'babel-preset-env/data/built-in-features.js' { 55 | declare module.exports: $Exports<'babel-preset-env/data/built-in-features'>; 56 | } 57 | declare module 'babel-preset-env/data/plugin-features.js' { 58 | declare module.exports: $Exports<'babel-preset-env/data/plugin-features'>; 59 | } 60 | declare module 'babel-preset-env/lib/default-includes.js' { 61 | declare module.exports: $Exports<'babel-preset-env/lib/default-includes'>; 62 | } 63 | declare module 'babel-preset-env/lib/index.js' { 64 | declare module.exports: $Exports<'babel-preset-env/lib/index'>; 65 | } 66 | declare module 'babel-preset-env/lib/module-transformations.js' { 67 | declare module.exports: $Exports<'babel-preset-env/lib/module-transformations'>; 68 | } 69 | declare module 'babel-preset-env/lib/normalize-options.js' { 70 | declare module.exports: $Exports<'babel-preset-env/lib/normalize-options'>; 71 | } 72 | declare module 'babel-preset-env/lib/transform-polyfill-require-plugin.js' { 73 | declare module.exports: $Exports<'babel-preset-env/lib/transform-polyfill-require-plugin'>; 74 | } 75 | -------------------------------------------------------------------------------- /src/Todo/models/TodoCollection.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import { Record, List } from "immutable"; 4 | import { getID } from "./TodoModel"; 5 | import * as ReverseLookup from "ReverseLookup"; 6 | import type { TodoModel } from "./TodoModel"; 7 | 8 | 9 | const _shape = { 10 | todos: List(), 11 | idTable: ReverseLookup.empty, 12 | creatorTable: ReverseLookup.empty, 13 | }; 14 | 15 | export class TodoCollection extends Record(_shape) {} 16 | 17 | export const empty = () => new TodoCollection(); 18 | 19 | export const all = (collection: TodoCollection): List => collection.todos; 20 | 21 | export const add = (collection: TodoCollection, todoModel: TodoModel) => { 22 | return collection.withMutations(state => { 23 | let i = state.todos.size; 24 | return state 25 | .update("todos", todos => todos.push(todoModel)) 26 | .update("idTable", idTable => ReverseLookup.add(idTable, todoModel.id, i)) 27 | .update("creatorTable", creatorTable => ReverseLookup.add(creatorTable, todoModel.creatorID, i)); 28 | }); 29 | }; 30 | 31 | export const addAll = (collection: TodoCollection, todos: TodoModel[]) => { 32 | return collection.withMutations(state => { 33 | todos.forEach(todo => { 34 | state = add(state, todo); 35 | }); 36 | 37 | return state; 38 | }); 39 | }; 40 | 41 | export const remove = (todoCollection: TodoCollection, id: string): TodoCollection => { 42 | return todoCollection.withMutations(state => { 43 | let i = ReverseLookup.get(todoCollection.idTable, id).first(); 44 | let creatorID = todoCollection.todos.get(i).creatorID; 45 | return state 46 | .update("todos", todos => todos.remove(i)) 47 | .update("idTable", idTable => ReverseLookup.remove(idTable, id)) 48 | .update("creatorTable", creatorTable => ReverseLookup.remove(creatorTable, creatorID, i)); 49 | }); 50 | }; 51 | 52 | export const get = (todoCollection: TodoCollection, id: string): TodoModel => { 53 | let i = ReverseLookup.get(todoCollection.idTable, id).first(); 54 | return todoCollection.todos.get(i); 55 | }; 56 | 57 | const getIndicesByCreatorID = (todoCollection: TodoCollection, ownerID: string): string[] => ReverseLookup.get(todoCollection.creatorTable, ownerID).toArray(); 58 | 59 | export const getIDsByCreatorID = (todoCollection: TodoCollection, ownerID: string): string[] => { 60 | let indices = getIndicesByCreatorID(todoCollection, ownerID); 61 | return indices.map(i => todoCollection.todos.get(i).id); 62 | }; 63 | 64 | export const getTodosByCreatorID = (todoCollection: TodoCollection, ownerID: string): TodoModel[] => { 65 | let indices = getIndicesByCreatorID(todoCollection, ownerID); 66 | return indices.map(i => todoCollection.todos.get(i)); 67 | }; 68 | -------------------------------------------------------------------------------- /flow-typed/npm/css-loader_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 178f4cb45dc5f12938efa1d676e643dc 2 | // flow-typed version: <>/css-loader_v^0.26.1/flow_v0.40.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'css-loader' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'css-loader' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'css-loader/lib/compile-exports' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'css-loader/lib/css-base' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'css-loader/lib/getImportPrefix' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'css-loader/lib/getLocalIdent' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'css-loader/lib/loader' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'css-loader/lib/localsLoader' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module 'css-loader/lib/processCss' { 50 | declare module.exports: any; 51 | } 52 | 53 | declare module 'css-loader/locals' { 54 | declare module.exports: any; 55 | } 56 | 57 | // Filename aliases 58 | declare module 'css-loader/index' { 59 | declare module.exports: $Exports<'css-loader'>; 60 | } 61 | declare module 'css-loader/index.js' { 62 | declare module.exports: $Exports<'css-loader'>; 63 | } 64 | declare module 'css-loader/lib/compile-exports.js' { 65 | declare module.exports: $Exports<'css-loader/lib/compile-exports'>; 66 | } 67 | declare module 'css-loader/lib/css-base.js' { 68 | declare module.exports: $Exports<'css-loader/lib/css-base'>; 69 | } 70 | declare module 'css-loader/lib/getImportPrefix.js' { 71 | declare module.exports: $Exports<'css-loader/lib/getImportPrefix'>; 72 | } 73 | declare module 'css-loader/lib/getLocalIdent.js' { 74 | declare module.exports: $Exports<'css-loader/lib/getLocalIdent'>; 75 | } 76 | declare module 'css-loader/lib/loader.js' { 77 | declare module.exports: $Exports<'css-loader/lib/loader'>; 78 | } 79 | declare module 'css-loader/lib/localsLoader.js' { 80 | declare module.exports: $Exports<'css-loader/lib/localsLoader'>; 81 | } 82 | declare module 'css-loader/lib/processCss.js' { 83 | declare module.exports: $Exports<'css-loader/lib/processCss'>; 84 | } 85 | declare module 'css-loader/locals.js' { 86 | declare module.exports: $Exports<'css-loader/locals'>; 87 | } 88 | -------------------------------------------------------------------------------- /src/Todo/components/TodoFormComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import TodoContainer from "../containers/TodoContainer"; 4 | import { Button } from "@blueprintjs/core"; 5 | import { DateInput } from "@blueprintjs/datetime"; 6 | import moment from "moment"; 7 | 8 | import * as models from "../models"; 9 | 10 | export default class TodoFormComponent extends Component { 11 | static propTypes = { 12 | addTodo: PropTypes.func, 13 | }; 14 | 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | name: "", 19 | description: "", 20 | date: "", 21 | }; 22 | } 23 | 24 | handleNameChange = (e) => { 25 | const name = e.target.value; 26 | this.setState((state, props) => { 27 | return { 28 | ...state, 29 | name, 30 | } 31 | }); 32 | }; 33 | 34 | handleDescriptionChange = (e) => { 35 | this.setState({ description: e.target.value }); 36 | }; 37 | 38 | handleDateChange = (date) => { 39 | this.setState({ date }); 40 | } 41 | 42 | resetState = () => { 43 | this.setState({ 44 | date: "", 45 | description: "", 46 | name: "", 47 | }); 48 | } 49 | 50 | handleSubmit = (e) => { 51 | e.preventDefault(); 52 | const { name, description, date } = this.state; 53 | this.props.addTodo(name, description, moment(date).format("MMM Do YY")); 54 | this.resetState(); 55 | }; 56 | 57 | render() { 58 | return (
59 |
Add Todo
60 | 61 | 62 |
63 | 64 |
65 | 66 |
67 |
68 | 69 |
70 | 71 |
72 | 73 |
74 |
75 | 76 |
77 | 78 |
79 | 80 |
81 |
82 | 83 |