diff --git a/app/app.js b/app/app.js
index 18185719dc8983967941321643eeb6d43fea0838..2331ea0b1d821fc145a7ca855d56f895b345285f 100644
--- a/app/app.js
+++ b/app/app.js
@@ -6,8 +6,8 @@ import { Root } from 'pubsweet-client'
 import { createBrowserHistory } from 'history'
 import theme from '@pubsweet/coko-theme'
 
-import { JournalProvider } from 'xpub-journal'
-import { XpubProvider } from 'xpub-with-context'
+import { JournalProvider } from './components/xpub-journal'
+import { XpubProvider } from './components/xpub-with-context'
 
 import * as journal from './config/journal'
 import routes from './routes'
diff --git a/app/components/component-xpub-dashboard/CHANGELOG.md b/app/components/component-xpub-dashboard/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..4e5da5db3d462328d7a536584b8ffc25ce401a85
--- /dev/null
+++ b/app/components/component-xpub-dashboard/CHANGELOG.md
@@ -0,0 +1,710 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [5.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.21...pubsweet-component-xpub-dashboard@5.1.0) (2019-11-11)
+
+
+### Features
+
+* **xpub:** bring back xpub components ([fb69994](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb69994098b4e2dbcca75b4786ebb1335af730b9))
+
+
+
+
+
+## [5.0.21](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.20...pubsweet-component-xpub-dashboard@5.0.21) (2019-09-11)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.20](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.19...pubsweet-component-xpub-dashboard@5.0.20) (2019-09-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.19](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.18...pubsweet-component-xpub-dashboard@5.0.19) (2019-08-30)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.17...pubsweet-component-xpub-dashboard@5.0.18) (2019-08-08)
+
+
+### Bug Fixes
+
+* **styleguide:** fix status of teams to reviewerItem ([dd6722e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/dd6722e))
+
+
+
+
+
+## [5.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.16...pubsweet-component-xpub-dashboard@5.0.17) (2019-08-05)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.15...pubsweet-component-xpub-dashboard@5.0.16) (2019-07-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.14...pubsweet-component-xpub-dashboard@5.0.15) (2019-07-09)
+
+
+### Bug Fixes
+
+* **ui:** remove attrs as objects deprecation warning ([19b20bc](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/19b20bc))
+
+
+
+
+
+## [5.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.13...pubsweet-component-xpub-dashboard@5.0.14) (2019-07-03)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.12...pubsweet-component-xpub-dashboard@5.0.13) (2019-06-28)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.11...pubsweet-component-xpub-dashboard@5.0.12) (2019-06-24)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.10...pubsweet-component-xpub-dashboard@5.0.11) (2019-06-21)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.9...pubsweet-component-xpub-dashboard@5.0.10) (2019-06-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.8...pubsweet-component-xpub-dashboard@5.0.9) (2019-06-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.7...pubsweet-component-xpub-dashboard@5.0.8) (2019-05-27)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.6...pubsweet-component-xpub-dashboard@5.0.7) (2019-04-25)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.5...pubsweet-component-xpub-dashboard@5.0.6) (2019-04-18)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.4...pubsweet-component-xpub-dashboard@5.0.5) (2019-04-09)
+
+
+### Bug Fixes
+
+* **xpub:** fix components in styleguide ([2db87cd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2db87cd))
+
+
+
+
+
+## [5.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.3...pubsweet-component-xpub-dashboard@5.0.4) (2019-03-06)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.2...pubsweet-component-xpub-dashboard@5.0.3) (2019-03-05)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.1...pubsweet-component-xpub-dashboard@5.0.2) (2019-02-19)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [5.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@5.0.0...pubsweet-component-xpub-dashboard@5.0.1) (2019-02-19)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+# [5.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@4.0.7...pubsweet-component-xpub-dashboard@5.0.0) (2019-02-01)
+
+
+### Bug Fixes
+
+* **styleguide:** temporarily disable styleguide ([e519ed1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e519ed1))
+
+
+### Code Refactoring
+
+* temporarily remove unmigrated components ([32db6ad](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/32db6ad))
+
+
+### BREAKING CHANGES
+
+* A lot of unmigrated (not yet moved from REST/Redux to GraphQL/Apollo system) bits
+have changed. There might be some breaking changes as a result. This is a big migration involving
+big changes - if you encounter anything weird, please contact us on GitLab or on Mattermost.
+
+
+
+
+
+## [4.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@4.0.6...pubsweet-component-xpub-dashboard@4.0.7) (2019-01-16)
+
+
+### Bug Fixes
+
+* **components:** change back teams model to previous model ([a5eeae0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a5eeae0))
+* **components:** fixing components after new manuscript version ([89537ff](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/89537ff))
+* **components:** generals fixes on the components ([4a78cfe](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4a78cfe))
+* **components:** graphql data model changes ([4b61093](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4b61093))
+* **formik:** improve formik usage ([24b42ff](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/24b42ff))
+* **graphql:** review components fixes ([8094d9e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8094d9e))
+* **manuscript:** wax did not show ([80ae8c6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/80ae8c6))
+* **test:** login tests ([438ab2e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/438ab2e))
+* **xpub-review:** changes tp reviews ([5ae4240](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5ae4240))
+
+
+
+
+
+## [4.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@4.0.5...pubsweet-component-xpub-dashboard@4.0.6) (2019-01-14)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [4.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@4.0.4...pubsweet-component-xpub-dashboard@4.0.5) (2019-01-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [4.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@4.0.3...pubsweet-component-xpub-dashboard@4.0.4) (2019-01-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [4.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@4.0.2...pubsweet-component-xpub-dashboard@4.0.3) (2018-12-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [4.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@4.0.1...pubsweet-component-xpub-dashboard@4.0.2) (2018-12-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+## [4.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@4.0.0...pubsweet-component-xpub-dashboard@4.0.1) (2018-11-30)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+
+
+
+
+# [4.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.1.0...pubsweet-component-xpub-dashboard@4.0.0) (2018-11-29)
+
+
+### Features
+
+* **various:** update styled-components ([5c51466](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5c51466))
+* **various:** upgrade styled-components ([9b886f6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9b886f6))
+
+
+### BREAKING CHANGES
+
+* **various:** Replace all styled-components .extend with styled()
+* **various:** Replace styled-components injectGlobal with new createGlobalStyle
+
+
+
+
+
+<a name="3.1.0"></a>
+# [3.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.15...pubsweet-component-xpub-dashboard@3.1.0) (2018-11-05)
+
+
+### Features
+
+* GraphQL Login component ([70df3de](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/70df3de))
+* GraphQL Xpub review component ([66b3e73](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/66b3e73))
+* GraphQL Xpub submit component ([ba07060](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ba07060))
+
+
+
+
+<a name="3.0.15"></a>
+## [3.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.14...pubsweet-component-xpub-dashboard@3.0.15) (2018-10-08)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.14"></a>
+## [3.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.13...pubsweet-component-xpub-dashboard@3.0.14) (2018-09-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.13"></a>
+## [3.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.12...pubsweet-component-xpub-dashboard@3.0.13) (2018-09-25)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.12"></a>
+## [3.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.11...pubsweet-component-xpub-dashboard@3.0.12) (2018-09-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.11"></a>
+## [3.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.10...pubsweet-component-xpub-dashboard@3.0.11) (2018-09-06)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.10"></a>
+## [3.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.9...pubsweet-component-xpub-dashboard@3.0.10) (2018-09-04)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.9"></a>
+## [3.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.8...pubsweet-component-xpub-dashboard@3.0.9) (2018-08-20)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.8"></a>
+## [3.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.7...pubsweet-component-xpub-dashboard@3.0.8) (2018-08-17)
+
+
+### Bug Fixes
+
+* **actions:** pubsweet ui responsive ([b1cab9a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b1cab9a))
+* **actions:** validationStatus fix ([762432f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/762432f))
+* **css:** fix responsiveness of actions ([9bff385](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9bff385))
+* **revert:** valildateStatus ([5d6f53e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5d6f53e))
+* **style:** remove  enter line ([e2de927](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e2de927))
+* **style:** responsive line tool ([c3219ec](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c3219ec))
+* **warnings:** don't pass every prop to Dom Element ([d8f5e93](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d8f5e93))
+* **warnings:** key actions ([2f176f0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2f176f0))
+* **warnings:** naming changes ([e4727ad](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e4727ad))
+* **warnings:** remove key from unneeded component ([2dda7a5](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2dda7a5))
+
+
+
+
+<a name="3.0.7"></a>
+## [3.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.6...pubsweet-component-xpub-dashboard@3.0.7) (2018-08-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.6"></a>
+## [3.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.5...pubsweet-component-xpub-dashboard@3.0.6) (2018-07-27)
+
+
+### Bug Fixes
+
+* **dashboard:** delete associated fragments ([f95c292](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f95c292))
+
+
+
+
+<a name="3.0.5"></a>
+## [3.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.4...pubsweet-component-xpub-dashboard@3.0.5) (2018-07-23)
+
+
+### Bug Fixes
+
+* **dashboard:** add keys to dashboard ([38f73f7](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/38f73f7))
+* **dashobard:** add key to compoent ([bc76925](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/bc76925))
+
+
+
+
+<a name="3.0.4"></a>
+## [3.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.3...pubsweet-component-xpub-dashboard@3.0.4) (2018-07-19)
+
+
+### Bug Fixes
+
+* **dashboard:** fixing typo message upload ([3fec4ef](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3fec4ef))
+
+
+
+
+<a name="3.0.3"></a>
+## [3.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.2...pubsweet-component-xpub-dashboard@3.0.3) (2018-07-12)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.2"></a>
+## [3.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.1...pubsweet-component-xpub-dashboard@3.0.2) (2018-07-09)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.1"></a>
+## [3.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@3.0.0...pubsweet-component-xpub-dashboard@3.0.1) (2018-07-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="3.0.0"></a>
+# [3.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@2.0.1...pubsweet-component-xpub-dashboard@3.0.0) (2018-07-02)
+
+
+### Features
+
+* **ui:** introduce more line height variables ([85c24e2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/85c24e2))
+
+
+### BREAKING CHANGES
+
+* **ui:** the existing fontLineHeight variable is gone and replaced by multiple new variables
+
+
+
+
+<a name="2.0.1"></a>
+## [2.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@2.0.0...pubsweet-component-xpub-dashboard@2.0.1) (2018-06-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="2.0.0"></a>
+# [2.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@1.0.1...pubsweet-component-xpub-dashboard@2.0.0) (2018-06-28)
+
+
+### Bug Fixes
+
+* **authorize:** add authorize to hide delete ([125a872](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/125a872))
+* **authsome:** update stucture of currentUpdate ([909b80f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/909b80f))
+* **components:** prevent Delete when paper is submitted status ([b556e25](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b556e25))
+* **dashboard:** make use of authorize ([ccc993c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ccc993c))
+* **dashboard:** mock authorize component ([198f95a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/198f95a))
+* **styleguide:** correct require path ([79a2b07](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/79a2b07))
+
+
+### Code Refactoring
+
+* **ui:** replace current gridunit variables with one small value ([cf48f29](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/cf48f29))
+
+
+### BREAKING CHANGES
+
+* **ui:** Your ui components will now be multiplying a much smaller value so they need to be
+adjusted
+
+
+
+
+<a name="1.0.1"></a>
+## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@1.0.0...pubsweet-component-xpub-dashboard@1.0.1) (2018-06-19)
+
+
+### Bug Fixes
+
+* **dashboard:** add actions to dashboard editorItem ([58733b6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/58733b6))
+* **dashboard:** empty declaration object ([919bde4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/919bde4))
+* **metadata:** empty values ([e6f55ea](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e6f55ea))
+* **pubsweet-ui:** tests are failing ([0e57798](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0e57798))
+
+
+
+
+<a name="1.0.0"></a>
+# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.3.2...pubsweet-component-xpub-dashboard@1.0.0) (2018-06-01)
+
+
+### Bug Fixes
+
+* **components:** dasboard fixing multiple submissions ([01fa2f9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/01fa2f9))
+* **dashboard:** empty dashboard collections ([3f4db98](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3f4db98))
+* **dashboard:** remove regenerate import and add it to styleguide ([96731cf](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/96731cf))
+* **dashboard:** section hide on empty ([7a139ec](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7a139ec))
+* **dashboard:** test change object dashboard ([906ccfd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/906ccfd))
+* **styleguide:** compile authsome ([8e9407f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8e9407f))
+* **test:** dashboard - reviewer test ([30f41b3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/30f41b3))
+
+
+### Features
+
+* **components:** add authsome to dashboard ([833a9de](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/833a9de))
+* **ui:** start ui-toolkit module ([2083b9c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2083b9c))
+
+
+### BREAKING CHANGES
+
+* **ui:** th now comes from the toolkit, so all th imports from ui are now broken
+
+
+
+
+<a name="0.3.2"></a>
+## [0.3.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.3.1...pubsweet-component-xpub-dashboard@0.3.2) (2018-05-21)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="0.3.1"></a>
+## [0.3.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.3.0...pubsweet-component-xpub-dashboard@0.3.1) (2018-05-18)
+
+
+### Bug Fixes
+
+* **components:** upload diasble during converting ([227b136](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/227b136))
+
+
+
+
+<a name="0.3.0"></a>
+# [0.3.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.2.4...pubsweet-component-xpub-dashboard@0.3.0) (2018-05-10)
+
+
+### Bug Fixes
+
+* **components:** dashboard if statment reject ([999587a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/999587a))
+* **components:** linter ([9aac3fa](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9aac3fa))
+* **components:** redirect submission add selectors ([53db5a7](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/53db5a7))
+
+
+### Features
+
+* **components:** create accordion component ([54f5b7d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/54f5b7d))
+
+
+
+
+<a name="0.2.4"></a>
+## [0.2.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.2.3...pubsweet-component-xpub-dashboard@0.2.4) (2018-05-09)
+
+
+### Bug Fixes
+
+* **xpub-dash:** fix reviewer item crash when status is revision ([fc79496](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fc79496))
+
+
+
+
+<a name="0.2.3"></a>
+## [0.2.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.2.2...pubsweet-component-xpub-dashboard@0.2.3) (2018-05-03)
+
+
+### Bug Fixes
+
+* **xpub-dashboard:** correct styles for author manuscripts ([1d8761e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1d8761e))
+
+
+
+
+<a name="0.2.2"></a>
+## [0.2.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.2.1...pubsweet-component-xpub-dashboard@0.2.2) (2018-05-03)
+
+
+### Bug Fixes
+
+* **components:** load all users to control panel ([90c88e6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/90c88e6))
+* **components:** remove from Dashboard assign editor ([751a63e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/751a63e))
+
+
+
+
+<a name="0.2.1"></a>
+## [0.2.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.2.0...pubsweet-component-xpub-dashboard@0.2.1) (2018-04-24)
+
+
+### Bug Fixes
+
+* **components:** add file streamlined data ([9dd6797](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9dd6797))
+* **components:** add metadata StreamLined ([29a1fcd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/29a1fcd))
+* **components:** add subinfo to upload ([446fc16](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/446fc16))
+* **components:** change order ([2020d49](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2020d49))
+* **components:** change text to create submission button ([d3b6385](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d3b6385))
+* **components:** statuses changed for revision ([3bc09dc](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3bc09dc))
+* **dashboard:** change stremlined metadata label ([992cc4f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/992cc4f))
+
+
+
+
+<a name="0.2.0"></a>
+# [0.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.1.3...pubsweet-component-xpub-dashboard@0.2.0) (2018-04-11)
+
+
+### Bug Fixes
+
+* **components:** change title styling ([3c154ba](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3c154ba))
+
+
+### Features
+
+* **components:** add Link to control panel ([85458b9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/85458b9))
+* **components:** fix import add link ([dfe4818](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/dfe4818))
+
+
+
+
+<a name="0.1.3"></a>
+## [0.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.1.2...pubsweet-component-xpub-dashboard@0.1.3) (2018-04-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="0.1.2"></a>
+## [0.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.1.1...pubsweet-component-xpub-dashboard@0.1.2) (2018-03-30)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="0.1.1"></a>
+## [0.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.1.0...pubsweet-component-xpub-dashboard@0.1.1) (2018-03-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="0.1.0"></a>
+# [0.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.0.5...pubsweet-component-xpub-dashboard@0.1.0) (2018-03-27)
+
+
+### Features
+
+* **styleguide:** page per section ([0bf0836](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0bf0836))
+
+
+
+
+<a name="0.0.5"></a>
+## [0.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.0.4...pubsweet-component-xpub-dashboard@0.0.5) (2018-03-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="0.0.4"></a>
+## [0.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-dashboard@0.0.3...pubsweet-component-xpub-dashboard@0.0.4) (2018-03-15)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
+
+<a name="0.0.3"></a>
+
+## 0.0.3 (2018-03-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-dashboard
diff --git a/app/components/component-xpub-dashboard/README.md b/app/components/component-xpub-dashboard/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..39792fbc4378f69d84f69d6ff0ea1d3be35e516a
--- /dev/null
+++ b/app/components/component-xpub-dashboard/README.md
@@ -0,0 +1,3 @@
+## pubsweet-component-xpub-dashboard
+
+A PubSweet component that provides the xpub dashboard, listing submissions that are available for the current user to take action on.
diff --git a/app/components/component-xpub-dashboard/package.json b/app/components/component-xpub-dashboard/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..f9255c944b5464d24560898ad2eff25da5bce7ed
--- /dev/null
+++ b/app/components/component-xpub-dashboard/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "pubsweet-component-xpub-dashboard",
+  "version": "5.1.0",
+  "main": "src",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "dependencies": {
+    "@apollo/react-hooks": "^3.1.3",
+    "@pubsweet/ui": "^12.1.0",
+    "@pubsweet/ui-toolkit": "^2.2.14",
+    "classnames": "^2.2.5",
+    "lodash": "^4.17.4",
+    "prop-types": "^15.5.10",
+    "react-dropzone": "^4.1.2",
+    "react-moment": "^0.9.6",
+    "react-router-dom": "^5.0.0",
+    "styled-components": "^4.1.1",
+    "xpub-journal": "^0.1.0",
+    "xpub-with-context": "^0.2.0"
+  },
+  "peerDependencies": {
+    "@apollo/react-components": ">=3.0.1",
+    "@apollo/react-hoc": ">=3.0.1",
+    "config": ">=3.0.1",
+    "graphql-tag": ">=2.10.0",
+    "pubsweet-client": ">=1.0.0",
+    "react": ">=16.9"
+  },
+  "gitHead": "6b100b76f21785e5e50fca082a2743d3d0b1c88a"
+}
diff --git a/app/components/component-xpub-dashboard/src/components/Dashboard.integration.test.js b/app/components/component-xpub-dashboard/src/components/Dashboard.integration.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..a8ccfc0f7af44cb85bb66569c7228a2cfbada9dc
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/Dashboard.integration.test.js
@@ -0,0 +1,219 @@
+// This test is very incomplete and doesn't actually test all that much.
+// Consider throwing it out, and replacing it with actual end to end integration tests.
+
+import React from 'react'
+import Enzyme, { mount } from 'enzyme'
+import Adapter from 'enzyme-adapter-react-16'
+import { MemoryRouter } from 'react-router-dom'
+import { MockedProvider } from '@apollo/react-testing'
+import { ThemeProvider } from 'styled-components'
+import { JournalProvider } from 'xpub-journal'
+import Dashboard from './Dashboard'
+import queries from '../graphql/queries'
+import { UploadContainer } from './molecules/Page'
+import OwnerItem from './sections/OwnerItem'
+import ReviewerItem from './sections/ReviewerItem'
+import EditorItem from './sections/EditorItem'
+
+function waitABit() {
+  return new Promise(resolve => setTimeout(resolve, 500))
+}
+
+// this should be elsewhere
+Enzyme.configure({ adapter: new Adapter() })
+
+jest.mock('config', () => ({
+  'pubsweet-client': {},
+  authsome: {
+    mode: 'authsome',
+  },
+  'pubsweet-component-xpub-dashboard': {
+    acceptUploadFiles: [
+      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+      'application/x-latex',
+      'text/vnd.latex-z',
+      'text/x-tex',
+      'application/pdf',
+      'application/epub+zip',
+      'application/zip',
+    ],
+  },
+}))
+
+jest.mock('pubsweet-client/src/helpers/Authorize', () => 'div')
+
+global.window.localStorage = {
+  getItem: jest.fn(() => 'tok123'),
+}
+
+const journal = {
+  reviewStatus: ['invited', 'accepted', 'rejected', 'completed'],
+  articleTypes: [
+    {
+      label: 'Original Research Report',
+      value: 'original-research',
+    },
+  ],
+  articleSections: [
+    {
+      label: 'Cognitive Psychology',
+      value: 'cognitive-psychology',
+    },
+  ],
+}
+
+const mockData = {
+  currentUser: {
+    id: '1',
+    username: 'test',
+    admin: true,
+  },
+  journals: {
+    id: '2',
+    title: 'Test Journal',
+    manuscripts: [
+      {
+        id: '3',
+        manuscriptVersions: [],
+        reviews: [
+          {
+            open: null,
+            recommendation: 'accepted',
+            created: '2019-10-24T23:49:13.694Z',
+            isDecision: false,
+            user: {
+              id: '1',
+              username: 'test',
+            },
+          },
+        ],
+        teams: [
+          {
+            id: '4',
+            role: 'author',
+            type: 'team',
+            name: 'Author',
+            object: {
+              objectId: '3',
+              objectType: 'Manuscript',
+            },
+            members: [
+              {
+                id: '5',
+                user: {
+                  id: '1',
+                  username: 'test',
+                },
+                status: null,
+              },
+            ],
+          },
+          {
+            id: '6',
+            role: 'reviewerEditor',
+            type: 'team',
+            name: 'Reviewer Editor',
+            object: {
+              objectId: '3',
+              objectType: 'Manuscript',
+            },
+            members: [
+              {
+                id: '7',
+                user: {
+                  id: '1',
+                  username: 'test',
+                },
+                status: 'completed',
+              },
+            ],
+          },
+        ],
+        status: 'submitted',
+        meta: {
+          title: 'Case report',
+          declarations: {
+            openData: 'No/Not Applicable',
+            openPeerReview: null,
+            preregistered: 'no',
+            previouslySubmitted: 'no',
+            researchNexus: null,
+            streamlinedReview: 'no',
+          },
+          articleSections: null,
+          articleType: 'opinion',
+          history: null,
+        },
+      },
+    ],
+  },
+}
+
+const getProjects = item => item.map(c => c.props().version)
+
+describe('Dashboard', () => {
+  const makeWrapper = async (opts = {}) => {
+    const mocks = [
+      {
+        request: {
+          query: queries.dashboard,
+        },
+        result: {
+          data: JSON.parse(JSON.stringify(mockData)),
+        },
+      },
+    ]
+
+    mocks[0].result.data.journals.manuscripts = opts.empty
+      ? []
+      : mocks[0].result.data.journals.manuscripts
+
+    const wrapper = mount(
+      <MemoryRouter>
+        <JournalProvider journal={journal}>
+          <ThemeProvider theme={{ colorPrimary: 'blue' }}>
+            <MockedProvider addTypename={false} mocks={mocks}>
+              <Dashboard conversion={{ converting: false }} />
+            </MockedProvider>
+          </ThemeProvider>
+        </JournalProvider>
+      </MemoryRouter>,
+    )
+    await waitABit()
+    wrapper.update()
+    return wrapper
+  }
+
+  it('shows a message when there are no projects', async () => {
+    const dashboard = await makeWrapper({ empty: true })
+    expect(dashboard.find(UploadContainer)).toHaveLength(2)
+    expect(dashboard.find(OwnerItem)).toHaveLength(0)
+    expect(dashboard.find(ReviewerItem)).toHaveLength(0)
+  })
+
+  it('shows a list of manuscripts submitted by the current user', async () => {
+    const dashboard = await makeWrapper({ empty: false })
+    expect(dashboard.find('.empty')).toHaveLength(0)
+    const section = dashboard.find(OwnerItem)
+    expect(section).toHaveLength(1)
+    expect(getProjects(section)).toEqual([mockData.journals.manuscripts[0]])
+  })
+
+  it('shows a list of manuscripts to be reviewed', async () => {
+    const dashboard = await makeWrapper({ empty: false })
+
+    expect(dashboard.find(UploadContainer)).toHaveLength(1)
+    const section = dashboard.find(ReviewerItem)
+    expect(section).toHaveLength(1)
+    expect(getProjects(section)).toEqual([mockData.journals.manuscripts[0]])
+  })
+
+  it('shows a list of projects where the current user is the editor', async () => {
+    const dashboard = await makeWrapper({ empty: false })
+
+    expect(dashboard.find(UploadContainer)).toHaveLength(1)
+    const section = dashboard.find(EditorItem)
+    expect(section).toHaveLength(1)
+    expect(getProjects(section)).toEqual([mockData.journals.manuscripts[0]])
+  })
+})
diff --git a/app/components/component-xpub-dashboard/src/components/Dashboard.js b/app/components/component-xpub-dashboard/src/components/Dashboard.js
new file mode 100644
index 0000000000000000000000000000000000000000..6cddfbe9be6bdef453d09b29c877480dab1d5cca
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/Dashboard.js
@@ -0,0 +1,166 @@
+import React from 'react'
+import { useQuery, useMutation, ApolloConsumer } from '@apollo/react-hooks'
+import Authorize from 'pubsweet-client/src/helpers/Authorize'
+
+import config from 'config'
+import queries from '../graphql/queries/'
+import mutations from '../graphql/mutations/'
+import { Page, Section, Heading, UploadContainer } from './molecules/Page'
+import UploadManuscript from './UploadManuscript'
+import EditorItem from './sections/EditorItem'
+import OwnerItem from './sections/OwnerItem'
+import ReviewerItem from './sections/ReviewerItem'
+
+const { acceptUploadFiles } = config['pubsweet-component-xpub-dashboard'] || {}
+
+const acceptFiles =
+  acceptUploadFiles.length > 0
+    ? acceptUploadFiles.join()
+    : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+
+const updateReviewer = (proxy, { data: { reviewerResponse } }) => {
+  const id = reviewerResponse.object.objectId
+  const data = proxy.readQuery({
+    query: queries.dashboard,
+    variables: {
+      id,
+    },
+  })
+
+  const manuscriptIndex = data.journals.manuscripts.findIndex(
+    manu => manu.id === id,
+  )
+  const teamIndex = data.journals.manuscripts[manuscriptIndex].teams.findIndex(
+    team => team.id === reviewerResponse.id,
+  )
+
+  data.journals.manuscripts[manuscriptIndex].teams[teamIndex] = reviewerResponse
+  proxy.writeQuery({ query: queries.dashboard, data })
+}
+
+// export default compose(
+//   withProps(({ journals, currentUser }) => ({
+//     dashboard: (journals || {}).manuscripts || [],
+//     journals,
+//     currentUser,
+//     acceptFiles,
+//   })),
+//   upload,
+// )(Dashboard)
+
+const Dashboard = ({
+  // acceptFiles,
+  // currentUser, // graphql
+  // dashboard, // graphql
+  // journals, // graphql
+  // deleteManuscript, // graphql
+  // reviewerResponse, // graphql
+  // uploadManuscript, // from compose
+  ...props
+}) => {
+  // const uploadManuscript = upload()
+
+  // const [conversion] = useContext(XpubContext)
+
+  const { loading, data } = useQuery(queries.dashboard)
+  const dashboard = ((data && data.journals) || {}).manuscripts || []
+  const journals = data && data.journals
+  const currentUser = data && data.currentUser
+
+  const [reviewerRespond] = useMutation(mutations.reviewerResponseMutation, {
+    // variables: { currentUserId, action, teamId },
+    update: updateReviewer,
+  })
+
+  const [deleteManuscript] = useMutation(mutations.deleteManuscriptMutation, {
+    // variables: { id: manuscript.id },
+    update: (proxy, { data: { deleteManuscript } }) => {
+      const data = proxy.readQuery({ query: queries.dashboard })
+      const manuscriptIndex = data.journals.manuscripts.findIndex(
+        manuscript => manuscript.id === deleteManuscript,
+      )
+      if (manuscriptIndex > -1) {
+        data.journals.manuscripts.splice(manuscriptIndex, 1)
+        proxy.writeQuery({ query: queries.dashboard, data })
+      }
+    },
+  })
+
+  if (loading) return <div>Loading...</div>
+
+  return (
+    <Page>
+      <UploadContainer>
+        <ApolloConsumer>
+          {client => (
+            <UploadManuscript
+              acceptFiles={acceptFiles}
+              client={client}
+              currentUser={currentUser}
+              history={props.history}
+              journals={journals}
+            />
+          )}
+        </ApolloConsumer>
+      </UploadContainer>
+
+      {!dashboard.length && (
+        <UploadContainer>
+          Nothing to do at the moment. Please upload a document.
+        </UploadContainer>
+      )}
+      <Authorize object={dashboard} operation="can view my submission section">
+        {dashboard.length > 0 ? (
+          <Section>
+            <Heading>My Submissions</Heading>
+            {dashboard.map(submission => (
+              <OwnerItem
+                deleteManuscript={() =>
+                  // eslint-disable-next-line no-alert
+                  window.confirm(
+                    'Are you sure you want to delete this submission?',
+                  ) && deleteManuscript({ variables: { id: submission.id } })
+                }
+                journals={journals}
+                key={`submission-${submission.id}`}
+                version={submission}
+              />
+            ))}
+          </Section>
+        ) : null}
+      </Authorize>
+      <Authorize object={dashboard} operation="can view review section">
+        {dashboard.length > 0 ? (
+          <Section>
+            <Heading>To review</Heading>
+            {dashboard.map(review => (
+              <ReviewerItem
+                currentUser={currentUser}
+                journals={journals}
+                key={review.id}
+                reviewerRespond={reviewerRespond}
+                version={review}
+              />
+            ))}
+          </Section>
+        ) : null}
+      </Authorize>
+
+      <Authorize object={dashboard} operation="can view my manuscripts section">
+        {dashboard.length > 0 ? (
+          <Section>
+            <Heading>My Manuscripts</Heading>
+            {dashboard.map(manuscript => (
+              <EditorItem
+                journals={journals}
+                key={`manuscript-${manuscript.id}`}
+                version={manuscript}
+              />
+            ))}
+          </Section>
+        ) : null}
+      </Authorize>
+    </Page>
+  )
+}
+export default Dashboard
diff --git a/app/components/component-xpub-dashboard/src/components/JournalLink.js b/app/components/component-xpub-dashboard/src/components/JournalLink.js
new file mode 100644
index 0000000000000000000000000000000000000000..af569d511d6215bfe6eb3d3caed2178f204330e0
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/JournalLink.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import { Link } from '@pubsweet/ui'
+
+const projectUrl = ({ journal, version, page, id }) => {
+  const parts = []
+
+  parts.push('journals')
+  parts.push(typeof journal === 'object' ? journal.id : journal)
+
+  if (version) {
+    parts.push('versions')
+    parts.push(typeof version === 'object' ? version.id : version)
+  }
+
+  if (page) {
+    parts.push(page)
+  }
+
+  if (id) {
+    parts.push(id)
+  }
+
+  return parts.join('/')
+}
+
+const JournalLink = props => (
+  <Link className={props.className} to={projectUrl(props)}>
+    {props.children}
+  </Link>
+)
+
+export default JournalLink
diff --git a/app/components/component-xpub-dashboard/src/components/JournalLink.md b/app/components/component-xpub-dashboard/src/components/JournalLink.md
new file mode 100644
index 0000000000000000000000000000000000000000..d64f052eb75525831fee7c20577848bd38091f26
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/JournalLink.md
@@ -0,0 +1,31 @@
+A link to a project, version, or page.
+
+```js
+<JournalLink journal="foo">journal</JournalLink>
+```
+
+```js
+<JournalLink journal="foo" version="bar">
+  version
+</JournalLink>
+```
+
+```js
+<JournalLink journal="foo" version="bar" page="baz">
+  page
+</JournalLink>
+```
+
+```js
+<JournalLink journal="foo" version="bar" page="baz" id={1}>
+  id
+</JournalLink>
+```
+
+The project and/or version can be an object with an id.
+
+```js
+<JournalLink journal={{ id: 'foo' }} version={{ id: 'bar' }}>
+  id
+</JournalLink>
+```
diff --git a/app/components/component-xpub-dashboard/src/components/Reviews.js b/app/components/component-xpub-dashboard/src/components/Reviews.js
new file mode 100644
index 0000000000000000000000000000000000000000..b2bb5ab11b9a99fe93ac93425ed27d8dbc2d0e68
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/Reviews.js
@@ -0,0 +1,64 @@
+import React from 'react'
+import styled from 'styled-components'
+import { sumBy } from 'lodash'
+import { JournalContext } from 'xpub-journal'
+import { Badge } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Root = styled.div`
+  display: inline-flex;
+  justify-content: flex-end;
+  margin-bottom: 0.6em;
+  margin-top: 0.3em;
+  padding-left: 1.5em;
+
+  font-family: ${th('fontReviewer')};
+  font-size: 0.9em;
+`
+
+const BadgeContainer = styled.span`
+  &:not(:last-child) {
+    margin-right: 10px;
+  }
+`
+
+const getUserFromTeam = (version, role) => {
+  if (!version.teams) return []
+
+  const teams = version.teams.filter(team => team.role === role)
+  return teams.length ? teams[0].members : []
+}
+
+const countStatus = (version, status) => {
+  const teamMember = getUserFromTeam(version, 'reviewerEditor')
+
+  if (status === 'rejected' || status === 'invited') {
+    return sumBy(teamMember, member => (member.status === status ? 1 : 0))
+  }
+
+  if (status === 'accepted') {
+    return sumBy(version.reviews, review => (review.recommendation ? 0 : 1))
+  }
+
+  if (status === 'completed') {
+    return sumBy(version.reviews, review => (review.recommendation ? 1 : 0))
+  }
+
+  return 0
+}
+
+const Reviews = ({ version, journal }) => (
+  <Root>
+    <JournalContext.Consumer>
+      {journal =>
+        journal.reviewStatus.map(status => (
+          <BadgeContainer key={status}>
+            <Badge count={countStatus(version, status)} label={status} />
+          </BadgeContainer>
+        ))
+      }
+    </JournalContext.Consumer>
+  </Root>
+)
+
+export default Reviews
diff --git a/app/components/component-xpub-dashboard/src/components/Status.js b/app/components/component-xpub-dashboard/src/components/Status.js
new file mode 100644
index 0000000000000000000000000000000000000000..14e8d3d3229af741f47cebe3a1c8b863fa864f83
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/Status.js
@@ -0,0 +1,23 @@
+import React from 'react'
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+// TODO: move labels to journal config
+
+const labels = {
+  accepted: 'Accepted',
+  assignedToEditor: 'Assigned to editor',
+  assigningReviewers: 'Assigning reviewers',
+  new: 'Unsubmitted',
+  rejected: 'Rejected',
+  submitted: 'Submitted',
+  revising: 'Back with Author for Revision',
+}
+
+const Root = styled.div`
+  color: ${th('colorPrimary')};
+`
+
+const Status = ({ status }) => <Root>{labels[status] || 'Unsubmitted'}</Root>
+
+export default Status
diff --git a/app/components/component-xpub-dashboard/src/components/UploadManuscript.js b/app/components/component-xpub-dashboard/src/components/UploadManuscript.js
new file mode 100644
index 0000000000000000000000000000000000000000..e80199b4c20c19f9803b3bd82ad4c6f5db270a34
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/UploadManuscript.js
@@ -0,0 +1,312 @@
+import React, { useContext } from 'react'
+import styled, { keyframes, withTheme } from 'styled-components'
+import { XpubContext } from 'xpub-with-context'
+import Dropzone from 'react-dropzone'
+import { Icon } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+import upload from '../upload'
+
+const StyledDropzone = styled(({ disableUpload, ...props }) => (
+  <Dropzone {...props} />
+))`
+  border: none;
+  cursor: pointer;
+  display: inline-block;
+  ${({ disableUpload }) => disableUpload && 'pointer-events: none;'};
+`
+
+const StatusIcon = withTheme(({ children, theme }) => (
+  <Icon color={theme.colorPrimary}>{children}</Icon>
+))
+
+const Status = styled.div`
+  align-items: center;
+  color: ${th('colorPrimary')};
+  display: inline-flex;
+`
+
+const StatusIdle = styled(Status).attrs(props => ({
+  children: <StatusIcon>plus_circle</StatusIcon>,
+}))``
+
+const spin = keyframes`
+  0% {
+    transform: rotate(0deg);
+    transform-origin: 50% 50%;
+  }
+
+  100% {
+    transform: rotate(360deg);
+    transform-origin: 50% 50%;
+  }
+`
+
+const StatusConverting = styled(Status).attrs(props => ({
+  children: <StatusIcon>plus_circle</StatusIcon>,
+}))`
+  &:hover {
+    cursor: wait;
+  }
+
+  line {
+    stroke-linejoin: round;
+  }
+
+  circle {
+    animation: ${spin} 2s infinite linear;
+    stroke-dasharray: 16;
+    stroke-dashoffset: 0;
+    stroke-linejoin: round;
+  }
+`
+
+const StatusError = styled(Status).attrs(props => ({
+  children: <StatusIcon>plus_circle</StatusIcon>,
+}))`
+  color: ${th('colorDanger')};
+  font-size: 1.5em;
+  font-style: italic;
+  font-weight: 400;
+
+  .icon circle {
+    display: none;
+  }
+
+  .icon line {
+    stroke: ${th('colorDanger')};
+    transform: rotate(45deg) scale(2.8);
+    transform-origin: 50% 50%;
+  }
+`
+
+const dash = keyframes`
+  from {
+    stroke-dashoffset: -100;
+  }
+
+  to {
+    stroke-dashoffset: 0;
+  }
+`
+
+const StatusCompleted = styled(Status).attrs(props => ({
+  children: <StatusIcon>check_circle</StatusIcon>,
+}))`
+  polyline {
+    animation: ${dash} 1.35s linear;
+    stroke-dasharray: 100;
+    stroke-dashoffset: 0;
+  }
+
+  path {
+    animation: ${dash} 0.75s linear;
+    stroke-dasharray: 100;
+    stroke-dashoffset: 0;
+  }
+`
+
+const Root = styled.div`
+  display: flex;
+  flex-direction: column;
+  font-weight: 200;
+  padding-bottom: 10px;
+  padding-top: 10px;
+
+  &:hover ${StatusIdle} {
+    circle {
+      fill: ${th('colorPrimary')};
+      stroke: ${th('colorPrimary')};
+    }
+
+    line {
+      stroke: white;
+    }
+  }
+`
+
+const Main = styled.div`
+  display: flex;
+  justify-content: center;
+  margin-left: 10px;
+`
+
+const Error = styled.div`
+  color: ${th('colorDanger')};
+  font-size: 1.5em;
+  font-style: italic;
+  font-weight: 400;
+`
+
+const Info = styled.div`
+  color: ${th('colorPrimary')};
+  font-size: 2em;
+  font-weight: 400;
+  text-transform: uppercase;
+`
+
+const SubInfo = styled.div`
+  display: flex;
+  justify-content: center;
+  color: #333;
+  line-height: 32px;
+`
+
+const UploadManuscript = ({ acceptFiles, ...props }) => {
+  // const [error, setError] = useState(false)
+  // const [completed, setCompleted] = useState(false)
+  const [conversion, setConversion] = useContext(XpubContext)
+  const { error, completed, converting } = conversion
+
+  // const error = conversion.error
+  // const
+  // setConversion({ error: 'yes' })
+
+  const uploadManuscript = upload({
+    client: props.client,
+    history: props.history,
+    journals: props.journals,
+    currentUser: props.currentUser,
+    setConversion,
+  })
+
+  // const status = completed
+  //   ? 'completed'
+  //   : error
+  //   ? 'error'
+  //   : conversion.converting
+  //   ? 'converting'
+  //   : 'idle'
+
+  // if (!completed && !conversion.converting) {
+  //   setCompleted(true)
+  //   setError(false)
+  // }
+
+  // Show and then hide the error
+  if (error) {
+    setTimeout(() => {
+      setConversion({})
+    }, 3000)
+  }
+
+  return (
+    <StyledDropzone
+      accept={acceptFiles}
+      disableUpload={converting ? 'disableUpload' : null}
+      onDrop={uploadManuscript}
+      data-testid="dropzone"
+    >
+      <Root>
+        <Main>
+          {completed && <StatusCompleted />}
+          {error && <StatusError />}
+          {converting && <StatusConverting />}
+          {!converting && !error && !completed && <StatusIdle />}
+          {error ? (
+            <Error>{error.message}</Error>
+          ) : (
+            <Info>
+              {completed ? 'Submission created' : 'Submit Manuscript'}
+            </Info>
+          )}
+        </Main>
+        <SubInfo>
+          {converting &&
+            'Your manuscript is being converted into a directly editable version. This might take a few seconds.'}
+          {!converting && 'Accepted file types: pdf, epub, zip, docx, latex'}
+        </SubInfo>
+      </Root>
+    </StyledDropzone>
+  )
+}
+
+export default UploadManuscript
+
+// class UploadManuscript extends Component {
+
+// constructor(props) {
+//   super(props)
+//   this.state = {
+//     completed: false,
+//     error: false,
+//   }
+//   this.showErrorAndHide = this.showErrorAndHide.bind(this)
+// }
+
+// getDerivedPropsFromState(props, state) {
+// if (!state.completed && !props.conversion.converting) {
+//   this.setState({
+//     completed: true,
+//     error: false,
+//   })
+// }
+
+//   if (props.conversion.error) {
+//     this.showErrorAndHide()
+//   }
+// }
+
+// showErrorAndHide() {
+//   this.setState({
+//     error: true,
+//     completed: false,
+//   })
+//   setTimeout(() => {
+//     this.setState({
+//       error: false,
+//       completed: false,
+//     })
+//   }, 3000)
+// }
+
+// get status() {
+//   if (this.state.completed) {
+//     return 'completed'
+//   }
+//   if (this.state.error) {
+//     return 'error'
+//   }
+//   if (this.props.conversion.converting) {
+//     return 'converting'
+//   }
+//   return 'idle'
+// }
+
+// render() {
+// const { acceptFiles, uploadManuscript, conversion } = this.props
+
+// return (
+//   <StyledDropzone
+//     accept={acceptFiles}
+//     disableUpload={this.status === 'converting' ? 'disableUpload' : null}
+//     onDrop={uploadManuscript}
+//   >
+//     <Root>
+//       <Main>
+//         {this.status === 'completed' && <StatusCompleted />}
+//         {this.status === 'error' && <StatusError />}
+//         {this.status === 'converting' && <StatusConverting />}
+//         {this.status === 'idle' && <StatusIdle />}
+//         {this.state.error ? (
+//           <Error>{conversion.error.message}</Error>
+//         ) : (
+//           <Info>
+//             {this.state.completed
+//               ? 'Submission created'
+//               : 'Submit Manuscript'}
+//           </Info>
+//         )}
+//       </Main>
+//       <SubInfo>
+//         {this.status === 'converting' &&
+//           'Your manuscript is being converted into a directly editable version. This might take a while.'}
+//         {this.status !== 'converting' &&
+//           'Accepted file types: pdf, epub, zip, docx, latex'}
+//       </SubInfo>
+//     </Root>
+//   </StyledDropzone>
+// )
+// }
+// }
+
+// export default UploadManuscript
diff --git a/app/components/component-xpub-dashboard/src/components/UploadManuscript.md b/app/components/component-xpub-dashboard/src/components/UploadManuscript.md
new file mode 100644
index 0000000000000000000000000000000000000000..bd968c7e0441d2b00d837a9010d8c1aacaf98231
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/UploadManuscript.md
@@ -0,0 +1,38 @@
+A button for uploading a manuscript (DOCX) file to start a submission.
+
+```js
+const conversion = {
+  converting: false,
+}
+;<UploadManuscript conversion={conversion} />
+```
+
+While the manuscript is converting, a spinner is displayed.
+
+```js
+const conversion = {
+  converting: true,
+}
+;<UploadManuscript conversion={conversion} />
+```
+
+When the manuscript is complete, the icon changes and a message can be displayed.
+
+```js
+const conversion = {
+  complete: true,
+  message: 'Submission created',
+}
+;<UploadManuscript conversion={conversion} />
+```
+
+If there is a conversion error, a error message is displayed.
+
+```js
+const conversion = {
+  error: {
+    message: 'There was an error',
+  },
+}
+;<UploadManuscript conversion={conversion} />
+```
diff --git a/app/components/component-xpub-dashboard/src/components/index.js b/app/components/component-xpub-dashboard/src/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..204e0c84d697d419546e1389167cf9c34c9e076e
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/index.js
@@ -0,0 +1 @@
+export { default as DashboardPage } from './Dashboard'
diff --git a/app/components/component-xpub-dashboard/src/components/metadata/Meta.js b/app/components/component-xpub-dashboard/src/components/metadata/Meta.js
new file mode 100644
index 0000000000000000000000000000000000000000..d77f8809ae851cda3ed2a55aae05e3b9264618ef
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/metadata/Meta.js
@@ -0,0 +1,15 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Meta = styled.div`
+  display: flex;
+  flex-wrap: nowrap;
+  font-size: ${th('fontSizeBaseSmall')};
+  line-height: ${th('lineHeightBaseSmall')};
+  white-space: nowrap;
+  > :not(:last-child):after {
+    content: '-';
+  }
+`
+
+export default Meta
diff --git a/app/components/component-xpub-dashboard/src/components/metadata/MetadataAuthors.js b/app/components/component-xpub-dashboard/src/components/metadata/MetadataAuthors.js
new file mode 100644
index 0000000000000000000000000000000000000000..c9d9f4d09847023e3a3b4e889675852cdd468f32
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/metadata/MetadataAuthors.js
@@ -0,0 +1,16 @@
+import React from 'react'
+
+const MetadataAuthors = ({ authors }) =>
+  authors.length ? (
+    <span>
+      {authors
+        .map(author => (
+          <span key={(author.user || {}).username || 'Anonymous'}>
+            {(author.user || {}).username || 'Anonymous'}
+          </span>
+        ))
+        .reduce((prev, curr) => [prev, ', ', curr])}
+    </span>
+  ) : null
+
+export default MetadataAuthors
diff --git a/app/components/component-xpub-dashboard/src/components/metadata/MetadataReviewType.js b/app/components/component-xpub-dashboard/src/components/metadata/MetadataReviewType.js
new file mode 100644
index 0000000000000000000000000000000000000000..95cec2654b3fb73a8be9b35728125957f06ae268
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/metadata/MetadataReviewType.js
@@ -0,0 +1,7 @@
+import React from 'react'
+
+const MetadataReviewType = ({ openPeerReview }) => (
+  <span>{openPeerReview === 'yes' ? 'Open review' : 'Closed review'}</span>
+)
+
+export default MetadataReviewType
diff --git a/app/components/component-xpub-dashboard/src/components/metadata/MetadataSections.js b/app/components/component-xpub-dashboard/src/components/metadata/MetadataSections.js
new file mode 100644
index 0000000000000000000000000000000000000000..42873245714388007aae5aa8d04dc0decf6bcbf5
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/metadata/MetadataSections.js
@@ -0,0 +1,25 @@
+import React from 'react'
+import { JournalContext } from 'xpub-journal'
+
+const MetadataSections = ({ sections }) =>
+  sections.length ? (
+    <JournalContext.Consumer>
+      {journal => (
+        <span>
+          {sections.length &&
+            sections
+              .map(section => (
+                <span key={section}>
+                  {
+                    journal.articleSections.find(item => item.value === section)
+                      .label
+                  }
+                </span>
+              ))
+              .reduce((prev, curr) => [prev, ', ', curr])}
+        </span>
+      )}
+    </JournalContext.Consumer>
+  ) : null
+
+export default MetadataSections
diff --git a/app/components/component-xpub-dashboard/src/components/metadata/MetadataStreamLined.js b/app/components/component-xpub-dashboard/src/components/metadata/MetadataStreamLined.js
new file mode 100644
index 0000000000000000000000000000000000000000..752e8b981be13563ea20b23587f941b4a458bf1f
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/metadata/MetadataStreamLined.js
@@ -0,0 +1,6 @@
+import React from 'react'
+
+const MetadataStreamLined = ({ streamlinedReview }) =>
+  streamlinedReview === 'yes' ? <span>Streamlined</span> : null
+
+export default MetadataStreamLined
diff --git a/app/components/component-xpub-dashboard/src/components/metadata/MetadataSubmittedDate.js b/app/components/component-xpub-dashboard/src/components/metadata/MetadataSubmittedDate.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc92f04fedbab3666852d671b5b090a2435a33c3
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/metadata/MetadataSubmittedDate.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import Moment from 'react-moment'
+
+const MetadataSubmittedDate = ({ submitted }) => (
+  <span>
+    <Moment format="YYYY-MM-DD">{submitted}</Moment>
+  </span>
+)
+
+export default MetadataSubmittedDate
diff --git a/app/components/component-xpub-dashboard/src/components/metadata/MetadataType.js b/app/components/component-xpub-dashboard/src/components/metadata/MetadataType.js
new file mode 100644
index 0000000000000000000000000000000000000000..aff01379a598af467a9c78ce3ddb5c06d87ab000
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/metadata/MetadataType.js
@@ -0,0 +1,15 @@
+import React from 'react'
+import { JournalContext } from 'xpub-journal'
+
+const MetadataType = ({ journal, type }) => (
+  <JournalContext.Consumer>
+    {journal => (
+      <span>
+        {(journal.articleTypes.find(item => item.value === type) || {}).label ||
+          'None'}
+      </span>
+    )}
+  </JournalContext.Consumer>
+)
+
+export default MetadataType
diff --git a/app/components/component-xpub-dashboard/src/components/molecules/Actions.js b/app/components/component-xpub-dashboard/src/components/molecules/Actions.js
new file mode 100644
index 0000000000000000000000000000000000000000..86cddd311d29363aa9c931c8ab58e82ff3b433fc
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/molecules/Actions.js
@@ -0,0 +1,11 @@
+import styled from 'styled-components'
+
+// TODO -- why two divs?
+
+const Actions = styled.div``
+
+const ActionContainer = styled.div`
+  display: inline-block;
+`
+
+export { Actions, ActionContainer }
diff --git a/app/components/component-xpub-dashboard/src/components/molecules/Item.js b/app/components/component-xpub-dashboard/src/components/molecules/Item.js
new file mode 100644
index 0000000000000000000000000000000000000000..c8dc3de553558ac3ac1476678bba195cc459433a
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/molecules/Item.js
@@ -0,0 +1,33 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Item = styled.div`
+  margin-bottom: calc(${th('gridUnit') * 4});
+`
+
+const Header = styled.div`
+  align-items: baseline;
+  display: flex;
+  justify-content: space-between;
+  text-transform: uppercase;
+`
+
+const Body = styled.div`
+  align-items: space-between;
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: calc(${th('gridUnit')} * 4);
+  padding-left: 1.5em;
+  & > div:last-child {
+    flex-shrink: 0;
+  }
+`
+
+const Divider = styled.span.attrs(props => ({
+  children: ` ${props.separator} `,
+}))`
+  color: ${th('colorFurniture')};
+  white-space: pre;
+`
+
+export { Item, Header, Body, Divider }
diff --git a/app/components/component-xpub-dashboard/src/components/molecules/Links.js b/app/components/component-xpub-dashboard/src/components/molecules/Links.js
new file mode 100644
index 0000000000000000000000000000000000000000..20508f6a5d53e3b67004e0c6aba65fecc74eb40d
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/molecules/Links.js
@@ -0,0 +1,15 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Links = styled.div`
+  align-items: flex-end;
+  display: flex;
+  justify-content: bottom;
+`
+
+const LinkContainer = styled.div`
+  font-size: ${th('fontSizeBaseSmall')};
+  line-height: ${th('lineHeightBaseSmall')};
+`
+
+export { Links, LinkContainer }
diff --git a/app/components/component-xpub-dashboard/src/components/molecules/Page.js b/app/components/component-xpub-dashboard/src/components/molecules/Page.js
new file mode 100644
index 0000000000000000000000000000000000000000..448d29a26e8807d94b3d9ad94448d81ca6756702
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/molecules/Page.js
@@ -0,0 +1,31 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Page = styled.div`
+  margin: auto;
+  max-width: 60em;
+`
+
+const Section = styled.div`
+  margin: calc(${th('gridUnit')} * 3) 0;
+
+  &:not(:last-of-type) {
+    margin-bottom: calc(${th('gridUnit')} * 6);
+  }
+`
+
+const Heading = styled.div`
+  color: ${th('colorPrimary')};
+  font-family: ${th('fontReading')};
+  font-size: ${th('fontSizeHeading3')};
+  line-height: ${th('lineHeightHeading3')};
+  margin: calc(${th('gridUnit')} * 3) 0;
+  text-transform: uppercase;
+`
+
+const UploadContainer = styled.div`
+  display: flex;
+  justify-content: center;
+`
+
+export { Page, Section, Heading, UploadContainer }
diff --git a/app/components/component-xpub-dashboard/src/components/sections/EditorItem.js b/app/components/component-xpub-dashboard/src/components/sections/EditorItem.js
new file mode 100644
index 0000000000000000000000000000000000000000..4cb8d02403568aaf3c8a76e98d9da6c271bbce53
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/sections/EditorItem.js
@@ -0,0 +1,100 @@
+import React from 'react'
+
+import styled from 'styled-components'
+import Authorize from 'pubsweet-client/src/helpers/Authorize'
+import { Action, ActionGroup } from '@pubsweet/ui'
+import { Item, Header, Body } from '../molecules/Item'
+import Status from '../Status'
+import Meta from '../metadata/Meta'
+import MetadataSections from '../metadata/MetadataSections'
+import MetadataType from '../metadata/MetadataType'
+import MetadataReviewType from '../metadata/MetadataReviewType'
+import MetadataSubmittedDate from '../metadata/MetadataSubmittedDate'
+import MetadataAuthors from '../metadata/MetadataAuthors'
+import MetadataStreamLined from '../metadata/MetadataStreamLined'
+import JournalLink from '../JournalLink'
+import Reviews from '../Reviews'
+import VersionTitle from './VersionTitle'
+
+const VersionTitleLink = styled(JournalLink)`
+  text-decoration: none;
+  color: #333;
+`
+
+const getUserFromTeam = (version, role) => {
+  if (!version.teams) return []
+
+  const teams = version.teams.filter(team => team.teamType === role)
+  return teams.length ? teams[0].members : []
+}
+
+const EditorItemLinks = ({ version, journals }) => (
+  <ActionGroup>
+    <Action to={`/journals/${journals.id}/versions/${version.id}/submit`}>
+      Summary Info
+    </Action>
+    <Action
+      to={`/journals/${journals.id}/versions/${version.id}/decisions/${version.id}`}
+    >
+      {version.decision && version.decision.status === 'submitted'
+        ? `Decision: ${version.decision.recommendation}`
+        : 'Control Panel'}
+    </Action>
+  </ActionGroup>
+)
+
+const getDeclarationsObject = (version, value) => {
+  if (!version.meta) version.meta = {}
+  const declarations = version.meta.declarations || {}
+
+  return declarations[value] || 'no'
+}
+
+const getMetadataObject = (version, value) => {
+  const metadata = version.meta || {}
+  return metadata[value] || []
+}
+
+const getSubmitedDate = version =>
+  getMetadataObject(version, 'history').find(
+    history => history.type === 'submitted',
+  ) || []
+
+const EditorItem = ({ version, journals }) => (
+  <Authorize object={[version]} operation="can view my manuscripts section">
+    <Item>
+      <Header>
+        <Status status={version.status} />
+        <Meta>
+          <MetadataStreamLined
+            streamlinedReview={getDeclarationsObject(
+              version,
+              'streamlinedReview',
+            )}
+          />
+          <MetadataAuthors authors={getUserFromTeam(version, 'author')} />
+          {getSubmitedDate(version) ? (
+            <MetadataSubmittedDate submitted={getSubmitedDate(version).date} />
+          ) : null}
+          <MetadataType type={getMetadataObject(version, 'articleType')} />
+          <MetadataSections
+            sections={getMetadataObject(version, 'articleSections')}
+          />
+          <MetadataReviewType
+            openPeerReview={getDeclarationsObject(version, 'openPeerReview')}
+          />
+        </Meta>
+      </Header>
+      <Body>
+        <VersionTitleLink id={version.id} page="decisions" version={version}>
+          <VersionTitle version={version} />
+        </VersionTitleLink>
+        <EditorItemLinks journals={journals} version={version} />
+      </Body>
+
+      <Reviews version={version} />
+    </Item>
+  </Authorize>
+)
+
+export default EditorItem
diff --git a/app/components/component-xpub-dashboard/src/components/sections/EditorItem.md b/app/components/component-xpub-dashboard/src/components/sections/EditorItem.md
new file mode 100644
index 0000000000000000000000000000000000000000..de3cc04f49041a4151e7815e3e28354f4eaa0e93
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/sections/EditorItem.md
@@ -0,0 +1,59 @@
+A dashboard item showing a project that the current user is handling as editor.
+
+```js
+const { JournalProvider } = require('xpub-journal')
+const { MockedProvider } = require('@apollo/react-testing')
+const journal = require('@pubsweet/styleguide/config/journal')
+const queries = require('../../graphql/queries')
+const gql = require('graphql-tag')
+
+const mocks = [
+  {
+    request: {
+      query: gql`
+        query CurrentUser {
+          currentUser {
+            admin
+            id
+            username
+          }
+        }
+      `,
+    },
+    result: {
+      data: {
+        currentUser: { id: faker.random.uuid(), username: 'test', admin: true },
+      },
+    },
+  },
+]
+
+const journals = {
+  id: faker.random.uuid(),
+  title: faker.lorem.sentence(15),
+  fragments: [faker.random.uuid()],
+}
+
+const version = {
+  id: faker.random.uuid(),
+  status: 'submitted',
+  meta: {
+    title: faker.lorem.sentence(10),
+    articleType: 'original-research',
+    articleSection: ['cognitive-psychology'],
+    declarations: {
+      streamlinedReview: 'yes',
+      openPeerReview: 'yes',
+    },
+  },
+  reviews: [
+    {
+      id: faker.random.uuid(),
+      user: {},
+    },
+  ],
+}
+;<JournalProvider journal={journal}>
+  <EditorItem journals={journals} version={version} />
+</JournalProvider>
+```
diff --git a/app/components/component-xpub-dashboard/src/components/sections/EditorItem.test.js b/app/components/component-xpub-dashboard/src/components/sections/EditorItem.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7b6080cc6aea3792d975d6ec968f904060cca27
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/sections/EditorItem.test.js
@@ -0,0 +1,169 @@
+import React from 'react'
+import Enzyme, { mount } from 'enzyme'
+import { MemoryRouter } from 'react-router-dom'
+import Adapter from 'enzyme-adapter-react-16'
+import faker from 'faker'
+import { JournalProvider } from 'xpub-journal'
+import { ThemeProvider } from 'styled-components'
+import EditorItem from './EditorItem'
+
+import MetadataAuthors from '../metadata/MetadataAuthors'
+import MetadataStreamLined from '../metadata/MetadataStreamLined'
+import MetadataSubmittedDate from '../metadata/MetadataSubmittedDate'
+import MetadataType from '../metadata/MetadataType'
+import MetadataSections from '../metadata/MetadataSections'
+import MetadataReviewType from '../metadata/MetadataReviewType'
+
+// this should be elsewhere
+Enzyme.configure({ adapter: new Adapter() })
+
+jest.mock('config', () => ({
+  'pubsweet-client': {},
+  authsome: {
+    mode: 'authsome',
+  },
+}))
+
+jest.mock('pubsweet-client/src/helpers/Authorize', () => 'div')
+
+const journal = {
+  reviewStatus: ['invited', 'accepted', 'rejected', 'completed'],
+  articleTypes: [
+    {
+      label: 'Original Research Report',
+      value: 'original-research',
+    },
+  ],
+  articleSections: [
+    {
+      label: 'Cognitive Psychology',
+      value: 'cognitive-psychology',
+    },
+  ],
+}
+
+describe('EditorItem', () => {
+  const makeWrapper = (props = {}) => {
+    props = Object.assign(
+      {
+        version: {
+          id: faker.random.uuid(),
+          created: '2018-06-07',
+          teams: [],
+          reviews: [],
+          status: props.status,
+          meta: {
+            history: [
+              {
+                type: 'submitted',
+                date: '2018-06-07',
+              },
+            ],
+          },
+        },
+        journals: {
+          id: faker.random.uuid(),
+          title: faker.lorem.sentence(15),
+        },
+      },
+      props,
+    )
+    return mount(
+      <MemoryRouter>
+        <ThemeProvider theme={{}}>
+          <JournalProvider journal={journal}>
+            <EditorItem {...props} />
+          </JournalProvider>
+        </ThemeProvider>
+      </MemoryRouter>,
+    )
+  }
+
+  it('shows empty metadata', () => {
+    const EditorItem = makeWrapper()
+    expect(EditorItem.find(MetadataStreamLined).children()).toHaveLength(0)
+    expect(EditorItem.find(MetadataAuthors).children()).toHaveLength(0)
+    expect(EditorItem.find(MetadataSections).children()).toHaveLength(0)
+    expect(
+      EditorItem.find(MetadataSubmittedDate)
+        .children()
+        .text(),
+    ).toEqual('2018-06-07')
+    expect(
+      EditorItem.find(MetadataType)
+        .children()
+        .text(),
+    ).toEqual('None')
+    expect(
+      EditorItem.find(MetadataReviewType)
+        .children()
+        .text(),
+    ).toEqual('Closed review')
+  })
+
+  it('shows all metadata', () => {
+    const username = faker.name.findName()
+    const EditorItem = makeWrapper({
+      version: {
+        teams: [
+          {
+            created: '2018-06-07',
+            members: [
+              {
+                user: {
+                  id: faker.random.uuid(),
+                  created: '2018-06-07',
+                  username,
+                  admin: true,
+                },
+              },
+            ],
+            teamType: 'author',
+          },
+        ],
+        meta: {
+          articleType: 'original-research',
+          articleSections: ['cognitive-psychology'],
+          declarations: {
+            openPeerReview: 'yes',
+            streamlinedReview: 'yes',
+          },
+          history: [
+            {
+              type: 'submitted',
+              date: '2018-06-07',
+            },
+          ],
+        },
+      },
+    })
+
+    expect(EditorItem.find(MetadataStreamLined).text()).toEqual('Streamlined')
+    expect(
+      EditorItem.find(MetadataAuthors)
+        .children()
+        .text(),
+    ).toEqual(username)
+
+    expect(
+      EditorItem.find(MetadataSections)
+        .children()
+        .text(),
+    ).toEqual(journal.articleSections[0].label)
+    expect(
+      EditorItem.find(MetadataSubmittedDate)
+        .children()
+        .text(),
+    ).toEqual('2018-06-07')
+    expect(
+      EditorItem.find(MetadataType)
+        .children()
+        .text(),
+    ).toEqual(journal.articleTypes[0].label)
+    expect(
+      EditorItem.find(MetadataReviewType)
+        .children()
+        .text(),
+    ).toEqual('Open review')
+  })
+})
diff --git a/app/components/component-xpub-dashboard/src/components/sections/OwnerItem.js b/app/components/component-xpub-dashboard/src/components/sections/OwnerItem.js
new file mode 100644
index 0000000000000000000000000000000000000000..58fdc9c2ce7c92dd08cc58e90fcfa5c79a3177ff
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/sections/OwnerItem.js
@@ -0,0 +1,73 @@
+import React from 'react'
+import { pickBy } from 'lodash'
+
+import { Action, ActionGroup } from '@pubsweet/ui'
+import Authorize from 'pubsweet-client/src/helpers/Authorize'
+
+import { Item, Header, Body } from '../molecules/Item'
+import Status from '../Status'
+import VersionTitle from './VersionTitle'
+
+const OwnerItem = ({ version, journals, deleteManuscript }) => {
+  const itemHeader = (
+    <Header>
+      <Status status={version.status} />
+    </Header>
+  )
+
+  const baseLink = `/journals/${journals.id}/versions/${version.id}`
+  const submitLink = `${baseLink}/submit`
+  const manuscriptLink = `${baseLink}/manuscript`
+
+  const actionButtons = {
+    submit: (
+      <Action key="submit-action" to={submitLink}>
+        Summary Info
+      </Action>
+    ),
+    manuscript: (
+      <Action key="manuscript-action" to={manuscriptLink}>
+        Manuscript
+      </Action>
+    ),
+    delete: (
+      <Action key="delete-action" onClick={() => deleteManuscript(version)}>
+        Delete
+      </Action>
+    ),
+  }
+
+  const unauthorized = (
+    <ActionGroup>
+      {Object.values(pickBy(actionButtons, (value, key) => key !== 'delete'))}
+    </ActionGroup>
+  )
+
+  const actions = (
+    <Authorize
+      object={version}
+      operation="can delete manuscript"
+      unauthorized={unauthorized}
+    >
+      <ActionGroup>{Object.values(actionButtons)}</ActionGroup>
+    </Authorize>
+  )
+
+  const body = (
+    <Body>
+      <VersionTitle version={version} />
+      {actions}
+    </Body>
+  )
+
+  return (
+    <Authorize object={[version]} operation="can view my submission section">
+      <Item>
+        {itemHeader}
+        {body}
+      </Item>
+    </Authorize>
+  )
+}
+
+export default OwnerItem
diff --git a/app/components/component-xpub-dashboard/src/components/sections/OwnerItem.md b/app/components/component-xpub-dashboard/src/components/sections/OwnerItem.md
new file mode 100644
index 0000000000000000000000000000000000000000..5476e35f7e9af50e51839ec53dced18fb41d5b69
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/sections/OwnerItem.md
@@ -0,0 +1,23 @@
+A dashboard item showing a project that the current user is an owner of.
+
+```js
+const journal = require('@pubsweet/styleguide/config/journal')
+
+const journals = {
+  id: faker.random.uuid(),
+  title: faker.lorem.sentence(15),
+  manuscripts: [faker.random.uuid()],
+}
+
+const version = {
+  id: faker.random.uuid(),
+  meta: {
+    title: faker.lorem.sentence(10),
+  },
+}
+;<OwnerItem
+  journals={journals}
+  version={version}
+  deleteManuscript={props => console.log(props)}
+/>
+```
diff --git a/app/components/component-xpub-dashboard/src/components/sections/ReviewerItem.js b/app/components/component-xpub-dashboard/src/components/sections/ReviewerItem.js
new file mode 100644
index 0000000000000000000000000000000000000000..9b017adf1ed0aefff71980308266803ff3473054
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/sections/ReviewerItem.js
@@ -0,0 +1,108 @@
+import React from 'react'
+import { Button } from '@pubsweet/ui'
+import Authorize from 'pubsweet-client/src/helpers/Authorize'
+import { Item, Body, Divider } from '../molecules/Item'
+import { Links, LinkContainer } from '../molecules/Links'
+import { Actions, ActionContainer } from '../molecules/Actions'
+
+import JournalLink from '../JournalLink'
+import VersionTitle from './VersionTitle'
+
+// TODO: only return links if version id is in reviewer.accepted array
+// TODO: only return actions if not accepted or rejected
+// TODO: review id in link
+
+const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => {
+  const team =
+    (version.teams || []).find(team => team.role === 'reviewerEditor') || {}
+
+  const currentMember =
+    team.members &&
+    team.members.find(member => member.user.id === currentUser.id)
+  const status = currentMember && currentMember.status
+
+  // Enable that when Team Models is updated
+  // const { status } =
+  //   getUserFromTeam(version, 'reviewerEditor').filter(
+  //     member => member.id === currentUser.id,
+  //   )[0] || {}
+
+  const review =
+    (version.reviews || []).find(
+      review =>
+        currentUser &&
+        review.user &&
+        review.user.id === currentUser.id &&
+        !review.isDecision,
+    ) || {}
+
+  return (
+    <Authorize
+      key={`${review.id}`}
+      object={[version]}
+      operation="can view review section"
+    >
+      <Item>
+        <Body>
+          <VersionTitle version={version} />
+
+          {(status === 'accepted' || status === 'completed') && (
+            <Links>
+              <LinkContainer>
+                <JournalLink
+                  id={version.id}
+                  journal={journals}
+                  page="reviews"
+                  version={version}
+                >
+                  {status === 'completed' ? 'Completed' : 'Do Review'}
+                </JournalLink>
+              </LinkContainer>
+            </Links>
+          )}
+
+          {status === 'invited' && (
+            <Actions>
+              <ActionContainer>
+                <Button
+                  onClick={() => {
+                    reviewerRespond({
+                      variables: {
+                        currentUserId: currentUser.id,
+                        action: 'accepted',
+                        teamId: team.id,
+                      },
+                    })
+                  }}
+                >
+                  accept
+                </Button>
+              </ActionContainer>
+
+              <Divider separator="|" />
+
+              <ActionContainer>
+                <Button
+                  onClick={() => {
+                    reviewerRespond({
+                      variables: {
+                        currentUserId: currentUser.id,
+                        action: 'rejected',
+                        teamId: team.id,
+                      },
+                    })
+                  }}
+                >
+                  reject
+                </Button>
+              </ActionContainer>
+            </Actions>
+          )}
+          {status === 'rejected' && 'rejected'}
+        </Body>
+      </Item>
+    </Authorize>
+  )
+}
+
+export default ReviewerItem
diff --git a/app/components/component-xpub-dashboard/src/components/sections/ReviewerItem.md b/app/components/component-xpub-dashboard/src/components/sections/ReviewerItem.md
new file mode 100644
index 0000000000000000000000000000000000000000..d0f88812dcec54c474bea516b705130738c823d6
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/sections/ReviewerItem.md
@@ -0,0 +1,128 @@
+A dashboard item showing a project that the current user is a reviewer of.
+
+```js
+const currentUserId = 1
+
+const currentUser = {
+  id: currentUserId,
+  username: faker.lorem.word(),
+}
+
+const version = {
+  meta: {
+    title: faker.lorem.sentence(10),
+  },
+  id: faker.random.uuid(),
+  reviews: [
+    {
+      id: faker.random.uuid(),
+      recommedation: 'review',
+    },
+  ],
+  teams: [
+    {
+      created: new Date().toDateString(),
+      updated: new Date().toDateString(),
+      status: [
+        {
+          user: currentUser.id,
+          status: 'invited',
+        },
+      ],
+      members: [
+        {
+          user: currentUser,
+        },
+      ],
+      teamType: 'reviewerEditor',
+    },
+  ],
+}
+;<ReviewerItem version={version} currentUser={currentUser} />
+```
+
+When the reviewer has accepted the invitation to review, a link to perform their review is displayed.
+
+```js
+const currentUserId = 1
+
+const currentUser = {
+  id: currentUserId,
+}
+
+const version = {
+  id: faker.random.uuid(),
+  meta: {
+    title: faker.lorem.sentence(10),
+  },
+  reviews: [
+    {
+      id: faker.random.uuid(),
+      recommedation: 'review',
+    },
+  ],
+  teams: [
+    {
+      created: new Date().toDateString(),
+      updated: new Date().toDateString(),
+      status: [
+        {
+          user: currentUser.id,
+          status: 'accepted',
+        },
+      ],
+      members: [
+        {
+          user: currentUser,
+          status: 'accepted',
+        },
+      ],
+      teamType: 'reviewerEditor',
+    },
+  ],
+}
+;<ReviewerItem version={version} currentUser={currentUser} />
+```
+
+When the reviewer has declined the invitation to review, they can't perform any further actions.
+
+```js
+const currentUserId = 1
+
+const currentUser = {
+  id: currentUserId,
+}
+
+const version = {
+  meta: {
+    title: faker.lorem.sentence(10),
+  },
+  id: faker.random.uuid(),
+  reviews: [
+    {
+      id: faker.random.uuid(),
+      recommedation: 'review',
+    },
+  ],
+  teams: [
+    {
+      created: new Date().toDateString(),
+      updated: new Date().toDateString(),
+      status: [
+        {
+          user: currentUser.id,
+          status: 'rejected',
+        },
+      ],
+      members: [
+        {
+          user: currentUser,
+          status: 'rejected',
+        },
+      ],
+      teamType: 'reviewerEditor',
+    },
+  ],
+}
+;<ReviewerItem version={version} currentUser={currentUser} />
+```
diff --git a/app/components/component-xpub-dashboard/src/components/sections/VersionTitle.js b/app/components/component-xpub-dashboard/src/components/sections/VersionTitle.js
new file mode 100644
index 0000000000000000000000000000000000000000..d51d00964aff556c6a6ee6944edadc9876e7c303
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/components/sections/VersionTitle.js
@@ -0,0 +1,17 @@
+import React from 'react'
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Root = styled.div`
+  font-size: ${th('fontSizeHeading4')};
+  line-height: ${th('lineHeightHeading4')};
+`
+
+export default ({ version, className }) => {
+  const title =
+    version && version.meta && version.meta.title
+      ? version.meta.title
+      : 'Untitled'
+
+  return <Root>{title}</Root>
+}
diff --git a/app/components/component-xpub-dashboard/src/graphql/mutations/index.js b/app/components/component-xpub-dashboard/src/graphql/mutations/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..98298141648012e81b84eccd96a70460048a2ce0
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/graphql/mutations/index.js
@@ -0,0 +1,100 @@
+import gql from 'graphql-tag'
+
+export default {
+  deleteManuscriptMutation: gql`
+    mutation($id: ID!) {
+      deleteManuscript(id: $id)
+    }
+  `,
+  reviewerResponseMutation: gql`
+    mutation($currentUserId: ID!, $action: String, $teamId: ID!) {
+      reviewerResponse(
+        currentUserId: $currentUserId
+        action: $action
+        teamId: $teamId
+      ) {
+        id
+        role
+        name
+        object {
+          objectId
+          objectType
+        }
+        members {
+          id
+          user {
+            id
+            username
+          }
+          status
+        }
+      }
+    }
+  `,
+  uploadManuscriptMutation: gql`
+    mutation($file: Upload!) {
+      upload(file: $file) {
+        url
+      }
+    }
+  `,
+  createManuscriptMutation: gql`
+    mutation($input: ManuscriptInput) {
+      createManuscript(input: $input) {
+        id
+        created
+        manuscriptVersions {
+          id
+        }
+        teams {
+          id
+          role
+          name
+          object {
+            objectId
+            objectType
+          }
+          members {
+            id
+            user {
+              id
+              username
+            }
+            status
+          }
+        }
+        status
+        reviews {
+          open
+          recommendation
+          created
+          user {
+            id
+            username
+          }
+        }
+        meta {
+          title
+          declarations {
+            openData
+            openPeerReview
+            preregistered
+            previouslySubmitted
+            researchNexus
+            streamlinedReview
+          }
+          articleSections
+          articleType
+          history {
+            type
+            date
+          }
+          notes {
+            notesType
+            content
+          }
+        }
+      }
+    }
+  `,
+}
diff --git a/app/components/component-xpub-dashboard/src/graphql/queries/index.js b/app/components/component-xpub-dashboard/src/graphql/queries/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..cffea494ba8826130183c271c98f79ae2186125b
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/graphql/queries/index.js
@@ -0,0 +1,82 @@
+import gql from 'graphql-tag'
+
+export default {
+  dashboard: gql`
+    {
+      currentUser {
+        id
+        username
+        admin
+      }
+
+      journals {
+        id
+        title
+        manuscripts {
+          id
+          manuscriptVersions {
+            id
+          }
+          reviews {
+            open
+            recommendation
+            created
+            isDecision
+            user {
+              id
+              username
+            }
+          }
+          teams {
+            id
+            role
+            type
+            name
+            object {
+              objectId
+              objectType
+            }
+            members {
+              id
+              user {
+                id
+                username
+              }
+              status
+            }
+          }
+          status
+          meta {
+            title
+            declarations {
+              openData
+              openPeerReview
+              preregistered
+              previouslySubmitted
+              researchNexus
+              streamlinedReview
+            }
+            articleSections
+            articleType
+            history {
+              type
+              date
+            }
+          }
+        }
+      }
+    }
+  `,
+  getUser: gql`
+    query GetUser($id: ID!) {
+      user(id: $id) {
+        id
+        username
+        admin
+        teams {
+          id
+        }
+      }
+    }
+  `,
+}
diff --git a/app/components/component-xpub-dashboard/src/index.js b/app/components/component-xpub-dashboard/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f1f42a64b1ecf4a71345e76fa416d89015cb96c
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/index.js
@@ -0,0 +1,5 @@
+module.exports = {
+  frontend: {
+    components: [() => require('./components')],
+  },
+}
diff --git a/app/components/component-xpub-dashboard/src/title.js b/app/components/component-xpub-dashboard/src/title.js
new file mode 100644
index 0000000000000000000000000000000000000000..7aae47d80f251b4a25f856dc77739ca626d280a9
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/title.js
@@ -0,0 +1,12 @@
+export const generateTitle = name =>
+  name
+    .replace(/[_-]+/g, ' ') // convert hyphens/underscores to space
+    .replace(/\.[^.]+$/, '') // remove file extension
+
+// TODO: preserve italics (use parse5?)
+export const extractTitle = source => {
+  const doc = new DOMParser().parseFromString(source, 'text/html')
+  const heading = doc.querySelector('h1')
+
+  return heading ? heading.textContent : null
+}
diff --git a/app/components/component-xpub-dashboard/src/upload.js b/app/components/component-xpub-dashboard/src/upload.js
new file mode 100644
index 0000000000000000000000000000000000000000..d3685d7083020abfd5613fb010a1af8b76cc6b84
--- /dev/null
+++ b/app/components/component-xpub-dashboard/src/upload.js
@@ -0,0 +1,126 @@
+// import { compose, withProps } from 'recompose'
+// import { withRouter } from 'react-router-dom'
+// import { withApollo } from '@apollo/react-hoc'
+import config from 'config'
+import request from 'pubsweet-client/src/helpers/api'
+import queries from './graphql/queries'
+import mutations from './graphql/mutations'
+import { extractTitle, generateTitle } from './title'
+// import { XpubContext } from 'xpub-with-context'
+// import { useContext } from 'react'
+
+const uploadPromise = (files, client) => () => {
+  const [file] = files
+  if (files.length > 1) {
+    throw new Error('Only one manuscript file can be uploaded')
+  }
+
+  return client.mutate({
+    mutation: mutations.uploadManuscriptMutation,
+    variables: { file },
+  })
+}
+
+const DocxToHTMLPromise = file => ({ data }) => {
+  const body = new FormData()
+  body.append('docx', file)
+
+  const url = `${config['pubsweet-client'].baseUrl}convertDocxToHTML`
+
+  return request(url, { method: 'POST', body }).then(response =>
+    Promise.resolve({
+      fileURL: data.upload.url,
+      response,
+    }),
+  )
+}
+
+const createManuscriptPromise = (file, client, currentUser) => ({
+  fileURL,
+  response,
+}) => {
+  // In the case of a Docx file, response is the HTML
+  // In the case of another type of file, response is true/false
+  if (!response) {
+    throw new Error('The file was not converted')
+  }
+
+  const source = typeof response === 'string' ? response : undefined
+  const title = extractTitle(response) || generateTitle(file.name)
+
+  const manuscript = {
+    files: [
+      {
+        filename: file.name,
+        url: fileURL,
+        mimeType: file.type,
+        size: file.size,
+      },
+    ],
+    meta: {
+      title,
+      source,
+    },
+  }
+
+  return client.mutate({
+    mutation: mutations.createManuscriptMutation,
+    variables: { input: manuscript },
+    update: (proxy, { data: { createManuscript } }) => {
+      let data = proxy.readQuery({ query: queries.dashboard })
+      data.journals.manuscripts.push(createManuscript)
+      proxy.writeQuery({ query: queries.dashboard, data })
+
+      data = proxy.readQuery({
+        query: queries.getUser,
+        variables: { id: currentUser.id },
+      })
+      data.user.teams.push(createManuscript.teams[0])
+      proxy.writeQuery({ query: queries.getUser, data })
+    },
+  })
+}
+
+const redirectPromise = (setConversionState, journals, history) => ({
+  data,
+}) => {
+  setConversionState(() => ({ converting: false, completed: true }))
+  const route = `/journals/${journals.id}/versions/${data.createManuscript.id}/submit`
+  // redirect after a short delay
+  window.setTimeout(() => {
+    history.push(route)
+  }, 2000)
+}
+
+const skipXSweet = file =>
+  !(
+    file.type ===
+    'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+  )
+
+export default ({
+  client,
+  history,
+  journals,
+  currentUser,
+  setConversion,
+}) => files => {
+  const [file] = files
+  setConversion({ converting: true })
+  return Promise.resolve()
+    .then(uploadPromise(files, client))
+    .then(
+      skipXSweet(file)
+        ? ({ data }) =>
+            Promise.resolve({
+              fileURL: data.upload.url,
+              response: true,
+            })
+        : DocxToHTMLPromise(file),
+    )
+    .then(createManuscriptPromise(file, client, currentUser))
+    .then(redirectPromise(setConversion, journals, history))
+    .catch(error => {
+      setConversion({ error })
+    })
+}
diff --git a/app/components/component-xpub-formbuilder/CHANGELOG.md b/app/components/component-xpub-formbuilder/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..1c229875011860c328f06e8d08890c4d55bae18a
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/CHANGELOG.md
@@ -0,0 +1,185 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [1.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@1.0.7...pubsweet-component-xpub-formbuilder@1.1.0) (2019-11-11)
+
+
+### Features
+
+* **xpub:** bring back xpub components ([fb69994](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb69994098b4e2dbcca75b4786ebb1335af730b9))
+
+
+
+
+
+## [1.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@1.0.6...pubsweet-component-xpub-formbuilder@1.0.7) (2019-01-16)
+
+
+### Bug Fixes
+
+* **components:** generals fixes on the components ([4a78cfe](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4a78cfe))
+* **formbuilder:** delete forms ([17c52d0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/17c52d0))
+* **formbuilder:** save to file ([c4f4196](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c4f4196))
+* **merge:** merging to master ([8603808](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8603808))
+* **test:** formbuilder ([93c55fd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/93c55fd))
+* **test:** problems with eslint and test ([48f7fe2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/48f7fe2))
+
+
+
+
+
+## [1.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@1.0.5...pubsweet-component-xpub-formbuilder@1.0.6) (2019-01-14)
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+
+
+
+
+## [1.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@1.0.4...pubsweet-component-xpub-formbuilder@1.0.5) (2019-01-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+
+
+
+
+## [1.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@1.0.3...pubsweet-component-xpub-formbuilder@1.0.4) (2019-01-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+
+
+
+
+## [1.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@1.0.2...pubsweet-component-xpub-formbuilder@1.0.3) (2018-12-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+
+
+
+
+## [1.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@1.0.1...pubsweet-component-xpub-formbuilder@1.0.2) (2018-12-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+
+
+
+
+## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@1.0.0...pubsweet-component-xpub-formbuilder@1.0.1) (2018-11-30)
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+
+
+
+
+# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@0.2.0...pubsweet-component-xpub-formbuilder@1.0.0) (2018-11-29)
+
+
+### Features
+
+* **various:** update styled-components ([5c51466](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5c51466))
+* **various:** upgrade styled-components ([9b886f6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9b886f6))
+
+
+### BREAKING CHANGES
+
+* **various:** Replace all styled-components .extend with styled()
+* **various:** Replace styled-components injectGlobal with new createGlobalStyle
+
+
+
+
+
+<a name="0.2.0"></a>
+# [0.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@0.1.5...pubsweet-component-xpub-formbuilder@0.2.0) (2018-11-05)
+
+
+### Features
+
+* GraphQL Login component ([70df3de](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/70df3de))
+
+
+
+
+<a name="0.1.5"></a>
+## [0.1.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@0.1.4...pubsweet-component-xpub-formbuilder@0.1.5) (2018-10-08)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+<a name="0.1.4"></a>
+## [0.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@0.1.3...pubsweet-component-xpub-formbuilder@0.1.4) (2018-09-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+<a name="0.1.3"></a>
+## [0.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@0.1.2...pubsweet-component-xpub-formbuilder@0.1.3) (2018-09-25)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+<a name="0.1.2"></a>
+## [0.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@0.1.1...pubsweet-component-xpub-formbuilder@0.1.2) (2018-09-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+<a name="0.1.1"></a>
+## [0.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-formbuilder@0.1.0...pubsweet-component-xpub-formbuilder@0.1.1) (2018-09-06)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-formbuilder
+
+<a name="0.1.0"></a>
+# 0.1.0 (2018-09-04)
+
+
+### Bug Fixes
+
+* **fomrbuilder:** fix validation ([98b3b5e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/98b3b5e))
+* **formbuilder:** create folder from scratch ([a2d533e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a2d533e))
+* **formbuilder:** erase files uneeded ([9bc04cd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9bc04cd))
+* **formbuilder:** test addition ([1379895](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1379895))
+* **readme:** change readme files ([eb0ff9b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/eb0ff9b))
+* **styleguide:** remove console ([c8f7bb2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c8f7bb2))
+* **test:** add data-test-id to tabs ([e8a42cb](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e8a42cb))
+* **test:** fix intgration test ([900f0db](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/900f0db))
+* **update:** update dependencies ([52c0002](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/52c0002))
+* **xpubformbuilder:** integration test and component ([6806f81](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6806f81))
+
+
+### Features
+
+* **backend:** add backend check path floder ([8c95e72](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8c95e72))
+* **components:** form builder backend requests ([7082b0f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7082b0f))
+* **formbuilder:** add components for the builder ([cfae1c0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/cfae1c0))
+* **formbuilder:** add form redux actions formbuilder ([7436353](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7436353))
+* **formbuilder:** add formbuilder component ([c24b9f7](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c24b9f7))
+* **formbuilder:** add forms layout ([0cd6b9d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0cd6b9d))
+* **formbuilder:** add validation for elements ([882935a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/882935a))
+* **formbuilder:** layout of form builder ([2bd4956](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2bd4956))
+
+
+
+
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
diff --git a/app/components/component-xpub-formbuilder/README.md b/app/components/component-xpub-formbuilder/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4e4a2b8f9f38c05d461ae20df876bdb0d1b31652
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/README.md
@@ -0,0 +1,3 @@
+## pubsweet-component-xpub-formbuilder
+
+A PubSweet component that provides an interface for admins to manage forms in xPub.
diff --git a/app/components/component-xpub-formbuilder/package.json b/app/components/component-xpub-formbuilder/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..78b5bee28cecc4cde95f62b7233fdf0ab6143081
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/package.json
@@ -0,0 +1,31 @@
+{
+  "name": "pubsweet-component-xpub-formbuilder",
+  "version": "1.1.0",
+  "description": "Form builder component for Xpub",
+  "main": "src/index.js",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "dependencies": {
+    "@apollo/react-hoc": "^3.1.3",
+    "@pubsweet/ui": "^12.1.0",
+    "@pubsweet/ui-toolkit": "^2.2.14",
+    "formik": "^1.4.2",
+    "passport": "^0.4.0",
+    "prop-types": "^15.5.10",
+    "react-dom": "^16.2.0",
+    "recompose": "^0.30.0",
+    "styled-components": "^4.1.1",
+    "xpub-edit": "^2.6.0"
+  },
+  "peerDependencies": {
+    "config": ">=3.0.1",
+    "graphql-tag": ">=2.10.0",
+    "lodash": ">=4.17.11",
+    "pubsweet-client": ">=2.1.0",
+    "react": ">=16"
+  }
+}
diff --git a/app/components/component-xpub-formbuilder/src/components/ComponentProperties.jsx b/app/components/component-xpub-formbuilder/src/components/ComponentProperties.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..49e206ed7373e1bd7f29c4437a67c679768a16bf
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/ComponentProperties.jsx
@@ -0,0 +1,120 @@
+import React from 'react'
+import { map, omitBy } from 'lodash'
+import {
+  branch,
+  renderComponent,
+  compose,
+  withState,
+  withHandlers,
+  withProps,
+} from 'recompose'
+import { ValidatedFieldFormik, Menu, Button } from '@pubsweet/ui'
+import { withFormik } from 'formik'
+
+import FormProperties from './FormProperties'
+import components from './config/Elements'
+import * as elements from './builderComponents'
+import { Page, Heading } from './molecules/Page'
+import { Section, Legend } from './styles'
+
+const MenuComponents = input => (
+  <Menu
+    options={Object.keys(components).map(value => ({
+      value,
+      label: value,
+    }))}
+    {...input}
+  />
+)
+
+const ComponentProperties = ({
+  properties,
+  changeComponent,
+  selectComponentValue,
+  handleSubmit,
+  setFieldValue,
+}) => (
+  <Page>
+    <form onSubmit={handleSubmit}>
+      <Heading>Component Properties</Heading>
+      <Section>
+        <Legend space>Choose Component</Legend>
+        <ValidatedFieldFormik
+          component={MenuComponents}
+          name="component"
+          onChange={value => {
+            changeComponent(value)
+            setFieldValue('component', value)
+          }}
+        />
+      </Section>
+      {selectComponentValue &&
+        map(components[selectComponentValue], (value, key) => (
+          <Section>
+            <Legend space>{`Field ${key}`}</Legend>
+            <ValidatedFieldFormik
+              component={elements[value.component].default}
+              key={`${selectComponentValue}-${key}`}
+              name={key}
+              onChange={event => {
+                let value = {}
+                if (event.target) {
+                  value = event.target.value
+                } else {
+                  value = event
+                }
+                setFieldValue(key, value)
+              }}
+              {...value.props}
+            />
+          </Section>
+        ))}
+      <Button primary type="submit">
+        Update Component
+      </Button>
+    </form>
+  </Page>
+)
+
+const UpdateForm = ({ onSubmitFn, properties, changeTabs }) => (
+  <FormProperties
+    mode="update"
+    onSubmitFn={onSubmitFn}
+    properties={properties}
+  />
+)
+
+const onSubmit = (values, { onSubmitFn, properties }) => {
+  if (!values.id || !values.component) return
+
+  const children = omitBy(values, value => value === '')
+  onSubmitFn({ id: properties.id }, Object.assign({}, { children }))
+}
+
+const ComponentForm = compose(
+  withProps(({ properties }) => ({
+    initialValues: { children: properties.properties },
+  })),
+  withFormik({
+    displayName: 'ComponentSubmit',
+    mapPropsToValues: data => data.properties.properties,
+    handleSubmit: (props, { props: { onSubmitFn, id, properties } }) =>
+      onSubmit(props, { onSubmitFn, properties }),
+  }),
+  withState(
+    'selectComponentValue',
+    'selectComponent',
+    ({ properties }) => properties.properties.component,
+  ),
+  withHandlers({
+    changeComponent: ({ selectComponent }) => component =>
+      selectComponent(() => component),
+  }),
+)(ComponentProperties)
+
+export default compose(
+  branch(
+    ({ properties }) => properties.type === 'form',
+    renderComponent(UpdateForm),
+  )(ComponentForm),
+)
diff --git a/app/components/component-xpub-formbuilder/src/components/FormBuilder.jsx b/app/components/component-xpub-formbuilder/src/components/FormBuilder.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..25c39956dd32239c31fd3fa5bd3b730b1f16c49b
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/FormBuilder.jsx
@@ -0,0 +1,156 @@
+import React from 'react'
+import {
+  compose,
+  withState,
+  withHandlers,
+  lifecycle,
+  // setDisplayName,
+} from 'recompose'
+import styled, { withTheme } from 'styled-components'
+import { unescape } from 'lodash'
+import { th } from '@pubsweet/ui-toolkit'
+import { Icon, Action } from '@pubsweet/ui'
+import { Page } from './molecules/Page'
+
+const Element = styled.div`
+  display: flex;
+  border: 1px solid #000;
+  padding: 10px;
+  margin: 10px;
+  justify-content: space-between;
+`
+
+const StatusIcon = withTheme(({ children, theme }) => (
+  <Icon color={theme.colorPrimary}>{children}</Icon>
+))
+
+const Status = styled.div`
+  align-items: center;
+  color: ${th('colorPrimary')};
+  display: inline-flex;
+`
+
+const StatusIdle = styled(Status).attrs({
+  children: () => <StatusIcon>plus_circle</StatusIcon>,
+})``
+
+const Root = styled.div`
+  display: flex;
+  flex-direction: column;
+  font-weight: 200;
+  padding-bottom: 10px;
+  padding-top: 10px;
+
+  &:hover ${StatusIdle} {
+    circle {
+      fill: ${th('colorPrimary')};
+      stroke: ${th('colorPrimary')};
+    }
+
+    line {
+      stroke: white;
+    }
+  }
+`
+
+const Main = styled.div`
+  display: flex;
+  justify-content: center;
+`
+
+const Info = styled.div`
+  color: ${th('colorPrimary')};
+  font-size: 2em;
+  font-weight: 400;
+  text-transform: uppercase;
+  display: inline-flex;
+  padding: calc(8px / 2);
+`
+
+const ElementTitle = styled.span``
+
+const createMarkup = encodedHtml => ({
+  __html: unescape(encodedHtml),
+})
+
+const BuilderElement = ({ elements, changeProperties, deleteElement, form }) =>
+  elements.map((value, key) => (
+    <Element key={`element-${value.id}`}>
+      <Action
+        onClick={() =>
+          changeProperties({
+            type: 'element',
+            properties: value,
+          })
+        }
+      >
+        <ElementTitle dangerouslySetInnerHTML={createMarkup(value.title)} /> (
+        {value.component})
+      </Action>
+      <Action onClick={() => deleteElement(form.id, value.id)}>x</Action>
+    </Element>
+  ))
+
+const AddButtonElement = ({ addElements }) => (
+  <Root>
+    <Main>
+      <Action
+        onClick={() =>
+          addElements({
+            title: 'New Component',
+            id: `${Date.now()}`,
+          })
+        }
+      >
+        <StatusIdle />
+        <Info>Add Element</Info>
+      </Action>
+    </Main>
+  </Root>
+)
+
+const FormBuilder = ({
+  form,
+  elements,
+  addElements,
+  changeProperties,
+  deleteElement,
+}) => (
+  <Page>
+    <AddButtonElement addElements={addElements} form={form} id="add-element" />
+    {elements && elements.length > 0 && (
+      <BuilderElement
+        changeProperties={changeProperties}
+        deleteElement={deleteElement}
+        elements={elements}
+        form={form}
+        id="builder-element"
+      />
+    )}
+  </Page>
+)
+
+FormBuilder.displayName = 'FormBuilder'
+
+export default compose(
+  withState('elements', 'onAddElements', ({ form }) => form.children || []),
+  withHandlers({
+    addElements: ({ onAddElements, form }) => addElement =>
+      onAddElements(() => {
+        const addEl = { children: form.children || [] }
+        addEl.children = [...addEl.children, addElement]
+        return addEl.children
+      }),
+  }),
+  lifecycle({
+    componentWillReceiveProps(nextProps) {
+      if (this.props.form.children !== nextProps.form.children) {
+        this.setState({ elements: nextProps.form.children })
+      }
+
+      if (this.props.elements !== nextProps.elements) {
+        this.setState({ elements: nextProps.elements })
+      }
+    },
+  }),
+)(FormBuilder)
diff --git a/app/components/component-xpub-formbuilder/src/components/FormBuilderLayout.jsx b/app/components/component-xpub-formbuilder/src/components/FormBuilderLayout.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..84d4c667cad1ad59e46edc82c11ed45f715b94ca
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/FormBuilderLayout.jsx
@@ -0,0 +1,97 @@
+import React from 'react'
+import { forEach } from 'lodash'
+import styled from 'styled-components'
+import { Tabs, Action } from '@pubsweet/ui'
+import { Columns, Admin } from './atoms/Columns'
+import ComponentProperties from './ComponentProperties'
+import FormBuilder from './FormBuilder'
+import FormProperties from './FormProperties'
+
+const DeleteIcon = styled(Action)`
+  margin-left: 10px;
+  line-height: 1.15;
+`
+
+const AdminStyled = styled(Admin)`
+  border-left: 1px solid black;
+  padding-left: 40px;
+`
+
+const FormBuilderLayout = ({
+  getForms,
+  properties,
+  deleteForm,
+  deleteElement,
+  updateForm,
+  createForm,
+  updateElements,
+  changeProperties,
+  changeTabs,
+  activeTab,
+}) => {
+  const Sections = []
+  forEach(getForms, (form, key) => {
+    Sections.push({
+      content: (
+        <FormBuilder
+          changeProperties={changeProperties}
+          deleteElement={deleteElement}
+          form={form}
+          key={`form-builder-${key}`}
+        />
+      ),
+      key: `${key}`,
+      label: [
+        form.name,
+        <DeleteIcon
+          key={`delete-form-${key}`}
+          onClick={e => {
+            e.preventDefault()
+            deleteForm(form.id)
+          }}
+        >
+          x
+        </DeleteIcon>,
+      ],
+    })
+  })
+
+  Sections.push({
+    content: (
+      <FormProperties
+        key="form-builder-new"
+        mode="create"
+        onSubmitFn={createForm}
+        properties={{}}
+      />
+    ),
+    key: 'new',
+    label: '+ Add Form',
+  })
+  return (
+    <Columns>
+      <Tabs
+        activeKey={`${activeTab}`}
+        onChange={tab => {
+          changeProperties({
+            type: 'form',
+            properties: getForms[tab],
+          })
+          changeTabs(tab)
+        }}
+        sections={Sections}
+        title="builder"
+      />
+      <AdminStyled>
+        <ComponentProperties
+          changeTabs={changeTabs}
+          key={`${properties.type}-${(properties.properties || {}).id}`}
+          onSubmitFn={properties.type === 'form' ? updateForm : updateElements}
+          properties={properties}
+        />
+      </AdminStyled>
+    </Columns>
+  )
+}
+
+export default FormBuilderLayout
diff --git a/app/components/component-xpub-formbuilder/src/components/FormBuilderLayout.md b/app/components/component-xpub-formbuilder/src/components/FormBuilderLayout.md
new file mode 100644
index 0000000000000000000000000000000000000000..ca3d1efb62d3a48bd517d1a319597203229e15af
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/FormBuilderLayout.md
@@ -0,0 +1,50 @@
+A form builder to create forms
+
+```js
+const forms = [
+  {
+    name: 'test form',
+    id: 'test',
+    children: [
+      {
+        title: 'Title',
+        id: '1531303631370',
+        component: 'AbstractEditor',
+        name: 'metadata.title',
+        placeholder: 'Enter Title...',
+        validate: ['required'],
+        validateValue: { minChars: '10' },
+      },
+      {
+        title: 'Title 1',
+        id: '1531303631371',
+        component: 'AbstractEditor',
+        name: 'metadata.title',
+        placeholder: 'Enter Title...',
+        validate: ['required'],
+        validateValue: { minChars: '10' },
+      },
+    ],
+  },
+  { name: 'test form 1', id: 'test1' },
+]
+
+initialState = {
+  properties: {
+    type: 'form',
+    properties: forms[0],
+  },
+  activeTab: 0,
+}
+;<div style={{ position: 'relative', height: '100%' }}>
+  <FormBuilderLayout
+    getForms={forms}
+    activeTab={state.activeTab}
+    properties={state.properties}
+    changeProperties={value => {
+      setState({ properties: value })
+    }}
+    changeTabs={value => setState({ activeTab: value })}
+  />
+</div>
+```
diff --git a/app/components/component-xpub-formbuilder/src/components/FormBuilderPage.js b/app/components/component-xpub-formbuilder/src/components/FormBuilderPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..57c20bb5a80f40658b4f3cb38418e76f26360ea3
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/FormBuilderPage.js
@@ -0,0 +1,126 @@
+import { compose, withState, withHandlers, withProps } from 'recompose'
+import { graphql } from '@apollo/react-hoc'
+import gql from 'graphql-tag'
+import { withLoader } from 'pubsweet-client'
+
+import FormBuilderLayout from './FormBuilderLayout'
+
+const createForm = gql`
+  mutation($form: String!) {
+    createForm(form: $form)
+  }
+`
+
+const updateForm = gql`
+  mutation($form: String!, $id: String!) {
+    updateForm(form: $form, id: $id)
+  }
+`
+
+const updateFormElements = gql`
+  mutation($form: String!, $formId: String!) {
+    updateFormElements(form: $form, formId: $formId)
+  }
+`
+
+const deleteFormElement = gql`
+  mutation($formId: ID!, $elementId: ID!) {
+    deleteFormElement(formId: $formId, elementId: $elementId)
+  }
+`
+
+const deleteForms = gql`
+  mutation($formId: ID!) {
+    deleteForms(formId: $formId)
+  }
+`
+
+const query = gql`
+  query {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    getForms
+  }
+`
+
+export default compose(
+  graphql(query),
+  graphql(deleteForms, {
+    name: 'deleteForms',
+  }),
+  graphql(deleteFormElement, {
+    name: 'deleteFormElement',
+  }),
+  graphql(updateForm, {
+    name: 'updateForm',
+  }),
+  graphql(createForm, {
+    name: 'createForm',
+  }),
+  graphql(updateFormElements, {
+    name: 'updateFormElements',
+  }),
+  withLoader(),
+  withProps(props => ({
+    deleteForm: formId =>
+      props.deleteForms({
+        variables: {
+          formId,
+        },
+      }),
+    deleteElement: (formId, elementId) =>
+      props.deleteFormElement({
+        variables: {
+          formId,
+          elementId,
+        },
+      }),
+    updateForm: (form, formProperties) =>
+      props.updateForm({
+        variables: {
+          form: JSON.stringify(formProperties),
+          id: form.id,
+        },
+      }),
+    createForm: formProperties =>
+      props.createForm({
+        variables: {
+          form: JSON.stringify(formProperties),
+        },
+      }),
+    updateElements: (form, formElements) =>
+      props.updateFormElements({
+        variables: {
+          form: JSON.stringify(formElements),
+          formId: form.id,
+        },
+      }),
+  })),
+  withState('properties', 'onChangeProperties', ({ getForms }) => ({
+    type: 'form',
+    properties: getForms[0] || {},
+  })),
+  withState('activeTab', 'onChangeTab', ({ getForms, activeTab }) =>
+    getForms.length === 0 ? 'new' : 0,
+  ),
+  withHandlers({
+    changeProperties: ({
+      onChangeProperties,
+      getForms,
+      activeTab,
+    }) => properties =>
+      onChangeProperties(
+        () =>
+          Object.assign({}, properties, {
+            id: (getForms[activeTab] || {}).id,
+          }) || undefined,
+      ),
+    changeTabs: ({ onChangeTab }) => activeTab => {
+      onChangeTab(() => activeTab || '')
+    },
+  }),
+)(FormBuilderLayout)
diff --git a/app/components/component-xpub-formbuilder/src/components/FormProperties.jsx b/app/components/component-xpub-formbuilder/src/components/FormProperties.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c50d59fd1eab546ff2db9b483c379673d844eff8
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/FormProperties.jsx
@@ -0,0 +1,141 @@
+import React from 'react'
+import { withFormik } from 'formik'
+import { pick, isEmpty } from 'lodash'
+import styled from 'styled-components'
+import { compose, withProps, withState, withHandlers } from 'recompose'
+import { Button, TextField, ValidatedFieldFormik } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+import { AbstractField, RadioBox } from './builderComponents'
+import { Page, Heading } from './molecules/Page'
+
+const nameText = input => <TextField {...input} />
+
+const idText = input => <TextField {...input} />
+
+export const Legend = styled.div`
+  font-size: ${th('fontSizeBase')};
+  font-weight: 600;
+  margin-bottom: ${({ space, theme }) => space && theme.gridUnit};
+`
+
+export const Section = styled.div`
+  margin: calc(${th('gridUnit')} * 6) 0;
+`
+
+const onSubmit = (values, { onSubmitFn, properties, mode }) => {
+  if (mode === 'create') {
+    onSubmitFn(Object.assign({}, values))
+  } else {
+    onSubmitFn({ id: properties.properties.id }, Object.assign({}, values))
+  }
+}
+
+const FormProperties = ({
+  handleSubmit,
+  properties,
+  mode,
+  selectPopup,
+  showPopupValue,
+  values,
+  setFieldValue,
+}) =>
+  isEmpty(properties.properties) && mode !== 'create' ? (
+    <Page>
+      <span>&nbsp;</span>
+    </Page>
+  ) : (
+    <Page>
+      <form onSubmit={handleSubmit}>
+        <Heading>{mode === 'create' ? 'Create Form' : 'Update Form'}</Heading>
+        <Section id="form.id" key="form.id">
+          <Legend>ID Form</Legend>
+          <ValidatedFieldFormik component={idText} name="id" />
+        </Section>
+        <Section id="form.name" key="form.name">
+          <Legend>Form Name</Legend>
+          <ValidatedFieldFormik component={nameText} name="name" />
+        </Section>
+        <Section id="form.description" key="form.description">
+          <Legend>Description</Legend>
+          <ValidatedFieldFormik
+            component={AbstractField.default}
+            name="description"
+            onChange={val => {
+              setFieldValue('description', val)
+            }}
+          />
+        </Section>
+        <Section id="form.submitpopup" key="form.submitpopup">
+          <Legend>Submit on Popup</Legend>
+          <ValidatedFieldFormik
+            component={RadioBox.default}
+            inline
+            name="haspopup"
+            onChange={(input, value) => {
+              setFieldValue('haspopup', input)
+              selectPopup(input)
+            }}
+            options={[
+              {
+                label: 'Yes',
+                value: 'true',
+              },
+              {
+                label: 'No',
+                value: 'false',
+              },
+            ]}
+          />
+        </Section>
+        {showPopupValue === 'true' && [
+          <Section id="popup.title" key="popup.title">
+            <Legend>Popup Title</Legend>
+            <ValidatedFieldFormik component={nameText} name="popuptitle" />
+          </Section>,
+          <Section id="popup.description" key="popup.description">
+            <Legend>Description</Legend>
+            <ValidatedFieldFormik
+              component={AbstractField.default}
+              name="popupdescription"
+              onChange={val => {
+                setFieldValue('popupdescription', val)
+              }}
+            />
+          </Section>,
+        ]}
+        <Button primary type="submit">
+          {mode === 'create' ? 'Create Form' : 'Update Form'}
+        </Button>
+      </form>
+    </Page>
+  )
+
+export default compose(
+  withProps(({ properties }) => {
+    const paths = [
+      'id',
+      'name',
+      'description',
+      'popupdescription',
+      'popuptitle',
+      'haspopup',
+    ]
+    return {
+      initialValues: pick(properties.properties, paths),
+    }
+  }),
+  withState(
+    'showPopupValue',
+    'selectPopup',
+    ({ properties }) => (properties.properties || {}).haspopup,
+  ),
+  withHandlers({
+    changeShowPopup: ({ selectPopup }) => value => selectPopup(() => value),
+  }),
+  withFormik({
+    displayName: 'FormSubmit',
+    mapPropsToValues: data => data.properties.properties,
+    handleSubmit: (props, { props: { mode, onSubmitFn, properties } }) =>
+      onSubmit(props, { mode, onSubmitFn, properties }),
+  }),
+)(FormProperties)
diff --git a/app/components/component-xpub-formbuilder/src/components/atoms/Columns.js b/app/components/component-xpub-formbuilder/src/components/atoms/Columns.js
new file mode 100644
index 0000000000000000000000000000000000000000..d51800fd26bff54474e912a3d23363a6f6f8dd2f
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/atoms/Columns.js
@@ -0,0 +1,22 @@
+import styled from 'styled-components'
+
+const Columns = styled.div`
+  display: grid;
+  grid-column-gap: 2em;
+  grid-template-areas: 'manuscript admin';
+  grid-template-columns: minmax(200px, 70ch) minmax(200px, 60ch);
+  justify-content: center;
+`
+
+const Manuscript = styled.div`
+  grid-area: manuscript;
+`
+
+const Admin = styled.div`
+  grid-area: admin;
+
+  border-left: 1px solid black;
+  padding-left: 40px;
+`
+
+export { Columns, Manuscript, Admin }
diff --git a/app/components/component-xpub-formbuilder/src/components/builderComponents/AbstractField.js b/app/components/component-xpub-formbuilder/src/components/builderComponents/AbstractField.js
new file mode 100644
index 0000000000000000000000000000000000000000..e044f1e21e4c95385c63acd3c2735898235b3f4b
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/builderComponents/AbstractField.js
@@ -0,0 +1,8 @@
+import React from 'react'
+import { AbstractEditor } from 'xpub-edit'
+
+const AbstractInput = ({ validationStatus, ...input }) => (
+  <AbstractEditor placeholder="Enter text" title="" {...input} />
+)
+
+export default AbstractInput
diff --git a/app/components/component-xpub-formbuilder/src/components/builderComponents/CheckBox.js b/app/components/component-xpub-formbuilder/src/components/builderComponents/CheckBox.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce802d6a7f40ada86df2a0b3eea911dfa2065a58
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/builderComponents/CheckBox.js
@@ -0,0 +1,5 @@
+import React from 'react'
+import { CheckboxGroup } from '@pubsweet/ui'
+
+const CheckboxFieldBuilder = input => <CheckboxGroup {...input} />
+export default CheckboxFieldBuilder
diff --git a/app/components/component-xpub-formbuilder/src/components/builderComponents/Menu.js b/app/components/component-xpub-formbuilder/src/components/builderComponents/Menu.js
new file mode 100644
index 0000000000000000000000000000000000000000..b64e39bd16a45f047072d7ac6c7da8a0253e6fed
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/builderComponents/Menu.js
@@ -0,0 +1,31 @@
+import React from 'react'
+import { Menu, TextField, ValidatedFieldFormik } from '@pubsweet/ui'
+import { compose, withState, withHandlers } from 'recompose'
+import { Legend, Section } from '../styles'
+
+const ValidationMenu = input => (
+  <div>
+    <Menu
+      {...input}
+      selectElement={value => {
+        input.onSelectElement(value)
+      }}
+    />
+    {input.selectelement && input.selectelement !== 'required' && (
+      <Section>
+        <Legend space>FIeld Min / Max</Legend>
+        <ValidatedFieldFormik
+          component={TextField}
+          name={`validateValue.${input.selectelement}`}
+        />
+      </Section>
+    )}
+  </div>
+)
+
+export default compose(
+  withState('selectelement', 'changeSelect', undefined),
+  withHandlers({
+    onSelectElement: ({ changeSelect }) => value => changeSelect(() => value),
+  }),
+)(ValidationMenu)
diff --git a/app/components/component-xpub-formbuilder/src/components/builderComponents/OptionsField.js b/app/components/component-xpub-formbuilder/src/components/builderComponents/OptionsField.js
new file mode 100644
index 0000000000000000000000000000000000000000..f429cf6180950dea655f6681720876a5a7f66c7d
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/builderComponents/OptionsField.js
@@ -0,0 +1,79 @@
+import React from 'react'
+import styled from 'styled-components'
+import { FieldArray } from 'formik'
+import { TextField, ValidatedFieldFormik, Button } from '@pubsweet/ui'
+
+const Inline = styled.div`
+  display: inline-block;
+  margin-right: 10px;
+`
+
+const UnbulletedList = styled.div`
+  list-style-type: none;
+  margin-left: -40px;
+`
+
+const Spacing = styled.div`
+  padding: 15px 0px;
+`
+
+const Option = styled.div`
+  padding-bottom: 10px;
+`
+
+const keyInput = input => (
+  <TextField label="Key Option" placeholder="Enter key…" {...input} />
+)
+
+const valueInput = input => (
+  <TextField label="Value Option" placeholder="Enter value…" {...input} />
+)
+
+const renderOptions = ({ form: { values }, push, remove }) => (
+  <ul>
+    <UnbulletedList>
+      <li>
+        <Button onClick={() => push()} plain type="button">
+          Add another option
+        </Button>
+      </li>
+      {(values.options || []).map((option, index) => (
+        <li>
+          <Spacing>
+            <Option>
+              Option:&nbsp;
+              {values.options.length > 1 && (
+                <Button onClick={() => remove(index)} type="button">
+                  Remove
+                </Button>
+              )}
+            </Option>
+            <div>
+              <Inline>
+                <ValidatedFieldFormik
+                  component={keyInput}
+                  name={`options.${index}.label`}
+                  required
+                />
+              </Inline>
+
+              <Inline>
+                <ValidatedFieldFormik
+                  component={valueInput}
+                  name={`options.${index}.value`}
+                  required
+                />
+              </Inline>
+            </div>
+          </Spacing>
+        </li>
+      ))}
+    </UnbulletedList>
+  </ul>
+)
+
+const OptionsField = () => (
+  <FieldArray component={renderOptions} name="options" />
+)
+
+export default OptionsField
diff --git a/app/components/component-xpub-formbuilder/src/components/builderComponents/RadioBox.js b/app/components/component-xpub-formbuilder/src/components/builderComponents/RadioBox.js
new file mode 100644
index 0000000000000000000000000000000000000000..f0302c923999488e0bff14579e63e136db2ed916
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/builderComponents/RadioBox.js
@@ -0,0 +1,5 @@
+import React from 'react'
+import { RadioGroup } from '@pubsweet/ui'
+
+const RadioboxFieldBuilder = input => <RadioGroup {...input} />
+export default RadioboxFieldBuilder
diff --git a/app/components/component-xpub-formbuilder/src/components/builderComponents/TextArea.js b/app/components/component-xpub-formbuilder/src/components/builderComponents/TextArea.js
new file mode 100644
index 0000000000000000000000000000000000000000..4be5acbfb0e1d00dcff892c88490aed66c18c9a7
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/builderComponents/TextArea.js
@@ -0,0 +1,4 @@
+import React from 'react'
+
+const TextareaFieldBuilder = input => <textarea {...input} />
+export default TextareaFieldBuilder
diff --git a/app/components/component-xpub-formbuilder/src/components/builderComponents/TextField.js b/app/components/component-xpub-formbuilder/src/components/builderComponents/TextField.js
new file mode 100644
index 0000000000000000000000000000000000000000..221ecb2153040cbf261b159c048cb5bd5383172b
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/builderComponents/TextField.js
@@ -0,0 +1,5 @@
+import React from 'react'
+import { TextField } from '@pubsweet/ui'
+
+const TextFieldBuilder = input => <TextField {...input} />
+export default TextFieldBuilder
diff --git a/app/components/component-xpub-formbuilder/src/components/builderComponents/index.js b/app/components/component-xpub-formbuilder/src/components/builderComponents/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..24858cd7d2a0478ba633bde7ff7d63afb0ccd6ea
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/builderComponents/index.js
@@ -0,0 +1,17 @@
+import * as OptionsField from './OptionsField'
+import * as TextField from './TextField'
+import * as Menu from './Menu'
+import * as AbstractField from './AbstractField'
+import * as CheckBox from './CheckBox'
+import * as RadioBox from './RadioBox'
+import * as TextArea from './TextArea'
+
+export {
+  OptionsField,
+  TextField,
+  Menu,
+  AbstractField,
+  CheckBox,
+  RadioBox,
+  TextArea,
+}
diff --git a/app/components/component-xpub-formbuilder/src/components/config/Elements.js b/app/components/component-xpub-formbuilder/src/components/config/Elements.js
new file mode 100644
index 0000000000000000000000000000000000000000..425c9247fcc53ab4b5e9334eef333b856e8754fc
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/config/Elements.js
@@ -0,0 +1,171 @@
+import config from 'config'
+
+const extendComponents = (config['pubsweet-component-xpub-formbuilder'] || {})
+  .components
+
+const textfield = {
+  component: 'TextField',
+}
+
+const orderfield = {
+  component: 'TextField',
+}
+
+const optionfield = {
+  component: 'OptionsField',
+}
+
+const editorfield = {
+  component: 'AbstractField',
+}
+
+const textarea = {
+  component: 'TextArea',
+  props: {
+    cols: 55,
+    rows: 5,
+  },
+}
+
+const validate = {
+  component: 'Menu',
+  props: {
+    multi: true,
+    options: [
+      {
+        value: 'required',
+        label: 'Required',
+      },
+      {
+        value: 'minSize',
+        label: 'minSize',
+      },
+      {
+        value: 'minChars',
+        label: 'minimum Characters',
+      },
+      {
+        value: 'maxChars',
+        label: 'maximum Characters',
+      },
+    ],
+  },
+}
+
+const radiofield = {
+  component: 'RadioBox',
+  props: {
+    inline: true,
+    options: [
+      {
+        value: 'true',
+        label: 'Yes',
+      },
+      {
+        value: 'false',
+        label: 'No',
+      },
+    ],
+    label: 'Inline',
+  },
+}
+
+const elements = {
+  Supplementary: {
+    id: textfield,
+    title: textfield,
+    name: textfield,
+    description: editorfield,
+    order: orderfield,
+    validate,
+  },
+  AuthorsInput: {
+    id: textfield,
+    title: textfield,
+    name: textfield,
+    order: orderfield,
+    validate,
+  },
+  AbstractEditor: {
+    id: textfield,
+    title: textfield,
+    name: textfield,
+    placeholder: textfield,
+    description: editorfield,
+    order: orderfield,
+    validate,
+  },
+  TextField: {
+    id: textfield,
+    title: textfield,
+    name: textfield,
+    placeholder: textfield,
+    description: editorfield,
+    order: orderfield,
+    validate,
+    parse: {
+      component: 'Menu',
+      props: {
+        label: 'Split with Comma Seperate',
+        options: [
+          {
+            value: 'false',
+            label: 'None',
+          },
+          {
+            value: 'split',
+            label: 'Split',
+          },
+        ],
+      },
+    },
+    format: {
+      component: 'Menu',
+      props: {
+        label: 'Join with Comma',
+        options: [
+          {
+            value: 'false',
+            label: 'None',
+          },
+          {
+            value: 'join',
+            label: 'Join',
+          },
+        ],
+      },
+    },
+  },
+  CheckboxGroup: {
+    id: textfield,
+    title: textfield,
+    name: textfield,
+    description: editorfield,
+    options: optionfield,
+    order: orderfield,
+    validate,
+  },
+  Menu: {
+    id: textfield,
+    title: textfield,
+    name: textfield,
+    placeholder: textfield,
+    description: editorfield,
+    options: optionfield,
+    order: orderfield,
+    validate,
+  },
+  RadioGroup: {
+    id: textfield,
+    title: textfield,
+    name: textfield,
+    description: editorfield,
+    options: optionfield,
+    order: orderfield,
+    inline: radiofield,
+    sectioncss: textarea,
+    validate,
+  },
+}
+
+export default Object.assign(elements, extendComponents)
diff --git a/app/components/component-xpub-formbuilder/src/components/index.js b/app/components/component-xpub-formbuilder/src/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..317c4b3b28f384a7ddc90809f213a4627609d96a
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/index.js
@@ -0,0 +1 @@
+export { default as FormBuilderPage } from './FormBuilderPage'
diff --git a/app/components/component-xpub-formbuilder/src/components/molecules/Page.js b/app/components/component-xpub-formbuilder/src/components/molecules/Page.js
new file mode 100644
index 0000000000000000000000000000000000000000..d9d9ed7c6c88bdd3eb67438e4c8e2922a4e92025
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/molecules/Page.js
@@ -0,0 +1,30 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Page = styled.div`
+  margin: auto;
+  max-width: 60em;
+`
+
+const Section = styled.div`
+  margin: ${th('gridUnit')} 0;
+
+  &:not(:last-of-type) {
+    margin-bottom: calc(${th('gridUnit')} * 2);
+  }
+`
+
+const Heading = styled.div`
+  color: ${th('colorPrimary')};
+  font-family: ${th('fontReading')};
+  font-size: ${th('fontSizeHeading3')};
+  margin: ${th('gridUnit')} 0;
+  text-transform: uppercase;
+`
+
+const UploadContainer = styled.div`
+  display: flex;
+  justify-content: center;
+`
+
+export { Page, Section, Heading, UploadContainer }
diff --git a/app/components/component-xpub-formbuilder/src/components/styles/index.js b/app/components/component-xpub-formbuilder/src/components/styles/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..96872aa8d8d415c2f0aaec78d036de0b5ef0a04e
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/components/styles/index.js
@@ -0,0 +1,12 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+export const Section = styled.div`
+  margin: calc(${th('gridUnit')} * 6) 0;
+`
+
+export const Legend = styled.div`
+  font-size: ${th('fontSizeBase')};
+  font-weight: 600;
+  margin-bottom: ${({ space, theme }) => space && theme.gridUnit};
+`
diff --git a/app/components/component-xpub-formbuilder/src/index.js b/app/components/component-xpub-formbuilder/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..61a1b3de54127ec6184ef96d26b0020e0aa38a12
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/index.js
@@ -0,0 +1,12 @@
+module.exports = {
+  frontend: {
+    components: [() => require('./components')],
+    actions: () => ({
+      getForm: require('./redux/FormBuilder').getForm,
+    }),
+    reducers: {
+      forms: () => require('./redux/FormBuilder').default,
+    },
+  },
+  server: () => require('./server/formRequestBackend'),
+}
diff --git a/app/components/component-xpub-formbuilder/src/redux/FormBuilder.js b/app/components/component-xpub-formbuilder/src/redux/FormBuilder.js
new file mode 100644
index 0000000000000000000000000000000000000000..edf54a138bfc7251860e5e3f3e200a684e48433b
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/redux/FormBuilder.js
@@ -0,0 +1,135 @@
+import * as api from 'pubsweet-client/src/helpers/api'
+
+export const GET_FORM_REQUEST = 'GET_FORM_REQUEST'
+export const GET_FORM_SUCCESS = 'GET_FORM_SUCCESS'
+export const GET_FORM_FAILURE = 'GET_FORM_FAILURE'
+
+function getFormRequest(project, version) {
+  return {
+    type: GET_FORM_REQUEST,
+  }
+}
+
+function getFormSuccess(forms) {
+  return {
+    type: GET_FORM_SUCCESS,
+    forms,
+  }
+}
+
+function getFormFailure(error) {
+  return {
+    type: GET_FORM_FAILURE,
+    error,
+  }
+}
+
+export function getForms() {
+  return dispatch => {
+    dispatch(getFormRequest())
+
+    return api
+      .get('/get-forms', {})
+      .then(result => {
+        dispatch(getFormSuccess(result))
+      })
+      .catch(error => dispatch(getFormFailure(error)))
+  }
+}
+
+export function getForm(formId) {
+  return dispatch => {
+    dispatch(getFormRequest())
+
+    return api
+      .get(`/get-form/${formId}`, {})
+      .then(result => {
+        dispatch(getFormSuccess(result))
+      })
+      .catch(error => dispatch(getFormFailure(error)))
+  }
+}
+
+export function updateForms(form, properties) {
+  return dispatch => {
+    dispatch(getFormRequest())
+
+    return api
+      .update(`/update-forms/${form.id}`, properties)
+      .then(result => {
+        dispatch(getFormSuccess(result))
+      })
+      .catch(error => dispatch(getFormFailure(error)))
+  }
+}
+
+export function updateElements(form, properties) {
+  return dispatch => {
+    dispatch(getFormRequest())
+
+    return api
+      .update(
+        `/update-forms/${form.id}/element/${properties.children.id}`,
+        properties,
+      )
+      .then(result => {
+        dispatch(getFormSuccess(result))
+      })
+      .catch(error => dispatch(getFormFailure(error)))
+  }
+}
+
+export function deleteForms(form) {
+  return dispatch => {
+    dispatch(getFormRequest())
+
+    return api
+      .remove(`/delete-forms/${form.id}`)
+      .then(result => {
+        dispatch(getFormSuccess(result))
+      })
+      .catch(error => dispatch(getFormFailure(error)))
+  }
+}
+
+export function deleteElements(form, element) {
+  return dispatch => {
+    dispatch(getFormRequest())
+
+    return api
+      .remove(`/delete-forms/${form.id}/elements/${element.id}`)
+      .then(result => {
+        dispatch(getFormSuccess(result))
+      })
+      .catch(error => dispatch(getFormFailure(error)))
+  }
+}
+
+export function createForms(properties) {
+  return dispatch => {
+    dispatch(getFormRequest())
+
+    return api
+      .create('/create-forms', properties)
+      .then(result => {
+        dispatch(getFormSuccess(result))
+      })
+      .catch(error => dispatch(getFormFailure(error)))
+  }
+}
+
+const initialState = {}
+export default (state = initialState, action) => {
+  switch (action.type) {
+    case GET_FORM_SUCCESS:
+      return {
+        forms: action.forms.forms,
+      }
+
+    case GET_FORM_FAILURE:
+      return { error: action.error }
+
+    default:
+      return state
+  }
+}
diff --git a/app/components/component-xpub-formbuilder/src/server/formRequestBackend.js b/app/components/component-xpub-formbuilder/src/server/formRequestBackend.js
new file mode 100644
index 0000000000000000000000000000000000000000..898f4625d237d1262aabd003ecee1aade6134d88
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/server/formRequestBackend.js
@@ -0,0 +1,204 @@
+// const { pick } = require('lodash')
+const config = require('config')
+const passport = require('passport')
+// const logger = require('@pubsweet/logger')
+// const User = require('pubsweet-server/src/models/User')
+// const authsome = require('pubsweet-server/src/helpers/authsome')
+// const AuthorizationError = require('pubsweet-server/src/errors/AuthorizationError')
+const fs = require('fs')
+// const filepath = require('path')
+const { readFiles, mkdirp } = require('./util')
+
+const authBearer = passport.authenticate('bearer', { session: false })
+
+const mergeFiles = path =>
+  readFiles(path).then(files => {
+    const forms = []
+    files.forEach((item, index) => {
+      // const { name } = filepath.parse(item.filename)
+      const content = JSON.parse(item.content)
+      if (!content.name) return
+      forms.push(content)
+    })
+    return { forms }
+  })
+
+module.exports = app => {
+  app.get('/api/get-forms', authBearer, async (req, res, next) => {
+    try {
+      const folderPath = `${config.get(
+        'pubsweet-component-xpub-formbuilder.path',
+      )}/`
+
+      mkdirp(folderPath)
+      mergeFiles(folderPath).then(forms =>
+        res.send({
+          forms,
+        }),
+      )
+    } catch (err) {
+      next(err)
+    }
+  })
+
+  app.get('/api/get-form/:formId', authBearer, async (req, res, next) => {
+    try {
+      const { formId } = req.params
+      const folderPath = `${config.get(
+        'pubsweet-component-xpub-formbuilder.path',
+      )}/`
+      const path = `${folderPath}${formId}.json`
+      const forms = JSON.parse(fs.readFileSync(path, 'utf8'))
+
+      res.send({
+        forms,
+      })
+    } catch (err) {
+      next(err)
+    }
+  })
+
+  app.patch(
+    '/api/update-forms/:formId/element/:elementId',
+    authBearer,
+    async (req, res, next) => {
+      try {
+        const { formId, elementId } = req.params
+        const content = req.body
+        const folderPath = `${config.get(
+          'pubsweet-component-xpub-formbuilder.path',
+        )}/`
+        const path = `${folderPath}${formId}.json`
+        const forms = JSON.parse(fs.readFileSync(path, 'utf8'))
+        if (!forms.children) {
+          forms.children = [content.children]
+        } else if (forms.children.some(e => e.id === elementId)) {
+          const children = forms.children.map(value =>
+            value.id === elementId ? content.children : value,
+          )
+          forms.children = children
+        } else {
+          forms.children.push(content.children)
+        }
+
+        fs.writeFileSync(path, JSON.stringify(forms))
+        mergeFiles(folderPath).then(forms =>
+          res.send({
+            forms,
+          }),
+        )
+      } catch (err) {
+        next(err)
+      }
+    },
+  )
+
+  app.patch('/api/update-forms/:formId', authBearer, async (req, res, next) => {
+    try {
+      const { formId } = req.params
+      let content = req.body
+      const folderPath = `${config.get(
+        'pubsweet-component-xpub-formbuilder.path',
+      )}/`
+      let path = `${folderPath}${formId}.json`
+
+      if (fs.existsSync(path)) {
+        let forms = JSON.parse(fs.readFileSync(path, 'utf8'))
+        forms = Object.assign(forms, content)
+        content = forms
+        if (formId !== content.id) {
+          fs.unlinkSync(path)
+          path = `${folderPath}${content.id}.json`
+        }
+      }
+
+      fs.writeFileSync(path, JSON.stringify(content))
+      mergeFiles(folderPath).then(forms =>
+        res.send({
+          forms,
+        }),
+      )
+    } catch (err) {
+      next(err)
+    }
+  })
+
+  app.post('/api/create-forms', authBearer, async (req, res, next) => {
+    try {
+      const content = req.body
+      const folderPath = `${config.get(
+        'pubsweet-component-xpub-formbuilder.path',
+      )}/`
+      const path = `${folderPath}/${content.id}.json`
+
+      if (!fs.existsSync(path)) {
+        mkdirp(folderPath)
+        fs.writeFileSync(path, JSON.stringify(content))
+      }
+
+      mergeFiles(folderPath).then(forms =>
+        res.send({
+          forms,
+        }),
+      )
+    } catch (err) {
+      next(err)
+    }
+  })
+
+  app.delete(
+    '/api/delete-forms/:formId/elements/:elementId',
+    authBearer,
+    async (req, res, next) => {
+      try {
+        const { formId, elementId } = req.params
+        const folderPath = `${config.get(
+          'pubsweet-component-xpub-formbuilder.path',
+        )}/`
+
+        const path = `${folderPath}/${formId}.json`
+        const forms = JSON.parse(fs.readFileSync(path, 'utf8'))
+
+        if (forms.children) {
+          const children = forms.children.filter(el => el.id !== elementId)
+          forms.children = children
+          fs.writeFileSync(path, JSON.stringify(forms))
+        }
+
+        mergeFiles(folderPath).then(forms =>
+          res.send({
+            forms,
+          }),
+        )
+      } catch (err) {
+        next(err)
+      }
+    },
+  )
+
+  app.delete(
+    '/api/delete-forms/:formId',
+    authBearer,
+    async (req, res, next) => {
+      try {
+        const { formId } = req.params
+        const folderPath = `${config.get(
+          'pubsweet-component-xpub-formbuilder.path',
+        )}/`
+        const path = `${folderPath}${formId}.json`
+
+        if (fs.existsSync(path)) {
+          fs.unlinkSync(path)
+        }
+
+        mergeFiles(folderPath).then(forms =>
+          res.send({
+            forms,
+          }),
+        )
+      } catch (err) {
+        next(err)
+      }
+    },
+  )
+}
diff --git a/app/components/component-xpub-formbuilder/src/server/util.js b/app/components/component-xpub-formbuilder/src/server/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..32976c84cf573aef9f389b5d7230685ed3e7604c
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/server/util.js
@@ -0,0 +1,58 @@
+const fs = require('fs')
+const path = require('path')
+
+const Util = {}
+
+const promiseAllP = (items, block) => {
+  const promises = []
+  items.forEach((item, index) => {
+    const prom = () =>
+      new Promise((resolve, reject) =>
+        block.apply(this, [item, index, resolve, reject]),
+      )
+    promises.push(prom(item, index))
+  })
+  return Promise.all(promises)
+}
+
+Util.mkdirp = dir =>
+  path
+    .resolve(dir)
+    .split(path.sep)
+    .reduce((acc, cur) => {
+      const currentPath = path.normalize(acc + path.sep + cur)
+      try {
+        fs.statSync(currentPath)
+      } catch (e) {
+        if (e.code === 'ENOENT') {
+          fs.mkdirSync(currentPath)
+        } else {
+          throw e
+        }
+      }
+      return currentPath
+    }, '')
+
+Util.readFiles = dirname =>
+  new Promise((resolve, reject) => {
+    fs.readdir(dirname, (err, filenames) => {
+      if (err) return reject(err)
+      return promiseAllP(filenames, (filename, index, resolve, reject) =>
+        fs.readFile(
+          path.resolve(dirname, filename),
+          'utf-8',
+          (err, content) => {
+            if (err) return reject(err)
+            return resolve({
+              filename,
+              content,
+            })
+          },
+        ),
+      )
+        .then(results => resolve(results))
+        .catch(error => reject(error))
+    })
+  })
+
+module.exports = Util
diff --git a/app/components/component-xpub-formbuilder/src/test/FormBuilder.test.js b/app/components/component-xpub-formbuilder/src/test/FormBuilder.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..441dd68197b420113d3690166ca1b2fbb102e179
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/test/FormBuilder.test.js
@@ -0,0 +1,110 @@
+import React from 'react'
+import Enzyme, { shallow } from 'enzyme'
+import Adapter from 'enzyme-adapter-react-16'
+import { Action } from '@pubsweet/ui'
+
+import { diveTo } from './util'
+import FormBuilder from '../components/FormBuilder'
+import forms from './config/test.json'
+import formsnoelements from './config/testnoelements.json'
+
+jest.mock('config', () => ({
+  'pubsweet-client': {},
+  authsome: {
+    mode: 'authsome',
+    teams: {
+      seniorEditor: {
+        name: 'Senior Editors',
+        permissions: '',
+      },
+      handlingEditor: {
+        name: 'Handling Editors',
+        permissions: '',
+      },
+      managingEditor: {
+        name: 'Managing Editors',
+        permissions: '',
+      },
+      reviewer: {
+        name: 'Reviewer',
+        permissions: '',
+      },
+    },
+  },
+}))
+
+// this should be elsewhere
+Enzyme.configure({ adapter: new Adapter() })
+
+describe('FormBuilder', () => {
+  const makeWrapper = (props = {}) => {
+    props = Object.assign(
+      {
+        form: formsnoelements[0],
+      },
+      props,
+    )
+
+    return shallow(<FormBuilder {...props} />)
+  }
+
+  it('shows just the add element button', () => {
+    const formbuilder = makeWrapper()
+    expect(
+      diveTo(formbuilder, 'lifecycle(FormBuilder)', {})
+        .dive()
+        .find('#builder-element'),
+    ).toHaveLength(0)
+
+    expect(
+      diveTo(formbuilder, 'lifecycle(FormBuilder)', {})
+        .dive()
+        .find('#add-element'),
+    ).toHaveLength(1)
+  })
+
+  // TODO: This has started failing by upgrading enzyme. There
+  // are a few issues with these tests that need to be fixed
+  // anyway.
+  it.skip('shows add element button and form elements', () => {
+    const formbuilder = makeWrapper({ form: forms[0] })
+
+    expect(
+      diveTo(formbuilder, 'lifecycle(FormBuilder)', {})
+        .dive()
+        .find('#builder-element')
+        .dive()
+        .at(0),
+    ).toHaveLength(2)
+
+    expect(
+      diveTo(formbuilder, 'lifecycle(FormBuilder)', {})
+        .dive()
+        .find('#add-element'),
+    ).toHaveLength(1)
+  })
+
+  it('adds empty element to the form', () => {
+    const formbuilder = makeWrapper()
+    expect(
+      diveTo(formbuilder, 'lifecycle(FormBuilder)', {})
+        .dive()
+        .find('#builder-element'),
+    ).toHaveLength(0)
+
+    diveTo(formbuilder, 'lifecycle(FormBuilder)', {})
+      .dive()
+      .find('#add-element')
+      .dive()
+      .find(Action)
+      .simulate('click')
+
+    formbuilder.update()
+
+    expect(
+      diveTo(formbuilder, 'lifecycle(FormBuilder)', {})
+        .dive()
+        .find('#builder-element'),
+    ).toHaveLength(1)
+  })
+})
diff --git a/app/components/component-xpub-formbuilder/src/test/FormBuilderLayout.test.js b/app/components/component-xpub-formbuilder/src/test/FormBuilderLayout.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..c747bf1e9a69b78a055e5cd23d405a4c02d7d02f
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/test/FormBuilderLayout.test.js
@@ -0,0 +1,142 @@
+import React from 'react'
+import faker from 'faker'
+import Enzyme, { mount } from 'enzyme'
+import { MemoryRouter } from 'react-router-dom'
+import { MockedProvider } from '@apollo/react-testing'
+import Adapter from 'enzyme-adapter-react-16'
+import { ThemeProvider } from 'styled-components'
+import gql from 'graphql-tag'
+
+import FormProperties from '../components/FormProperties'
+import FormBuilderLayout from '../components/FormBuilderLayout'
+import FormBuilder from '../components/FormBuilder'
+import noforms from './config/noforms.json'
+import testforms from './config/test.json'
+
+// this should be elsewhere
+Enzyme.configure({ adapter: new Adapter() })
+
+jest.mock('config', () => ({
+  'pubsweet-client': {},
+  authsome: {
+    mode: 'authsome',
+    teams: {
+      seniorEditor: {
+        name: 'Senior Editors',
+        permissions: '',
+      },
+      handlingEditor: {
+        name: 'Handling Editors',
+        permissions: '',
+      },
+      managingEditor: {
+        name: 'Managing Editors',
+        permissions: '',
+      },
+      reviewer: {
+        name: 'Reviewer',
+        permissions: '',
+      },
+    },
+  },
+}))
+
+const query = gql`
+  query {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    getForms
+  }
+`
+
+const mocks = [
+  {
+    request: {
+      query,
+    },
+    result: {
+      data: {
+        currentUser: { id: faker.random.uuid(), username: 'test', admin: true },
+        getForms: noforms,
+      },
+    },
+  },
+]
+
+describe('FormBuilder Layout', () => {
+  const makeWrapper = (props = {}) =>
+    mount(
+      <MemoryRouter>
+        <ThemeProvider
+          theme={{
+            colorPrimary: 'blue',
+            colorSecondary: '#E7E7E7',
+          }}
+        >
+          <MockedProvider addTypename={false} mocks={mocks}>
+            <FormBuilderLayout {...props} />
+          </MockedProvider>
+        </ThemeProvider>
+      </MemoryRouter>,
+    )
+
+  it('shows just the create form tab', () => {
+    const formbuilder = makeWrapper({
+      properties: {
+        type: 'form',
+      },
+      activeTab: 'new',
+    })
+
+    expect(
+      formbuilder.find('[data-test-id="tab-container"]').children(),
+    ).toHaveLength(1)
+
+    expect(
+      formbuilder
+        .find(FormProperties)
+        .find('form')
+        .text(),
+    ).toContain('Create Form')
+
+    expect(
+      formbuilder
+        .find(FormProperties)
+        .at(1)
+        .find('form'),
+    ).toHaveLength(0)
+  })
+
+  it('shows three tabs and make the first active', () => {
+    const formbuilder = makeWrapper({
+      properties: {
+        type: 'form',
+        properties: testforms[0],
+      },
+      activeTab: 0,
+      getForms: testforms,
+    })
+
+    expect(
+      formbuilder.find('[data-test-id="tab-container"]').children(),
+    ).toHaveLength(3)
+
+    expect(
+      formbuilder
+        .find(FormBuilder)
+        .find('BuilderElement')
+        .children(),
+    ).toHaveLength(testforms[0].children.length)
+
+    expect(
+      formbuilder
+        .find(FormProperties)
+        .at(0)
+        .find('form'),
+    ).toHaveLength(1)
+  })
+})
diff --git a/app/components/component-xpub-formbuilder/src/test/FormBuilderPage.integration.test.js b/app/components/component-xpub-formbuilder/src/test/FormBuilderPage.integration.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..76719ffcccef5cb16d57075d760497ad18127f5d
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/test/FormBuilderPage.integration.test.js
@@ -0,0 +1,111 @@
+import React from 'react'
+import faker from 'faker'
+import { MemoryRouter } from 'react-router-dom'
+import { MockedProvider } from '@apollo/react-testing'
+import gql from 'graphql-tag'
+
+import Enzyme, { mount } from 'enzyme'
+import Adapter from 'enzyme-adapter-react-16'
+
+import { ThemeProvider } from 'styled-components'
+
+import FormBuilderPage from '../components/FormBuilderPage'
+
+import forms from './config/test.json'
+
+import FormProperties from '../components/FormProperties'
+
+// this should be elsewhere
+Enzyme.configure({ adapter: new Adapter() })
+
+jest.mock('config', () => {
+  const path = require('path')
+
+  return {
+    'pubsweet-client': {},
+    'pubsweet-component-xpub-formbuilder': {
+      path: path.resolve('../test/config'),
+    },
+    authsome: {
+      mode: 'authsome',
+      teams: {
+        seniorEditor: {
+          name: 'Senior Editors',
+          permissions: '',
+        },
+        handlingEditor: {
+          name: 'Handling Editors',
+          permissions: '',
+        },
+        managingEditor: {
+          name: 'Managing Editors',
+          permissions: '',
+        },
+        reviewer: {
+          name: 'Reviewer',
+          permissions: '',
+        },
+      },
+    },
+  }
+})
+
+jest.mock('pubsweet-client/src/helpers/Authorize', () => 'Authorize')
+
+global.window.localStorage = {
+  getItem: jest.fn(() => 'tok123'),
+}
+
+const query = gql`
+  query {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    getForms
+  }
+`
+
+const mocks = [
+  {
+    request: {
+      query,
+    },
+    result: {
+      data: {
+        currentUser: { id: faker.random.uuid(), username: 'test', admin: true },
+        getForms: forms,
+      },
+    },
+  },
+]
+
+describe('FormBuilderPage', () => {
+  it('runs', done => {
+    const page = mount(
+      <MemoryRouter>
+        <ThemeProvider
+          theme={{
+            colorPrimary: 'blue',
+            colorSecondary: '#E7E7E7',
+          }}
+        >
+          <MockedProvider addTypename={false} mocks={mocks}>
+            <FormBuilderPage />
+          </MockedProvider>
+        </ThemeProvider>
+      </MemoryRouter>,
+    )
+
+    setTimeout(() => {
+      page.update()
+      expect(page.find('#builder-element').children()).toHaveLength(
+        forms[0].children.length,
+      )
+      expect(page.find(FormProperties)).toHaveLength(1)
+      done()
+    }, 1000)
+  })
+})
diff --git a/app/components/component-xpub-formbuilder/src/test/config/noforms.json b/app/components/component-xpub-formbuilder/src/test/config/noforms.json
new file mode 100644
index 0000000000000000000000000000000000000000..fe51488c7066f6687ef680d6bfaa4f7768ef205c
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/test/config/noforms.json
@@ -0,0 +1 @@
+[]
diff --git a/app/components/component-xpub-formbuilder/src/test/config/test.json b/app/components/component-xpub-formbuilder/src/test/config/test.json
new file mode 100644
index 0000000000000000000000000000000000000000..824c3c087542e81ed3009e3b111fcbc2dfbb598a
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/test/config/test.json
@@ -0,0 +1,27 @@
+[
+  {
+    "name": "test form",
+    "id": "test",
+    "children": [
+      {
+        "title": "Title",
+        "id": "1531303631370",
+        "component": "AbstractEditor",
+        "name": "metadata.title",
+        "placeholder": "Enter Title...",
+        "validate": ["required"],
+        "validateValue": { "minChars": "10" }
+      },
+      {
+        "title": "Title 1",
+        "id": "1531303631371",
+        "component": "AbstractEditor",
+        "name": "metadata.title",
+        "placeholder": "Enter Title...",
+        "validate": ["required"],
+        "validateValue": { "minChars": "10" }
+      }
+    ]
+  },
+  { "name": "test form 1", "id": "test1" }
+]
diff --git a/app/components/component-xpub-formbuilder/src/test/config/testnoelements.json b/app/components/component-xpub-formbuilder/src/test/config/testnoelements.json
new file mode 100644
index 0000000000000000000000000000000000000000..af4bcbc4e9ed5dfa232103a8d7ca40ff9f30d516
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/test/config/testnoelements.json
@@ -0,0 +1 @@
+[{ "name": "test form", "id": "test" }]
diff --git a/app/components/component-xpub-formbuilder/src/test/util.js b/app/components/component-xpub-formbuilder/src/test/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..639fb1b87d6d02a27f561ca56a22fc7e183e8a7c
--- /dev/null
+++ b/app/components/component-xpub-formbuilder/src/test/util.js
@@ -0,0 +1,27 @@
+import { merge } from 'lodash'
+
+export const diveTo = (
+  shallowWrapper,
+  identifier,
+  options = { context: {} },
+) => {
+  const element = shallowWrapper.getElement()
+  if (!(element && element.type)) {
+    throw new Error(
+      `Failed to dive to ${identifier} - is it not in the component tree?`,
+    )
+  }
+  const instance = shallowWrapper.instance()
+
+  if (instance && instance.constructor.displayName === identifier) {
+    return shallowWrapper
+  }
+
+  const context = merge(
+    {},
+    instance && instance.getChildContext ? instance.getChildContext() : {},
+    options.context,
+  )
+
+  return diveTo(shallowWrapper.dive({ context }), identifier, { context })
+}
diff --git a/app/components/component-xpub-manuscript/CHANGELOG.md b/app/components/component-xpub-manuscript/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..024f1ac68fa57fe69a0e956f2a2dd1dba0f79eb4
--- /dev/null
+++ b/app/components/component-xpub-manuscript/CHANGELOG.md
@@ -0,0 +1,650 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [2.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@2.0.2...pubsweet-component-xpub-manuscript@2.1.0) (2019-11-11)
+
+
+### Features
+
+* **xpub:** bring back xpub components ([fb69994](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb69994098b4e2dbcca75b4786ebb1335af730b9))
+
+
+
+
+
+## [2.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@2.0.1...pubsweet-component-xpub-manuscript@2.0.2) (2019-09-11)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [2.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@2.0.0...pubsweet-component-xpub-manuscript@2.0.1) (2019-09-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+# [2.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.18...pubsweet-component-xpub-manuscript@2.0.0) (2019-08-30)
+
+
+### Code Refactoring
+
+* **apollo:** update react-dom version ([e001d01](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e001d01))
+
+
+### BREAKING CHANGES
+
+* **apollo:** The minimum supported React version by @apollo is now 16.8
+
+
+
+
+
+## [1.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.17...pubsweet-component-xpub-manuscript@1.0.18) (2019-08-08)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.16...pubsweet-component-xpub-manuscript@1.0.17) (2019-08-05)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.15...pubsweet-component-xpub-manuscript@1.0.16) (2019-07-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.14...pubsweet-component-xpub-manuscript@1.0.15) (2019-07-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.13...pubsweet-component-xpub-manuscript@1.0.14) (2019-07-03)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.12...pubsweet-component-xpub-manuscript@1.0.13) (2019-06-28)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.11...pubsweet-component-xpub-manuscript@1.0.12) (2019-06-24)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.10...pubsweet-component-xpub-manuscript@1.0.11) (2019-06-21)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.9...pubsweet-component-xpub-manuscript@1.0.10) (2019-06-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.8...pubsweet-component-xpub-manuscript@1.0.9) (2019-06-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.7...pubsweet-component-xpub-manuscript@1.0.8) (2019-05-27)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.6...pubsweet-component-xpub-manuscript@1.0.7) (2019-04-25)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.5...pubsweet-component-xpub-manuscript@1.0.6) (2019-04-18)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.4...pubsweet-component-xpub-manuscript@1.0.5) (2019-04-09)
+
+
+### Bug Fixes
+
+* **xpub:** fix components in styleguide ([2db87cd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2db87cd))
+
+
+
+
+
+## [1.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.3...pubsweet-component-xpub-manuscript@1.0.4) (2019-03-06)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.2...pubsweet-component-xpub-manuscript@1.0.3) (2019-03-05)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.1...pubsweet-component-xpub-manuscript@1.0.2) (2019-02-19)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@1.0.0...pubsweet-component-xpub-manuscript@1.0.1) (2019-02-19)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.8...pubsweet-component-xpub-manuscript@1.0.0) (2019-02-01)
+
+
+### Bug Fixes
+
+* **styleguide:** temporarily disable styleguide ([e519ed1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e519ed1))
+
+
+### Code Refactoring
+
+* temporarily remove unmigrated components ([32db6ad](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/32db6ad))
+
+
+### BREAKING CHANGES
+
+* A lot of unmigrated (not yet moved from REST/Redux to GraphQL/Apollo system) bits
+have changed. There might be some breaking changes as a result. This is a big migration involving
+big changes - if you encounter anything weird, please contact us on GitLab or on Mattermost.
+
+
+
+
+
+## [0.6.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.7...pubsweet-component-xpub-manuscript@0.6.8) (2019-01-16)
+
+
+### Bug Fixes
+
+* **graphql:** review components fixes ([8094d9e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8094d9e))
+* **manuscript:** wax did not show ([80ae8c6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/80ae8c6))
+
+
+
+
+
+## [0.6.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.6...pubsweet-component-xpub-manuscript@0.6.7) (2019-01-14)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [0.6.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.5...pubsweet-component-xpub-manuscript@0.6.6) (2019-01-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [0.6.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.4...pubsweet-component-xpub-manuscript@0.6.5) (2019-01-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [0.6.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.3...pubsweet-component-xpub-manuscript@0.6.4) (2018-12-12)
+
+
+### Bug Fixes
+
+* **xpub-manuscript:** ensure file is present before accessing properties ([15945d4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/15945d4))
+
+
+
+
+
+## [0.6.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.2...pubsweet-component-xpub-manuscript@0.6.3) (2018-12-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [0.6.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.1...pubsweet-component-xpub-manuscript@0.6.2) (2018-11-30)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+## [0.6.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.6.0...pubsweet-component-xpub-manuscript@0.6.1) (2018-11-29)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+
+
+
+
+<a name="0.6.0"></a>
+# [0.6.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.5.7...pubsweet-component-xpub-manuscript@0.6.0) (2018-11-05)
+
+
+### Features
+
+* GraphQL Xpub review component ([66b3e73](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/66b3e73))
+
+
+
+
+<a name="0.5.7"></a>
+## [0.5.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.5.6...pubsweet-component-xpub-manuscript@0.5.7) (2018-10-08)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.5.6"></a>
+## [0.5.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.5.5...pubsweet-component-xpub-manuscript@0.5.6) (2018-09-29)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.5.5"></a>
+## [0.5.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.5.4...pubsweet-component-xpub-manuscript@0.5.5) (2018-09-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.5.4"></a>
+## [0.5.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.5.3...pubsweet-component-xpub-manuscript@0.5.4) (2018-09-25)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.5.3"></a>
+## [0.5.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.5.2...pubsweet-component-xpub-manuscript@0.5.3) (2018-09-20)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.5.2"></a>
+## [0.5.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.5.1...pubsweet-component-xpub-manuscript@0.5.2) (2018-09-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.5.1"></a>
+## [0.5.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.5.0...pubsweet-component-xpub-manuscript@0.5.1) (2018-09-06)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.5.0"></a>
+# [0.5.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.4.5...pubsweet-component-xpub-manuscript@0.5.0) (2018-09-04)
+
+
+### Features
+
+* add wax-prose-mirror to xpub ([c397a97](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c397a97))
+
+
+
+
+<a name="0.4.5"></a>
+## [0.4.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.4.4...pubsweet-component-xpub-manuscript@0.4.5) (2018-08-23)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.4.4"></a>
+## [0.4.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.4.3...pubsweet-component-xpub-manuscript@0.4.4) (2018-08-22)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.4.3"></a>
+## [0.4.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.4.2...pubsweet-component-xpub-manuscript@0.4.3) (2018-08-20)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.4.2"></a>
+## [0.4.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.4.1...pubsweet-component-xpub-manuscript@0.4.2) (2018-08-17)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.4.1"></a>
+## [0.4.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.4.0...pubsweet-component-xpub-manuscript@0.4.1) (2018-08-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.4.0"></a>
+# [0.4.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.3.0...pubsweet-component-xpub-manuscript@0.4.0) (2018-07-27)
+
+
+### Features
+
+* add Attachments pubsweet comp for image upload ([a2dc8ca](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a2dc8ca))
+* remove Wax from manuscript page ([da1147b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/da1147b))
+
+
+
+
+<a name="0.3.0"></a>
+# [0.3.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.2.0...pubsweet-component-xpub-manuscript@0.3.0) (2018-07-19)
+
+
+### Bug Fixes
+
+* add  wax till features done ([6f9fa2c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6f9fa2c))
+* linting errors ([1759e9e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1759e9e))
+* wax ver 0.2.5 ([9dce588](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9dce588))
+
+
+### Features
+
+* add dependency ([6bc93e8](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6bc93e8))
+* add resize cursor ([15a17ca](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/15a17ca))
+* basic styling of the editor ([7cc9f59](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7cc9f59))
+* create new table command ([429d7ed](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/429d7ed))
+* some styling ([e4f7f44](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e4f7f44))
+* styles for table to work properly ([1defda3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1defda3))
+
+
+
+
+<a name="0.2.0"></a>
+# [0.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.1.0...pubsweet-component-xpub-manuscript@0.2.0) (2018-07-12)
+
+
+### Bug Fixes
+
+* change shortcut and label text ([882a490](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/882a490))
+* have wax back till all feautures are done ([615d77f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/615d77f))
+
+
+### Features
+
+* add bullet list ([f528fd0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f528fd0))
+* create main editor ([b0eeca3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b0eeca3))
+* join and lift lists ([505e306](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/505e306))
+* ordered lists in progress ([2ba933f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2ba933f))
+* replace wax with noteEditor/add basic,list schema as dependencies ([64ba50a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/64ba50a))
+
+
+
+
+<a name="0.1.0"></a>
+# [0.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.24...pubsweet-component-xpub-manuscript@0.1.0) (2018-07-09)
+
+
+### Features
+
+* update dependency versions ([51486f4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/51486f4))
+
+
+
+
+<a name="0.0.24"></a>
+## [0.0.24](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.23...pubsweet-component-xpub-manuscript@0.0.24) (2018-07-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.23"></a>
+## [0.0.23](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.22...pubsweet-component-xpub-manuscript@0.0.23) (2018-07-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.22"></a>
+## [0.0.22](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.21...pubsweet-component-xpub-manuscript@0.0.22) (2018-06-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.21"></a>
+## [0.0.21](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.20...pubsweet-component-xpub-manuscript@0.0.21) (2018-06-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.20"></a>
+## [0.0.20](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.19...pubsweet-component-xpub-manuscript@0.0.20) (2018-06-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.19"></a>
+## [0.0.19](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.18...pubsweet-component-xpub-manuscript@0.0.19) (2018-06-01)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.18"></a>
+## [0.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.17...pubsweet-component-xpub-manuscript@0.0.18) (2018-05-21)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.17"></a>
+## [0.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.16...pubsweet-component-xpub-manuscript@0.0.17) (2018-05-18)
+
+
+### Bug Fixes
+
+* use MIT on all package.json files ([4558ae4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4558ae4))
+
+
+
+
+<a name="0.0.16"></a>
+## [0.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.15...pubsweet-component-xpub-manuscript@0.0.16) (2018-05-10)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.15"></a>
+## [0.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.14...pubsweet-component-xpub-manuscript@0.0.15) (2018-05-09)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.14"></a>
+## [0.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.13...pubsweet-component-xpub-manuscript@0.0.14) (2018-05-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.13"></a>
+## [0.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.12...pubsweet-component-xpub-manuscript@0.0.13) (2018-04-25)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.12"></a>
+## [0.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.11...pubsweet-component-xpub-manuscript@0.0.12) (2018-04-24)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.11"></a>
+## [0.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.10...pubsweet-component-xpub-manuscript@0.0.11) (2018-04-11)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.10"></a>
+## [0.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.9...pubsweet-component-xpub-manuscript@0.0.10) (2018-04-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.9"></a>
+## [0.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.8...pubsweet-component-xpub-manuscript@0.0.9) (2018-03-30)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.8"></a>
+## [0.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.7...pubsweet-component-xpub-manuscript@0.0.8) (2018-03-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.7"></a>
+## [0.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.6...pubsweet-component-xpub-manuscript@0.0.7) (2018-03-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.6"></a>
+## [0.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.5...pubsweet-component-xpub-manuscript@0.0.6) (2018-03-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.5"></a>
+## [0.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.4...pubsweet-component-xpub-manuscript@0.0.5) (2018-03-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.4"></a>
+## [0.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-manuscript@0.0.3...pubsweet-component-xpub-manuscript@0.0.4) (2018-03-15)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
+
+<a name="0.0.3"></a>
+
+## 0.0.3 (2018-03-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-manuscript
diff --git a/app/components/component-xpub-manuscript/README.md b/app/components/component-xpub-manuscript/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b482b833a38e600c1769f7ee717d5259022ec22e
--- /dev/null
+++ b/app/components/component-xpub-manuscript/README.md
@@ -0,0 +1,3 @@
+## pubsweet-component-xpub-manuscript
+
+A PubSweet component that provides the interface for an author to edit their manuscript.
diff --git a/app/components/component-xpub-manuscript/package.json b/app/components/component-xpub-manuscript/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..14630b5513fe3f96f7988d5d651df7707128116b
--- /dev/null
+++ b/app/components/component-xpub-manuscript/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "pubsweet-component-xpub-manuscript",
+  "version": "2.1.0",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "main": "src",
+  "dependencies": {
+    "recompose": "^0.30.0",
+    "wax-prosemirror-core": "^0.0.2",
+    "wax-prosemirror-schema": "^0.0.2",
+    "wax-prosemirror-themes": "^0.0.2"
+  },
+  "peerDependencies": {
+    "@apollo/react-hoc": ">=3.0.1",
+    "apollo-client-preset": ">=1.0.8",
+    "pubsweet-client": ">=2.1.0",
+    "react": ">=16.9",
+    "react-router-dom": ">=5.0.0",
+    "styled-components": ">=4.1.3"
+  },
+  "gitHead": "6b100b76f21785e5e50fca082a2743d3d0b1c88a"
+}
diff --git a/app/components/component-xpub-manuscript/src/components/Manuscript.js b/app/components/component-xpub-manuscript/src/components/Manuscript.js
new file mode 100644
index 0000000000000000000000000000000000000000..83bd2a839b025fa8c7ecbcb0a0a9ce7ddd478b1f
--- /dev/null
+++ b/app/components/component-xpub-manuscript/src/components/Manuscript.js
@@ -0,0 +1,54 @@
+import React from 'react'
+import { withRouter } from 'react-router-dom'
+import styled from 'styled-components'
+import { Wax, CreateSchema } from 'wax-prosemirror-core'
+import { XpubSchema } from 'wax-prosemirror-schema'
+import 'wax-prosemirror-themes/themes/default-theme.css'
+
+const options = {
+  schema: new CreateSchema(XpubSchema),
+}
+
+const ManuScript = styled.div`
+  .wax-container {
+    top: 10%;
+    height: 90%;
+  }
+`
+
+const Info = styled.span`
+  padding: 0;
+  margin: 0;
+  list-style: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 500px;
+`
+
+const Manuscript = ({
+  file,
+  content,
+  currentUser,
+  // fileUpload,
+  history,
+  // updateManuscript,
+}) =>
+  file &&
+  file.mimeType ===
+    'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ? (
+    <ManuScript>
+      <Wax
+        key={1}
+        options={options}
+        theme="default"
+        // fileUpload={fileUpload}
+        // onChange={source => updateManuscript({ source })}
+        value={content}
+      />
+    </ManuScript>
+  ) : (
+    <Info>No supported view of the file</Info>
+  )
+
+export default withRouter(Manuscript)
diff --git a/app/components/component-xpub-manuscript/src/components/Manuscript.md b/app/components/component-xpub-manuscript/src/components/Manuscript.md
new file mode 100644
index 0000000000000000000000000000000000000000..3b13f77ee4e04e19927379706b4c06324e22d0f2
--- /dev/null
+++ b/app/components/component-xpub-manuscript/src/components/Manuscript.md
@@ -0,0 +1,20 @@
+An editor for a manuscript.
+
+```js
+const content = `
+<container id="main">
+<h1>This is a heading</h1>
+<p>This is a paragraph.</p>
+</container>`
+
+const currentUser = {
+  teams: [{ teamType: { name: 'Production Editor' } }],
+}
+;<Manuscript
+  content={content}
+  currentUser={currentUser}
+  fileUpload={data => console.log(data)}
+  updateManuscript={data => console.log(data)}
+  version={{}}
+/>
+```
diff --git a/app/components/component-xpub-manuscript/src/components/ManuscriptPage.js b/app/components/component-xpub-manuscript/src/components/ManuscriptPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..0dd249fba8f0fb8484742141474c9bb0cec8a39e
--- /dev/null
+++ b/app/components/component-xpub-manuscript/src/components/ManuscriptPage.js
@@ -0,0 +1,50 @@
+import { compose, withProps } from 'recompose'
+import { graphql } from '@apollo/react-hoc'
+import { gql } from 'apollo-client-preset'
+import { withLoader } from 'pubsweet-client'
+
+import Manuscript from './Manuscript'
+
+const fragmentFields = `
+  id
+  created
+  status
+  files {
+    id
+    fileType
+    mimeType
+  }
+  meta {
+    title
+    source
+  }
+`
+
+const query = gql`
+  query($id: ID!) {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    manuscript(id: $id) {
+      ${fragmentFields}
+    }
+  }
+`
+
+export default compose(
+  graphql(query, {
+    options: ({ match }) => ({
+      variables: {
+        id: match.params.version,
+      },
+    }),
+  }),
+  withLoader(),
+  withProps(({ manuscript }) => ({
+    content: manuscript.meta.source,
+    file: manuscript.files.find(file => file.fileType === 'manuscript') || {},
+  })),
+)(Manuscript)
diff --git a/app/components/component-xpub-manuscript/src/components/index.js b/app/components/component-xpub-manuscript/src/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2e5b2d910d00c3a5fa12f0d7ebaf5363ea8d28f
--- /dev/null
+++ b/app/components/component-xpub-manuscript/src/components/index.js
@@ -0,0 +1 @@
+export { default as ManuscriptPage } from './ManuscriptPage'
diff --git a/app/components/component-xpub-manuscript/src/index.js b/app/components/component-xpub-manuscript/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f1f42a64b1ecf4a71345e76fa416d89015cb96c
--- /dev/null
+++ b/app/components/component-xpub-manuscript/src/index.js
@@ -0,0 +1,5 @@
+module.exports = {
+  frontend: {
+    components: [() => require('./components')],
+  },
+}
diff --git a/app/components/component-xpub-review/CHANGELOG.md b/app/components/component-xpub-review/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..17abd9aa6245b1bbebebd8bf292cc2312e1c131f
--- /dev/null
+++ b/app/components/component-xpub-review/CHANGELOG.md
@@ -0,0 +1,626 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [4.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@4.0.7...pubsweet-component-xpub-review@4.1.0) (2019-11-11)
+
+
+### Features
+
+* **xpub:** bring back xpub components ([fb69994](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb69994098b4e2dbcca75b4786ebb1335af730b9))
+
+
+
+
+
+## [4.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@4.0.6...pubsweet-component-xpub-review@4.0.7) (2019-01-16)
+
+
+### Bug Fixes
+
+* **components:** change back teams model to previous model ([a5eeae0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a5eeae0))
+* **components:** fixing components after new manuscript version ([89537ff](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/89537ff))
+* **components:** graphql data model changes ([4b61093](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4b61093))
+* **formik:** improve formik usage ([24b42ff](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/24b42ff))
+* **graphql:** review components fixes ([8094d9e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8094d9e))
+* **manuscript:** wax did not show ([80ae8c6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/80ae8c6))
+* **merge:** merging to master ([8603808](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8603808))
+* **test:** problems with eslint and test ([48f7fe2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/48f7fe2))
+* **xpub-review:** changes tp reviews ([5ae4240](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5ae4240))
+
+
+
+
+
+## [4.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@4.0.5...pubsweet-component-xpub-review@4.0.6) (2019-01-14)
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+
+
+
+
+## [4.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@4.0.4...pubsweet-component-xpub-review@4.0.5) (2019-01-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+
+
+
+
+## [4.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@4.0.3...pubsweet-component-xpub-review@4.0.4) (2019-01-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+
+
+
+
+## [4.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@4.0.2...pubsweet-component-xpub-review@4.0.3) (2018-12-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+
+
+
+
+## [4.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@4.0.1...pubsweet-component-xpub-review@4.0.2) (2018-12-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+
+
+
+
+## [4.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@4.0.0...pubsweet-component-xpub-review@4.0.1) (2018-11-30)
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+
+
+
+
+# [4.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.2.0...pubsweet-component-xpub-review@4.0.0) (2018-11-29)
+
+
+### Features
+
+* **various:** update styled-components ([5c51466](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5c51466))
+* **various:** upgrade styled-components ([9b886f6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9b886f6))
+
+
+### BREAKING CHANGES
+
+* **various:** Replace all styled-components .extend with styled()
+* **various:** Replace styled-components injectGlobal with new createGlobalStyle
+
+
+
+
+
+<a name="3.2.0"></a>
+# [3.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.13...pubsweet-component-xpub-review@3.2.0) (2018-11-05)
+
+
+### Features
+
+* GraphQL Xpub review component ([66b3e73](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/66b3e73))
+
+
+
+
+<a name="3.1.13"></a>
+## [3.1.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.12...pubsweet-component-xpub-review@3.1.13) (2018-10-08)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.12"></a>
+## [3.1.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.11...pubsweet-component-xpub-review@3.1.12) (2018-09-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.11"></a>
+## [3.1.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.10...pubsweet-component-xpub-review@3.1.11) (2018-09-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.10"></a>
+## [3.1.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.9...pubsweet-component-xpub-review@3.1.10) (2018-09-06)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.9"></a>
+## [3.1.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.8...pubsweet-component-xpub-review@3.1.9) (2018-09-04)
+
+
+### Bug Fixes
+
+* **editor:** replace wax with wax-prosemirror ([d7b12f4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d7b12f4))
+
+
+
+
+<a name="3.1.8"></a>
+## [3.1.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.7...pubsweet-component-xpub-review@3.1.8) (2018-08-22)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.7"></a>
+## [3.1.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.6...pubsweet-component-xpub-review@3.1.7) (2018-08-20)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.6"></a>
+## [3.1.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.5...pubsweet-component-xpub-review@3.1.6) (2018-08-17)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.5"></a>
+## [3.1.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.4...pubsweet-component-xpub-review@3.1.5) (2018-08-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.4"></a>
+## [3.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.3...pubsweet-component-xpub-review@3.1.4) (2018-07-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.3"></a>
+## [3.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.2...pubsweet-component-xpub-review@3.1.3) (2018-07-23)
+
+
+### Bug Fixes
+
+* **review:** change attachment files ([bdfa18d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/bdfa18d))
+* **review:** change user assign editor ([eadca2b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/eadca2b))
+
+
+
+
+<a name="3.1.2"></a>
+## [3.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.1...pubsweet-component-xpub-review@3.1.2) (2018-07-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.1"></a>
+## [3.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.1.0...pubsweet-component-xpub-review@3.1.1) (2018-07-12)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.1.0"></a>
+# [3.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.0.1...pubsweet-component-xpub-review@3.1.0) (2018-07-09)
+
+
+### Features
+
+* correct version number ([56b467a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/56b467a))
+* update dependency versions ([51486f4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/51486f4))
+
+
+
+
+<a name="3.0.1"></a>
+## [3.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@3.0.0...pubsweet-component-xpub-review@3.0.1) (2018-07-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="3.0.0"></a>
+# [3.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@2.0.1...pubsweet-component-xpub-review@3.0.0) (2018-07-02)
+
+
+### Features
+
+* **ui:** introduce more line height variables ([85c24e2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/85c24e2))
+
+
+### BREAKING CHANGES
+
+* **ui:** the existing fontLineHeight variable is gone and replaced by multiple new variables
+
+
+
+
+<a name="2.0.1"></a>
+## [2.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@2.0.0...pubsweet-component-xpub-review@2.0.1) (2018-06-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="2.0.0"></a>
+# [2.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@1.0.1...pubsweet-component-xpub-review@2.0.0) (2018-06-28)
+
+
+### Bug Fixes
+
+* **monorepo:** fix versions of ui across repo ([72ada07](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/72ada07))
+* **styleguide:** correct require path ([79a2b07](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/79a2b07))
+
+
+### Code Refactoring
+
+* **ui:** replace current gridunit variables with one small value ([cf48f29](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/cf48f29))
+
+
+### BREAKING CHANGES
+
+* **ui:** Your ui components will now be multiplying a much smaller value so they need to be
+adjusted
+
+
+
+
+<a name="1.0.1"></a>
+## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@1.0.0...pubsweet-component-xpub-review@1.0.1) (2018-06-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="1.0.0"></a>
+# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.4.2...pubsweet-component-xpub-review@1.0.0) (2018-06-01)
+
+
+### Features
+
+* **ui:** start ui-toolkit module ([2083b9c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2083b9c))
+
+
+### BREAKING CHANGES
+
+* **ui:** th now comes from the toolkit, so all th imports from ui are now broken
+
+
+
+
+<a name="0.4.2"></a>
+## [0.4.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.4.1...pubsweet-component-xpub-review@0.4.2) (2018-05-21)
+
+
+### Bug Fixes
+
+* **components:** use Tabs from pubsweet ui ([8e9fd3c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8e9fd3c))
+
+
+
+
+<a name="0.4.1"></a>
+## [0.4.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.4.0...pubsweet-component-xpub-review@0.4.1) (2018-05-18)
+
+
+### Bug Fixes
+
+* **components:** authors assigning problem ([50baa94](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/50baa94))
+
+
+
+
+<a name="0.4.0"></a>
+# [0.4.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.3.2...pubsweet-component-xpub-review@0.4.0) (2018-05-10)
+
+
+### Bug Fixes
+
+* **components:** decision linter ([5679ce0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5679ce0))
+* **components:** html parse, styled components ([8b24552](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8b24552))
+* **components:** lint issues ([ff56878](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ff56878))
+* **components:** redux form ([2f7f1ed](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2f7f1ed))
+* **components:** review page layout ([4ea2cdd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4ea2cdd))
+
+
+### Features
+
+* **components:** add tabs to submission ([0e45892](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0e45892))
+
+
+
+
+<a name="0.3.2"></a>
+## [0.3.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.3.1...pubsweet-component-xpub-review@0.3.2) (2018-05-09)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="0.3.1"></a>
+## [0.3.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.3.0...pubsweet-component-xpub-review@0.3.1) (2018-05-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="0.3.0"></a>
+# [0.3.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.2.0...pubsweet-component-xpub-review@0.3.0) (2018-05-03)
+
+
+### Bug Fixes
+
+* **components:** align columns cp page ([a5968b0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a5968b0))
+* **components:** align dropdown horizontally ([a7081e3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a7081e3))
+* **components:** change position and direction of assign ([7a7eeb3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7a7eeb3))
+* **components:** change supplymentary to attachment component ([143c452](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/143c452))
+* **components:** fix lint errors ([be173db](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/be173db))
+* **components:** fix lint errors ([c2b8e52](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c2b8e52))
+* **components:** fix lint errors ([98046fb](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/98046fb))
+* **components:** fix lint errors ([2503ff9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2503ff9))
+* **components:** fix unsued lint error ([20c282c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/20c282c))
+* **components:** load all users to control panel ([90c88e6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/90c88e6))
+* **components:** load all users to control panel ([92dac6b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/92dac6b))
+* **components:** load all users to control panel ([f20e44d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f20e44d))
+* **components:** load all users to control panel ([85fa14f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/85fa14f))
+* **components:** loading data in the decision form ([8f499aa](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8f499aa))
+* **components:** take care of case of zero files ([b70f728](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b70f728))
+
+
+### Features
+
+* **components:** add assign editors to cp ([3cca44d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3cca44d))
+* **components:** add assign editors to cp ([987310d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/987310d))
+
+
+
+
+<a name="0.2.0"></a>
+# [0.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.1.4...pubsweet-component-xpub-review@0.2.0) (2018-04-24)
+
+
+### Bug Fixes
+
+* **compoenents:** fix cases of empty objects in metadata ([7a5bfbc](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7a5bfbc))
+* **components:** change placeholder ([d80a41a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d80a41a))
+* **components:** change text input fields ([775a961](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/775a961))
+* **components:** change value to files at upload components ([aa2b45e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/aa2b45e))
+* **components:** fix file name problem ([73f33c9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/73f33c9))
+* **components:** passport through route ([fdf9dce](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fdf9dce))
+* **components:** passport through route ([593eeda](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/593eeda))
+* **components:** remove tables - add list of metadata ([b9d57cd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b9d57cd))
+
+
+### Features
+
+* **component:** add make invitation request ([659eb64](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/659eb64))
+* **component:** add make invitation request ([44f1574](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/44f1574))
+* **component:** add make invitation request ([7245d35](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7245d35))
+* **component:** add make invitation request ([bda7d95](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/bda7d95))
+* **component:** add make invitation request ([b049aa3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b049aa3))
+* **component:** add make invitation request ([36faf21](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/36faf21))
+* **component:** add make invitation request ([5a9b393](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5a9b393))
+* **component:** add make invitation request ([ff3f8fb](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ff3f8fb))
+* **component:** add make invitation request ([335d0f0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/335d0f0))
+* **component:** add make invitation request ([d070bb1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d070bb1))
+* **component:** add make invitation request ([f9cae33](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f9cae33))
+* **component:** add make invitation request ([947c846](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/947c846))
+* **component:** add make invitation request ([38e5728](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/38e5728))
+* **component:** add make invitation request ([430e9e2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/430e9e2))
+* **component:** add make invitation request ([30193b3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/30193b3))
+* **component:** add make invitation request ([721bbaf](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/721bbaf))
+* **component:** add make invitation request ([c4317bb](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c4317bb))
+* **component:** add make invitation request ([ae785b9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ae785b9))
+* **component:** add make invitation request ([9d0ad57](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9d0ad57))
+* **component:** add make invitation request ([1412d87](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1412d87))
+* **component:** add make invitation request ([1911bab](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1911bab))
+* **component:** add make invitation request ([6f27a3e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6f27a3e))
+* **component:** add make invitation request ([3817c8a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3817c8a))
+* **component:** add make invitation request ([61a1f0b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/61a1f0b))
+* **component:** add make invitation request ([4820e45](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4820e45))
+* **component:** add make invitation request ([27e2984](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/27e2984))
+* **component:** add make invitation request ([9e00e11](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9e00e11))
+* **component:** add make invitation request ([3d13943](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3d13943))
+* **component:** add make invitation request ([9d731d3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9d731d3))
+* **component:** add make invitation request ([251381c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/251381c))
+* **component:** add make invitation request ([dcd1f46](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/dcd1f46))
+* **component:** add make invitation request ([20628a4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/20628a4))
+* **components:** add API endpoint invitation ([4739b84](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4739b84))
+* **components:** add API endpoint invitation ([9276ef9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9276ef9))
+* **components:** add API endpoint invitation ([24810b8](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/24810b8))
+* **components:** add API endpoint invitation ([16bf6a1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/16bf6a1))
+* **components:** add API endpoint invitation ([d19bf84](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d19bf84))
+* **components:** add API endpoint invitation ([ee82d6b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ee82d6b))
+* **components:** add API endpoint invitation ([17d9532](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/17d9532))
+* **components:** add API endpoint invitation ([009b693](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/009b693))
+* **components:** add API endpoint invitation ([e50383f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e50383f))
+* **components:** add API endpoint invitation ([19932d7](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/19932d7))
+* **components:** add API endpoint invitation ([fc40c17](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fc40c17))
+* **components:** add API endpoint invitation ([3c5cbea](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3c5cbea))
+* **components:** add API endpoint invitation ([d21afe6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d21afe6))
+* **components:** add API endpoint invitation ([d06ae4b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d06ae4b))
+* **components:** add API endpoint invitation ([2703f3e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2703f3e))
+* **components:** add API endpoint invitation ([d6bb84a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d6bb84a))
+* **components:** add API endpoint invitation ([24ee6ca](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/24ee6ca))
+* **components:** add API endpoint invitation ([f963b76](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f963b76))
+* **components:** add API endpoint invitation ([48e8e12](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/48e8e12))
+* **components:** add API endpoint invitation ([ae7d9aa](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ae7d9aa))
+* **components:** add API endpoint invitation ([c6d0d94](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c6d0d94))
+* **components:** add API endpoint invitation ([db805c4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/db805c4))
+* **components:** add API endpoint invitation ([d897187](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d897187))
+* **components:** add API endpoint invitation ([daff65a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/daff65a))
+* **components:** add API endpoint invitation ([9e9b4ce](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9e9b4ce))
+* **components:** add API endpoint invitation ([0ca4cfa](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0ca4cfa))
+* **components:** add API endpoint invitation ([8de6954](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8de6954))
+* **components:** add API endpoint invitation ([abdf121](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/abdf121))
+* **components:** add API endpoint invitation ([a781136](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a781136))
+* **components:** add API endpoint invitation ([10fb6e2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/10fb6e2))
+* **components:** add API endpoint invitation ([3fcc322](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3fcc322))
+* **components:** add API endpoint invitation ([98d568e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/98d568e))
+* **components:** create invite reviewer endpoints ([fc6cad4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fc6cad4))
+* **components:** fix correct update of reviewer ([6a423e4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6a423e4))
+* **components:** fix correct update of reviewer ([4a26556](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4a26556))
+* **components:** fix correct update of reviewer ([4c9a7d9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4c9a7d9))
+* **components:** fix correct update of reviewer ([5988b36](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5988b36))
+* **components:** fix correct update of reviewer ([b937db4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b937db4))
+* **components:** fix correct update of reviewer ([48a70a8](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/48a70a8))
+* **components:** fix correct update of reviewer ([b22a250](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b22a250))
+* **components:** fix correct update of reviewer ([c030cce](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c030cce))
+* **components:** fix correct update of reviewer ([b4ac0ec](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b4ac0ec))
+* **components:** fix correct update of reviewer ([d7553ef](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d7553ef))
+* **components:** fix correct update of reviewer ([a30f25e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a30f25e))
+* **components:** fix correct update of reviewer ([c1b734e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c1b734e))
+* **components:** fix correct update of reviewer ([ec31850](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ec31850))
+* **components:** fix correct update of reviewer ([fe67780](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fe67780))
+* **components:** fix correct update of reviewer ([acd3a2d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/acd3a2d))
+* **components:** fix correct update of reviewer ([dcd2f94](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/dcd2f94))
+* **components:** fix correct update of reviewer ([ee775be](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ee775be))
+* **components:** fix correct update of reviewer ([bc8ef13](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/bc8ef13))
+* **components:** fix correct update of reviewer ([45b7375](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/45b7375))
+* **components:** fix correct update of reviewer ([1b449ab](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1b449ab))
+* **components:** fix correct update of reviewer ([be3ce7b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/be3ce7b))
+* **components:** fix correct update of reviewer ([b700d30](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b700d30))
+* **components:** fix correct update of reviewer ([30559e0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/30559e0))
+* **components:** fix correct update of reviewer ([358a8f6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/358a8f6))
+* **components:** fix correct update of reviewer ([3d2d412](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3d2d412))
+* **components:** fix correct update of reviewer ([d5de1d0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d5de1d0))
+* **components:** fix correct update of reviewer ([fb692e9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb692e9))
+* **components:** fix correct update of reviewer ([11c44ae](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/11c44ae))
+* **components:** fix correct update of reviewer ([46fdc54](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/46fdc54))
+* **components:** fix correct update of reviewer ([b42550f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b42550f))
+* **components:** fix correct update of reviewer ([2476402](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2476402))
+* **components:** fix correct update of reviewer ([895bc73](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/895bc73))
+* **components:** invitation email ([da5d78d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/da5d78d))
+* **components:** invitation email ([b479847](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b479847))
+* **components:** invitation email ([f6d93cf](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f6d93cf))
+* **components:** invitation email ([6ee4a13](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6ee4a13))
+* **components:** invitation email ([2c3e61d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2c3e61d))
+* **components:** invitation email ([b27816d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b27816d))
+* **components:** invitation email ([af6f384](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/af6f384))
+* **components:** invitation email ([1ca6d60](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1ca6d60))
+* **components:** invitation email ([e55a10a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e55a10a))
+* **components:** invitation email ([de5f871](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/de5f871))
+* **components:** invitation email ([087d151](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/087d151))
+* **components:** invitation email ([43a53a9](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/43a53a9))
+* **components:** invitation email ([74720ee](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/74720ee))
+* **components:** invitation email ([f252b17](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f252b17))
+* **components:** invitation email ([ae51c4c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ae51c4c))
+* **components:** invitation email ([1e4bc4f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1e4bc4f))
+* **components:** invitation email ([f557112](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f557112))
+* **components:** invitation email ([34d3054](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/34d3054))
+* **components:** invitation email ([d0d30af](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d0d30af))
+* **components:** invitation email ([77a0ae4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/77a0ae4))
+* **components:** invitation email ([75d3ac1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/75d3ac1))
+* **components:** invitation email ([3aab5d1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3aab5d1))
+* **components:** invitation email ([f8bea7f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f8bea7f))
+* **components:** invitation email ([c2ef155](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c2ef155))
+* **components:** invitation email ([fbac60d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fbac60d))
+* **components:** invitation email ([8afcd9c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8afcd9c))
+* **components:** invitation email ([860f233](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/860f233))
+* **components:** invitation email ([5d4d10e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5d4d10e))
+* **components:** invitation email ([b8c9d1d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b8c9d1d))
+* **components:** invitation email ([3d6b89b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3d6b89b))
+* **components:** invitation email ([0da74f6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0da74f6))
+* **components:** invitation email ([c2ebb60](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c2ebb60))
+
+
+
+
+<a name="0.1.4"></a>
+## [0.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.1.3...pubsweet-component-xpub-review@0.1.4) (2018-04-11)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="0.1.3"></a>
+## [0.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.1.2...pubsweet-component-xpub-review@0.1.3) (2018-04-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="0.1.2"></a>
+## [0.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.1.1...pubsweet-component-xpub-review@0.1.2) (2018-03-30)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="0.1.1"></a>
+## [0.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.1.0...pubsweet-component-xpub-review@0.1.1) (2018-03-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="0.1.0"></a>
+# [0.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.0.5...pubsweet-component-xpub-review@0.1.0) (2018-03-27)
+
+
+### Bug Fixes
+
+* **components:** use version.id as key ([0ca2f56](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0ca2f56))
+
+
+### Features
+
+* **components:** add Link from review page back to control panel ([860b737](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/860b737))
+* **styleguide:** page per section ([0bf0836](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0bf0836))
+
+
+
+
+<a name="0.0.5"></a>
+## [0.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.0.4...pubsweet-component-xpub-review@0.0.5) (2018-03-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="0.0.4"></a>
+## [0.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-review@0.0.3...pubsweet-component-xpub-review@0.0.4) (2018-03-15)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-review
+
+<a name="0.0.3"></a>
+
+## 0.0.3 (2018-03-09)
+
+### Bug Fixes
+
+* **xpub:** dubiously ignore linting errors ([a60d0ad](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a60d0ad))
diff --git a/app/components/component-xpub-review/README.md b/app/components/component-xpub-review/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..816d2d31210b61c9d5113bc99ca19448ffec8abe
--- /dev/null
+++ b/app/components/component-xpub-review/README.md
@@ -0,0 +1,6 @@
+## pubsweet-component-xpub-review
+
+A PubSweet component that provides interfaces for editors to invite reviewers, reviewers to submit their reviews, and editors to make their decisions.
+
+_Note:  
+Some of the container components for these pages are a bit complex, as they need to also connect child components that are nested deeper in the tree and pass them down as props - there may be a better solution to this._
diff --git a/app/components/component-xpub-review/package.json b/app/components/component-xpub-review/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..aa4500d9b34d090e18b8b0e7090957edd1b42ba0
--- /dev/null
+++ b/app/components/component-xpub-review/package.json
@@ -0,0 +1,38 @@
+{
+  "name": "pubsweet-component-xpub-review",
+  "version": "4.1.0",
+  "main": "src",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "dependencies": {
+    "@apollo/react-hoc": "^3.1.3",
+    "@pubsweet/ui": "^12.1.0",
+    "@pubsweet/ui-toolkit": "^2.2.14",
+    "formik": "^1.4.2",
+    "lodash": "^4.17.4",
+    "moment": "^2.18.1",
+    "prop-types": "^15.5.10",
+    "react-select": "^1.0.0-rc.10",
+    "recompose": "^0.30.0",
+    "styled-components": "^4.1.1",
+    "wax-prosemirror-core": "^0.0.2",
+    "wax-prosemirror-schema": "^0.0.2",
+    "wax-prosemirror-themes": "^0.0.2",
+    "xpub-edit": "^2.6.0",
+    "xpub-journal": "^0.1.0",
+    "xpub-validators": "^0.0.6"
+  },
+  "peerDependencies": {
+    "apollo-client-preset": ">=1.0.8",
+    "config": ">=3.0.1",
+    "formik": ">=1.4.2",
+    "pubsweet-client": ">=2.1.0",
+    "react": ">=16",
+    "react-redux": ">=5.0.2",
+    "react-router-dom": ">=5.0.0"
+  }
+}
diff --git a/app/components/component-xpub-review/src/components/DecisionPage.js b/app/components/component-xpub-review/src/components/DecisionPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..7310071acba306aaaade01a3e441260dceb8d4cf
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/DecisionPage.js
@@ -0,0 +1,325 @@
+import { compose, withProps } from 'recompose'
+import { graphql } from '@apollo/react-hoc'
+import { gql } from 'apollo-client-preset'
+import { withFormik } from 'formik'
+import { withLoader } from 'pubsweet-client'
+import { getCommentContent } from './review/util'
+
+import DecisionLayout from './decision/DecisionLayout'
+
+const reviewFields = `
+  id
+  created
+  updated
+  comments {
+    type
+    content
+    files {
+      id
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
+    }
+  }
+  isDecision
+  recommendation
+  user {
+    id
+    username
+  }
+`
+
+const fragmentFields = `
+  id
+  created
+  files {
+    id
+    created
+    label
+    filename
+    fileType
+    mimeType
+    size
+    url
+  }
+  reviews {
+    ${reviewFields}
+  }
+  decision
+  teams {
+    id
+    name
+    role
+    object {
+      objectId
+      objectType
+    }
+    members {
+      id
+      user {
+        id
+        username
+      }
+      status
+    }
+  }
+  status
+  meta {
+    title
+    source
+    abstract
+    declarations {
+      openData
+      openPeerReview
+      preregistered
+      previouslySubmitted
+      researchNexus
+      streamlinedReview
+    }
+    articleSections
+    articleType
+    history {
+      type
+      date
+    }
+    notes {
+      notesType
+      content
+    }
+    keywords
+  }
+  suggestions {
+    reviewers {
+      opposed
+      suggested
+    }
+    editors {
+      opposed
+      suggested
+    }
+  }
+`
+
+const query = gql`
+  query($id: ID!) {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    manuscript(id: $id) {
+      ${fragmentFields}
+      manuscriptVersions {
+        ${fragmentFields}
+      }
+    }
+  }
+`
+
+const updateReviewMutation = gql`
+  mutation($id: ID, $input: ReviewInput) {
+    updateReview(id: $id, input: $input) {
+      ${reviewFields}
+    }
+  }
+`
+
+const uploadReviewFilesMutation = gql`
+  mutation($file: Upload!) {
+    upload(file: $file) {
+      url
+    }
+  }
+`
+
+const createFileMutation = gql`
+  mutation($file: Upload!) {
+    createFile(file: $file) {
+      id
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
+    }
+  }
+`
+
+const submitMutation = gql`
+  mutation($id: ID!, $input: String) {
+    submitManuscript(id: $id, input: $input) {
+      id
+      ${fragmentFields}
+    }
+  }
+`
+
+export default compose(
+  graphql(query, {
+    options: ({ match }) => ({
+      variables: {
+        id: match.params.version,
+      },
+    }),
+  }),
+  graphql(uploadReviewFilesMutation, { name: 'uploadReviewFilesMutation' }),
+  graphql(updateReviewMutation, { name: 'updateReviewMutation' }),
+  graphql(createFileMutation, {
+    props: ({ mutate, ownProps: { match } }) => ({
+      createFile: file => {
+        mutate({
+          variables: {
+            file,
+          },
+          update: (proxy, { data: { createFile } }) => {
+            const data = proxy.readQuery({
+              query,
+              variables: {
+                id: match.params.version,
+              },
+            })
+
+            data.manuscript.reviews.map(review => {
+              if (review.id === file.objectId) {
+                review.comments.map(comment => {
+                  if (comment.type === createFile.fileType) {
+                    comment.files = [createFile]
+                  }
+                  return comment
+                })
+              }
+              return review
+            })
+
+            proxy.writeQuery({ query, data })
+          },
+        })
+      },
+    }),
+  }),
+  graphql(submitMutation, {
+    props: ({ mutate, ownProps }) => ({
+      completeDecision: ({ history, manuscript }) => {
+        mutate({
+          variables: {
+            id: manuscript.id,
+            input: JSON.stringify({
+              decision: manuscript.reviews.find(review => review.isDecision)
+                .recommendation,
+            }),
+          },
+        }).then(() => {
+          history.push('/')
+        })
+      },
+    }),
+  }),
+  withLoader(),
+  withProps(
+    ({
+      currentUser,
+      manuscript,
+      createFile,
+      updateReviewMutation,
+      uploadReviewFilesMutation,
+      match: {
+        params: { journal },
+      },
+    }) => ({
+      journal: { id: journal },
+      updateReview: (data, file) => {
+        const reviewData = {
+          isDecision: true,
+          manuscriptId: manuscript.id,
+        }
+
+        if (data.comment) {
+          reviewData.comments = [data.comment]
+        }
+
+        if (data.recommendation) {
+          reviewData.recommendation = data.recommendation
+        }
+
+        const review =
+          manuscript.reviews.find(review => review.isDecision) || {}
+        return updateReviewMutation({
+          variables: {
+            id: review.id || undefined,
+            input: reviewData,
+          },
+          update: (proxy, { data: { updateReview } }) => {
+            const data = proxy.readQuery({
+              query,
+              variables: {
+                id: manuscript.id,
+              },
+            })
+            const reviewIndex = data.manuscript.reviews.findIndex(
+              review => review.id === updateReview.id,
+            )
+            if (reviewIndex < 0) {
+              data.manuscript.reviews.push(updateReview)
+            } else {
+              data.manuscript.reviews[reviewIndex] = updateReview
+            }
+            proxy.writeQuery({ query, data })
+          },
+        })
+      },
+      uploadFile: (file, updateReview, type) =>
+        uploadReviewFilesMutation({
+          variables: {
+            file,
+          },
+        }).then(({ data }) => {
+          const newFile = {
+            url: data.upload.url,
+            filename: file.name,
+            size: file.size,
+            object: 'Review',
+            objectId: updateReview.id,
+            fileType: type,
+          }
+          createFile(newFile)
+        }),
+    }),
+  ),
+  withFormik({
+    mapPropsToValues: props =>
+      props.manuscript.reviews.find(review => review.isDecision) || {
+        comments: [],
+        recommendation: null,
+      },
+    isInitialValid: ({ manuscript }) => {
+      const rv = manuscript.reviews.find(review => review.isDecision) || {}
+      const isRecommendation = rv.recommendation != null
+      const isCommented = getCommentContent(rv, 'note') !== ''
+
+      return isCommented && isRecommendation
+    },
+    validate: (values, props) => {
+      const errors = {}
+      if (getCommentContent(values, 'note') === '') {
+        errors.comments = 'Required'
+      }
+
+      if (values.recommendation === null) {
+        errors.recommendation = 'Required'
+      }
+      return errors
+    },
+    displayName: 'decision',
+    handleSubmit: (
+      props,
+      { props: { completeDecision, history, manuscript } },
+    ) => completeDecision({ history, manuscript }),
+  }),
+)(DecisionLayout)
diff --git a/app/components/component-xpub-review/src/components/ReviewPage.js b/app/components/component-xpub-review/src/components/ReviewPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..65493408ca2e43eec720c54042de4e90275d1630
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/ReviewPage.js
@@ -0,0 +1,345 @@
+import { compose, withProps } from 'recompose'
+import { graphql } from '@apollo/react-hoc'
+import { gql } from 'apollo-client-preset'
+import { withFormik } from 'formik'
+import { withLoader } from 'pubsweet-client'
+import { cloneDeep } from 'lodash'
+import { getCommentContent } from './review/util'
+import ReviewLayout from '../components/review/ReviewLayout'
+
+const reviewFields = `
+  id
+  created
+  updated
+  comments {
+    type
+    content
+    files {
+      id
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
+    }
+  }
+  isDecision
+  recommendation
+  user {
+    id
+    username
+  }
+`
+
+const teamFields = `
+  id
+  name
+  role
+  object {
+    objectId
+    objectType
+  }
+  members {
+    id
+    user {
+      id
+      username
+    }
+  }
+`
+
+const fragmentFields = `
+  id
+  created
+  files {
+    id
+    created
+    label
+    filename
+    fileType
+    mimeType
+    size
+    url
+  }
+  reviews {
+    ${reviewFields}
+  }
+  decision
+  teams {
+    id
+    name
+    role
+    object {
+      objectId
+      objectType
+    }
+    members {
+      id
+      user {
+        id
+        username
+      }
+      status
+    }
+  }
+  status
+  meta {
+    title
+    source
+    abstract
+    declarations {
+      openData
+      openPeerReview
+      preregistered
+      previouslySubmitted
+      researchNexus
+      streamlinedReview
+    }
+    articleSections
+    articleType
+    history {
+      type
+      date
+    }
+    notes {
+      notesType
+      content
+    }
+    keywords
+  }
+  suggestions {
+    reviewers {
+      opposed
+      suggested
+    }
+    editors {
+      opposed
+      suggested
+    }
+  }
+`
+
+const query = gql`
+  query($id: ID!) {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    manuscript(id: $id) {
+      ${fragmentFields}
+      manuscriptVersions {
+        ${fragmentFields}
+      }
+    }
+  }
+`
+
+const updateTeam = gql`
+  mutation($id: ID!, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
+      ${teamFields}
+    }
+  }
+`
+
+const updateReviewMutation = gql`
+  mutation($id: ID, $input: ReviewInput) {
+    updateReview(id: $id, input: $input) {
+      ${reviewFields}
+    }
+  }
+`
+
+const uploadReviewFilesMutation = gql`
+  mutation($file: Upload!) {
+    upload(file: $file) {
+      url
+    }
+  }
+`
+
+const createFileMutation = gql`
+  mutation($file: Upload!) {
+    createFile(file: $file) {
+      id
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
+    }
+  }
+`
+
+export default compose(
+  graphql(query, {
+    options: ({ match }) => ({
+      variables: {
+        id: match.params.version,
+      },
+    }),
+  }),
+  graphql(uploadReviewFilesMutation, { name: 'uploadReviewFilesMutation' }),
+  graphql(updateReviewMutation, { name: 'updateReviewMutation' }),
+  graphql(updateTeam, { name: 'updateTeam' }),
+  graphql(createFileMutation, {
+    props: ({ mutate, ownProps: { match } }) => ({
+      createFile: file => {
+        mutate({
+          variables: {
+            file,
+          },
+          update: (proxy, { data: { createFile } }) => {
+            const data = proxy.readQuery({
+              query,
+              variables: {
+                id: match.params.version,
+              },
+            })
+
+            data.manuscript.reviews.map(review => {
+              if (review.id === file.objectId) {
+                review.comments.map(comment => {
+                  if (comment.type === createFile.fileType) {
+                    comment.files = [createFile]
+                  }
+                  return comment
+                })
+              }
+              return review
+            })
+
+            proxy.writeQuery({ query, data })
+          },
+        })
+      },
+    }),
+  }),
+  withLoader(),
+  withProps(
+    ({
+      manuscript,
+      currentUser,
+      match: {
+        params: { journal },
+      },
+      updateReviewMutation,
+      uploadReviewFilesMutation,
+      updateTeam,
+      createFile,
+    }) => ({
+      journal: { id: journal },
+      review:
+        manuscript.reviews.find(
+          review => review.user.id === currentUser.id && !review.isDecision,
+        ) || {},
+      status: (
+        (
+          (manuscript.teams.find(team => team.role === 'reviewerEditor') || {})
+            .status || []
+        ).find(status => status.user === currentUser.id) || {}
+      ).status,
+      updateReview: (review, file) => {
+        ;(review.comments || []).map(comment => {
+          delete comment.files
+          delete comment.__typename
+          return comment
+        })
+
+        const reviewData = {
+          recommendation: review.recommendation,
+          comments: review.comments,
+          manuscriptId: manuscript.id,
+        }
+
+        return updateReviewMutation({
+          variables: {
+            id: review.id || undefined,
+            input: reviewData,
+          },
+          update: (proxy, { data: { updateReview } }) => {
+            const data = proxy.readQuery({
+              query,
+              variables: {
+                id: manuscript.id,
+              },
+            })
+            let reviewIndex = data.manuscript.reviews.findIndex(
+              review => review.id === updateReview.id,
+            )
+            reviewIndex = reviewIndex < 0 ? 0 : reviewIndex
+            data.manuscript.reviews[reviewIndex] = updateReview
+            proxy.writeQuery({ query, data })
+          },
+        })
+      },
+      uploadFile: (file, updateReview, type) =>
+        uploadReviewFilesMutation({
+          variables: {
+            file,
+          },
+        }).then(({ data }) => {
+          const newFile = {
+            url: data.upload.url,
+            filename: file.name,
+            mimeType: file.type,
+            size: file.size,
+            object: 'Review',
+            objectId: updateReview.id,
+            fileType: type,
+          }
+          createFile(newFile)
+        }),
+      completeReview: history => {
+        const team = cloneDeep(manuscript.teams).find(
+          team => team.role === 'reviewerEditor',
+        )
+        team.members = team.members
+          .map(m => {
+            if (m.user.id === currentUser.id) {
+              return { user: { id: m.user.id }, status: 'completed' }
+            }
+            return undefined
+          })
+          .filter(m => m)
+
+        updateTeam({
+          variables: {
+            id: team.id,
+            input: {
+              members: team.members,
+            },
+          },
+        }).then(() => {
+          history.push('/')
+        })
+      },
+    }),
+  ),
+  withFormik({
+    enableReinitialize: true,
+    mapPropsToValues: props =>
+      props.manuscript.reviews.find(review => !review.isDecision) || {
+        id: null,
+        comments: [],
+        recommendation: null,
+      },
+    isInitialValid: ({ review }) => {
+      if (!review.id) return false
+      const hasRecommendation = review.recommendation !== null
+      const comment = getCommentContent(review, 'note')
+      const isCommented = comment !== null && comment !== ''
+
+      return isCommented && hasRecommendation
+    },
+    displayName: 'review',
+    handleSubmit: (props, { props: { completeReview, history } }) =>
+      completeReview(history),
+  }),
+)(ReviewLayout)
diff --git a/app/components/component-xpub-review/src/components/ReviewersPage.js b/app/components/component-xpub-review/src/components/ReviewersPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..42b05b69f0373a920b8da00df4aa683e680144e2
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/ReviewersPage.js
@@ -0,0 +1,217 @@
+import { compose, withProps } from 'recompose'
+import { withFormik } from 'formik'
+import { graphql } from '@apollo/react-hoc'
+import { gql } from 'apollo-client-preset'
+import { withLoader } from 'pubsweet-client'
+import { cloneDeep, omit } from 'lodash'
+
+import Reviewers from '../components/reviewers/Reviewers'
+import ReviewerContainer from '../components/reviewers/ReviewerContainer'
+
+const teamFields = `
+  id
+  role
+  name
+  object {
+    objectId
+    objectType
+  }
+  members {
+    id
+    user {
+      id
+      username
+    }
+    status
+  }
+`
+
+const fragmentFields = `
+  id
+  created
+  files {
+    id
+    created
+    label
+    filename
+    mimeType
+    fileType
+    size
+    url
+  }
+  reviews {
+    open
+    recommendation
+    created
+    comments {
+      type
+      content
+      files {
+        fileType
+        id
+        label
+        url
+        filename
+      }
+    }
+    user {
+      id
+      username
+    }
+  }
+  decision
+  teams {
+    ${teamFields}
+  }
+  status
+`
+
+const createTeamMutation = gql`
+  mutation($input: TeamInput!) {
+    createTeam(input: $input) {
+      ${teamFields}
+    }
+  }
+`
+
+const updateTeamMutation = gql`
+  mutation($id: ID, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
+      ${teamFields}
+    }
+  }
+`
+
+const query = gql`
+  query($id: ID!) {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    users {
+      id
+      username
+      admin
+    }
+
+    teams {
+      ${teamFields}
+    }
+
+    manuscript(id: $id) {
+      ${fragmentFields}
+    }
+  }
+`
+
+const update = match => (proxy, { data: { updateTeam, createTeam } }) => {
+  const data = proxy.readQuery({
+    query,
+    variables: {
+      id: match.params.version,
+    },
+  })
+
+  if (updateTeam) {
+    const teamIndex = data.teams.findIndex(team => team.id === updateTeam.id)
+    const manuscriptTeamIndex = data.manuscript.teams.findIndex(
+      team => team.id === updateTeam.id,
+    )
+    data.teams[teamIndex] = updateTeam
+    data.manuscript.teams[manuscriptTeamIndex] = updateTeam
+  }
+
+  if (createTeam) {
+    data.teams.push(createTeam)
+    data.manuscript.teams.push(createTeam)
+  }
+  proxy.writeQuery({ query, data })
+}
+
+const handleSubmit = (
+  { user },
+  { props: { manuscript, updateTeamMutation, createTeamMutation, match } },
+) => {
+  const team =
+    manuscript.teams.find(team => team.role === 'reviewerEditor') || {}
+
+  const teamAdd = {
+    objectId: manuscript.id,
+    objectType: 'Manuscript',
+    // status: [{ user: user.id, status: 'invited' }],
+    name: 'Reviewer Editor',
+    role: 'reviewerEditor',
+    members: [{ user: { id: user.id }, status: 'invited' }],
+  }
+  if (team.id) {
+    const newTeam = {
+      ...omit(team, ['object', 'id', '__typename']),
+      members: cloneDeep(team.members),
+    }
+
+    newTeam.members.push({ user: { id: user.id }, status: 'invited' })
+    // newTeam.status.push({ user: user.id, status: 'invited' })
+    updateTeamMutation({
+      variables: {
+        id: team.id,
+        input: newTeam,
+      },
+      update: update(match),
+    })
+  } else {
+    createTeamMutation({
+      variables: {
+        input: teamAdd,
+      },
+      update: update(match),
+    })
+  }
+}
+
+export default compose(
+  graphql(query, {
+    options: ({ match }) => ({
+      variables: {
+        id: match.params.version,
+      },
+    }),
+  }),
+  graphql(createTeamMutation, { name: 'createTeamMutation' }),
+  graphql(updateTeamMutation, { name: 'updateTeamMutation' }),
+  withLoader(),
+  withProps(
+    ({
+      manuscript,
+      teams = [],
+      users,
+      match: {
+        params: { journal },
+      },
+    }) => {
+      const reviewersTeam =
+        teams.find(
+          team =>
+            team.role === 'reviewerEditor' &&
+            team.object.objectId === manuscript.id &&
+            team.object.objectType === 'Manuscript',
+        ) || {}
+
+      return {
+        reviewers: reviewersTeam.members || [],
+        journal: { id: journal },
+        reviewerUsers: users,
+        Reviewer: ReviewerContainer,
+      }
+    },
+  ),
+  // withHandlers({
+  //   loadOptions: props => props.reviewerUsers, // loadOptions(props),
+  // }),
+  withFormik({
+    mapPropsToValues: () => ({ user: '' }),
+    displayName: 'reviewers',
+    handleSubmit,
+  }),
+)(Reviewers)
diff --git a/app/components/component-xpub-review/src/components/assignEditors/AssignEditor.js b/app/components/component-xpub-review/src/components/assignEditors/AssignEditor.js
new file mode 100644
index 0000000000000000000000000000000000000000..11811a22ece4704d922ded8f9cf20da15eca5acb
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/assignEditors/AssignEditor.js
@@ -0,0 +1,144 @@
+import React from 'react'
+import config from 'config'
+import { compose, withProps } from 'recompose'
+import { cloneDeep, get } from 'lodash'
+import { Menu } from '@pubsweet/ui'
+import { graphql } from '@apollo/react-hoc'
+import { gql } from 'apollo-client-preset'
+import { withLoader } from 'pubsweet-client'
+
+const editorOption = user => ({
+  label: user.username, // TODO: name
+  value: user.id,
+})
+
+const teamFields = `
+  id
+  name
+  role
+  object {
+    objectId
+    objectType
+  }
+  members {
+    id
+    user {
+      id
+      username
+    }
+  }
+`
+
+const query = gql`
+  {
+    users {
+      id
+      username
+      admin
+    }
+  }
+`
+
+const updateTeam = gql`
+  mutation($id: ID!, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
+      ${teamFields}
+    }
+  }
+`
+
+const createTeamMutation = gql`
+  mutation($input: TeamInput!) {
+    createTeam(input: $input) {
+      ${teamFields}
+    }
+  }
+`
+
+// TODO: select multiple editors
+const AssignEditor = ({
+  updateTeam,
+  createTeam,
+  teamName,
+  teamRole,
+  value,
+  options,
+}) => (
+  <Menu
+    label={teamName}
+    onChange={user => {
+      if (value) {
+        updateTeam(user, teamRole)
+      } else {
+        createTeam(user, teamRole)
+      }
+    }}
+    options={options}
+    placeholder="Assign an editor…"
+    value={value}
+  />
+)
+
+export default compose(
+  graphql(query),
+  graphql(updateTeam, {
+    props: ({ mutate, ownProps }) => {
+      const updateTeam = (userId, teamRole) => {
+        const team = cloneDeep(ownProps.manuscript.teams).find(
+          team => team.role === teamRole,
+        )
+        mutate({
+          variables: {
+            id: team.id,
+            input: {
+              members: [{ id: userId }],
+            },
+          },
+        })
+      }
+
+      return {
+        updateTeam,
+      }
+    },
+  }),
+  graphql(createTeamMutation, {
+    props: ({ mutate, ownProps }) => {
+      const createTeam = (userId, teamRole) => {
+        const input = {
+          objectId: ownProps.manuscript.id,
+          objectType: 'Manuscript',
+          name:
+            teamRole === 'seniorEditor' ? 'Senior Editor' : 'Handling Editor',
+          role: teamRole,
+          members: [{ id: userId }],
+        }
+
+        mutate({
+          variables: {
+            input,
+          },
+        })
+      }
+
+      return {
+        createTeam,
+      }
+    },
+  }),
+  withProps(({ teamRole, manuscript, data = {} }) => {
+    const optionUsers = (data.users || []).map(user => editorOption(user))
+
+    const team =
+      (manuscript.teams || []).find(team => team.role === teamRole) || {}
+
+    const members = team.members || []
+    const teamName = get(config, `authsome.teams.${teamRole}.name`)
+    return {
+      teamName,
+      options: optionUsers,
+      value: members.length > 0 ? members[0].id : undefined,
+    }
+  }),
+  withLoader(),
+)(AssignEditor)
diff --git a/app/components/component-xpub-review/src/components/assignEditors/AssignEditor.md b/app/components/component-xpub-review/src/components/assignEditors/AssignEditor.md
new file mode 100644
index 0000000000000000000000000000000000000000..49085706311761ea72d1fda712a31c5b85ccf935
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/assignEditors/AssignEditor.md
@@ -0,0 +1,92 @@
+A drop-down menu for assigning an editor to a project.
+
+```js
+const { JournalProvider } = require('xpub-journal')
+const journal = require('@pubsweet/styleguide/config/journal')
+
+const project = {
+  id: faker.random.uuid(),
+}
+
+const team = {
+  members: [],
+}
+
+const manuscriptTemplate = () => ({
+  id: faker.random.uuid(),
+  teams: [
+    {
+      id: faker.random.uuid(),
+      role: 'reviewerEditor',
+      name: 'reviewer',
+      object: {
+        id: faker.random.uuid(),
+        __typename: 'Manuscript',
+      },
+      objectType: 'manuscript',
+      members: [
+        {
+          user: { id: 1 },
+        },
+      ],
+    },
+  ],
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  decision: {
+    id: faker.random.uuid(),
+    comments: [{ type: 'note', content: 'this needs review' }],
+    created: 'Thu Oct 11 2018',
+    open: false,
+    status: '<p>This is a decision</p>',
+    user: { id: 1 },
+  },
+  reviews: [
+    {
+      comments: [{ content: 'this needs review' }],
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: 'revise',
+      user: { id: 1, username: 'test user' },
+    },
+  ],
+})
+
+const manuscript = Object.assign({}, manuscriptTemplate())
+
+const options = [
+  {
+    value: faker.random.uuid(),
+    label: faker.internet.userName(),
+  },
+  {
+    value: faker.random.uuid(),
+    label: faker.internet.userName(),
+  },
+  {
+    value: faker.random.uuid(),
+    label: faker.internet.userName(),
+  },
+]
+;<JournalProvider journal={journal}>
+  <AssignEditor
+    manuscript={manuscript}
+    team={team}
+    teamName="Senior Editor"
+    teamTypeName="seniorEditor"
+    options={options}
+    addUserToTeam={value => console.log(value)}
+  />
+</JournalProvider>
+```
diff --git a/app/components/component-xpub-review/src/components/assignEditors/AssignEditorsReviewers.js b/app/components/component-xpub-review/src/components/assignEditors/AssignEditorsReviewers.js
new file mode 100644
index 0000000000000000000000000000000000000000..75e8167261a3fc0237503837283397cac131a74d
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/assignEditors/AssignEditorsReviewers.js
@@ -0,0 +1,25 @@
+import React, { useContext } from 'react'
+import styled from 'styled-components'
+import { Link } from 'react-router-dom'
+import { JournalContext } from 'xpub-journal'
+import { Roles } from '../molecules/Roles'
+
+const Root = styled.div``
+const Title = styled.div``
+
+const AssignEditorsReviewers = ({ manuscript, AssignEditor }) => {
+  const journal = useContext(JournalContext)
+  return (
+    <Root>
+      <Title>Assign Editors</Title>
+      <Roles>
+        <AssignEditor manuscript={manuscript} teamRole="seniorEditor" />
+        <AssignEditor manuscript={manuscript} teamRole="handlingEditor" />
+      </Roles>
+      <Link to={`/journals/${journal.id}/versions/${manuscript.id}/reviewers`}>
+        Assign Reviewers
+      </Link>
+    </Root>
+  )
+}
+export default AssignEditorsReviewers
diff --git a/app/components/component-xpub-review/src/components/atoms/AdminSection.js b/app/components/component-xpub-review/src/components/atoms/AdminSection.js
new file mode 100644
index 0000000000000000000000000000000000000000..a78c83a7940e8a25709eedbc68c24b0da2d5abea
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/atoms/AdminSection.js
@@ -0,0 +1,8 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+const AdminSection = styled.div`
+  margin-bottom: calc(${th('gridUnit')} * 3);
+`
+
+export default AdminSection
diff --git a/app/components/component-xpub-review/src/components/atoms/Columns.js b/app/components/component-xpub-review/src/components/atoms/Columns.js
new file mode 100644
index 0000000000000000000000000000000000000000..406921bd16fbf005b8d72e365c9a089387856917
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/atoms/Columns.js
@@ -0,0 +1,19 @@
+import styled from 'styled-components'
+
+const Columns = styled.div`
+  display: grid;
+  grid-column-gap: 2em;
+  grid-template-areas: 'manuscript admin';
+  grid-template-columns: minmax(200px, 80ch) minmax(200px, 50ch);
+  justify-content: center;
+`
+
+const Manuscript = styled.div`
+  grid-area: manuscript;
+`
+
+const Admin = styled.div`
+  grid-area: admin;
+`
+
+export { Columns, Manuscript, Admin }
diff --git a/app/components/component-xpub-review/src/components/decision/Decision.js b/app/components/component-xpub-review/src/components/decision/Decision.js
new file mode 100644
index 0000000000000000000000000000000000000000..1a64dce359f77cf418266cae7208d978ab0996da
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/Decision.js
@@ -0,0 +1,56 @@
+import React from 'react'
+import styled from 'styled-components'
+
+import { NoteViewer } from 'xpub-edit'
+import { Attachment } from '@pubsweet/ui'
+
+const Heading = styled.div``
+const Note = styled.div``
+const Content = styled.div``
+const DecisionStatus = styled.div``
+
+const findComments = (decision = {}, type) => {
+  const comments = decision.comments || []
+  return comments.find(comment => comment.type === type)
+}
+
+// Due to migration to new Data Model
+// Attachement component needs different data structure to work
+// needs to change the pubsweet ui Attachement to support the new Data Model
+const filesToAttachment = file => ({
+  name: file.filename,
+  url: file.url,
+})
+
+const Decision = ({ review }) => (
+  <div>
+    <div>
+      {findComments(review, 'note') && [
+        <Heading>Note</Heading>,
+        <Note>
+          <Content>
+            <NoteViewer value={findComments(review, 'note').content} />
+          </Content>
+          {findComments(review, 'note') &&
+            (findComments(review, 'note').files || []).map(attachment => (
+              <Attachment
+                file={filesToAttachment(attachment)}
+                key={attachment.url}
+                uploaded
+              />
+            ))}
+        </Note>,
+      ]}
+    </div>
+
+    <div>
+      <Heading>Decision</Heading>
+
+      <DecisionStatus>
+        {(review && review.recommendation) || 'N/A'}
+      </DecisionStatus>
+    </div>
+  </div>
+)
+
+export default Decision
diff --git a/app/components/component-xpub-review/src/components/decision/Decision.md b/app/components/component-xpub-review/src/components/decision/Decision.md
new file mode 100644
index 0000000000000000000000000000000000000000..6217d8575f5c2d39ebf842ca4d5c0436183ce2d4
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/Decision.md
@@ -0,0 +1,13 @@
+A decision on a version of a project.
+
+```js
+const decision = {
+  id: faker.random.uuid(),
+  comments: [{ type: 'note', content: 'this needs review' }],
+  created: 'Thu Oct 11 2018',
+  open: false,
+  status: '<p>This is a decision</p>',
+  user: { identities: [] },
+}
+;<Decision decision={decision} />
+```
diff --git a/app/components/component-xpub-review/src/components/decision/DecisionForm.js b/app/components/component-xpub-review/src/components/decision/DecisionForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..896b0c188bb8445d6c8f6442a02c9092a2455a2f
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/DecisionForm.js
@@ -0,0 +1,156 @@
+import React, { useContext } from 'react'
+import { NoteEditor } from 'xpub-edit'
+import { cloneDeep, omit } from 'lodash'
+import { FieldArray, Field } from 'formik'
+import { JournalContext } from 'xpub-journal'
+import { required } from 'xpub-validators'
+import {
+  Button,
+  Flexbox,
+  RadioGroup,
+  UploadButton,
+  UploadingFile,
+} from '@pubsweet/ui'
+
+import {
+  getCommentFiles,
+  getCommentContent,
+  stripHtml,
+  createComments,
+} from '../review/util'
+
+import AdminSection from '../atoms/AdminSection'
+
+const NoteDecision = (updateReview, uploadFile) => props => (
+  <AdminSection>
+    <Field
+      component={NoteInput}
+      name="comments"
+      updateReview={updateReview}
+      validate={required}
+    />
+    <Field
+      component={AttachmentsInput('note')}
+      updateReview={updateReview}
+      uploadFile={uploadFile}
+    />
+  </AdminSection>
+)
+
+const NoteInput = ({
+  field,
+  form: { values, setFieldValue },
+  updateReview,
+}) => (
+  <NoteEditor
+    key="note-input"
+    onBlur={value => {
+      const { updateIndex, comment } = createComments(
+        values,
+        {
+          type: 'note',
+          content: stripHtml(value),
+        },
+        'note',
+      )
+
+      setFieldValue(`comments.${updateIndex}`, comment)
+      updateReview(
+        cloneDeep(omit({ comment }, ['comment.files', 'comment.__typename'])),
+      )
+    }}
+    placeholder="Write/paste your decision letter here, or upload it using the upload button on the right."
+    title="Decision"
+    value={getCommentContent({ comments: field.value }, 'note')}
+  />
+)
+
+const AttachmentsInput = type => ({
+  field,
+  form: { values, setFieldValue },
+  updateReview,
+  uploadFile,
+}) => (
+  <>
+    <UploadButton
+      buttonText="↑ Upload files"
+      key="note-attachment"
+      onChange={event => {
+        const val = event.target.files[0]
+        const file = cloneDeep(val)
+        file.filename = val.name
+        file.type = type
+
+        const { updateIndex, comment } = createComments(
+          field.value,
+          { files: [file] },
+          type,
+        )
+
+        setFieldValue(`comments.${updateIndex}.files`, comment.files)
+
+        updateReview({}).then(({ data: { updateReview } }) => {
+          uploadFile(val, updateReview, type)
+        })
+      }}
+    />
+    <Flexbox>
+      {getCommentFiles(field.value, 'note').map(val => {
+        const file = cloneDeep(val)
+        file.name = file.filename
+        return <UploadingFile file={file} key={file.name} uploaded />
+      })}
+    </Flexbox>
+  </>
+)
+
+const RecommendationInput = ({
+  field,
+  form: { setFieldValue },
+  updateReview,
+}) => {
+  const journal = useContext(JournalContext)
+  return (
+    <RadioGroup
+      {...field}
+      inline
+      onChange={val => {
+        setFieldValue(`recommendation`, val)
+        updateReview({ recommendation: val })
+      }}
+      options={journal.recommendations}
+      value={field.value === '' ? null : field.value}
+    />
+  )
+}
+
+const DecisionForm = ({ handleSubmit, uploadFile, updateReview, isValid }) => (
+  <form onSubmit={handleSubmit}>
+    <AdminSection key="note">
+      <div name="note">
+        <FieldArray
+          component={NoteDecision(updateReview, uploadFile)}
+          key="comments-array"
+          name="comments"
+        />
+      </div>
+    </AdminSection>
+
+    <AdminSection key="recommendation">
+      <Field
+        component={RecommendationInput}
+        name="recommendation"
+        updateReview={updateReview}
+        validate={required}
+      />
+    </AdminSection>
+
+    <AdminSection key="submit">
+      <Button disabled={!isValid} primary type="submit">
+        Submit
+      </Button>
+    </AdminSection>
+  </form>
+)
+
+export default DecisionForm
diff --git a/app/components/component-xpub-review/src/components/decision/DecisionForm.md b/app/components/component-xpub-review/src/components/decision/DecisionForm.md
new file mode 100644
index 0000000000000000000000000000000000000000..71222d66e3beea80c4c083ee18be52bbf7570e4f
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/DecisionForm.md
@@ -0,0 +1,51 @@
+A form for entering a decision on a version of a project.
+
+```js
+const { withFormik } = require('formik')
+
+const manuscript = {
+  id: faker.random.uuid(),
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  decision: {
+    id: faker.random.uuid(),
+    comments: { type: 'note', content: 'this needs review' },
+    created: 'Thu Oct 11 2018',
+    open: false,
+    recommendation: '<p>This is a decision</p>',
+    user: { identities: [] },
+  },
+  reviews: [
+    {
+      comments: { content: 'this needs review' },
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: '',
+      user: { identities: [] },
+    },
+  ],
+}
+
+const ConnectedDecisionForm = withFormik({
+  initialValues: {},
+  mapPropsToValues: ({ manuscript }) => manuscript,
+  displayName: 'decision',
+  handleSubmit: (props, { props: { onSubmit, history } }) =>
+    onSubmit(props, { history }),
+})(DecisionForm)
+;<ConnectedDecisionForm
+  manuscript={manuscript}
+  uploadFile={() => new XMLHttpRequest()}
+/>
+```
diff --git a/app/components/component-xpub-review/src/components/decision/DecisionLayout.js b/app/components/component-xpub-review/src/components/decision/DecisionLayout.js
new file mode 100644
index 0000000000000000000000000000000000000000..fb9dccd8d04c4dab083a4616f7cad290cbd4a6f1
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/DecisionLayout.js
@@ -0,0 +1,108 @@
+import React from 'react'
+import moment from 'moment'
+
+import { Tabs } from '@pubsweet/ui'
+import DecisionForm from './DecisionForm'
+import DecisionReviews from './DecisionReviews'
+import AssignEditorsReviewers from '../assignEditors/AssignEditorsReviewers'
+import AssignEditor from '../assignEditors/AssignEditor'
+import ReviewMetadata from '../metadata/ReviewMetadata'
+import Decision from './Decision'
+import EditorSection from './EditorSection'
+import { Columns, Manuscript, Admin } from '../atoms/Columns'
+import AdminSection from '../atoms/AdminSection'
+
+const addEditor = (manuscript, label) => ({
+  content: <EditorSection manuscript={manuscript} />,
+  key: manuscript.id,
+  label,
+})
+
+const DecisionLayout = ({
+  handleSubmit,
+  updateReview,
+  uploadFile,
+  manuscript,
+  isValid,
+}) => {
+  const decisionSections = []
+  const editorSections = []
+  const manuscriptVersions = manuscript.manuscriptVersions || []
+  manuscriptVersions.forEach(manuscript => {
+    const submittedMoment = moment(manuscript.updated)
+    const label = submittedMoment.format('YYYY-MM-DD')
+
+    decisionSections.push({
+      content: (
+        <div>
+          <ReviewMetadata manuscript={manuscript} />
+          <DecisionReviews manuscript={manuscript} />
+          <Decision
+            review={manuscript.reviews.find(review => review.isDecision)}
+          />
+        </div>
+      ),
+      key: manuscript.id,
+      label,
+    })
+
+    editorSections.push(addEditor(manuscript, label))
+  }, [])
+
+  const submittedMoment = moment()
+  const label = submittedMoment.format('YYYY-MM-DD')
+  if (manuscript.status !== 'revising') {
+    decisionSections.push({
+      content: (
+        <div>
+          <AdminSection key="assign-editors">
+            <AssignEditorsReviewers
+              AssignEditor={AssignEditor}
+              manuscript={manuscript}
+            />
+          </AdminSection>
+          <AdminSection key="review-metadata">
+            <ReviewMetadata manuscript={manuscript} />
+          </AdminSection>
+          <AdminSection key="decision-review">
+            <DecisionReviews manuscript={manuscript} />
+          </AdminSection>
+          <AdminSection key="decision-form">
+            <DecisionForm
+              handleSubmit={handleSubmit}
+              isValid={isValid}
+              updateReview={updateReview}
+              uploadFile={uploadFile}
+            />
+          </AdminSection>
+        </div>
+      ),
+      key: manuscript.id,
+      label,
+    })
+
+    editorSections.push(addEditor(manuscript, label))
+  }
+
+  return (
+    <Columns>
+      <Manuscript>
+        <Tabs
+          activeKey={editorSections[editorSections.length - 1].key}
+          sections={editorSections}
+          title="Versions"
+        />
+      </Manuscript>
+
+      <Admin>
+        <Tabs
+          activeKey={decisionSections[decisionSections.length - 1].key}
+          sections={decisionSections}
+          title="Versions"
+        />
+      </Admin>
+    </Columns>
+  )
+}
+
+export default DecisionLayout
diff --git a/app/components/component-xpub-review/src/components/decision/DecisionLayout.md b/app/components/component-xpub-review/src/components/decision/DecisionLayout.md
new file mode 100644
index 0000000000000000000000000000000000000000..1fdf0824955bfd45f8b1216a25c6db5f85053af6
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/DecisionLayout.md
@@ -0,0 +1,114 @@
+A page for an editor to make a decision on a version of a project.
+
+```js
+const { withFormik } = require('formik')
+const { JournalProvider } = require('xpub-journal')
+const AssignEditor = require('../assignEditors/AssignEditor').default
+
+const journal = {
+  id: faker.random.uuid(),
+}
+
+const manuscriptTemplate = () => ({
+  id: faker.random.uuid(),
+  teams: [
+    {
+      id: faker.random.uuid(),
+      role: 'author',
+      name: 'Authors',
+      object: {
+        id: faker.random.uuid(),
+        __typename: 'Manuscript',
+      },
+      objectType: 'manuscript',
+      members: [
+        {
+          user: {},
+        },
+      ],
+    },
+  ],
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  decision: {
+    id: faker.random.uuid(),
+    comments: [{ type: 'note', content: 'this needs review' }],
+    created: 'Thu Oct 11 2018',
+    open: false,
+    status: '<p>This is a decision</p>',
+    user: { identities: [] },
+  },
+  reviews: [
+    {
+      comments: { content: 'this needs review' },
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: '',
+      user: { identities: [] },
+    },
+  ],
+})
+
+const manuscript = Object.assign({}, manuscriptTemplate(), {
+  manuscriptVersions: [manuscriptTemplate()],
+})
+
+const team = {
+  members: [],
+}
+
+const options = [
+  {
+    value: faker.random.uuid(),
+    label: faker.internet.userName(),
+  },
+  {
+    value: faker.random.uuid(),
+    label: faker.internet.userName(),
+  },
+  {
+    value: faker.random.uuid(),
+    label: faker.internet.userName(),
+  },
+]
+
+const AssignEditorContainer = ({
+  project,
+  teamName,
+  teamTypeName,
+  addUserToTeam,
+}) => (
+  <AssignEditor
+    team={team}
+    options={options}
+    manuscript={manuscript}
+    teamName={teamName}
+    teamTypeName={teamTypeName}
+    addUserToTeam={addUserToTeam}
+  />
+)
+
+const ConnectedDecisionLayout = withFormik({
+  initialValues: {},
+  mapPropsToValues: ({ manuscript }) => manuscript,
+  displayName: 'decision',
+  handleSubmit: (props, { props: { onSubmit, history } }) =>
+    onSubmit(props, { history }),
+})(DecisionLayout)
+;<div style={{ position: 'relative', height: 600 }}>
+  <JournalProvider journal={journal}>
+    <ConnectedDecisionLayout manuscript={manuscript} uploadFile={() => {}} />
+  </JournalProvider>
+</div>
+```
diff --git a/app/components/component-xpub-review/src/components/decision/DecisionReview.js b/app/components/component-xpub-review/src/components/decision/DecisionReview.js
new file mode 100644
index 0000000000000000000000000000000000000000..d87a3d62c77dcbe43920e72a788d0188571e9137
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/DecisionReview.js
@@ -0,0 +1,106 @@
+import React, { useContext } from 'react'
+import styled from 'styled-components'
+import { compose, withState, withHandlers } from 'recompose'
+import { JournalContext } from 'xpub-journal'
+import { Button } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+import Review from '../review/Review'
+
+const ToggleReview = ({ open, toggle }) => (
+  <Button onClick={toggle} plain>
+    {open ? 'Hide' : 'Show'}
+  </Button>
+)
+
+const Bullet = ({ journal, recommendation }) => {
+  const recommendationColor = () =>
+    recommendation
+      ? journal.recommendations.find(item => item.value === recommendation)
+          .color
+      : 'black'
+
+  const Dot = styled.span`
+    border-radius: 100%;
+    display: inline-block;
+    height: 10px;
+    margin-right: 10px;
+    width: 10px;
+    background-color: ${recommendationColor};
+  `
+
+  return <Dot />
+}
+
+const ReviewHeading = ({
+  journal,
+  name,
+  open,
+  ordinal,
+  recommendation,
+  toggleOpen,
+}) => {
+  const Root = styled.div`
+    display: flex;
+    align-items: baseline;
+  `
+  const Ordinal = styled.span``
+  const Name = styled.span``
+  const Controls = styled.span`
+    flex-grow: 1;
+    text-align: right;
+  `
+
+  return (
+    <Root>
+      <Bullet journal={journal} recommendation={recommendation} />
+      <Ordinal>Review {ordinal}</Ordinal>
+      &nbsp;
+      <Name>{name || 'Anonymous'}</Name>
+      <Controls>
+        <ToggleReview open={open} toggle={toggleOpen} />
+      </Controls>
+    </Root>
+  )
+}
+
+const DecisionReview = ({ review, reviewer, open, toggleOpen }) => {
+  const { recommendation } = review
+  const { name, ordinal } = reviewer
+
+  const Root = styled.div`
+    margin-bottom: calc(${th('gridUnit')} * 3);
+  `
+
+  const ReviewBody = styled.div`
+    margin-left: 1em;
+  `
+
+  const journal = useContext(JournalContext)
+  return (
+    <Root>
+      <ReviewHeading
+        journal={journal}
+        name={name}
+        open={open}
+        ordinal={ordinal}
+        recommendation={recommendation}
+        toggleOpen={toggleOpen}
+      />
+
+      {open && (
+        <ReviewBody>
+          <Review review={review} />
+        </ReviewBody>
+      )}
+    </Root>
+  )
+}
+
+export default compose(
+  withState('open', 'setOpen', ({ open }) => open),
+  withHandlers({
+    toggleOpen: props => () => {
+      props.setOpen(open => !open)
+    },
+  }),
+)(DecisionReview)
diff --git a/app/components/component-xpub-review/src/components/decision/DecisionReview.md b/app/components/component-xpub-review/src/components/decision/DecisionReview.md
new file mode 100644
index 0000000000000000000000000000000000000000..e0220b5793d9b916177a535427f4294b128727a9
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/DecisionReview.md
@@ -0,0 +1,35 @@
+Reviews of a version of a project, as shown when making a decision.
+
+```js
+const review = {
+  comments: [{ content: 'this needs review' }],
+  created: 'Thu Oct 11 2018',
+  open: false,
+  recommendation: 'revise',
+  user: { id: 1, username: 'test user' },
+}
+
+const reviewer = {
+  ordinal: faker.random.number({ min: 1, max: 5 }),
+  name: faker.name.findName(),
+}
+;<DecisionReview open review={review} reviewer={{ name: 'test user' }} />
+```
+
+The review is hidden by default, but can be toggled to display the review.
+
+```js
+const review = {
+  comments: [{ content: 'this needs review' }],
+  created: 'Thu Oct 11 2018',
+  open: false,
+  recommendation: 'revise',
+  user: { id: 1, username: 'test user' },
+}
+
+const reviewer = {
+  ordinal: faker.random.number({ min: 1, max: 5 }),
+  name: faker.name.findName(),
+}
+;<DecisionReview review={review} reviewer={{ name: 'test user' }} />
+```
diff --git a/app/components/component-xpub-review/src/components/decision/DecisionReviews.js b/app/components/component-xpub-review/src/components/decision/DecisionReviews.js
new file mode 100644
index 0000000000000000000000000000000000000000..d888b77284bcf0fedc96f7a6b9c23f9f2554abc1
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/DecisionReviews.js
@@ -0,0 +1,42 @@
+import React from 'react'
+import DecisionReview from './DecisionReview'
+
+// TODO: read reviewer ordinal and name from project reviewer
+// const { status } =
+//     getUserFromTeam(manuscript, 'reviewerEditor').filter(
+//       member => member.user.id === currentUser.id,
+//     )[0] || {}
+//   return status
+const getCompletedReviews = (manuscript, currentUser) => {
+  const team =
+    manuscript.teams.find(team => team.role === 'reviewerEditor') || {}
+
+  const currentMember = team.members.find(m => m.user.id === currentUser.id)
+  return currentMember && currentMember.status
+}
+
+const DecisionReviews = ({ manuscript }) => (
+  <div>
+    {manuscript.reviews &&
+      manuscript.reviews
+        .filter(
+          review =>
+            getCompletedReviews(manuscript, review.user) === 'completed' &&
+            review.isDecision === false,
+        )
+        .map((review, index) => (
+          <div key={review.id}>
+            <DecisionReview
+              open
+              review={review}
+              reviewer={{
+                name: review.user.username,
+                ordinal: index + 1,
+              }}
+            />
+          </div>
+        ))}
+  </div>
+)
+
+export default DecisionReviews
diff --git a/app/components/component-xpub-review/src/components/decision/DecisionReviews.md b/app/components/component-xpub-review/src/components/decision/DecisionReviews.md
new file mode 100644
index 0000000000000000000000000000000000000000..d8b2a158e9253fd837c2316feba9dce7ad651428
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/DecisionReviews.md
@@ -0,0 +1,68 @@
+Reviews of a version of a project, as shown when making a decision.
+
+```js
+const { JournalProvider } = require('xpub-journal')
+const journal = require('@pubsweet/styleguide/config/journal')
+
+const manuscriptTemplate = () => ({
+  id: faker.random.uuid(),
+  teams: [
+    {
+      id: faker.random.uuid(),
+      role: 'reviewerEditor',
+      name: 'Reviewer',
+      object: {
+        id: faker.random.uuid(),
+        __typename: 'Manuscript',
+      },
+      objectType: 'manuscript',
+      members: [
+        {
+          user: {
+            id: 1,
+            username: 'test user',
+          },
+          status: 'accepted',
+        },
+      ],
+    },
+  ],
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  decision: {
+    id: faker.random.uuid(),
+    comments: [{ type: 'note', content: 'this needs review' }],
+    created: 'Thu Oct 11 2018',
+    open: false,
+    status: '<p>This is a decision</p>',
+    user: { id: 1 },
+  },
+  reviews: [
+    {
+      comments: [{ content: 'this needs review' }],
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: 'revise',
+      user: { id: 1, username: 'test user' },
+    },
+  ],
+})
+
+const manuscript = Object.assign({}, manuscriptTemplate(), {
+  manuscriptVersions: [manuscriptTemplate()],
+})
+;<JournalProvider journal={journal}>
+  <DecisionReviews manuscript={manuscript} />
+</JournalProvider>
+```
diff --git a/app/components/component-xpub-review/src/components/decision/EditorSection.js b/app/components/component-xpub-review/src/components/decision/EditorSection.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e72889f01ac87e8828973e14a622d4b2f5ba451
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/decision/EditorSection.js
@@ -0,0 +1,28 @@
+import React from 'react'
+import { Wax, CreateSchema } from 'wax-prosemirror-core'
+import { XpubSchema } from 'wax-prosemirror-schema'
+import 'wax-prosemirror-themes/themes/default-theme.css'
+
+import { EditorWrapper } from '../molecules/EditorWrapper'
+import { Info } from '../molecules/Info'
+
+const options = {
+  schema: new CreateSchema(XpubSchema),
+}
+
+export default ({ manuscript }) =>
+  ((manuscript.files || []).find(file => file.fileType === 'manuscript') || '')
+    .mimeType ===
+  'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ? (
+    <EditorWrapper>
+      <Wax
+        key={manuscript.id}
+        options={options}
+        readonly
+        theme="default"
+        value={manuscript.meta.source}
+      />
+    </EditorWrapper>
+  ) : (
+    <Info>No supported view of the file</Info>
+  )
diff --git a/app/components/component-xpub-review/src/components/index.js b/app/components/component-xpub-review/src/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..0290b63d6625a6c8d8aafae1af2f328f6f35a9fa
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/index.js
@@ -0,0 +1,2 @@
+export { default as DecisionPage } from './DecisionPage'
+export { default as ReviewPage } from './ReviewPage'
diff --git a/app/components/component-xpub-review/src/components/metadata/ReviewMetadata.js b/app/components/component-xpub-review/src/components/metadata/ReviewMetadata.js
new file mode 100644
index 0000000000000000000000000000000000000000..18cbfab494d37d627754e4ab0cb7deb85fee79c5
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/metadata/ReviewMetadata.js
@@ -0,0 +1,144 @@
+import React from 'react'
+import styled from 'styled-components'
+
+import { Attachment } from '@pubsweet/ui'
+
+const Root = styled.div``
+
+const Title = styled.div``
+
+const Heading = styled.span`
+  font-weight: inherit;
+  padding: 0 1em 0 0;
+  white-space: nowrap;
+  text-align: right;
+  flex-grow: 0;
+  flex-shrink: 0;
+  flex-basis: 50%;
+`
+const Metadata = styled.div`
+  div {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+  }
+`
+
+const Cell = styled.span`
+  padding: 0;
+  flex-grow: 0;
+  flex-shrink: 0;
+  flex-basis: 50%;
+`
+
+const getNote = (notes, type) =>
+  notes.find(note => note.notesType === type) || {}
+
+const getDeclarations = (manuscript, field) =>
+  ((manuscript.meta || {}).declarations || {})[field]
+
+const getSupplementaryFiles = supplementary =>
+  (supplementary || []).filter(file => file.fileType === 'supplementary') || []
+
+// Due to migration to new Data Model
+// Attachement component needs different data structure to work
+// needs to change the pubsweet ui Attachement to support the new Data Model
+const filesToAttachment = file => ({
+  name: file.filename,
+  url: file.url,
+})
+
+const ReviewMetadata = ({ manuscript }) => (
+  <Root>
+    <Title>Metadata</Title>
+    <Metadata>
+      <div>
+        <Heading>Open Peer Review :</Heading>
+        <Cell>
+          {getDeclarations(manuscript, 'openPeerReview') === 'yes'
+            ? 'Yes'
+            : 'No'}
+        </Cell>
+      </div>
+      <div>
+        <Heading>Streamlined Review :</Heading>
+        <Cell>
+          {getDeclarations(manuscript, 'streamlinedReview') === 'yes'
+            ? 'Please view supplementary uploaded files'
+            : 'No'}
+        </Cell>
+      </div>
+      <div>
+        <Heading>Part of Research Nexus :</Heading>
+        <Cell>
+          {getDeclarations(manuscript, 'researchNexus') === 'yes'
+            ? 'Yes'
+            : 'No'}
+        </Cell>
+      </div>
+      <div>
+        <Heading>Pre-registered :</Heading>
+        <Cell>
+          {getDeclarations(manuscript, 'preregistered') === 'yes'
+            ? 'Yes'
+            : 'No'}
+        </Cell>
+      </div>
+      <div>
+        <Heading>Suggested Reviewers :</Heading>
+        <Cell>
+          {((manuscript.suggestions || {}).reviewers || {}).suggested || 'None'}
+        </Cell>
+      </div>
+      <div>
+        <Heading>Opposed Reviewers :</Heading>
+        <Cell>
+          {((manuscript.suggestions || {}).reviewers || {}).opposed || 'None'}
+        </Cell>
+      </div>
+      <div>
+        <Heading>Suggested Editors :</Heading>
+        <Cell>
+          {((manuscript.suggestions || {}).editors || {}).suggested || 'None'}
+        </Cell>
+      </div>
+      <div>
+        <Heading>Opposed Editors :</Heading>
+        <Cell>
+          {((manuscript.suggestions || {}).editors || {}).opposed || 'None'}
+        </Cell>
+      </div>
+      <div>
+        <Heading>Special Instructions :</Heading>
+        <Cell>
+          {getNote(manuscript.meta.notes || [], 'specialInstructions')
+            .content || 'None'}
+        </Cell>
+      </div>
+      {getSupplementaryFiles(manuscript.files).length > 0 && (
+        <div>
+          <Heading>
+            {getSupplementaryFiles(manuscript.files).length} supplementary{' '}
+            {getSupplementaryFiles(manuscript.files).length === 1
+              ? 'file'
+              : 'files'}
+            :
+          </Heading>
+          {!!getSupplementaryFiles(manuscript.files).length && (
+            <Cell>
+              {getSupplementaryFiles(manuscript.files).map(file => (
+                <Attachment
+                  file={filesToAttachment(file)}
+                  key={file.url}
+                  uploaded
+                />
+              ))}
+            </Cell>
+          )}
+        </div>
+      )}
+    </Metadata>
+  </Root>
+)
+
+export default ReviewMetadata
diff --git a/app/components/component-xpub-review/src/components/metadata/ReviewMetadata.md b/app/components/component-xpub-review/src/components/metadata/ReviewMetadata.md
new file mode 100644
index 0000000000000000000000000000000000000000..7666b612eec17aaec702fcc2e584472effbb85d2
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/metadata/ReviewMetadata.md
@@ -0,0 +1,63 @@
+Project metadata, displayed at the top of the review form.
+
+```js
+const manuscriptTemplate = () => ({
+  id: faker.random.uuid(),
+  teams: [
+    {
+      id: faker.random.uuid(),
+      role: 'reviewerEditor',
+      name: 'Reviewer',
+      object: {
+        id: faker.random.uuid(),
+        __typename: 'Manuscript',
+      },
+      objectType: 'manuscript',
+      members: [
+        {
+          user: {
+            id: 1,
+            username: 'test user',
+          },
+          status: 'accepted',
+        },
+      ],
+    },
+  ],
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  decision: {
+    id: faker.random.uuid(),
+    comments: [{ type: 'note', content: 'this needs review' }],
+    created: 'Thu Oct 11 2018',
+    open: false,
+    status: '<p>This is a decision</p>',
+    user: { id: 1 },
+  },
+  reviews: [
+    {
+      comments: [{ content: 'this needs review' }],
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: 'revise',
+      user: { id: 1, username: 'test user' },
+    },
+  ],
+})
+
+const manuscript = Object.assign({}, manuscriptTemplate(), {
+  manuscriptVersions: [manuscriptTemplate()],
+})
+;<ReviewMetadata manuscript={manuscript} />
+```
diff --git a/app/components/component-xpub-review/src/components/molecules/EditorWrapper.js b/app/components/component-xpub-review/src/components/molecules/EditorWrapper.js
new file mode 100644
index 0000000000000000000000000000000000000000..8db6b31efc82249ef9301905e75ad91c7d95a619
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/molecules/EditorWrapper.js
@@ -0,0 +1,12 @@
+import styled from 'styled-components'
+
+const EditorWrapper = styled.div`
+  .wax-container {
+    position: relative;
+    .main-editor {
+      margin: 3% 0 0 0;
+    }
+  }
+`
+
+export { EditorWrapper }
diff --git a/app/components/component-xpub-review/src/components/molecules/Info.js b/app/components/component-xpub-review/src/components/molecules/Info.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce196e93b7a44292b7357a6a5e9b67f4395a0f9a
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/molecules/Info.js
@@ -0,0 +1,13 @@
+import styled from 'styled-components'
+
+const Info = styled.span`
+  padding: 0;
+  margin: 0;
+  list-style: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 500px;
+`
+
+export { Info }
diff --git a/app/components/component-xpub-review/src/components/molecules/Roles.js b/app/components/component-xpub-review/src/components/molecules/Roles.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7499552f13d0f3d101dab4c1cac89e315e19be1
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/molecules/Roles.js
@@ -0,0 +1,23 @@
+import styled from 'styled-components'
+
+const Roles = styled.div`
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+  font-size: 0.8em;
+  margin-bottom: 0.6em;
+  margin-left: 0.5em;
+  margin-top: 0;
+  padding-left: 1.5em;
+  text-transform: uppercase;
+`
+
+const Role = styled.div`
+  display: flex;
+
+  &:not(:last-of-type) {
+    margin-right: 3em;
+  }
+`
+
+export { Roles, Role }
diff --git a/app/components/component-xpub-review/src/components/review/Review.js b/app/components/component-xpub-review/src/components/review/Review.js
new file mode 100644
index 0000000000000000000000000000000000000000..6dafc509580015dcbc22d92795af470bdd45d9bb
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/review/Review.js
@@ -0,0 +1,70 @@
+import React from 'react'
+import styled from 'styled-components'
+import { NoteViewer } from 'xpub-edit'
+import { Attachment } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+import { getCommentFiles } from './util'
+
+const Heading = styled.div``
+const Note = styled.div`
+  font-size: ${th('fontSizeBaseSmall')};
+  line-height: ${th('lineHeightBaseSmall')};
+`
+const Recommendation = styled(Note)``
+const Content = styled.div``
+
+// Due to migration to new Data Model
+// Attachement component needs different data structure to work
+// needs to change the pubsweet ui Attachement to support the new Data Model
+const filesToAttachment = file => ({
+  name: file.filename,
+  url: file.url,
+})
+
+const findComments = (review = {}, type) => {
+  const comments = review.comments || []
+  return comments.find(comment => comment.type === type)
+}
+
+const ReviewComments = (review, type) => (
+  <Note>
+    <Content>
+      <NoteViewer value={findComments(review, type).content} />
+    </Content>
+    {getCommentFiles(review, type).map(attachment => (
+      <Attachment
+        file={filesToAttachment(attachment)}
+        key={attachment.url}
+        uploaded
+      />
+    ))}
+  </Note>
+)
+
+const Review = ({ review }) => (
+  <div>
+    {findComments(review, 'note') && (
+      <div>
+        <Heading>Note</Heading>
+
+        {ReviewComments(review, 'note')}
+      </div>
+    )}
+    {findComments(review, 'confidential') && (
+      <div>
+        <Heading>Confidential</Heading>
+
+        {ReviewComments(review, 'confidential')}
+      </div>
+    )}
+    {review.recommendation && (
+      <div>
+        <Heading>Recommendation</Heading>
+
+        <Recommendation>{review.recommendation}</Recommendation>
+      </div>
+    )}
+  </div>
+)
+
+export default Review
diff --git a/app/components/component-xpub-review/src/components/review/Review.md b/app/components/component-xpub-review/src/components/review/Review.md
new file mode 100644
index 0000000000000000000000000000000000000000..c1b178e534ab57206ce5c33c907c123df384be37
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/review/Review.md
@@ -0,0 +1,12 @@
+A review of a version of a project.
+
+```js
+const review = {
+  comments: [{ content: 'this needs review' }],
+  created: 'Thu Oct 11 2018',
+  open: false,
+  recommendation: 'revise',
+  user: { id: 1 },
+}
+;<Review review={review} />
+```
diff --git a/app/components/component-xpub-review/src/components/review/ReviewForm.js b/app/components/component-xpub-review/src/components/review/ReviewForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..1eefa22857eec26c780d9f8ebc2052f7f651d777
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/review/ReviewForm.js
@@ -0,0 +1,203 @@
+import React, { useContext } from 'react'
+import styled from 'styled-components'
+import { cloneDeep, set } from 'lodash'
+import { Field } from 'formik'
+import { NoteEditor } from 'xpub-edit'
+import {
+  Button,
+  Flexbox,
+  RadioGroup,
+  UploadButton,
+  UploadingFile,
+} from '@pubsweet/ui'
+
+import { JournalContext } from 'xpub-journal'
+import { getCommentFiles, stripHtml, createComments } from './util'
+import AdminSection from '../atoms/AdminSection'
+
+const AttachmentsInput = ({
+  field,
+  form: { values },
+  updateReview,
+  uploadFile,
+  type,
+}) => (
+  <>
+    <UploadButton
+      buttonText="↑ Upload files"
+      onChange={event => {
+        const val = event.target.files[0]
+        const file = cloneDeep(val)
+        file.filename = val.name
+        file.type = type
+
+        const { updateIndex, comment } = createComments(
+          values,
+          { files: [file] },
+          type,
+        )
+
+        const data = cloneDeep(values)
+        set(data, `comments.${updateIndex}`, comment)
+
+        updateReview(data).then(({ data: { updateReview } }) => {
+          uploadFile(val, updateReview, type)
+        })
+      }}
+    />
+    <Flexbox>
+      {getCommentFiles(values, type).map(val => {
+        const file = cloneDeep(val)
+        file.name = file.filename
+        return <UploadingFile file={file} key={file.name} uploaded />
+      })}
+    </Flexbox>
+  </>
+)
+
+const NoteInput = ({
+  field,
+  form: { values, setFieldValue },
+  updateReview,
+}) => (
+  <NoteEditor
+    key="note-comment"
+    placeholder="Enter your review…"
+    title="Comments to the Author"
+    {...field}
+    onChange={value => {
+      const { comment } = createComments(
+        values,
+        {
+          type: 'note',
+          content: stripHtml(value),
+        },
+        'note',
+      )
+
+      setFieldValue(`comments.0`, comment)
+      const data = cloneDeep(values)
+      set(data, `comments.0`, comment)
+      updateReview(data)
+    }}
+    value={field.value || ''}
+  />
+)
+
+const ConfidentialInput = ({
+  field,
+  form: { values, setFieldValue },
+  updateReview,
+}) => (
+  <NoteEditor
+    key="confidential-comment"
+    placeholder="Enter a confidential note to the editor (optional)…"
+    title="Confidential Comments to Editor (Optional)"
+    {...field}
+    onChange={value => {
+      const { comment } = createComments(
+        values,
+        {
+          type: 'confidential',
+          content: stripHtml(value),
+        },
+        'confidential',
+      )
+
+      setFieldValue(`comments.1`, comment)
+      const data = cloneDeep(values)
+      set(data, `comments.1`, comment)
+      updateReview(data)
+    }}
+    value={field.value || ''}
+  />
+)
+
+const RecommendationInput = ({ field, form: { values }, updateReview }) => {
+  const journal = useContext(JournalContext)
+  return (
+    <RadioGroup
+      inline
+      {...field}
+      onChange={val => {
+        const data = cloneDeep(values)
+        set(data, 'recommendation', val)
+        updateReview(data)
+      }}
+      options={journal.recommendations}
+    />
+  )
+}
+const ReviewComment = props => (
+  <>
+    <AdminSection>
+      <div name="note">
+        <Field
+          component={extraProps => <NoteInput {...props} {...extraProps} />}
+          key="noteField"
+          name="comments.0.content"
+        />
+        <Field
+          component={extraProps => (
+            <AttachmentsInput type="note" {...props} {...extraProps} />
+          )}
+        />
+      </div>
+    </AdminSection>
+    <AdminSection>
+      <div name="confidential">
+        <Field
+          component={extraProps => (
+            <ConfidentialInput {...props} {...extraProps} />
+          )}
+          key="confidentialField"
+          name="comments.1.content"
+        />
+        <Field
+          component={extraProps => (
+            <AttachmentsInput type="confidential" {...props} {...extraProps} />
+          )}
+        />
+      </div>
+    </AdminSection>
+  </>
+)
+
+const Title = styled.div``
+
+const ReviewForm = ({
+  journal,
+  isValid,
+  handleSubmit,
+  updateReview,
+  uploadFile,
+  review,
+}) => (
+  <form onSubmit={handleSubmit}>
+    <ReviewComment updateReview={updateReview} uploadFile={uploadFile} />
+    <AdminSection>
+      <div name="Recommendation">
+        <Title>Recommendation</Title>
+        <Field
+          component={props => (
+            <RecommendationInput
+              journal={journal}
+              updateReview={updateReview}
+              {...props}
+            />
+          )}
+          name="recommendation"
+          updateReview={updateReview}
+        />
+      </div>
+    </AdminSection>
+
+    <AdminSection>
+      <Button disabled={!isValid} primary type="submit">
+        Submit
+      </Button>
+    </AdminSection>
+  </form>
+)
+
+export default ReviewForm
diff --git a/app/components/component-xpub-review/src/components/review/ReviewForm.md b/app/components/component-xpub-review/src/components/review/ReviewForm.md
new file mode 100644
index 0000000000000000000000000000000000000000..9650dc63eec6549bd0e3e790761aa9479126914f
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/review/ReviewForm.md
@@ -0,0 +1,92 @@
+A form for entering a review of a version of a project.
+
+```js
+const { withFormik } = require('formik')
+
+const journal = {
+  id: faker.random.uuid(),
+}
+
+const manuscriptTemplate = () => ({
+  id: faker.random.uuid(),
+  teams: [
+    {
+      id: faker.random.uuid(),
+      role: 'reviewerEditor',
+      name: 'reviewer',
+      object: {
+        id: faker.random.uuid(),
+        __typename: 'Manuscript',
+      },
+      objectType: 'manuscript',
+      members: [
+        {
+          user: { id: 1 },
+        },
+      ],
+    },
+  ],
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  decision: {
+    id: faker.random.uuid(),
+    comments: [{ type: 'note', content: 'this needs review' }],
+    created: 'Thu Oct 11 2018',
+    open: false,
+    status: '<p>This is a decision</p>',
+    user: { id: 1 },
+  },
+  reviews: [
+    {
+      comments: [{ content: 'this needs review' }],
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: '',
+      user: { id: 1 },
+    },
+  ],
+})
+
+const manuscript = Object.assign({}, manuscriptTemplate(), {
+  manuscriptVersions: [manuscriptTemplate()],
+})
+
+const review = {
+  comments: [{ content: 'this needs review' }],
+  created: 'Thu Oct 11 2018',
+  open: false,
+  recommendation: '',
+  user: { id: 1 },
+}
+
+const currentUser = {
+  id: 1,
+}
+
+const ConnectedReviewForm = withFormik({
+  initialValues: {},
+  mapPropsToValues: ({ manuscript, currentUser }) =>
+    manuscript.reviews.find(review => review.user.id === currentUser.id),
+  displayName: 'review',
+  handleSubmit: (props, { props: { onSubmit, history } }) =>
+    onSubmit(props, { history }),
+})(ReviewForm)
+;<JournalProvider journal={journal}>
+  <ConnectedReviewForm
+    manuscript={manuscript}
+    currentUser={currentUser}
+    uploadFile={() => new XMLHttpRequest()}
+  />
+</JournalProvider>
+```
diff --git a/app/components/component-xpub-review/src/components/review/ReviewLayout.js b/app/components/component-xpub-review/src/components/review/ReviewLayout.js
new file mode 100644
index 0000000000000000000000000000000000000000..3872593897fe2b7aa6c1f800ddced47b245ac349
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/review/ReviewLayout.js
@@ -0,0 +1,96 @@
+import React from 'react'
+
+import moment from 'moment'
+import { Tabs } from '@pubsweet/ui'
+
+import ReviewForm from './ReviewForm'
+import ReviewMetadata from '../metadata/ReviewMetadata'
+import Review from './Review'
+import EditorSection from '../decision/EditorSection'
+import { Columns, Manuscript, Admin } from '../atoms/Columns'
+
+const addEditor = (manuscript, label) => ({
+  content: <EditorSection manuscript={manuscript} />,
+  key: manuscript.id,
+  label,
+})
+
+const ReviewLayout = ({
+  currentUser,
+  manuscript,
+  review,
+  reviewer,
+  handleSubmit,
+  isValid,
+  status,
+  updateReview,
+  uploadFile,
+}) => {
+  const reviewSections = []
+  const editorSections = []
+  const manuscriptVersions = manuscript.manuscriptVersions || []
+  manuscriptVersions.forEach(manuscript => {
+    const label = moment().format('YYYY-MM-DD')
+    reviewSections.push({
+      content: (
+        <div>
+          <ReviewMetadata manuscript={manuscript} />
+          <Review
+            review={manuscript.reviews.find(review => !review.isDecision) || {}}
+          />
+        </div>
+      ),
+      key: manuscript.id,
+      label,
+    })
+
+    editorSections.push(addEditor(manuscript, label))
+  }, [])
+
+  if (manuscript.status !== 'revising') {
+    const label = moment().format('YYYY-MM-DD')
+    reviewSections.push({
+      content: (
+        <div>
+          <ReviewMetadata manuscript={manuscript} />
+          {status === 'completed' ? (
+            <Review review={review} />
+          ) : (
+            <ReviewForm
+              handleSubmit={handleSubmit}
+              isValid={isValid}
+              review={review}
+              updateReview={updateReview}
+              uploadFile={uploadFile}
+            />
+          )}
+        </div>
+      ),
+      key: manuscript.id,
+      label,
+    })
+
+    editorSections.push(addEditor(manuscript, label))
+  }
+  return (
+    <Columns>
+      <Manuscript>
+        <Tabs
+          activeKey={editorSections[editorSections.length - 1].key}
+          sections={editorSections}
+          title="Versions"
+        />
+      </Manuscript>
+
+      <Admin>
+        <Tabs
+          activeKey={reviewSections[reviewSections.length - 1].key}
+          sections={reviewSections}
+          title="Versions"
+        />
+      </Admin>
+    </Columns>
+  )
+}
+
+export default ReviewLayout
diff --git a/app/components/component-xpub-review/src/components/review/ReviewLayout.md b/app/components/component-xpub-review/src/components/review/ReviewLayout.md
new file mode 100644
index 0000000000000000000000000000000000000000..2ff154f99436bf6077e326c3c14c61b12c5a64aa
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/review/ReviewLayout.md
@@ -0,0 +1,94 @@
+A page for a reviewer to submit a review of a version of a project.
+
+```js
+const { withFormik } = require('formik')
+
+const journal = {
+  id: faker.random.uuid(),
+}
+
+const manuscriptTemplate = () => ({
+  id: faker.random.uuid(),
+  teams: [
+    {
+      id: faker.random.uuid(),
+      role: 'reviewerEditor',
+      name: 'reviewer',
+      object: {
+        id: faker.random.uuid(),
+        __typename: 'Manuscript',
+      },
+      objectType: 'manuscript',
+      members: [
+        {
+          user: { id: 1 },
+        },
+      ],
+    },
+  ],
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  decision: {
+    id: faker.random.uuid(),
+    comments: [{ type: 'note', content: 'this needs review' }],
+    created: 'Thu Oct 11 2018',
+    open: false,
+    status: '<p>This is a decision</p>',
+    user: { id: 1 },
+  },
+  reviews: [
+    {
+      comments: [{ content: 'this needs review' }],
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: '',
+      user: { id: 1 },
+    },
+  ],
+})
+
+const manuscript = Object.assign({}, manuscriptTemplate(), {
+  manuscriptVersions: [manuscriptTemplate()],
+})
+
+const review = {
+  comments: [{ content: 'this needs review' }],
+  created: 'Thu Oct 11 2018',
+  open: false,
+  recommendation: '',
+  user: { id: 1 },
+}
+
+const currentUser = {
+  id: 1,
+}
+
+const ConnectedReviewLayout = withFormik({
+  initialValues: {},
+  mapPropsToValues: ({ manuscript, currentUser }) =>
+    manuscript.reviews.find(review => review.user.id === currentUser.id),
+  displayName: 'review',
+  handleSubmit: (props, { props: { onSubmit, history } }) =>
+    onSubmit(props, { history }),
+})(ReviewLayout)
+;<div style={{ position: 'relative', height: 600 }}>
+  <ConnectedReviewLayout
+    journal={journal}
+    manuscript={manuscript}
+    review={review}
+    uploadFile={() => {}}
+    currentUser={currentUser}
+  />
+</div>
+```
diff --git a/app/components/component-xpub-review/src/components/review/util.js b/app/components/component-xpub-review/src/components/review/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..a760b1ba6da04d9e430ba7cbd223e6fe334d8364
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/review/util.js
@@ -0,0 +1,38 @@
+export const stripHtml = htmlString => {
+  const temp = document.createElement('span')
+  temp.innerHTML = htmlString
+  return temp.textContent
+}
+
+export const getCommentFiles = (review = {}, type) => {
+  const comments =
+    (review.comments || []).find(comment => (comment || {}).type === type) || {}
+  return comments.files || []
+}
+
+export const getCommentContent = (review = {}, type) => {
+  const comments =
+    (review.comments || []).find(comment => (comment || {}).type === type) || {}
+  return comments.content || ''
+}
+
+export const createComments = (values, val, type) => {
+  let updateIndex = (values.comments || []).findIndex(
+    comment => (comment || {}).type === type,
+  )
+  updateIndex =
+    (values.comments || []).length > 0 && updateIndex < 0 ? 1 : updateIndex
+  updateIndex = updateIndex < 0 ? 0 : updateIndex
+
+  const comment = Object.assign(
+    {
+      type,
+      content: '',
+      files: [],
+    },
+    (values.comments || [])[updateIndex],
+    val,
+  )
+
+  return { updateIndex, comment }
+}
diff --git a/app/components/component-xpub-review/src/components/reviewers/Reviewer.js b/app/components/component-xpub-review/src/components/reviewers/Reviewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..f79fcf8b74880608e86dc74699d55123c1be34e4
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/reviewers/Reviewer.js
@@ -0,0 +1,39 @@
+import React from 'react'
+import styled from 'styled-components'
+// import { map } from 'lodash'
+// import Moment from 'react-moment'
+import { Avatar } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Root = styled.div`
+  font-family: ${th('fontReviewer')};
+  margin-right: ${th('gridUnit')};
+  padding: ${th('gridUnit')};
+`
+
+const ordinalLetter = ordinal =>
+  ordinal ? String.fromCharCode(96 + ordinal) : null
+
+const Reviewer = ({ reviewer, removeReviewer }) => (
+  <Root>
+    <Avatar
+      height={70}
+      reviewerLetter={ordinalLetter(null)}
+      status={reviewer.status || ''}
+      width={100}
+    />
+    <div>{reviewer.user.username}</div>
+    {/* <div>
+      {map(reviewer.events, (event, key) => (
+        <Event key={`${key}-${event}`}>
+          {key} on <Moment format="YYYY-MM-DD">{event}</Moment>
+        </Event>
+      ))}
+    </div>
+    {reviewer.status === 'Pending' && (
+      <Button onClick={removeReviewer}>x</Button>
+    )} */}
+  </Root>
+)
+
+export default Reviewer
diff --git a/app/components/component-xpub-review/src/components/reviewers/Reviewer.md b/app/components/component-xpub-review/src/components/reviewers/Reviewer.md
new file mode 100644
index 0000000000000000000000000000000000000000..e25abb2229cc063d0a3781e0809e006b46c5a5bd
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/reviewers/Reviewer.md
@@ -0,0 +1,47 @@
+A reviewer who has been invited:
+
+```js
+const reviewer = {
+  status: 'invited',
+  user: {
+    username: faker.internet.userName(),
+  },
+}
+;<Reviewer reviewer={reviewer} removeReviewer={() => console.log('remove')} />
+```
+
+A reviewer who has accepted their invitation:
+
+```js
+const reviewer = {
+  status: 'accepted',
+  user: {
+    username: faker.internet.userName(),
+  },
+}
+;<Reviewer reviewer={reviewer} removeReviewer={() => console.log('remove')} />
+```
+
+A reviewer who has declined their invitation:
+
+```js
+const reviewer = {
+  status: 'declined',
+  user: {
+    username: faker.internet.userName(),
+  },
+}
+;<Reviewer reviewer={reviewer} removeReviewer={() => console.log('remove')} />
+```
+
+A reviewer who has submitted their review:
+
+```js
+const reviewer = {
+  status: 'reviewed',
+  user: {
+    username: faker.internet.userName(),
+  },
+}
+;<Reviewer reviewer={reviewer} removeReviewer={() => console.log('remove')} />
+```
diff --git a/app/components/component-xpub-review/src/components/reviewers/ReviewerContainer.js b/app/components/component-xpub-review/src/components/reviewers/ReviewerContainer.js
new file mode 100644
index 0000000000000000000000000000000000000000..89b9a93c1b04a6bfc6d6e6e4deb6f483cd7ce599
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/reviewers/ReviewerContainer.js
@@ -0,0 +1,23 @@
+// import { compose, withHandlers } from 'recompose'
+// import { connect } from 'react-redux'
+// import { actions } from 'pubsweet-client'
+import Reviewer from './Reviewer'
+
+// const removeReviewer = props => () => {
+//   const { id } = props.reviewer
+
+//   return props.deleteFragment(props.project, { id })
+// }
+
+export default Reviewer
+// export default compose(
+//   connect(
+//     null,
+//     {
+//       deleteFragment: actions.deleteFragment,
+//     },
+//   ),
+//   withHandlers({
+//     removeReviewer: props => removeReviewer(props),
+//   }),
+// )(Reviewer)
diff --git a/app/components/component-xpub-review/src/components/reviewers/ReviewerForm.js b/app/components/component-xpub-review/src/components/reviewers/ReviewerForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..db27927ff3931edc6e27c825e6c9af772f9f1b35
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/reviewers/ReviewerForm.js
@@ -0,0 +1,55 @@
+import React from 'react'
+import Select from 'react-select'
+import { Field } from 'formik'
+import { Button } from '@pubsweet/ui'
+import { required } from 'xpub-validators'
+import 'react-select/dist/react-select.css'
+
+const OptionRenderer = option => (
+  <div>
+    <div>{option.username}</div>
+    <div>{option.email}</div>
+  </div>
+)
+
+const ReviewerInput = ({
+  field,
+  form: { values, setFieldValue },
+  push,
+  replace,
+  reviewerUsers,
+}) => (
+  <Select.Creatable
+    {...field}
+    labelKey="username"
+    onChange={user => {
+      setFieldValue('user', user)
+    }}
+    optionRenderer={OptionRenderer}
+    options={reviewerUsers}
+    promptTextCreator={label => `Add ${label}?`}
+    valueKey="id"
+  />
+)
+
+const ReviewerForm = ({
+  reset,
+  isValid,
+  handleSubmit,
+  onSubmit,
+  reviewerUsers,
+}) => (
+  <form onSubmit={handleSubmit}>
+    <Field
+      component={ReviewerInput}
+      name="user"
+      reviewerUsers={reviewerUsers}
+      validate={required}
+    />
+    <Button disabled={!isValid} primary type="submit">
+      Invite reviewer
+    </Button>
+  </form>
+)
+
+export default ReviewerForm
diff --git a/app/components/component-xpub-review/src/components/reviewers/ReviewerForm.md b/app/components/component-xpub-review/src/components/reviewers/ReviewerForm.md
new file mode 100644
index 0000000000000000000000000000000000000000..28ba3f44b2e30c6e50ac6e0bf0c41100539edef3
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/reviewers/ReviewerForm.md
@@ -0,0 +1,40 @@
+A form for inviting a reviewer to a version of a project.
+
+```js
+const { withFormik } = require('formik')
+
+const reviewerUsers = [
+  {
+    id: faker.random.uuid(),
+    email: faker.internet.email(),
+    username: faker.internet.userName(),
+  },
+  {
+    id: faker.random.uuid(),
+    email: faker.internet.email(),
+    username: faker.internet.userName(),
+  },
+  {
+    id: faker.random.uuid(),
+    email: faker.internet.email(),
+    username: faker.internet.userName(),
+  },
+]
+
+const loadOptions = input => {
+  // TODO: filter users
+
+  return Promise.resolve({ options: reviewerUsers })
+}
+
+const ConnectedReviewerForm = withFormik({
+  initialValues: {},
+  mapPropsToValues: ({ manuscript }) => manuscript,
+  displayName: 'reviewers',
+  handleSubmit: () => {},
+})(ReviewerForm)
+;<ConnectedReviewerForm
+  loadOptions={loadOptions}
+  form={{ values: { teams: [] } }}
+/>
+```
diff --git a/app/components/component-xpub-review/src/components/reviewers/Reviewers.js b/app/components/component-xpub-review/src/components/reviewers/Reviewers.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ccc8df39213d16c490a704448a998fa13a6348b
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/reviewers/Reviewers.js
@@ -0,0 +1,55 @@
+import React from 'react'
+import styled from 'styled-components'
+import { Link } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+import ReviewerForm from './ReviewerForm'
+
+const Root = styled.div`
+  display: flex;
+  margin-top: calc(${th('gridUnit')} * 3);
+`
+const Form = styled.div``
+const ReviewersList = styled.div`
+  display: flex;
+  flex-wrap: wrap;
+`
+
+const Reviewers = ({
+  Reviewer,
+  journal,
+  isValid,
+  loadOptions,
+  version,
+  reviewers,
+  reviewerUsers,
+  manuscript,
+  handleSubmit,
+  teams,
+}) => (
+  <Root>
+    <Form>
+      <ReviewerForm
+        handleSubmit={handleSubmit}
+        isValid={isValid}
+        journal={journal}
+        loadOptions={loadOptions}
+        reviewerUsers={reviewerUsers}
+      />
+      <Link
+        to={`/journals/${journal.id}/versions/${manuscript.id}/decisions/${manuscript.id}`}
+      >
+        Back to Control Panel
+      </Link>
+    </Form>
+
+    {reviewers && (
+      <ReviewersList>
+        {reviewers.map(reviewer => (
+          <Reviewer journal={journal} key={reviewer.id} reviewer={reviewer} />
+        ))}
+      </ReviewersList>
+    )}
+  </Root>
+)
+
+export default Reviewers
diff --git a/app/components/component-xpub-review/src/components/reviewers/Reviewers.md b/app/components/component-xpub-review/src/components/reviewers/Reviewers.md
new file mode 100644
index 0000000000000000000000000000000000000000..af59ba8a78bd87cf531da1aca607646a3701194b
--- /dev/null
+++ b/app/components/component-xpub-review/src/components/reviewers/Reviewers.md
@@ -0,0 +1,146 @@
+On the reviewers page, the handling editor can:
+
+- Search users by entering a username or email address.
+- Add a user as a reviewer of this version (which also adds them as a reviewer of the project, if not already present).
+- View a list of reviewers of this version and perform actions on each reviewer.
+
+```js
+const { withFormik } = require('formik')
+const { compose, withHandlers } = require('recompose')
+const Reviewer = require('./Reviewer').default
+const ReviewerForm = require('./ReviewerForm').default
+
+const journal = {
+  id: faker.random.uuid(),
+  reviewers: [
+    {
+      id: faker.random.uuid(),
+      user: faker.random.uuid(),
+    },
+  ],
+}
+
+const manuscriptTemplate = () => ({
+  id: faker.random.uuid(),
+  teams: [
+    {
+      id: faker.random.uuid(),
+      role: 'reviewerEditor',
+      name: 'reviewer',
+      object: {
+        id: faker.random.uuid(),
+        __typename: 'Manuscript',
+      },
+      objectType: 'manuscript',
+      members: [
+        {
+          user: { id: 1 },
+        },
+      ],
+    },
+  ],
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  decision: {
+    id: faker.random.uuid(),
+    comments: [{ type: 'note', content: 'this needs review' }],
+    created: 'Thu Oct 11 2018',
+    open: false,
+    status: '<p>This is a decision</p>',
+    user: { id: 1 },
+  },
+  reviews: [
+    {
+      comments: [{ content: 'this needs review' }],
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: 'revise',
+      user: { id: 1, username: 'test user' },
+    },
+  ],
+})
+
+const manuscript = Object.assign({}, manuscriptTemplate())
+
+const reviewers = [
+  {
+    status: 'invited',
+    user: { id: 1, username: 'test user' },
+  },
+]
+
+const reviewerUsers = [
+  {
+    id: faker.random.uuid(),
+    email: faker.internet.email(),
+    username: faker.internet.userName(),
+  },
+  {
+    id: faker.random.uuid(),
+    email: faker.internet.email(),
+    username: faker.internet.userName(),
+  },
+  {
+    id: faker.random.uuid(),
+    email: faker.internet.email(),
+    username: faker.internet.userName(),
+  },
+]
+
+initialState = {
+  reviewers,
+}
+
+const ReviewerFormContainer = compose(
+  withFormik({
+    form: 'reviewers',
+    handleSubmit: ({ user }) => {
+      setState({
+        reviewers: state.reviewers.concat({
+          id: faker.random.uuid(),
+          reviewer: faker.random.uuid(),
+          events: {
+            invited: new Date().toISOString(),
+          },
+          _user: user,
+          _reviewer: {
+            ordinal: null,
+          },
+        }),
+      })
+    },
+  }),
+  withHandlers({
+    loadOptions: props => input =>
+      Promise.resolve({ options: props.reviewerUsers }),
+  }),
+)(ReviewerForm)
+
+const ReviewerContainer = withHandlers({
+  removeReviewer: props => () =>
+    setState({
+      reviewers: state.reviewers.filter(
+        reviewer => reviewer.id !== props.reviewer.id,
+      ),
+    }),
+})(Reviewer)
+;<Reviewers
+  ReviewerForm={ReviewerFormContainer}
+  Reviewer={ReviewerContainer}
+  journal={journal}
+  manuscript={manuscript}
+  reviewers={state.reviewers}
+  reviewerUsers={reviewerUsers}
+/>
+```
diff --git a/app/components/component-xpub-review/src/index.js b/app/components/component-xpub-review/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f1f42a64b1ecf4a71345e76fa416d89015cb96c
--- /dev/null
+++ b/app/components/component-xpub-review/src/index.js
@@ -0,0 +1,5 @@
+module.exports = {
+  frontend: {
+    components: [() => require('./components')],
+  },
+}
diff --git a/app/components/component-xpub-submit/CHANGELOG.md b/app/components/component-xpub-submit/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..e50efda395053f4eea409ed8a067deea08700b1b
--- /dev/null
+++ b/app/components/component-xpub-submit/CHANGELOG.md
@@ -0,0 +1,708 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [6.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.21...pubsweet-component-xpub-submit@6.1.0) (2019-11-11)
+
+
+### Features
+
+* **xpub:** bring back xpub components ([fb69994](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb69994098b4e2dbcca75b4786ebb1335af730b9))
+
+
+
+
+
+## [6.0.21](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.20...pubsweet-component-xpub-submit@6.0.21) (2019-09-11)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.20](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.19...pubsweet-component-xpub-submit@6.0.20) (2019-09-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.19](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.18...pubsweet-component-xpub-submit@6.0.19) (2019-08-30)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.17...pubsweet-component-xpub-submit@6.0.18) (2019-08-08)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.16...pubsweet-component-xpub-submit@6.0.17) (2019-08-05)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.15...pubsweet-component-xpub-submit@6.0.16) (2019-07-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.14...pubsweet-component-xpub-submit@6.0.15) (2019-07-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.13...pubsweet-component-xpub-submit@6.0.14) (2019-07-03)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.12...pubsweet-component-xpub-submit@6.0.13) (2019-06-28)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.11...pubsweet-component-xpub-submit@6.0.12) (2019-06-24)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.10...pubsweet-component-xpub-submit@6.0.11) (2019-06-21)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.9...pubsweet-component-xpub-submit@6.0.10) (2019-06-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.8...pubsweet-component-xpub-submit@6.0.9) (2019-06-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.7...pubsweet-component-xpub-submit@6.0.8) (2019-05-27)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.6...pubsweet-component-xpub-submit@6.0.7) (2019-04-25)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.5...pubsweet-component-xpub-submit@6.0.6) (2019-04-18)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.4...pubsweet-component-xpub-submit@6.0.5) (2019-04-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.3...pubsweet-component-xpub-submit@6.0.4) (2019-03-06)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.2...pubsweet-component-xpub-submit@6.0.3) (2019-03-05)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.1...pubsweet-component-xpub-submit@6.0.2) (2019-02-19)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [6.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@6.0.0...pubsweet-component-xpub-submit@6.0.1) (2019-02-19)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+# [6.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@5.0.7...pubsweet-component-xpub-submit@6.0.0) (2019-02-01)
+
+
+### Bug Fixes
+
+* **eslint:** submit form ([1462c10](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1462c10))
+* **styleguide:** temporarily disable styleguide ([e519ed1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e519ed1))
+* **submit:** fixing submit confirm button ([8aee1e5](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8aee1e5))
+
+
+### Code Refactoring
+
+* temporarily remove unmigrated components ([32db6ad](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/32db6ad))
+
+
+### BREAKING CHANGES
+
+* A lot of unmigrated (not yet moved from REST/Redux to GraphQL/Apollo system) bits
+have changed. There might be some breaking changes as a result. This is a big migration involving
+big changes - if you encounter anything weird, please contact us on GitLab or on Mattermost.
+
+
+
+
+
+## [5.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@5.0.6...pubsweet-component-xpub-submit@5.0.7) (2019-01-16)
+
+
+### Bug Fixes
+
+* **components:** fixing components after new manuscript version ([89537ff](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/89537ff))
+* **components:** graphql data model changes ([4b61093](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4b61093))
+* **graphql:** review components fixes ([8094d9e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8094d9e))
+* **merge:** merging to master ([8603808](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8603808))
+* **submit:** intro template ([6c850e1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6c850e1))
+* **submit:** intro template fix eslint ([fcc2599](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fcc2599))
+* **test:** formbuilder ([93c55fd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/93c55fd))
+* **test:** problems with eslint and test ([48f7fe2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/48f7fe2))
+* **xpub-review:** changes tp reviews ([5ae4240](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5ae4240))
+
+
+
+
+
+## [5.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@5.0.5...pubsweet-component-xpub-submit@5.0.6) (2019-01-14)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [5.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@5.0.4...pubsweet-component-xpub-submit@5.0.5) (2019-01-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [5.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@5.0.3...pubsweet-component-xpub-submit@5.0.4) (2019-01-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [5.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@5.0.2...pubsweet-component-xpub-submit@5.0.3) (2018-12-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [5.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@5.0.1...pubsweet-component-xpub-submit@5.0.2) (2018-12-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+## [5.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@5.0.0...pubsweet-component-xpub-submit@5.0.1) (2018-11-30)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+
+
+
+
+# [5.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.2.0...pubsweet-component-xpub-submit@5.0.0) (2018-11-29)
+
+
+### Features
+
+* **various:** update styled-components ([5c51466](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5c51466))
+* **various:** upgrade styled-components ([9b886f6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9b886f6))
+
+
+### BREAKING CHANGES
+
+* **various:** Replace all styled-components .extend with styled()
+* **various:** Replace styled-components injectGlobal with new createGlobalStyle
+
+
+
+
+
+<a name="4.2.0"></a>
+# [4.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.1.4...pubsweet-component-xpub-submit@4.2.0) (2018-11-05)
+
+
+### Features
+
+* GraphQL Xpub submit component ([ba07060](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ba07060))
+
+
+
+
+<a name="4.1.4"></a>
+## [4.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.1.3...pubsweet-component-xpub-submit@4.1.4) (2018-10-08)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.1.3"></a>
+## [4.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.1.2...pubsweet-component-xpub-submit@4.1.3) (2018-09-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.1.2"></a>
+## [4.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.1.1...pubsweet-component-xpub-submit@4.1.2) (2018-09-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.1.1"></a>
+## [4.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.1.0...pubsweet-component-xpub-submit@4.1.1) (2018-09-06)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.1.0"></a>
+# [4.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.10...pubsweet-component-xpub-submit@4.1.0) (2018-09-04)
+
+
+### Bug Fixes
+
+* **editor:** remove warnings ([b563efd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/b563efd))
+* **fomrbuilder:** fix validation ([98b3b5e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/98b3b5e))
+* **foormbuilder:** linter prettier ([db290ff](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/db290ff))
+* **formbuilder:** create folder from scratch ([a2d533e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a2d533e))
+* **formbuilder:** fixing styling issues to submit ([54bc9a3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/54bc9a3))
+* **submit:** fixing authors save field ([bb89f08](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/bb89f08))
+* **submit:** form was updated correctly ([94f9844](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/94f9844))
+
+
+### Features
+
+* **formbuilder:** add formbuilder component ([c24b9f7](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c24b9f7))
+* **formbuilder:** add validation for elements ([882935a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/882935a))
+* **submit:** import dynamically the form template ([ac4649e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ac4649e))
+
+
+
+
+<a name="4.0.10"></a>
+## [4.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.9...pubsweet-component-xpub-submit@4.0.10) (2018-08-22)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.0.9"></a>
+## [4.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.8...pubsweet-component-xpub-submit@4.0.9) (2018-08-20)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.0.8"></a>
+## [4.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.7...pubsweet-component-xpub-submit@4.0.8) (2018-08-17)
+
+
+### Bug Fixes
+
+* **actions:** validationStatus fix ([762432f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/762432f))
+
+
+
+
+<a name="4.0.7"></a>
+## [4.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.6...pubsweet-component-xpub-submit@4.0.7) (2018-08-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.0.6"></a>
+## [4.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.5...pubsweet-component-xpub-submit@4.0.6) (2018-07-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.0.5"></a>
+## [4.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.4...pubsweet-component-xpub-submit@4.0.5) (2018-07-23)
+
+
+### Bug Fixes
+
+* **submit:** authors Input start open ([03bbe31](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/03bbe31))
+* **submit:** fix lint error ([d6c2077](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d6c2077))
+
+
+
+
+<a name="4.0.4"></a>
+## [4.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.3...pubsweet-component-xpub-submit@4.0.4) (2018-07-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.0.3"></a>
+## [4.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.2...pubsweet-component-xpub-submit@4.0.3) (2018-07-12)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.0.2"></a>
+## [4.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.1...pubsweet-component-xpub-submit@4.0.2) (2018-07-09)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.0.1"></a>
+## [4.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@4.0.0...pubsweet-component-xpub-submit@4.0.1) (2018-07-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="4.0.0"></a>
+# [4.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@3.0.1...pubsweet-component-xpub-submit@4.0.0) (2018-07-02)
+
+
+### Features
+
+* **ui:** introduce more line height variables ([85c24e2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/85c24e2))
+
+
+### BREAKING CHANGES
+
+* **ui:** the existing fontLineHeight variable is gone and replaced by multiple new variables
+
+
+
+
+<a name="3.0.1"></a>
+## [3.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@3.0.0...pubsweet-component-xpub-submit@3.0.1) (2018-06-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="3.0.0"></a>
+# [3.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@2.0.1...pubsweet-component-xpub-submit@3.0.0) (2018-06-28)
+
+
+### Bug Fixes
+
+* **submit:** add text SubNotes to submit ([d034b05](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d034b05))
+* **submit:** change texts get from config ([fbd209f](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fbd209f))
+
+
+### Code Refactoring
+
+* **ui:** replace current gridunit variables with one small value ([cf48f29](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/cf48f29))
+
+
+### BREAKING CHANGES
+
+* **ui:** Your ui components will now be multiplying a much smaller value so they need to be
+adjusted
+
+
+
+
+<a name="2.0.1"></a>
+## [2.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@2.0.0...pubsweet-component-xpub-submit@2.0.1) (2018-06-19)
+
+
+### Bug Fixes
+
+* **pubsweet-ui:** tests are failing ([0e57798](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0e57798))
+
+
+
+
+<a name="2.0.0"></a>
+# [2.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@1.2.2...pubsweet-component-xpub-submit@2.0.0) (2018-06-01)
+
+
+### Bug Fixes
+
+* **components:** submit button show ([2635368](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2635368))
+
+
+### Features
+
+* **ui:** start ui-toolkit module ([2083b9c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2083b9c))
+
+
+### BREAKING CHANGES
+
+* **ui:** th now comes from the toolkit, so all th imports from ui are now broken
+
+
+
+
+<a name="1.2.2"></a>
+## [1.2.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@1.2.1...pubsweet-component-xpub-submit@1.2.2) (2018-05-21)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="1.2.1"></a>
+## [1.2.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@1.2.0...pubsweet-component-xpub-submit@1.2.1) (2018-05-18)
+
+
+### Bug Fixes
+
+* **components:** add tests to suggestions component ([50777b3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/50777b3))
+* **components:** rewrite conditional checks to more clean ([c41d79d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c41d79d))
+* **components:** submit submitted versions ([48d07ee](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/48d07ee))
+
+
+
+
+<a name="1.2.0"></a>
+# [1.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@1.1.0...pubsweet-component-xpub-submit@1.2.0) (2018-05-10)
+
+
+### Bug Fixes
+
+* **components:** fix lint errors ([4e22ec1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4e22ec1))
+* **components:** fix linting issues ([4385b58](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4385b58))
+* **components:** fixes in linter ([7c31f6b](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7c31f6b))
+* **components:** html parse, styled components ([8b24552](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8b24552))
+* **components:** linter ([9aac3fa](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9aac3fa))
+* **components:** merge two commponets two one ([4e2ed76](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4e2ed76))
+* **components:** redirect submission add selectors ([53db5a7](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/53db5a7))
+* **components:** review page layout ([4ea2cdd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4ea2cdd))
+* **components:** take care of case of zero files ([82cff08](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/82cff08))
+* **components:** title wording ([0c293f4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0c293f4))
+
+
+### Features
+
+* **components:** add columns to submission and tabs ([40470a0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/40470a0))
+* **components:** add current version files ([4c77f3c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4c77f3c))
+* **components:** add tabs to submission ([0e45892](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0e45892))
+* **components:** create accordion component ([05a23e4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/05a23e4))
+* **components:** create accordion component ([54f5b7d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/54f5b7d))
+
+
+
+
+<a name="1.1.0"></a>
+# [1.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@1.0.0...pubsweet-component-xpub-submit@1.1.0) (2018-05-09)
+
+
+### Bug Fixes
+
+* fixed misnamed redux form props in authors input ([940edc0](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/940edc0))
+* fixed misnamed redux form props in authors input ([bb4af56](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/bb4af56))
+* fixed misnamed redux form props in authors input ([fb362b2](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb362b2))
+
+
+### Features
+
+* add AuthorsInput component ([f7d12b3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f7d12b3))
+* authors input, added padding around fields ([1e5d742](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1e5d742))
+* authors input, fixed merge error ([c908fa4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c908fa4))
+* authors input, fixed prettier errors ([0657143](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0657143))
+* authors input,component  updated to ensure at least one author ([d43dd92](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/d43dd92))
+* list styles for authors input ([3f85bbd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3f85bbd))
+* two inputs per line ([aa0544a](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/aa0544a))
+* update MetadataFields to use AuthorsInput component ([fa1640e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fa1640e))
+* update MetadataFields to use AuthorsInput component ([1baac87](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1baac87))
+* update MetadataFields to use AuthorsInput component ([355f282](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/355f282))
+
+
+
+
+<a name="1.0.0"></a>
+# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.1.1...pubsweet-component-xpub-submit@1.0.0) (2018-05-03)
+
+
+### Bug Fixes
+
+* **theme:** remove warning color ([c0897c8](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c0897c8))
+
+
+### BREAKING CHANGES
+
+* **theme:** might break components that used the warning colors
+
+
+
+
+<a name="0.1.1"></a>
+## [0.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.1.0...pubsweet-component-xpub-submit@0.1.1) (2018-05-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="0.1.0"></a>
+# [0.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.0.10...pubsweet-component-xpub-submit@0.1.0) (2018-04-24)
+
+
+### Bug Fixes
+
+* **compoenents:** fix cases of empty objects in metadata ([7a5bfbc](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/7a5bfbc))
+* **component:** put striphtml function back to place ([2a69dca](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2a69dca))
+* **components:** change value to files at upload components ([aa2b45e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/aa2b45e))
+* **xpub-submit:** use no-redux version of uploadFile ([cc904a3](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/cc904a3))
+
+
+### Features
+
+* **xpub-submit:** move GraphQL functionality into separate component ([cfb2a81](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/cfb2a81))
+
+
+
+
+<a name="0.0.10"></a>
+## [0.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.0.9...pubsweet-component-xpub-submit@0.0.10) (2018-04-11)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="0.0.9"></a>
+## [0.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.0.8...pubsweet-component-xpub-submit@0.0.9) (2018-04-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="0.0.8"></a>
+## [0.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.0.7...pubsweet-component-xpub-submit@0.0.8) (2018-03-30)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="0.0.7"></a>
+## [0.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.0.6...pubsweet-component-xpub-submit@0.0.7) (2018-03-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="0.0.6"></a>
+## [0.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.0.5...pubsweet-component-xpub-submit@0.0.6) (2018-03-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="0.0.5"></a>
+## [0.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.0.4...pubsweet-component-xpub-submit@0.0.5) (2018-03-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="0.0.4"></a>
+## [0.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-submit@0.0.3...pubsweet-component-xpub-submit@0.0.4) (2018-03-15)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
+
+<a name="0.0.3"></a>
+
+## 0.0.3 (2018-03-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-submit
diff --git a/app/components/component-xpub-submit/README.md b/app/components/component-xpub-submit/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b4d5981c2b06d849a90c3ae327084373100394fc
--- /dev/null
+++ b/app/components/component-xpub-submit/README.md
@@ -0,0 +1,6 @@
+## pubsweet-component-xpub-submit
+
+A PubSweet component that provides the form which the author completes before submitting a manuscript.
+
+_Note:
+The previously-released version of this component used JSON configuration to describe the form, and that can be implemented here too, once we know more about the types of input that are needed in submission forms for other journals._
diff --git a/app/components/component-xpub-submit/package.json b/app/components/component-xpub-submit/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..d0d99520553c671389373949abbec621403183f1
--- /dev/null
+++ b/app/components/component-xpub-submit/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "pubsweet-component-xpub-submit",
+  "version": "6.1.0",
+  "main": "src",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "dependencies": {
+    "@apollo/react-hoc": "^3.0.1",
+    "@pubsweet/ui": "^12.1.0",
+    "@pubsweet/ui-toolkit": "^2.2.14",
+    "apollo-client-preset": "^1.0.8",
+    "formik": "^1.4.2",
+    "lodash": "^4.17.11",
+    "moment": "^2.23.0",
+    "prop-types": "^15.5.10",
+    "react-feather": "^1.1.5",
+    "react-html-parser": "^2.0.2",
+    "recompose": "^0.30.0",
+    "styled-components": "^4.1.1",
+    "xpub-edit": "^2.6.0",
+    "xpub-journal": "^0.1.0",
+    "xpub-validators": "^0.0.28"
+  },
+  "peerDependencies": {
+    "pubsweet-client": ">=2.1.0",
+    "react": ">=16.9",
+    "react-router-dom": ">=5.0.0"
+  },
+  "gitHead": "6b100b76f21785e5e50fca082a2743d3d0b1c88a"
+}
diff --git a/app/components/component-xpub-submit/src/components/AuthorsInput.js b/app/components/component-xpub-submit/src/components/AuthorsInput.js
new file mode 100644
index 0000000000000000000000000000000000000000..63233effb3ff99800aa9606c0a9b8bc04ea580b5
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/AuthorsInput.js
@@ -0,0 +1,141 @@
+import React from 'react'
+import styled from 'styled-components'
+import { FieldArray } from 'formik'
+import { cloneDeep, set } from 'lodash'
+import { TextField, Button, ValidatedFieldFormik } from '@pubsweet/ui'
+import { minSize } from 'xpub-validators'
+
+const minSize1 = minSize(1)
+
+const Inline = styled.div`
+  display: inline-block;
+  margin-right: 30px;
+`
+
+const UnbulletedList = styled.div`
+  list-style-type: none;
+  margin-left: -40px;
+`
+
+const Spacing = styled.div`
+  padding: 15px 0px;
+`
+
+const Author = styled.div`
+  padding-bottom: 10px;
+`
+
+const firstNameInput = input => (
+  <TextField label="First name" placeholder="Enter first name…" {...input} />
+)
+
+const lastNameInput = input => (
+  <TextField label="Last name" placeholder="Enter last name…" {...input} />
+)
+
+const emailAddressInput = input => (
+  <TextField
+    label="Email address"
+    placeholder="Enter email address…"
+    {...input}
+  />
+)
+
+const affiliationInput = input => (
+  <TextField label="Affiliation" placeholder="Enter affiliation…" {...input} />
+)
+
+const onChangeFn = (onChange, setFieldValue, values) => value => {
+  const val = value.target ? value.target.value : value
+  setFieldValue(value.target.name, val, true)
+
+  const data = cloneDeep(values)
+  set(data, value.target.name, val)
+  onChange(data.authors, 'authors')
+}
+
+const renderAuthors = onChange => ({
+  form: { values, setFieldValue },
+  insert,
+  remove,
+}) => (
+  <ul>
+    <UnbulletedList>
+      <li>
+        <Button
+          onClick={() =>
+            insert((values.authors || []).length, {
+              firstName: '',
+              lastName: '',
+              email: '',
+              affiliation: '',
+            })
+          }
+          plain
+          type="button"
+        >
+          Add another author
+        </Button>
+      </li>
+      {(values.authors || []).map((author, index) => (
+        <li key={`author-${author}`}>
+          <Spacing>
+            <Author>
+              Author:&nbsp;
+              {values.authors.length > 1 && (
+                <Button onClick={() => remove(index)} type="button">
+                  Remove
+                </Button>
+              )}
+            </Author>
+            <div>
+              <Inline>
+                <ValidatedFieldFormik
+                  component={firstNameInput}
+                  name={`authors.${index}.firstName`}
+                  onChange={onChangeFn(onChange, setFieldValue, values)}
+                  validate={minSize1}
+                />
+              </Inline>
+
+              <Inline>
+                <ValidatedFieldFormik
+                  component={lastNameInput}
+                  name={`authors.[${index}].lastName`}
+                  onChange={onChangeFn(onChange, setFieldValue, values)}
+                  validate={minSize1}
+                />
+              </Inline>
+            </div>
+
+            <div>
+              <Inline>
+                <ValidatedFieldFormik
+                  component={emailAddressInput}
+                  name={`authors.[${index}].email`}
+                  onChange={onChangeFn(onChange, setFieldValue, values)}
+                  validate={minSize1}
+                />
+              </Inline>
+
+              <Inline>
+                <ValidatedFieldFormik
+                  component={affiliationInput}
+                  name={`authors.[${index}].affiliation`}
+                  onChange={onChangeFn(onChange, setFieldValue, values)}
+                  validate={minSize1}
+                />
+              </Inline>
+            </div>
+          </Spacing>
+        </li>
+      ))}
+    </UnbulletedList>
+  </ul>
+)
+
+const AuthorsInput = ({ onChange }) => (
+  <FieldArray name="authors" render={renderAuthors(onChange)} />
+)
+
+export default AuthorsInput
diff --git a/app/components/component-xpub-submit/src/components/Confirm.js b/app/components/component-xpub-submit/src/components/Confirm.js
new file mode 100644
index 0000000000000000000000000000000000000000..b181aa850eb7d18efe8dd9af1e47fb30b13f11a3
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Confirm.js
@@ -0,0 +1,49 @@
+import React from 'react'
+import styled from 'styled-components'
+import { Button, PlainButton } from '@pubsweet/ui'
+import { unescape } from 'lodash'
+import { th } from '@pubsweet/ui-toolkit'
+import { Heading1 } from '../styles'
+
+const Wrapper = styled.div`
+  background: ${th('colorBackground')};
+  color: ${th('colorText')};
+  line-height: ${th('lineHeightBase')};
+  max-height: 100%;
+  max-width: 60em;
+  overflow-y: auto;
+  padding: calc(${th('gridUnit')} * 6);
+`
+
+const Paragraph = styled.p`
+  font-size: ${th('fontSizeBase')};
+  margin-bottom: calc(${th('gridUnit')} * 3);
+  width: 100%;
+`
+
+const Divider = styled.span`
+  margin: 0 ${th('gridUnit')};
+`
+const createMarkup = encodedHtml => ({
+  __html: unescape(encodedHtml),
+})
+
+const Confirm = ({ toggleConfirming, form, submitSubmission }) => (
+  <Wrapper>
+    <article>
+      <Heading1 dangerouslySetInnerHTML={createMarkup(form.popuptitle)} />
+      <Paragraph
+        dangerouslySetInnerHTML={createMarkup(form.popupdescription)}
+      />
+      <Button onClick={submitSubmission} primary type="submit">
+        Submit your manuscript
+      </Button>
+      <Divider> or </Divider>
+      <PlainButton onClick={toggleConfirming}>
+        get back to your submission
+      </PlainButton>
+    </article>
+  </Wrapper>
+)
+
+export default Confirm
diff --git a/app/components/component-xpub-submit/src/components/Confirm.md b/app/components/component-xpub-submit/src/components/Confirm.md
new file mode 100644
index 0000000000000000000000000000000000000000..a5773b2ca69e1727b22fa61800b0969d2ed5ae51
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Confirm.md
@@ -0,0 +1,14 @@
+A series of confirmation paragraphs that the user must read and agree to before confirming the submission.
+
+The user can confirm submission using the primary button, or return to the submission using a link.
+
+```js
+const form = {
+  haspopup: 'true',
+  id: 'submit',
+  name: 'Submission information',
+  popupdescription: faker.lorem.sentences(50),
+  popuptitle: faker.lorem.words(3),
+}
+;<Confirm form={form} />
+```
diff --git a/app/components/component-xpub-submit/src/components/CurrentVersion.js b/app/components/component-xpub-submit/src/components/CurrentVersion.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e6ab4516d31fef5ca7895a7736674e51a3d5f50
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/CurrentVersion.js
@@ -0,0 +1,81 @@
+import React from 'react'
+import styled from 'styled-components'
+import { Link } from 'react-router-dom'
+import { Attachment } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+import Metadata from './MetadataFields'
+import Declarations from './Declarations'
+import Suggestions from './Suggestions'
+import SupplementaryFiles from './SupplementaryFiles'
+
+import { Heading1, Section, Legend } from '../styles'
+
+const Wrapper = styled.div`
+  font-family: ${th('fontInterface')};
+  line-height: 1.3;
+  margin: auto;
+  max-width: 60em;
+
+  overflow: ${({ confirming }) => confirming && 'hidden'};
+`
+
+const Intro = styled.div`
+  font-style: italic;
+  line-height: 1.4;
+`
+
+const filterFileManuscript = files =>
+  files.filter(
+    file =>
+      file.type === 'manuscript' &&
+      file.mimeType ===
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+  )
+
+// Due to migration to new Data Model
+// Attachement component needs different data structure to work
+// needs to change the pubsweet ui Attachement to support the new Data Model
+const filesToAttachment = file => ({
+  name: file.filename,
+  url: file.url,
+})
+
+const CurrentVersion = ({ journal, forms, manuscript }) => (
+  <Wrapper>
+    <Heading1>Submission information</Heading1>
+
+    <Intro>
+      <div>
+        We have ingested your manuscript. To access your manuscript in an
+        editor, please{' '}
+        <Link
+          to={`/journals/${journal.id}/versions/${manuscript.id}/manuscript`}
+        >
+          view here
+        </Link>
+        .
+      </div>
+      <div>
+        To complete your submission, please answer the following questions.
+      </div>
+      <div>The answers will be automatically saved.</div>
+    </Intro>
+
+    <Metadata manuscript={manuscript} />
+    <Declarations forms={forms} manuscript={manuscript} />
+    <Suggestions manuscript={manuscript} />
+    <SupplementaryFiles manuscript={manuscript} />
+    {filterFileManuscript(manuscript.files || []).length > 0 && (
+      <Section id="files.manuscript">
+        <Legend space>Submitted Manuscript</Legend>
+        <Attachment
+          file={filesToAttachment(filterFileManuscript(manuscript.files)[0])}
+          key={filterFileManuscript(manuscript.files)[0].url}
+          uploaded
+        />
+      </Section>
+    )}
+  </Wrapper>
+)
+
+export default CurrentVersion
diff --git a/app/components/component-xpub-submit/src/components/CurrentVersion.md b/app/components/component-xpub-submit/src/components/CurrentVersion.md
new file mode 100644
index 0000000000000000000000000000000000000000..2dde98ad20be1788b814f5917e4e50725d277746
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/CurrentVersion.md
@@ -0,0 +1,62 @@
+A form for entering information about the submission.
+
+```js
+const journal = {
+  id: faker.random.uuid(),
+}
+
+const manuscript = {
+  id: faker.random.uuid(),
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  suggestions: {
+    reviewers: {
+      opposed: faker.name.findName(),
+    },
+  },
+  reviews: [
+    {
+      comments: { content: 'this needs review' },
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: '',
+      user: { identities: [] },
+    },
+  ],
+}
+
+const forms = {
+  children: [
+    {
+      title: faker.lorem.sentence(5),
+      name: 'meta.declarations.openData',
+    },
+    {
+      title: faker.lorem.sentence(5),
+      name: 'meta.declarations.openPeerReview',
+    },
+    {
+      title: faker.lorem.sentence(5),
+      name: 'meta.declarations.previouslySubmitted',
+    },
+    {
+      title: faker.lorem.sentence(5),
+      name: 'meta.declarations.researchNexus',
+    },
+  ],
+}
+;<div style={{ position: 'relative', paddingRight: 100 }}>
+  <CurrentVersion forms={forms} manuscript={manuscript} journal={journal} />
+</div>
+```
diff --git a/app/components/component-xpub-submit/src/components/DecisionReviewColumn.js b/app/components/component-xpub-submit/src/components/DecisionReviewColumn.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b7b3e90e1b91e8a29d1027359afdc1128206226
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/DecisionReviewColumn.js
@@ -0,0 +1,65 @@
+import React from 'react'
+import styled from 'styled-components'
+import { Section } from '../styles'
+import { Review } from './atoms/Columns'
+import Accordion from './molecules/Accordion'
+
+const ReviewAccord = styled.div``
+
+const ReviewsItem = styled.div`
+  margin-left: 1em;
+`
+
+const ReviewAccordion = ({ reviews }) => (
+  <ReviewAccord>
+    {reviews.length > 0 &&
+      reviews.map(
+        (review, reviewId) =>
+          review.comments.length &&
+          review.comments.map((comment, commentId) => (
+            <Accordion
+              Component={comment.content}
+              key={`accordion-review-${review.id}`}
+              ordinal={reviewId + 1}
+              title="Review"
+              withDots="true"
+            />
+          )),
+      )}
+  </ReviewAccord>
+)
+
+const DecisionReviewColumn = ({
+  manuscript,
+  handleSubmit,
+  toggleOpen,
+  open,
+}) => (
+  <Review>
+    <Accordion
+      Component={<ReviewsItem>{manuscript.decision}</ReviewsItem>}
+      key="decision"
+      status="revise"
+      title="Decision"
+    />
+    <ReviewsItem>
+      {manuscript.reviews && (
+        <Section id="accordion.review">
+          <Accordion
+            Component={
+              <ReviewAccordion
+                reviews={manuscript.reviews.filter(
+                  review => !review.isDecision,
+                )}
+              />
+            }
+            key="review"
+            title="Reviews"
+          />
+        </Section>
+      )}
+    </ReviewsItem>
+  </Review>
+)
+
+export default DecisionReviewColumn
diff --git a/app/components/component-xpub-submit/src/components/Declarations.js b/app/components/component-xpub-submit/src/components/Declarations.js
new file mode 100644
index 0000000000000000000000000000000000000000..45dc867f1e2c074540d0e90d2390f890551590a5
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Declarations.js
@@ -0,0 +1,29 @@
+import React from 'react'
+import styled from 'styled-components'
+import ReactHtmlParser from 'react-html-parser'
+import { get } from 'lodash'
+import { th } from '@pubsweet/ui-toolkit'
+import { Section, Legend } from '../styles'
+
+const DeclarationSection = styled(Section)`
+  margin: calc(${th('gridUnit')} * 6) 0;
+`
+
+const Declarations = ({ forms, manuscript }) => (
+  <div>
+    <DeclarationSection>
+      <Legend>Type of article</Legend>
+      {manuscript.meta.articleType}
+    </DeclarationSection>
+    {(forms.children || [])
+      .filter(child => child.name.includes('meta.declarations'))
+      .map(question => (
+        <DeclarationSection key={`declaration-${question.id}`}>
+          <Legend>{ReactHtmlParser(question.title)}</Legend>
+          {get(manuscript, question.name)}
+        </DeclarationSection>
+      ))}
+  </div>
+)
+
+export default Declarations
diff --git a/app/components/component-xpub-submit/src/components/Declarations.md b/app/components/component-xpub-submit/src/components/Declarations.md
new file mode 100644
index 0000000000000000000000000000000000000000..8bf81ab7e5ee43e83633583a3cd003888e903e24
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Declarations.md
@@ -0,0 +1,57 @@
+A list of questions that must be answered before submission. The questions are
+configured via the journal config on the context.
+
+```js
+const forms = {
+  children: [
+    {
+      title: faker.lorem.sentence(5),
+      name: 'meta.declarations.openData',
+    },
+    {
+      title: faker.lorem.sentence(5),
+      name: 'meta.declarations.openPeerReview',
+    },
+    {
+      title: faker.lorem.sentence(5),
+      name: 'meta.declarations.previouslySubmitted',
+    },
+    {
+      title: faker.lorem.sentence(5),
+      name: 'meta.declarations.researchNexus',
+    },
+  ],
+}
+
+const manuscript = {
+  id: faker.random.uuid(),
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(100),
+    articleType: 'original-research',
+    declarations: {
+      openData: 'yes',
+      openPeerReview: 'no',
+      preregistered: 'yes',
+      previouslySubmitted: 'yes',
+      researchNexus: 'no',
+      streamlinedReview: 'no',
+    },
+  },
+  suggestions: {
+    reviewers: {
+      opposed: faker.name.findName(),
+    },
+  },
+  reviews: [
+    {
+      comments: { content: 'this needs review' },
+      created: 'Thu Oct 11 2018',
+      open: false,
+      recommendation: '',
+      user: { identities: [] },
+    },
+  ],
+}
+;<Declarations forms={forms} manuscript={manuscript} />
+```
diff --git a/app/components/component-xpub-submit/src/components/FormTemplate.js b/app/components/component-xpub-submit/src/components/FormTemplate.js
new file mode 100644
index 0000000000000000000000000000000000000000..984e1c24cd92b2fcaf99582277d6133c5cbb123c
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/FormTemplate.js
@@ -0,0 +1,338 @@
+import React from 'react'
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+import { unescape, groupBy, isArray, get, set, cloneDeep } from 'lodash'
+import { FieldArray } from 'formik'
+import * as uiComponents from '@pubsweet/ui'
+import * as validators from 'xpub-validators'
+import { AbstractEditor } from 'xpub-edit'
+import { Heading1, Section, Legend, SubNote } from '../styles'
+import AuthorsInput from './AuthorsInput'
+import Supplementary from './Supplementary'
+import Confirm from './Confirm'
+
+const Wrapper = styled.div`
+  font-family: ${th('fontInterface')};
+  line-height: 1.3;
+  margin: auto;
+  max-width: 60em;
+
+  overflow: ${({ confirming }) => confirming && 'hidden'};
+`
+
+const Intro = styled.div`
+  font-style: italic;
+  line-height: 1.4;
+`
+
+const ModalWrapper = styled.div`
+  align-items: center;
+  background: rgba(255, 255, 255, 0.95);
+  bottom: 0;
+  display: flex;
+  justify-content: center;
+  left: 0;
+  position: fixed;
+  right: 0;
+  top: 0;
+`
+
+// Due to migration to new Data Model
+// Attachement component needs different data structure to work
+// needs to change the pubsweet ui Attachement to support the new Data Model
+const filesToAttachment = file => ({
+  name: file.filename,
+  url: file.url,
+})
+
+const stripHtml = htmlString => {
+  const temp = document.createElement('span')
+  temp.innerHTML = htmlString
+  return temp.textContent
+}
+
+const filterFileManuscript = files =>
+  files.filter(
+    file =>
+      file.fileType === 'manuscript' &&
+      file.mimeType !==
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+  )
+
+const { ValidatedFieldFormik, Button, Attachment } = uiComponents
+
+// Add the AbstractEditor and AuthorsInput to the list of available form elements
+const elements = uiComponents
+elements.AbstractEditor = ({
+  validationStatus,
+  setTouched,
+  onChange,
+  value,
+  values,
+  ...rest
+}) => (
+  <AbstractEditor
+    value={get(values, rest.name) || ''}
+    {...rest}
+    onChange={val => {
+      setTouched(set({}, rest.name, true))
+      onChange(stripHtml(val))
+    }}
+  />
+)
+
+elements.AuthorsInput = AuthorsInput
+
+const rejectProps = (obj, keys) =>
+  Object.keys(obj)
+    .filter(k => !keys.includes(k))
+    .map(k => Object.assign({}, { [k]: obj[k] }))
+    .reduce(
+      (res, o) =>
+        Object.values(o).includes('false')
+          ? Object.assign({}, res)
+          : Object.assign(res, o),
+      {},
+    )
+
+const link = (journal, manuscript) =>
+  String.raw`<a href=/journals/${journal.id}/versions/${manuscript.id}/manuscript>view here</a>`
+
+const createMarkup = encodedHtml => ({
+  __html: unescape(encodedHtml),
+})
+
+const composeValidate = (vld = [], valueField = {}) => value => {
+  const validator = vld || []
+  if (validator.length === 0) return undefined
+  const errors = []
+  validator.map(validatorFn => {
+    const error =
+      validatorFn === 'required'
+        ? validators[validatorFn](value)
+        : validators[validatorFn](valueField[validatorFn])(value)
+    if (error) {
+      errors.push(error)
+    }
+    return validatorFn
+  })
+  return errors.length > 0 ? errors[0] : undefined
+}
+
+const groupElements = elements => {
+  const grouped = groupBy(elements, n => n.group || 'default')
+
+  Object.keys(grouped).forEach(element => {
+    grouped[element].sort(
+      (obj1, obj2) => parseInt(obj1.order, 10) - parseInt(obj2.order, 10),
+    )
+  })
+
+  let startArr = grouped.default
+  delete grouped.default
+
+  Object.keys(grouped).forEach(element => {
+    const { order } = grouped[element][0]
+    const first = startArr.findIndex(elem => elem.order === order)
+    startArr = startArr
+      .slice(0, first)
+      .concat([grouped[element]])
+      .concat(startArr.slice(first)) // eslint-disable-line no-use-before-define
+  })
+  return startArr
+}
+
+const renderArray = (elementsComponentArray, onChange) => ({
+  form: { values, setTouched },
+  replace,
+  name,
+}) =>
+  get(values, name).map((elValues, index) => {
+    const element = elementsComponentArray.find(elv =>
+      Object.values(elValues).includes(elv.type),
+    )
+    return (
+      <Section
+        cssOverrides={JSON.parse(element.sectioncss || '{}')}
+        key={`${element.id}`}
+      >
+        <Legend dangerouslySetInnerHTML={createMarkup(element.title)} />
+        {/* <p>{JSON.stringify(values)}</p> */}
+        <ValidatedFieldFormik
+          {...rejectProps(element, [
+            'component',
+            'title',
+            'sectioncss',
+            'parse',
+            'format',
+            'validate',
+            'validateValue',
+            'description',
+            'order',
+            'value',
+          ])}
+          component={elements[element.component]}
+          data-testid={element.name}
+          key={`notes-validate-${element.id}`}
+          name={`${name}.${index}.content`}
+          onChange={value => {
+            const data = {
+              notesType: element.type,
+              content: value,
+            }
+            replace(index, data, `${name}.[${index}]`, true)
+            const notes = cloneDeep(values)
+            set(notes, `${name}.[${index}]`, data)
+            onChange(notes.meta.notes, `${name}`)
+          }}
+          readonly={false}
+          setTouched={setTouched}
+          validate={composeValidate(element.validate, element.validateValue)}
+          values={values}
+        />
+        <SubNote dangerouslySetInnerHTML={createMarkup(element.description)} />
+      </Section>
+    )
+  })
+
+const ElementComponentArray = ({
+  elementsComponentArray,
+  onChange,
+  uploadFile,
+}) => (
+  <FieldArray
+    name={elementsComponentArray[0].group}
+    render={renderArray(elementsComponentArray, onChange)}
+  />
+)
+
+export default ({
+  form,
+  handleSubmit,
+  journal,
+  toggleConfirming,
+  confirming,
+  manuscript,
+  setTouched,
+  values,
+  setFieldValue,
+  uploadFile,
+  createFile,
+  onChange,
+  onSubmit,
+  submitSubmission,
+  ...props
+}) => (
+  <Wrapper>
+    <Heading1>{form.name}</Heading1>
+    <Intro
+      dangerouslySetInnerHTML={createMarkup(
+        (form.description || '').replace(
+          '###link###',
+          link(journal, manuscript),
+        ),
+      )}
+    />
+    <form onSubmit={handleSubmit}>
+      {groupElements(form.children || []).map((element, i) =>
+        !isArray(element) ? (
+          <Section
+            cssOverrides={JSON.parse(element.sectioncss || '{}')}
+            key={`${element.id}`}
+          >
+            {/* <p>{JSON.stringify(element)}</p> */}
+            <Legend dangerouslySetInnerHTML={createMarkup(element.title)} />
+            {element.component === 'SupplementaryFiles' && (
+              <Supplementary
+                createFile={createFile}
+                onChange={onChange}
+                uploadFile={uploadFile}
+              />
+            )}
+            {element.component === 'AuthorsInput' && (
+              <AuthorsInput data-testid={element.name} onChange={onChange} />
+            )}
+            {element.component !== 'AuthorsInput' &&
+              element.component !== 'SupplementaryFiles' && (
+                <ValidatedFieldFormik
+                  component={elements[element.component]}
+                  data-testid={element.name}
+                  key={`validate-${element.id}`}
+                  name={element.name}
+                  onChange={value => {
+                    const val = value.target ? value.target.value : value
+                    setFieldValue(element.name, val, true)
+                    onChange(val, element.name)
+                  }}
+                  readonly={false}
+                  setTouched={setTouched}
+                  {...rejectProps(element, [
+                    'component',
+                    'title',
+                    'sectioncss',
+                    'parse',
+                    'format',
+                    'validate',
+                    'validateValue',
+                    'description',
+                    'order',
+                  ])}
+                  validate={composeValidate(
+                    element.validate,
+                    element.validateValue,
+                  )}
+                  values={values}
+                />
+              )}
+            <SubNote
+              dangerouslySetInnerHTML={createMarkup(element.description)}
+            />
+          </Section>
+        ) : (
+          <ElementComponentArray
+            elementsComponentArray={element}
+            // eslint-disable-next-line
+            key={i}
+            onChange={onChange}
+            setFieldValue={setFieldValue}
+            setTouched={setTouched}
+          />
+        ),
+      )}
+
+      {filterFileManuscript(values.files || []).length > 0 ? (
+        <Section id="files.manuscript">
+          <Legend space>Submitted Manuscript</Legend>
+          <Attachment
+            file={filesToAttachment(filterFileManuscript(values.files)[0])}
+            key={filterFileManuscript(values.files)[0].url}
+            uploaded
+          />
+        </Section>
+      ) : null}
+
+      {values.status !== 'submitted' && form.haspopup === 'false' && (
+        <Button onClick={handleSubmit} primary type="submit">
+          Submit your manuscript
+        </Button>
+      )}
+
+      {values.status !== 'submitted' && form.haspopup === 'true' && (
+        <div>
+          <Button onClick={toggleConfirming} primary type="button">
+            Submit your manuscript
+          </Button>
+        </div>
+      )}
+      {confirming && (
+        <ModalWrapper>
+          <Confirm
+            form={form}
+            submitSubmission={handleSubmit}
+            toggleConfirming={toggleConfirming}
+          />
+        </ModalWrapper>
+      )}
+    </form>
+  </Wrapper>
+)
diff --git a/app/components/component-xpub-submit/src/components/MetadataFields.js b/app/components/component-xpub-submit/src/components/MetadataFields.js
new file mode 100644
index 0000000000000000000000000000000000000000..425c3e77df04b0e11def379624158ec22f9ea113
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/MetadataFields.js
@@ -0,0 +1,24 @@
+import React from 'react'
+import styled from 'styled-components'
+import { Section, Legend } from '../styles'
+
+const Abstract = styled.div`
+  word-wrap: break-word;
+`
+
+const MetadataFields = ({ manuscript }) => [
+  <Section id="meta.title" key="meta.title">
+    <Legend>Title</Legend>
+    <div>{manuscript.meta.title}</div>
+  </Section>,
+  <Section id="meta.abstract" key="meta.abstract">
+    <Legend>Abstract</Legend>
+    <Abstract>{manuscript.meta.abstract}</Abstract>
+  </Section>,
+  <Section id="meta.keywords" key="meta.keywords">
+    <Legend>Keywords</Legend>
+    <div>{manuscript.meta.keywords}</div>
+  </Section>,
+]
+
+export default MetadataFields
diff --git a/app/components/component-xpub-submit/src/components/MetadataFields.md b/app/components/component-xpub-submit/src/components/MetadataFields.md
new file mode 100644
index 0000000000000000000000000000000000000000..30ec5fb3430ca2caa016fc367aaecc246f9a8c4f
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/MetadataFields.md
@@ -0,0 +1,13 @@
+A form for entering the submission's metadata.
+
+```js
+const manuscript = {
+  meta: {
+    title: faker.lorem.sentence(25),
+    abstract: faker.lorem.sentence(50),
+    articleType: 'original-research',
+    keywords: 'test, test1',
+  },
+}
+;<MetadataFields manuscript={manuscript} />
+```
diff --git a/app/components/component-xpub-submit/src/components/Submit.js b/app/components/component-xpub-submit/src/components/Submit.js
new file mode 100644
index 0000000000000000000000000000000000000000..b11a1578f2afc404b10bc25e8c9c464bc86e1d12
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Submit.js
@@ -0,0 +1,80 @@
+import React from 'react'
+import styled from 'styled-components'
+import { Tabs } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+import moment from 'moment'
+import CurrentVersion from './CurrentVersion'
+import DecisionReviewColumn from './DecisionReviewColumn'
+import { Columns, SubmissionVersion } from './atoms/Columns'
+import FormTemplate from './FormTemplate'
+
+const Wrapper = styled.div`
+  font-family: ${th('fontInterface')};
+  line-height: 1.3;
+  margin: auto;
+  max-width: 60em;
+
+  overflow: ${({ confirming }) => confirming && 'hidden'};
+`
+
+const SubmittedVersionColumns = props => (
+  <Wrapper>
+    <Columns>
+      <SubmissionVersion>
+        <CurrentVersion
+          forms={props.forms}
+          journal={props.journal}
+          manuscript={props.manuscript}
+          readonly
+        />
+        ,
+      </SubmissionVersion>
+      <DecisionReviewColumn {...props} />
+    </Columns>
+  </Wrapper>
+)
+
+const Submit = ({ journal, manuscript, forms, ...formProps }) => {
+  const decisionSections = []
+  const manuscriptVersions = manuscript.manuscriptVersions || []
+  manuscriptVersions.forEach(versionElem => {
+    const submittedMoment = moment(versionElem.submitted)
+    const label = submittedMoment.format('YYYY-MM-DD')
+    decisionSections.push({
+      content: (
+        <SubmittedVersionColumns
+          forms={forms}
+          journal={journal}
+          manuscript={versionElem}
+        />
+      ),
+      key: versionElem.id,
+      label,
+    })
+  })
+
+  decisionSections.push({
+    content: (
+      <FormTemplate
+        {...formProps}
+        form={forms}
+        journal={journal}
+        manuscript={manuscript}
+      />
+    ),
+    key: manuscript.id,
+    label: 'Current Version',
+  })
+
+  return (
+    <Wrapper>
+      <Tabs
+        activeKey={manuscript.id}
+        sections={decisionSections}
+        title="Versions"
+      />
+    </Wrapper>
+  )
+}
+
+export default Submit
diff --git a/app/components/component-xpub-submit/src/components/SubmitPage.js b/app/components/component-xpub-submit/src/components/SubmitPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e63f237a807bb0b9a90a9baf55318f06527139d
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/SubmitPage.js
@@ -0,0 +1,251 @@
+import { throttle, cloneDeep, isEmpty, set } from 'lodash'
+import { compose, withProps, withState, withHandlers } from 'recompose'
+import { graphql } from '@apollo/react-hoc'
+import { gql } from 'apollo-client-preset'
+import { withFormik } from 'formik'
+import { withLoader } from 'pubsweet-client'
+import Submit from './Submit'
+
+const nullToEmpty = obj =>
+  JSON.parse(JSON.stringify(obj, (k, v) => (v === null ? '' : v)))
+
+const emptyToUndefined = obj =>
+  JSON.parse(JSON.stringify(obj, (k, v) => (v === '' ? undefined : v)))
+
+const fragmentFields = `
+  id
+  created
+  files {
+    id
+    created
+    label
+    filename
+    fileType
+    mimeType
+    size
+    url
+  }
+  reviews {
+    open
+    recommendation
+    created
+    isDecision
+    comments {
+      content
+    }
+    user {
+      id
+      username
+    }
+  }
+  teams {
+    id
+    role
+    members {
+      id
+      user {
+        id
+        username
+      }
+    }
+  }
+  decision
+  status
+  meta {
+    title
+    abstract
+    declarations {
+      openData
+      openPeerReview
+      preregistered
+      previouslySubmitted
+      researchNexus
+      streamlinedReview
+    }
+    articleSections
+    articleType
+    history {
+      type
+      date
+    }
+    notes {
+      notesType
+      content
+    }
+    keywords
+  }
+  suggestions {
+    reviewers {
+      opposed
+      suggested
+    }
+    editors {
+      opposed
+      suggested
+    }
+  }
+  authors {
+    firstName
+    lastName
+    email
+    affiliation
+  }
+`
+
+const query = gql`
+  query($id: ID!, $form: String!) {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    manuscript(id: $id) {
+      ${fragmentFields}
+      manuscriptVersions {
+        ${fragmentFields}
+      }
+    }
+
+    getFile(form: $form)
+  }
+`
+
+const updateMutation = gql`
+  mutation($id: ID!, $input: String) {
+    updateManuscript(id: $id, input: $input) {
+      id
+      ${fragmentFields}
+    }
+  }
+`
+
+const uploadSuplementaryFilesMutation = gql`
+  mutation($file: Upload!) {
+    upload(file: $file) {
+      url
+    }
+  }
+`
+
+const createFileMutation = gql`
+  mutation($file: Upload!) {
+    createFile(file: $file) {
+      id
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
+    }
+  }
+`
+
+export default compose(
+  graphql(query, {
+    options: ({ match }) => ({
+      variables: {
+        id: match.params.version,
+        form: 'submit',
+      },
+    }),
+    props: ({ data }) => ({ data: nullToEmpty(data) }),
+  }),
+  graphql(createFileMutation, {
+    props: ({ mutate, ownProps }) => ({
+      createFile: value => {
+        const file = {
+          url: value.url,
+          filename: value.filename,
+          mimeType: value.mimeType,
+          size: value.size,
+          fileType: 'supplementary',
+          object: 'Manuscript',
+          objectId: ownProps.match.params.version,
+        }
+
+        mutate({
+          variables: {
+            file,
+          },
+        })
+      },
+    }),
+  }),
+  graphql(uploadSuplementaryFilesMutation, {
+    props: ({ mutate, ownProps }) => ({
+      uploadFile: file =>
+        mutate({
+          variables: {
+            file,
+          },
+        }),
+    }),
+  }),
+  graphql(updateMutation, {
+    props: ({ mutate, ownProps }) => {
+      const updateManuscript = (value, path) => {
+        const input = {}
+        set(input, path, value)
+        mutate({
+          variables: {
+            id: ownProps.match.params.version,
+            input: JSON.stringify(emptyToUndefined(input)),
+          },
+        })
+      }
+
+      return {
+        // TODO: do this on blur, rather than on every keystroke?
+        onChange: throttle(updateManuscript, 1000, { trailing: false }),
+      }
+    },
+  }),
+  graphql(updateMutation, {
+    props: ({ mutate, ownProps }) => ({
+      onSubmit: (manuscript, { history }) => {
+        const updateManuscript = {
+          status: 'submitted',
+        }
+
+        mutate({
+          variables: {
+            id: ownProps.match.params.version,
+            input: JSON.stringify(updateManuscript),
+          },
+        }).then(() => {
+          history.push('/')
+        })
+      },
+    }),
+  }),
+  withLoader(),
+  withProps(({ getFile, manuscript, match: { params: { journal } } }) => ({
+    journal: { id: journal },
+    forms: cloneDeep(getFile),
+    manuscript,
+    submitSubmission: ({ validateForm, setSubmitting, handleSubmit }) =>
+      validateForm().then(props =>
+        isEmpty(props) ? setSubmitting(false) : handleSubmit(),
+      ),
+  })),
+  withFormik({
+    initialValues: {},
+    mapPropsToValues: ({ manuscript }) => manuscript,
+    displayName: 'submit',
+    handleSubmit: (
+      props,
+      { validateForm, setSubmitting, props: { onSubmit, history } },
+    ) =>
+      validateForm().then(props =>
+        isEmpty(props) ? onSubmit(props, { history }) : setSubmitting(false),
+      ),
+  }),
+  withState('confirming', 'setConfirming', false),
+  withHandlers({
+    toggleConfirming: ({ validateForm, setConfirming, handleSubmit }) => () =>
+      setConfirming(confirming => !confirming),
+  }),
+)(Submit)
diff --git a/app/components/component-xpub-submit/src/components/Suggestions.js b/app/components/component-xpub-submit/src/components/Suggestions.js
new file mode 100644
index 0000000000000000000000000000000000000000..b5640c78150476965d618c76dc4f2e6ad32527da
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Suggestions.js
@@ -0,0 +1,39 @@
+import React from 'react'
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+import { Section, Legend } from '../styles'
+
+const SubLegend = styled(Legend)`
+  font-weight: normal;
+  margin-top: calc(${th('gridUnit')} * 3);
+`
+
+const SuggestionsNonEditable = ({ manuscript }) => {
+  const suggestions = manuscript.suggestions || {}
+
+  return [
+    <Section id="suggestions.reviewers" key="suggestions.reviewers">
+      <Legend>Suggested or opposed reviewers</Legend>
+      <SubLegend>Suggested reviewers</SubLegend>
+      <div>{suggestionsText(suggestions.reviewers, 'suggested')}</div>
+      <SubLegend>Opposed reviewers</SubLegend>
+      <div>{suggestionsText(suggestions.reviewers, 'opposed')}</div>
+    </Section>,
+    <Section id="suggestions.editors" key="suggestions.editors">
+      <Legend>Suggested or opposed editors</Legend>
+      <SubLegend>Suggested editors</SubLegend>
+      <div>{suggestionsText(suggestions.editors, 'suggested')}</div>
+      <SubLegend>Opposed editors</SubLegend>
+      <div>{suggestionsText(suggestions.editors, 'opposed')}</div>
+    </Section>,
+  ]
+}
+
+const suggestionsText = (source, property) => {
+  if (source && source[property]) {
+    return source[property]
+  }
+  return 'none'
+}
+
+export default SuggestionsNonEditable
diff --git a/app/components/component-xpub-submit/src/components/Suggestions.md b/app/components/component-xpub-submit/src/components/Suggestions.md
new file mode 100644
index 0000000000000000000000000000000000000000..ee1f366c9dd8c798bd9f341d86e6f515d9c6428d
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Suggestions.md
@@ -0,0 +1,12 @@
+A list of questions that must be answered before submission.
+
+```js
+const manuscript = {
+  suggestions: {
+    reviewers: {
+      opposed: faker.name.findName(),
+    },
+  },
+}
+;<Suggestions manuscript={manuscript} />
+```
diff --git a/app/components/component-xpub-submit/src/components/Suggestions.test.js b/app/components/component-xpub-submit/src/components/Suggestions.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..4508a5df5e4679f7a67df0e5da17467a591bd859
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Suggestions.test.js
@@ -0,0 +1,95 @@
+import React from 'react'
+import Enzyme, { mount } from 'enzyme'
+import Adapter from 'enzyme-adapter-react-16'
+
+import Suggestions from './Suggestions'
+
+Enzyme.configure({ adapter: new Adapter() })
+
+jest.mock('config', () => ({ 'pubsweet-client': {} }))
+
+const suggestionsObject = {
+  suggestions: {
+    editors: {
+      opposed: 'editor, opposed',
+      suggested: 'editor, suggested',
+    },
+    reviewers: {
+      opposed: 'reviewer, opposed',
+      suggested: 'reviewer, suggested',
+    },
+  },
+  readonly: true,
+}
+
+const getSuggested = object =>
+  object
+    .children('div')
+    .first()
+    .text()
+
+const getOpposed = object =>
+  object
+    .children('div')
+    .last()
+    .text()
+
+const makeWrapper = (props = { readonly: true, suggestions: {} }) => {
+  const manuscript = Object.assign(
+    {
+      suggestions: {
+        editors: {
+          opposed: [],
+          suggested: [],
+        },
+        reviewers: {
+          opposed: [],
+          suggested: [],
+        },
+      },
+    },
+    { suggestions: props.suggestions },
+  )
+
+  return mount(
+    <Suggestions manuscript={manuscript} readonly={props.readonly} />,
+  )
+}
+
+const suggestions = makeWrapper(
+  Object.assign(suggestionsObject, { readonly: true }),
+)
+
+describe('Suggestions', () => {
+  it('shows a non-editable form', () => {
+    const formSection = suggestions.children()
+    expect(formSection).toHaveLength(2)
+  })
+
+  it('shows none suggested, opposed editors', () => {
+    const noneEditors = makeWrapper().find('div[id="suggestions.editors"]')
+    expect(getSuggested(noneEditors)).toEqual('none')
+    expect(getOpposed(noneEditors)).toEqual('none')
+  })
+
+  it('shows none suggested, opposed reviewers', () => {
+    const noneReviewers = makeWrapper().find('div[id="suggestions.reviewers"]')
+    expect(getSuggested(noneReviewers)).toEqual('none')
+    expect(getOpposed(noneReviewers)).toEqual('none')
+  })
+
+  it('shows a comma seperated suggested, opposed reviewers', () => {
+    const sectionReviewers = suggestions.find('div[id="suggestions.reviewers"]')
+    expect(getSuggested(sectionReviewers)).toEqual('reviewer, suggested')
+
+    expect(getOpposed(sectionReviewers)).toEqual('reviewer, opposed')
+  })
+
+  it('shows a comma seperated suggested, opposed editor', () => {
+    const sectionEditors = suggestions.find('div[id="suggestions.editors"]')
+
+    expect(getSuggested(sectionEditors)).toEqual('editor, suggested')
+
+    expect(getOpposed(sectionEditors)).toEqual('editor, opposed')
+  })
+})
diff --git a/app/components/component-xpub-submit/src/components/Supplementary.js b/app/components/component-xpub-submit/src/components/Supplementary.js
new file mode 100644
index 0000000000000000000000000000000000000000..2580f3e0353de173b074856e9b47d4364f72b351
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/Supplementary.js
@@ -0,0 +1,54 @@
+import React from 'react'
+import { cloneDeep } from 'lodash'
+import { FieldArray } from 'formik'
+import { Flexbox, UploadButton, UploadingFile } from '@pubsweet/ui'
+
+const renderFilesUpload = (onChange, uploadFile, createFile) => ({
+  form: { values, setFieldValue },
+  push,
+  insert,
+}) => (
+  <>
+    <UploadButton
+      buttonText="↑ Upload files"
+      onChange={event => {
+        const fileArray = Array.from(event.target.files).map(file => {
+          const fileUpload = {
+            fileType: 'supplementary',
+            filename: file.name,
+          }
+          return fileUpload
+        })
+        setFieldValue('files', fileArray.concat(values.files))
+        Array.from(event.target.files).forEach(file => {
+          uploadFile(file).then(({ data }) => {
+            const newFile = {
+              url: data.upload.url,
+              filename: file.name,
+              mimeType: file.type,
+              size: file.size,
+            }
+            createFile(newFile)
+          })
+        })
+      }}
+    />
+    <Flexbox>
+      {cloneDeep(values.files || [])
+        .filter(val => val.fileType === 'supplementary')
+        .map(val => {
+          val.name = val.filename
+          return <UploadingFile file={val} key={val.name} uploaded />
+        })}
+    </Flexbox>
+  </>
+)
+
+const Supplementary = ({ onChange, uploadFile, createFile }) => (
+  <FieldArray
+    name="files"
+    render={renderFilesUpload(onChange, uploadFile, createFile)}
+  />
+)
+
+export default Supplementary
diff --git a/app/components/component-xpub-submit/src/components/SupplementaryFiles.js b/app/components/component-xpub-submit/src/components/SupplementaryFiles.js
new file mode 100644
index 0000000000000000000000000000000000000000..8b8660e6db1b0f0417c824baf98072efbfbee3b0
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/SupplementaryFiles.js
@@ -0,0 +1,25 @@
+import React from 'react'
+import { Attachment } from '@pubsweet/ui'
+import { Section, Legend } from '../styles'
+
+const SupplementaryFiles = ({ manuscript }) => (
+  <Section id="files.supplementary">
+    {(manuscript.files || []).filter(file => file.type === 'supplementary')
+      .length > 0
+      ? [
+          <Legend htmlFor="supplementary">
+            Supplementary materials uploaded
+          </Legend>,
+          <div>
+            {manuscript.files
+              .filter(file => file.type === 'supplementary')
+              .map(attachment => (
+                <Attachment file={attachment} key={attachment.url} uploaded />
+              ))}
+          </div>,
+        ]
+      : null}
+  </Section>
+)
+
+export default SupplementaryFiles
diff --git a/app/components/component-xpub-submit/src/components/SupplementaryFiles.md b/app/components/component-xpub-submit/src/components/SupplementaryFiles.md
new file mode 100644
index 0000000000000000000000000000000000000000..46399c40d007bb451511d6d4058816e54ce18ab5
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/SupplementaryFiles.md
@@ -0,0 +1,20 @@
+A form for entering the submission's supplementary material.
+
+```js
+const file = () => ({
+  file: {
+    url: faker.internet.url(),
+    name: faker.system.commonFileName(),
+  },
+  filename: faker.system.commonFileName(),
+  type: 'supplementary',
+})
+
+const manuscript = {
+  files: [file(), file(), file()],
+}
+;<SupplementaryFiles
+  manuscript={manuscript}
+  onChange={values => console.log(values)}
+/>
+```
diff --git a/app/components/component-xpub-submit/src/components/atoms/Columns.js b/app/components/component-xpub-submit/src/components/atoms/Columns.js
new file mode 100644
index 0000000000000000000000000000000000000000..220e4c451f94ef96da10a2194c0e48fdb222b7b5
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/atoms/Columns.js
@@ -0,0 +1,19 @@
+import styled from 'styled-components'
+
+const Columns = styled.div`
+  display: grid;
+  grid-column-gap: 2em;
+  grid-template-areas: 'SubmissionVersion Review';
+  grid-template-columns: minmax(200px, 80ch) minmax(200px, 50ch);
+  justify-content: center;
+`
+
+const SubmissionVersion = styled.div`
+  grid-area: SubmissionVersion;
+`
+
+const Review = styled.div`
+  grid-area: Review;
+`
+
+export { Columns, SubmissionVersion, Review }
diff --git a/app/components/component-xpub-submit/src/components/atoms/Icon.js b/app/components/component-xpub-submit/src/components/atoms/Icon.js
new file mode 100644
index 0000000000000000000000000000000000000000..be73f6787298853af0995b343e9c99f306edde52
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/atoms/Icon.js
@@ -0,0 +1,24 @@
+import React from 'react'
+import { withTheme } from 'styled-components'
+import { get, has } from 'lodash'
+import * as reactFeatherIcons from 'react-feather'
+
+const Icon = ({ iconName, overrideName, className, theme, ...props }) => {
+  const isOverrideInTheme = has(theme.icons, overrideName)
+  if (isOverrideInTheme) {
+    const OverrideIcon = get(theme.icons, overrideName)
+    return <OverrideIcon className={className} {...props} />
+  }
+  const isIconInDefaultSet = reactFeatherIcons[iconName]
+  // TODO: conversation with Pubsweet - what should we default to when
+  // there's no obvious react-feather equivalent?
+  if (!isIconInDefaultSet) {
+    // eslint-disable-next-line no-console
+    console.warn("Icon '%s' not found", iconName)
+    return null
+  }
+  const DefaultIcon = reactFeatherIcons[iconName]
+  return <DefaultIcon className={className} {...props} />
+}
+
+export default withTheme(Icon)
diff --git a/app/components/component-xpub-submit/src/components/index.js b/app/components/component-xpub-submit/src/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..10af0b29a9d008fa0a098886c73050bbe5a61bd6
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/index.js
@@ -0,0 +1 @@
+export { default as SubmitPage } from './SubmitPage'
diff --git a/app/components/component-xpub-submit/src/components/molecules/Accordion.js b/app/components/component-xpub-submit/src/components/molecules/Accordion.js
new file mode 100644
index 0000000000000000000000000000000000000000..fbb85ac9303177c2d3178bc66a8952826d14e2fd
--- /dev/null
+++ b/app/components/component-xpub-submit/src/components/molecules/Accordion.js
@@ -0,0 +1,133 @@
+import React from 'react'
+import styled from 'styled-components'
+import { compose, withState, withHandlers } from 'recompose'
+import { JournalContext } from 'xpub-journal'
+import { Button } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Root = styled.div``
+
+const AccordionBody = styled.div``
+const Title = styled.span`
+  font-size: ${th('fontSizeHeading5')};
+  font-family: ${th('fontHeading')};
+  line-height: ${th('lineHeightHeading5')};
+`
+
+const ToggleReview = ({ open, toggle }) => (
+  <Button onClick={toggle} plain>
+    {open ? 'Hide' : 'Show'}
+  </Button>
+)
+
+const Bullet = ({ journal, recommendation }) => {
+  const recommendationColor = () =>
+    recommendation
+      ? journal.recommendations.find(item => item.value === recommendation)
+          .color
+      : 'black'
+
+  const Dot = styled.span`
+    border-radius: 100%;
+    display: inline-block;
+    height: 10px;
+    margin-right: 10px;
+    width: 10px;
+    background-color: ${recommendationColor};
+  `
+
+  return <Dot />
+}
+
+const AccordionHeading = ({
+  journal,
+  name,
+  open,
+  ordinal,
+  recommendation,
+  toggleOpen,
+  component,
+  withDots,
+}) => {
+  const Root = styled.div`
+    display: flex;
+    align-items: baseline;
+    margin-bottom: calc(${th('gridUnit')} * 3);
+  `
+
+  const Ordinal = styled(Title)``
+  const Controls = styled.span``
+
+  const Head = styled.div`
+    ${() => !withDots && 'border-bottom: 1px solid #000;'};
+    align-items: baseline;
+    display: flex;
+    flex: 1;
+    justify-content: space-between;
+  `
+
+  const Dots = styled.span`
+    background-image: linear-gradient(to right, #666 50%, white 0%);
+    background-position: 0 90%;
+    background-repeat: repeat-x;
+    background-size: 6px 1px;
+    position: relative;
+    height: 1px;
+    flex: 1;
+    margin-left: 10px;
+  `
+
+  return (
+    <Root>
+      {recommendation && (
+        <Bullet journal={journal} recommendation={recommendation} />
+      )}
+      <Head>
+        <Ordinal>
+          {name} {ordinal}
+        </Ordinal>
+        {withDots && <Dots />}
+        <Controls>
+          <ToggleReview open={open} toggle={toggleOpen} />
+        </Controls>
+      </Head>
+      {component}
+    </Root>
+  )
+}
+
+const Accordion = ({
+  open,
+  ordinal,
+  toggleOpen,
+  title,
+  status,
+  Component,
+  withDots,
+}) => (
+  <Root>
+    <JournalContext.Consumer>
+      {journal => (
+        <AccordionHeading
+          journal={journal}
+          name={title}
+          open={open}
+          ordinal={ordinal}
+          recommendation={status}
+          toggleOpen={toggleOpen}
+          withDots={withDots || false}
+        />
+      )}
+    </JournalContext.Consumer>
+    {open && <AccordionBody>{Component}</AccordionBody>}
+  </Root>
+)
+
+export default compose(
+  withState('open', 'setOpen', ({ open }) => open || true),
+  withHandlers({
+    toggleOpen: props => () => {
+      props.setOpen(open => !open)
+    },
+  }),
+)(Accordion)
diff --git a/app/components/component-xpub-submit/src/index.js b/app/components/component-xpub-submit/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f1f42a64b1ecf4a71345e76fa416d89015cb96c
--- /dev/null
+++ b/app/components/component-xpub-submit/src/index.js
@@ -0,0 +1,5 @@
+module.exports = {
+  frontend: {
+    components: [() => require('./components')],
+  },
+}
diff --git a/app/components/component-xpub-submit/src/styles/index.js b/app/components/component-xpub-submit/src/styles/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..f4f0f8bd9d0f4adb1a435ebbbf764b3cf0a70039
--- /dev/null
+++ b/app/components/component-xpub-submit/src/styles/index.js
@@ -0,0 +1,32 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+export const Heading1 = styled.h1`
+  margin: 0 0 calc(${th('gridUnit')} * 3);
+  font-size: ${th('fontSizeHeading1')};
+  line-height: ${th('lineHeightHeading1')};
+`
+
+export const Section = styled.div`
+  margin: calc(${th('gridUnit')} * 6) 0;
+  display: flex;
+  flex-direction: ${({ cssOverrides }) =>
+    cssOverrides && cssOverrides['flex-direction']
+      ? cssOverrides['flex-direction']
+      : 'column'};
+  flex-wrap: ${({ cssOverrides }) =>
+    cssOverrides && cssOverrides.wrap ? cssOverrides.wrap : 'nowrap'};
+  justify-content: space-between;
+`
+
+export const Legend = styled.div`
+  font-size: ${th('fontSizeBase')};
+  font-weight: 600;
+  margin-bottom: ${({ space, theme }) => space && theme.gridUnit};
+`
+export const SubNote = styled.span`
+  font-size: ${th('fontSizeBaseSmall')};
+  line-height: ${th('lineHeightBaseSmall')};
+  color: ${th('colorTextPlaceholder')};
+  width: 100%;
+`
diff --git a/app/components/component-xpub-teams-manager/CHANGELOG.md b/app/components/component-xpub-teams-manager/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..60e74b26ea979075862d8249c333a76e4635a36e
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/CHANGELOG.md
@@ -0,0 +1,367 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [3.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@3.0.2...pubsweet-component-xpub-teams-manager@3.1.0) (2019-11-11)
+
+
+### Features
+
+* **xpub:** bring back xpub components ([fb69994](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb69994098b4e2dbcca75b4786ebb1335af730b9))
+
+
+
+
+
+## [3.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@3.0.1...pubsweet-component-xpub-teams-manager@3.0.2) (2019-09-11)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [3.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@3.0.0...pubsweet-component-xpub-teams-manager@3.0.1) (2019-09-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+# [3.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.18...pubsweet-component-xpub-teams-manager@3.0.0) (2019-08-30)
+
+
+### Code Refactoring
+
+* **apollo:** update react-dom version ([e001d01](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e001d01))
+
+
+### BREAKING CHANGES
+
+* **apollo:** The minimum supported React version by @apollo is now 16.8
+
+
+
+
+
+## [2.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.17...pubsweet-component-xpub-teams-manager@2.0.18) (2019-08-08)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.16...pubsweet-component-xpub-teams-manager@2.0.17) (2019-08-05)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.15...pubsweet-component-xpub-teams-manager@2.0.16) (2019-07-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.14...pubsweet-component-xpub-teams-manager@2.0.15) (2019-07-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.13...pubsweet-component-xpub-teams-manager@2.0.14) (2019-07-03)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.12...pubsweet-component-xpub-teams-manager@2.0.13) (2019-06-28)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.11...pubsweet-component-xpub-teams-manager@2.0.12) (2019-06-24)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.10...pubsweet-component-xpub-teams-manager@2.0.11) (2019-06-21)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.9...pubsweet-component-xpub-teams-manager@2.0.10) (2019-06-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.8...pubsweet-component-xpub-teams-manager@2.0.9) (2019-06-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.7...pubsweet-component-xpub-teams-manager@2.0.8) (2019-05-27)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.6...pubsweet-component-xpub-teams-manager@2.0.7) (2019-04-25)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.5...pubsweet-component-xpub-teams-manager@2.0.6) (2019-04-18)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.4...pubsweet-component-xpub-teams-manager@2.0.5) (2019-04-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.3...pubsweet-component-xpub-teams-manager@2.0.4) (2019-03-06)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.2...pubsweet-component-xpub-teams-manager@2.0.3) (2019-03-05)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.1...pubsweet-component-xpub-teams-manager@2.0.2) (2019-02-19)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [2.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@2.0.0...pubsweet-component-xpub-teams-manager@2.0.1) (2019-02-19)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+# [2.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@1.0.7...pubsweet-component-xpub-teams-manager@2.0.0) (2019-02-01)
+
+
+### Bug Fixes
+
+* **styleguide:** temporarily disable styleguide ([e519ed1](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e519ed1))
+
+
+### Code Refactoring
+
+* temporarily remove unmigrated components ([32db6ad](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/32db6ad))
+
+
+### BREAKING CHANGES
+
+* A lot of unmigrated (not yet moved from REST/Redux to GraphQL/Apollo system) bits
+have changed. There might be some breaking changes as a result. This is a big migration involving
+big changes - if you encounter anything weird, please contact us on GitLab or on Mattermost.
+
+
+
+
+
+## [1.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@1.0.6...pubsweet-component-xpub-teams-manager@1.0.7) (2019-01-16)
+
+
+### Bug Fixes
+
+* **components:** graphql data model changes ([4b61093](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4b61093))
+* **test:** formbuilder ([93c55fd](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/93c55fd))
+* **xpub-review:** changes tp reviews ([5ae4240](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/5ae4240))
+
+
+
+
+
+## [1.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@1.0.5...pubsweet-component-xpub-teams-manager@1.0.6) (2019-01-14)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [1.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@1.0.4...pubsweet-component-xpub-teams-manager@1.0.5) (2019-01-13)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [1.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@1.0.3...pubsweet-component-xpub-teams-manager@1.0.4) (2019-01-09)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [1.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@1.0.2...pubsweet-component-xpub-teams-manager@1.0.3) (2018-12-12)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [1.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@1.0.1...pubsweet-component-xpub-teams-manager@1.0.2) (2018-12-04)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@1.0.0...pubsweet-component-xpub-teams-manager@1.0.1) (2018-11-30)
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+
+
+
+
+# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@0.2.0...pubsweet-component-xpub-teams-manager@1.0.0) (2018-11-29)
+
+
+### Features
+
+* **various:** upgrade styled-components ([9b886f6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9b886f6))
+
+
+### BREAKING CHANGES
+
+* **various:** Replace styled-components injectGlobal with new createGlobalStyle
+
+
+
+
+
+<a name="0.2.0"></a>
+# [0.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@0.1.4...pubsweet-component-xpub-teams-manager@0.2.0) (2018-11-05)
+
+
+### Features
+
+* GraphQL Login component ([70df3de](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/70df3de))
+
+
+
+
+<a name="0.1.4"></a>
+## [0.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@0.1.3...pubsweet-component-xpub-teams-manager@0.1.4) (2018-10-08)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+<a name="0.1.3"></a>
+## [0.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@0.1.2...pubsweet-component-xpub-teams-manager@0.1.3) (2018-09-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+<a name="0.1.2"></a>
+## [0.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@0.1.1...pubsweet-component-xpub-teams-manager@0.1.2) (2018-09-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+<a name="0.1.1"></a>
+## [0.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-xpub-teams-manager@0.1.0...pubsweet-component-xpub-teams-manager@0.1.1) (2018-08-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-xpub-teams-manager
+
+<a name="0.1.0"></a>
+# 0.1.0 (2018-07-09)
+
+
+### Bug Fixes
+
+* **components:** button to styledButton ([0404203](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0404203))
+* **menu:** menu component of layout ([19bebef](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/19bebef))
+* **menu:** reset function ([2961a85](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/2961a85))
+* **teamanager:** remove name ([60cd6ee](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/60cd6ee))
+* **teammanager:** test fix failing ([c559488](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c559488))
+* **teammanager:** update styles component ([c92bbd5](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/c92bbd5))
+* **teams:** update styleguide ([427bcc6](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/427bcc6))
+* **teams:** update ui ([23f49d5](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/23f49d5))
+* **test:** mock authorize ([9c89540](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9c89540))
+* **test:** update snapshot ([8cd93a4](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/8cd93a4))
+* **whitespaces:** remove whitespaces ([a4803cb](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a4803cb))
+* **xpub-team-manager:** move files to components ([4421a68](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/4421a68))
+
+
+### Features
+
+* **team-management:** create new team component ([1f4c677](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/1f4c677))
+* **team-management:** fix page layout ([9f5c6b5](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/9f5c6b5))
+
+
+
+
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
diff --git a/app/components/component-xpub-teams-manager/README.md b/app/components/component-xpub-teams-manager/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..d82910029ee09cc45c1e7263bce5b597450f430c
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/README.md
@@ -0,0 +1,4 @@
+## pubsweet-component-xpub-teams-manager
+
+A PubSweet component that provides interface for admin to manage the teams of Xpub
+by assigning Managing Editor, Handling Editor, Senior Editor.
diff --git a/app/components/component-xpub-teams-manager/package.json b/app/components/component-xpub-teams-manager/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..78680c3cc72b99692e794772ff8f9ea7444c351c
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/package.json
@@ -0,0 +1,29 @@
+{
+  "name": "pubsweet-component-xpub-teams-manager",
+  "version": "3.1.0",
+  "description": "Teams manager component for Xpub",
+  "main": "src/index.js",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "dependencies": {
+    "@pubsweet/ui": "^12.1.0",
+    "@pubsweet/ui-toolkit": "^2.2.14",
+    "lodash": "^4.17.11",
+    "react-dom": "^16.9.0",
+    "recompose": "^0.30.0",
+    "styled-components": "^4.1.1"
+  },
+  "peerDependencies": {
+    "@apollo/react-hoc": ">=3.0.1",
+    "apollo-client-preset": ">=1.0.8",
+    "config": ">=3.0.1",
+    "graphql-tag": ">=2.10.0",
+    "pubsweet-client": ">=2.1.0",
+    "react": ">=16.9"
+  },
+  "gitHead": "6b100b76f21785e5e50fca082a2743d3d0b1c88a"
+}
diff --git a/app/components/component-xpub-teams-manager/src/components/Team.jsx b/app/components/component-xpub-teams-manager/src/components/Team.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f979cee6662bb1b33892e66087fd8ccfaebbbae4
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/Team.jsx
@@ -0,0 +1,35 @@
+import React from 'react'
+import styled from 'styled-components'
+import { Button, Menu } from '@pubsweet/ui'
+import { TeamTableCell } from './molecules/Table'
+
+const StyledMenu = styled(Menu)`
+  width: 100%;
+`
+
+const Team = ({ team, number, userOptions, deleteTeam, updateTeam }) => (
+  <>
+    <TeamTableCell width={5}>{number}</TeamTableCell>
+    <TeamTableCell>
+      {team.name} {team.role}
+    </TeamTableCell>
+    <TeamTableCell>
+      {team.object.objectType} {team.object.objectId}
+    </TeamTableCell>
+    <TeamTableCell width={40}>
+      <StyledMenu
+        inline
+        multi
+        name="members"
+        onChange={members => updateTeam(members, team)}
+        options={userOptions}
+        value={team.members.map(member => member.user && member.user.id)}
+      />
+    </TeamTableCell>
+    <TeamTableCell width={15}>
+      <Button onClick={() => deleteTeam(team)}>Delete</Button>
+    </TeamTableCell>
+  </>
+)
+
+export default Team
diff --git a/app/components/component-xpub-teams-manager/src/components/TeamCreator.jsx b/app/components/component-xpub-teams-manager/src/components/TeamCreator.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1fb0aab0d6860db8017acd57dfbd3644882ebf10
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/TeamCreator.jsx
@@ -0,0 +1,82 @@
+import React from 'react'
+import { find } from 'lodash'
+import { compose, withHandlers, withState } from 'recompose'
+import { Button, Menu } from '@pubsweet/ui'
+
+const TeamCreator = ({
+  teamTypeSelected,
+  manuscriptSelected,
+  manuscriptsOptions,
+  typesOptions,
+  onChangeManuscript,
+  onChangeType,
+  onSave,
+}) => (
+  <form onSubmit={onSave}>
+    <h3>Create a new team</h3>
+    <h4>Team type</h4>
+    <Menu
+      name="teamType"
+      onChange={onChangeType}
+      options={typesOptions}
+      required
+      reset={teamTypeSelected}
+      value={teamTypeSelected}
+    />
+    <h4>Manuscript</h4>
+    <Menu
+      name="collection"
+      onChange={onChangeManuscript}
+      options={manuscriptsOptions}
+      required
+      reset={manuscriptSelected}
+      value={manuscriptSelected}
+    />
+    <Button primary type="submit">
+      Create
+    </Button>
+  </form>
+)
+
+export default compose(
+  withState('manuscriptSelected', 'onManuscriptSelect', false),
+  withState('teamTypeSelected', 'onTeamTypeSelect', false),
+  withHandlers({
+    onChangeManuscript: ({ onManuscriptSelect }) => collectionId =>
+      onManuscriptSelect(() => collectionId || false),
+    onChangeType: ({ onTeamTypeSelect }) => teamType =>
+      onTeamTypeSelect(() => teamType || false),
+    onSave: ({
+      teamTypeSelected,
+      manuscriptSelected,
+      create,
+      typesOptions,
+      onTeamTypeSelect,
+      onManuscriptSelect,
+    }) => event => {
+      event.preventDefault()
+      const role = teamTypeSelected
+
+      let objectId
+      let objectType
+
+      if (manuscriptSelected) {
+        objectId = manuscriptSelected
+        objectType = 'Manuscript'
+      }
+
+      if (role && objectId && objectType) {
+        create({
+          name: find(typesOptions, types => types.value === role).label,
+          role,
+          objectId,
+          objectType,
+          members: [],
+        })
+
+        onTeamTypeSelect(() => true)
+        onManuscriptSelect(() => true)
+      }
+    },
+  }),
+)(TeamCreator)
diff --git a/app/components/component-xpub-teams-manager/src/components/TeamsManager.jsx b/app/components/component-xpub-teams-manager/src/components/TeamsManager.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a7e75164e88770b167da1b3bd52e8e7ecea3009b
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/TeamsManager.jsx
@@ -0,0 +1,47 @@
+import React from 'react'
+import { Page } from './molecules/Page'
+import { TeamTableCell, TeamTable } from './molecules/Table'
+import Team from './Team'
+import TeamCreator from './TeamCreator'
+
+const TeamsManager = ({
+  teams = [],
+  updateTeam,
+  deleteTeam,
+  createTeam,
+  error,
+  userOptions,
+  manuscriptsOptions,
+  typesOptions,
+}) => (
+  <Page>
+    {error ? <div>{error}</div> : null}
+    {teams.length > 0 && (
+      <TeamTable>
+        <TeamTableCell width={5}>#</TeamTableCell>
+        <TeamTableCell>Type</TeamTableCell>
+        <TeamTableCell>Object</TeamTableCell>
+        <TeamTableCell width={40}>Members</TeamTableCell>
+        <TeamTableCell width={15}>Actions</TeamTableCell>
+
+        {teams.map((team, i) => (
+          <Team
+            deleteTeam={deleteTeam}
+            key={team.id}
+            number={i + 1}
+            team={team}
+            updateTeam={updateTeam}
+            userOptions={userOptions}
+          />
+        ))}
+      </TeamTable>
+    )}
+    <TeamCreator
+      create={createTeam}
+      manuscriptsOptions={manuscriptsOptions}
+      typesOptions={typesOptions}
+    />
+  </Page>
+)
+
+export default TeamsManager
diff --git a/app/components/component-xpub-teams-manager/src/components/TeamsManager.test.js b/app/components/component-xpub-teams-manager/src/components/TeamsManager.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..dd22df93a54aa3d0cc1aa6e457e75d55f13f681e
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/TeamsManager.test.js
@@ -0,0 +1,94 @@
+import React from 'react'
+import Enzyme, { shallow } from 'enzyme'
+import Adapter from 'enzyme-adapter-react-16'
+
+import TeamsManager from './TeamsManager'
+
+import Team from './Team'
+import TeamCreator from './TeamCreator'
+
+// this should be elsewhere
+Enzyme.configure({ adapter: new Adapter() })
+
+jest.mock('config', () => ({
+  'pubsweet-client': {},
+  authsome: {
+    mode: 'authsome',
+    teams: {
+      seniorEditor: {
+        name: 'Senior Editors',
+        permissions: '',
+      },
+      handlingEditor: {
+        name: 'Handling Editors',
+        permissions: '',
+      },
+      managingEditor: {
+        name: 'Managing Editors',
+        permissions: '',
+      },
+      reviewer: {
+        name: 'Reviewer',
+        permissions: '',
+      },
+    },
+  },
+}))
+
+describe('TeamsManager', () => {
+  const makeWrapper = (props = {}) => {
+    props = Object.assign(
+      {
+        teams: [],
+        userOptions: [
+          { id: '1', username: 'author' },
+          { id: '2', username: 'managing Editor' },
+        ],
+      },
+      props,
+    )
+
+    return shallow(<TeamsManager {...props} />)
+  }
+
+  it('shows nothing when there are no teams', () => {
+    const teammanager = makeWrapper()
+    expect(teammanager.find(Team)).toHaveLength(0)
+    expect(teammanager.find(TeamCreator)).toHaveLength(1)
+  })
+
+  it('shows a list of teams created', () => {
+    const teammanager = makeWrapper({
+      teams: [
+        {
+          id: 1,
+          name: 'team1',
+          teamType: {
+            name: 'Senior Editors',
+            permissions: '',
+          },
+          object: {
+            type: 'collection',
+            id: '1',
+          },
+          members: [],
+        },
+        {
+          id: 1,
+          name: 'team2',
+          teamType: {
+            name: 'Handling Editors',
+            permissions: '',
+          },
+          object: {
+            type: 'collection',
+            id: '1',
+          },
+          members: [],
+        },
+      ],
+    })
+
+    expect(teammanager.find(Team)).toHaveLength(2)
+  })
+})
diff --git a/app/components/component-xpub-teams-manager/src/components/TeamsManagerPage.integration.test.js b/app/components/component-xpub-teams-manager/src/components/TeamsManagerPage.integration.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..579e52ef5174dec4294fe690e2db6822d66e55f0
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/TeamsManagerPage.integration.test.js
@@ -0,0 +1,100 @@
+import React from 'react'
+import faker from 'faker'
+import { MemoryRouter } from 'react-router-dom'
+import { MockedProvider } from '@apollo/react-testing'
+
+import Enzyme, { mount } from 'enzyme'
+import Adapter from 'enzyme-adapter-react-16'
+
+import { ThemeProvider } from 'styled-components'
+import queries from './graphql/queries'
+
+import TeamsManagerPage from './TeamsManagerPage'
+
+import TeamsManager from './TeamsManager'
+import TeamCreator from './TeamCreator'
+
+// this should be elsewhere
+Enzyme.configure({ adapter: new Adapter() })
+
+jest.mock('config', () => ({
+  'pubsweet-client': {},
+  authsome: {
+    mode: 'authsome',
+    teams: {
+      seniorEditor: {
+        name: 'Senior Editors',
+        permissions: '',
+      },
+      handlingEditor: {
+        name: 'Handling Editors',
+        permissions: '',
+      },
+      managingEditor: {
+        name: 'Managing Editors',
+        permissions: '',
+      },
+      reviewer: {
+        name: 'Reviewer',
+        permissions: '',
+      },
+    },
+  },
+}))
+
+// Mock out the API
+jest.mock('pubsweet-client/src/helpers/api', () => ({
+  get: jest.fn(url => {
+    // Whatever the request is, return an empty array
+    const response = []
+    return new Promise(resolve => resolve(response))
+  }),
+}))
+
+jest.mock('pubsweet-client/src/helpers/Authorize', () => 'Authorize')
+
+global.window.localStorage = {
+  getItem: jest.fn(() => 'tok123'),
+}
+
+const mocks = [
+  {
+    request: {
+      query: queries.teamManager,
+    },
+    result: {
+      data: {
+        currentUser: { id: faker.random.uuid(), username: 'test', admin: true },
+        teams: [],
+        users: [],
+        manuscripts: [],
+      },
+    },
+  },
+]
+
+describe('TeamsManagerPage', () => {
+  it('runs', done => {
+    const page = mount(
+      <MemoryRouter>
+        <ThemeProvider
+          theme={{
+            colorPrimary: 'blue',
+            colorSecondary: '#E7E7E7',
+          }}
+        >
+          <MockedProvider addTypename={false} mocks={mocks}>
+            <TeamsManagerPage />
+          </MockedProvider>
+        </ThemeProvider>
+      </MemoryRouter>,
+    )
+
+    setImmediate(() => {
+      page.update()
+      expect(page.find(TeamsManager)).toHaveLength(1)
+      expect(page.find(TeamCreator)).toHaveLength(1)
+      done()
+    })
+  })
+})
diff --git a/app/components/component-xpub-teams-manager/src/components/TeamsManagerPage.js b/app/components/component-xpub-teams-manager/src/components/TeamsManagerPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..feaf5d641b19b3781787bdb1316af1e2a7275deb
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/TeamsManagerPage.js
@@ -0,0 +1,174 @@
+import { compose } from 'recompose'
+import { omit } from 'lodash'
+import config from 'config'
+import { graphql } from '@apollo/react-hoc'
+import { gql } from 'apollo-client-preset'
+
+import queries from './graphql/queries'
+import TeamsManager from './TeamsManager'
+
+const deleteTeamMutation = gql`
+  mutation($id: ID) {
+    deleteTeam(id: $id) {
+      id
+      type
+      role
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        user {
+          id
+          username
+        }
+      }
+    }
+  }
+`
+
+const createTeamMutation = gql`
+  mutation($input: TeamInput!) {
+    createTeam(input: $input) {
+      id
+      type
+      role
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        user {
+          id
+          username
+        }
+      }
+    }
+  }
+`
+
+const updateTeamMutation = gql`
+  mutation($id: ID, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
+      id
+      type
+      role
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        user {
+          id
+          username
+        }
+      }
+    }
+  }
+`
+
+export default compose(
+  graphql(queries.teamManager, {
+    props: ({ data }) => {
+      const userOptions = ((data || {}).users || []).map(user => ({
+        value: user.id,
+        label: user.username,
+      }))
+
+      const manuscriptsOptions = ((data || {}).manuscripts || []).map(manu => ({
+        value: manu.id,
+        label: manu.meta.title,
+      }))
+
+      const types = config.authsome.teams
+      const typesOptions = Object.keys(types).map(type => ({
+        value: type,
+        label: `${types[type].name} ${types[type].permissions}`,
+      }))
+      return {
+        teams: (data || {}).teams,
+        manuscriptsOptions,
+        userOptions,
+        typesOptions,
+      }
+    },
+  }),
+  graphql(updateTeamMutation, {
+    props: ({ mutate }) => {
+      const updateTeam = (members, team) => {
+        let input = {
+          ...team,
+          objectId: team.object.objectId,
+          objectType: team.object.objectType,
+          object: undefined,
+        }
+
+        input.members = members.map(m => ({ user: { id: m } }))
+        input = omit(input, ['id', 'object.__typename', '__typename'])
+
+        mutate({
+          variables: {
+            id: team.id,
+            input,
+          },
+        })
+      }
+
+      return {
+        updateTeam,
+      }
+    },
+  }),
+  graphql(deleteTeamMutation, {
+    props: ({ mutate }) => {
+      const deleteTeam = data => {
+        mutate({
+          variables: {
+            id: data.id,
+          },
+        })
+      }
+
+      return {
+        deleteTeam,
+      }
+    },
+    options: {
+      update: (proxy, { data: { deleteTeam } }) => {
+        const data = proxy.readQuery({ query: queries.teamManager })
+        const teamsIndex = data.teams.findIndex(
+          team => team.id === deleteTeam.id,
+        )
+        if (teamsIndex > -1) {
+          data.teams.splice(teamsIndex, 1)
+          proxy.writeQuery({ query: queries.teamManager, data })
+        }
+      },
+    },
+  }),
+  graphql(createTeamMutation, {
+    props: ({ mutate }) => {
+      const createTeam = input => {
+        mutate({
+          variables: {
+            input,
+          },
+        })
+      }
+
+      return {
+        createTeam,
+      }
+    },
+    options: {
+      update: (proxy, { data: { createTeam } }) => {
+        const data = proxy.readQuery({ query: queries.teamManager })
+        data.teams.push(createTeam)
+        proxy.writeQuery({ query: queries.teamManager, data })
+      },
+    },
+  }),
+)(TeamsManager)
diff --git a/app/components/component-xpub-teams-manager/src/components/graphql/queries/index.js b/app/components/component-xpub-teams-manager/src/components/graphql/queries/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..b522b5e93705ad3042aff17436c15326ed0aa48c
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/graphql/queries/index.js
@@ -0,0 +1,43 @@
+import gql from 'graphql-tag'
+
+const fragmentFields = `
+  id
+  created
+  meta {
+    title
+  }
+`
+
+export default {
+  teamManager: gql`
+  query {
+    teams {
+      id
+      role
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        user {
+          id
+        }
+      }
+    }
+
+    users {
+      id
+      username
+      admin
+    }
+
+    manuscripts {
+      ${fragmentFields}
+      manuscriptVersions {
+        ${fragmentFields}
+      }
+    }
+  }
+  `,
+}
diff --git a/app/components/component-xpub-teams-manager/src/components/molecules/Page.js b/app/components/component-xpub-teams-manager/src/components/molecules/Page.js
new file mode 100644
index 0000000000000000000000000000000000000000..d9d9ed7c6c88bdd3eb67438e4c8e2922a4e92025
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/molecules/Page.js
@@ -0,0 +1,30 @@
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+const Page = styled.div`
+  margin: auto;
+  max-width: 60em;
+`
+
+const Section = styled.div`
+  margin: ${th('gridUnit')} 0;
+
+  &:not(:last-of-type) {
+    margin-bottom: calc(${th('gridUnit')} * 2);
+  }
+`
+
+const Heading = styled.div`
+  color: ${th('colorPrimary')};
+  font-family: ${th('fontReading')};
+  font-size: ${th('fontSizeHeading3')};
+  margin: ${th('gridUnit')} 0;
+  text-transform: uppercase;
+`
+
+const UploadContainer = styled.div`
+  display: flex;
+  justify-content: center;
+`
+
+export { Page, Section, Heading, UploadContainer }
diff --git a/app/components/component-xpub-teams-manager/src/components/molecules/Table.js b/app/components/component-xpub-teams-manager/src/components/molecules/Table.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e7c813148d4f07c73b3b385e2fb94dfd85e638d
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/components/molecules/Table.js
@@ -0,0 +1,26 @@
+import styled from 'styled-components'
+
+const TeamTable = styled.div`
+  display: flex;
+  flex-wrap: wrap;
+  margin: 0 0 3px 0;
+  padding: 0;
+`
+
+const TeamTableCell = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-sizing: border-box;
+  flex-grow: 1;
+  padding: 20px 10px 0 0;
+  list-style: none;
+  border: solid @bw white;
+  background: fade(slategrey, 20%);
+  width: ${props => props.width || 20}%;
+  > h4 {
+    margin: 0;
+  }
+`
+
+export { TeamTable, TeamTableCell }
diff --git a/app/components/component-xpub-teams-manager/src/index.js b/app/components/component-xpub-teams-manager/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..0b91ce74b9fa94404bcd77c1b1784b65875382e1
--- /dev/null
+++ b/app/components/component-xpub-teams-manager/src/index.js
@@ -0,0 +1,5 @@
+module.exports = {
+  frontend: {
+    components: [() => require('./components/TeamsManagerPage')],
+  },
+}
diff --git a/app/components/xpub-journal/CHANGELOG.md b/app/components/xpub-journal/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..ba85b2e66b5e305c368459de6842a1555de1b5cf
--- /dev/null
+++ b/app/components/xpub-journal/CHANGELOG.md
@@ -0,0 +1,173 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.22...xpub-journal@0.1.0) (2019-11-11)
+
+
+### Features
+
+* **xpub:** bring back xpub components ([fb69994](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb69994098b4e2dbcca75b4786ebb1335af730b9))
+
+
+
+
+
+## [0.0.22](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.21...xpub-journal@0.0.22) (2019-09-11)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.21](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.20...xpub-journal@0.0.21) (2019-09-04)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.20](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.19...xpub-journal@0.0.20) (2019-08-30)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.19](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.18...xpub-journal@0.0.19) (2019-08-08)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.17...xpub-journal@0.0.18) (2019-08-05)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.16...xpub-journal@0.0.17) (2019-07-12)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.15...xpub-journal@0.0.16) (2019-07-09)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.14...xpub-journal@0.0.15) (2019-07-03)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.13...xpub-journal@0.0.14) (2019-06-28)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.12...xpub-journal@0.0.13) (2019-06-24)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.11...xpub-journal@0.0.12) (2019-06-21)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.10...xpub-journal@0.0.11) (2019-06-13)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.9...xpub-journal@0.0.10) (2019-06-12)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.8...xpub-journal@0.0.9) (2019-05-27)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.7...xpub-journal@0.0.8) (2019-04-25)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+## [0.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.6...xpub-journal@0.0.7) (2019-04-18)
+
+**Note:** Version bump only for package xpub-journal
+
+
+
+
+
+<a name="0.0.6"></a>
+## [0.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.5...xpub-journal@0.0.6) (2018-04-03)
+
+
+
+
+**Note:** Version bump only for package xpub-journal
+
+<a name="0.0.5"></a>
+## [0.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.4...xpub-journal@0.0.5) (2018-03-27)
+
+
+
+
+**Note:** Version bump only for package xpub-journal
+
+<a name="0.0.4"></a>
+## [0.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-journal@0.0.3...xpub-journal@0.0.4) (2018-03-15)
+
+
+
+
+**Note:** Version bump only for package xpub-journal
+
+<a name="0.0.3"></a>
+
+## 0.0.3 (2018-03-09)
+
+**Note:** Version bump only for package xpub-journal
diff --git a/app/components/xpub-journal/README.md b/app/components/xpub-journal/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f0f0deea9439376c420f22253c81c6b2e19fead3
--- /dev/null
+++ b/app/components/xpub-journal/README.md
@@ -0,0 +1,7 @@
+## xpub-journal
+
+`xpub-collabra` is built for a single journal. As there was no place for a journal in the data model, the journal configuration is passed into the app via this JournalProvider component.
+
+_Note:  
+This will probably need to be replaced by journal configuration in the database, in order to support multiple journals in xpub apps.  
+To be revisited after we make a more final decision on xpub's data model._
diff --git a/app/components/xpub-journal/package.json b/app/components/xpub-journal/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..d0375e91ffedb8d55724edc28cd0ce990d885180
--- /dev/null
+++ b/app/components/xpub-journal/package.json
@@ -0,0 +1,19 @@
+{
+  "name": "xpub-journal",
+  "version": "0.1.0",
+  "main": "src",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "dependencies": {
+    "prop-types": "^15.5.10",
+    "recompose": "^0.30.0"
+  },
+  "peerDependencies": {
+    "react": ">=16.9"
+  },
+  "gitHead": "6b100b76f21785e5e50fca082a2743d3d0b1c88a"
+}
diff --git a/app/components/xpub-journal/src/components/JournalProvider.js b/app/components/xpub-journal/src/components/JournalProvider.js
new file mode 100644
index 0000000000000000000000000000000000000000..28328034957f4a7800b1059b3dfbd7f683bf255d
--- /dev/null
+++ b/app/components/xpub-journal/src/components/JournalProvider.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { withContext, getContext } from 'recompose'
+
+export const JournalProvider = withContext(
+  { journal: PropTypes.object },
+  ({ journal }) => ({ journal }),
+)(props => React.Children.only(props.children))
+
+export const withJournal = getContext({
+  journal: PropTypes.object,
+})
diff --git a/app/components/xpub-journal/src/components/index.js b/app/components/xpub-journal/src/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..2a193d6a18395e6f912765a66bc316d8c297e8c8
--- /dev/null
+++ b/app/components/xpub-journal/src/components/index.js
@@ -0,0 +1 @@
+export { JournalProvider, withJournal } from './JournalProvider'
diff --git a/app/components/xpub-journal/src/index.js b/app/components/xpub-journal/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..b939e8a731989ceec3330aa2740f2395aebba956
--- /dev/null
+++ b/app/components/xpub-journal/src/index.js
@@ -0,0 +1,11 @@
+import React from 'react'
+
+const JournalContext = React.createContext()
+
+const JournalProvider = props => (
+  <JournalContext.Provider value={props.journal}>
+    {props.children}
+  </JournalContext.Provider>
+)
+
+export { JournalContext, JournalProvider }
diff --git a/app/components/xpub-validators/CHANGELOG.md b/app/components/xpub-validators/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..0c2ed41c7135c5bdb2af71cdcf01169cffe535a7
--- /dev/null
+++ b/app/components/xpub-validators/CHANGELOG.md
@@ -0,0 +1,213 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.0.28](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.27...xpub-validators@0.0.28) (2019-11-11)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.27](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.26...xpub-validators@0.0.27) (2019-09-11)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.26](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.25...xpub-validators@0.0.26) (2019-09-04)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.25](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.24...xpub-validators@0.0.25) (2019-08-30)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.24](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.23...xpub-validators@0.0.24) (2019-08-08)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.23](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.22...xpub-validators@0.0.23) (2019-08-05)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.22](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.21...xpub-validators@0.0.22) (2019-07-12)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.21](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.20...xpub-validators@0.0.21) (2019-07-09)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.20](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.19...xpub-validators@0.0.20) (2019-07-03)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.19](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.18...xpub-validators@0.0.19) (2019-06-28)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.17...xpub-validators@0.0.18) (2019-06-24)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.16...xpub-validators@0.0.17) (2019-06-21)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.15...xpub-validators@0.0.16) (2019-06-13)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.14...xpub-validators@0.0.15) (2019-06-12)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.13...xpub-validators@0.0.14) (2019-05-27)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.12...xpub-validators@0.0.13) (2019-04-25)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.11...xpub-validators@0.0.12) (2019-04-18)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.10...xpub-validators@0.0.11) (2019-04-09)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.9...xpub-validators@0.0.10) (2019-03-06)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.8...xpub-validators@0.0.9) (2019-03-05)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.7...xpub-validators@0.0.8) (2019-02-19)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+## [0.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.6...xpub-validators@0.0.7) (2019-02-19)
+
+**Note:** Version bump only for package xpub-validators
+
+
+
+
+
+<a name="0.0.6"></a>
+## [0.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.5...xpub-validators@0.0.6) (2018-09-04)
+
+
+### Bug Fixes
+
+* **fomrbuilder:** fix validation ([98b3b5e](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/98b3b5e))
+
+
+
+
+<a name="0.0.5"></a>
+## [0.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.4...xpub-validators@0.0.5) (2018-04-03)
+
+
+
+
+**Note:** Version bump only for package xpub-validators
+
+<a name="0.0.4"></a>
+## [0.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-validators@0.0.3...xpub-validators@0.0.4) (2018-03-15)
+
+
+
+
+**Note:** Version bump only for package xpub-validators
+
+<a name="0.0.3"></a>
+
+## 0.0.3 (2018-03-09)
+
+**Note:** Version bump only for package xpub-validators
diff --git a/app/components/xpub-validators/README.md b/app/components/xpub-validators/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ebb99c0a9cc40ce5f272e98ded21fdd911723ebd
--- /dev/null
+++ b/app/components/xpub-validators/README.md
@@ -0,0 +1,3 @@
+# xpub-validators
+
+Common validator functions for form fields, for use with `redux-form`.
diff --git a/app/components/xpub-validators/package.json b/app/components/xpub-validators/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..99a3bdcc245124c5b9fb23feaf22cc5b97313c2b
--- /dev/null
+++ b/app/components/xpub-validators/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "xpub-validators",
+  "version": "0.0.28",
+  "description": "Validators for use with redux-form",
+  "main": "src",
+  "license": "MIT",
+  "dependencies": {
+    "striptags": "^3.1.0"
+  },
+  "gitHead": "6b100b76f21785e5e50fca082a2743d3d0b1c88a"
+}
diff --git a/app/components/xpub-validators/src/index.js b/app/components/xpub-validators/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..99599bd4e1c940307924c61b618cad9b74d0d04f
--- /dev/null
+++ b/app/components/xpub-validators/src/index.js
@@ -0,0 +1,62 @@
+import striptags from 'striptags'
+
+export const split = (separator = ',') => value =>
+  value ? value.split(separator) : []
+
+export const join = (separator = ',') => value =>
+  value ? value.join(separator) : value
+
+export const required = value => {
+  if (value === undefined || value === '') {
+    return 'Required'
+  }
+
+  if (Array.isArray(value) && !value.length) {
+    return 'Required'
+  }
+
+  return undefined
+}
+
+export const minChars = min => {
+  const message = `Enter at least ${min} characters`
+
+  return value => {
+    const text = striptags(value)
+    if (!text || text.length < min) {
+      return message
+    }
+
+    return undefined
+  }
+}
+
+export const maxChars = max => {
+  const message = `Enter no more than ${max} characters`
+
+  return value => {
+    const text = striptags(value)
+
+    if (!text || text.length > max) {
+      return message
+    }
+
+    return undefined
+  }
+}
+
+export const minSize = min => {
+  const message = `Enter at least ${min} ${min === 1 ? 'item' : 'items'}`
+
+  return value => {
+    if (!value) {
+      return message
+    }
+
+    if (value.length < min) {
+      return message
+    }
+
+    return undefined
+  }
+}
diff --git a/app/components/xpub-with-context/CHANGELOG.md b/app/components/xpub-with-context/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..9fc79ef58def38fa784e0190d2eee7f533aedd0b
--- /dev/null
+++ b/app/components/xpub-with-context/CHANGELOG.md
@@ -0,0 +1,271 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.30...xpub-with-context@0.2.0) (2019-11-11)
+
+
+### Features
+
+* **xpub:** bring back xpub components ([fb69994](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/fb69994098b4e2dbcca75b4786ebb1335af730b9))
+
+
+
+
+
+## [0.1.30](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.29...xpub-with-context@0.1.30) (2019-09-11)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.29](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.28...xpub-with-context@0.1.29) (2019-09-04)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.28](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.27...xpub-with-context@0.1.28) (2019-08-30)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.27](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.26...xpub-with-context@0.1.27) (2019-08-08)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.26](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.25...xpub-with-context@0.1.26) (2019-08-05)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.25](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.24...xpub-with-context@0.1.25) (2019-07-12)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.24](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.23...xpub-with-context@0.1.24) (2019-07-09)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.23](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.22...xpub-with-context@0.1.23) (2019-07-03)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.22](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.21...xpub-with-context@0.1.22) (2019-06-28)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.21](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.20...xpub-with-context@0.1.21) (2019-06-24)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.20](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.19...xpub-with-context@0.1.20) (2019-06-21)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.19](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.18...xpub-with-context@0.1.19) (2019-06-13)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.17...xpub-with-context@0.1.18) (2019-06-12)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.16...xpub-with-context@0.1.17) (2019-05-27)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.15...xpub-with-context@0.1.16) (2019-04-25)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.14...xpub-with-context@0.1.15) (2019-04-18)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.13...xpub-with-context@0.1.14) (2019-04-09)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.12...xpub-with-context@0.1.13) (2019-03-06)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.11...xpub-with-context@0.1.12) (2019-03-05)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.10...xpub-with-context@0.1.11) (2019-02-19)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.9...xpub-with-context@0.1.10) (2019-02-19)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.8...xpub-with-context@0.1.9) (2019-02-01)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.7...xpub-with-context@0.1.8) (2019-01-16)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.6...xpub-with-context@0.1.7) (2019-01-14)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.5...xpub-with-context@0.1.6) (2019-01-13)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.4...xpub-with-context@0.1.5) (2019-01-09)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.3...xpub-with-context@0.1.4) (2018-12-12)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.2...xpub-with-context@0.1.3) (2018-12-04)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.1...xpub-with-context@0.1.2) (2018-11-30)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+## [0.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/xpub-with-context@0.1.0...xpub-with-context@0.1.1) (2018-11-29)
+
+**Note:** Version bump only for package xpub-with-context
+
+
+
+
+
+<a name="0.1.0"></a>
+# 0.1.0 (2018-11-05)
+
+
+### Features
+
+* GraphQL Xpub submit component ([ba07060](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ba07060))
+
+
+
+
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
diff --git a/app/components/xpub-with-context/README.md b/app/components/xpub-with-context/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f0f0deea9439376c420f22253c81c6b2e19fead3
--- /dev/null
+++ b/app/components/xpub-with-context/README.md
@@ -0,0 +1,7 @@
+## xpub-journal
+
+`xpub-collabra` is built for a single journal. As there was no place for a journal in the data model, the journal configuration is passed into the app via this JournalProvider component.
+
+_Note:  
+This will probably need to be replaced by journal configuration in the database, in order to support multiple journals in xpub apps.  
+To be revisited after we make a more final decision on xpub's data model._
diff --git a/app/components/xpub-with-context/package.json b/app/components/xpub-with-context/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..e4f60269f91b62a3ce30c2f7a9d5df64b5e32a17
--- /dev/null
+++ b/app/components/xpub-with-context/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "xpub-with-context",
+  "version": "0.2.0",
+  "main": "src",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "peerDependencies": {
+    "react": ">=16.9"
+  },
+  "gitHead": "6b100b76f21785e5e50fca082a2743d3d0b1c88a"
+}
diff --git a/app/components/xpub-with-context/src/index.js b/app/components/xpub-with-context/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..3689e8ab7ced2b99d8ba1c581dac996f0d8467a1
--- /dev/null
+++ b/app/components/xpub-with-context/src/index.js
@@ -0,0 +1,13 @@
+import React, { useState } from 'react'
+
+const XpubContext = React.createContext([{ converting: false }, () => {}])
+
+const XpubProvider = props => {
+  const [state, setState] = useState({ converting: false })
+  return (
+    <XpubContext.Provider value={[state, setState]}>
+      {props.children}
+    </XpubContext.Provider>
+  )
+}
+export { XpubContext, XpubProvider }
diff --git a/app/routes.js b/app/routes.js
index 2e8fb5942e13a5458038a90b9c883ac3186fa908..ba04b64b5110fe8e3f20f4bf28cff447fb3bd428 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -1,26 +1,22 @@
 import React from 'react'
 import { Route, Switch, Redirect } from 'react-router-dom'
 
+// Common PubSweet components
 import Login from 'pubsweet-component-login'
 import Signup from 'pubsweet-component-signup'
 import PasswordReset from '@pubsweet/component-password-reset-client'
+import UsersManager from 'pubsweet-component-users-manager'
 
-// import {
-//   FindReviewersPage,
-//   AuthorPage as FindReviewersAuthorPage,
-//   PaperPage as FindReviewersPaperPage,
-// } from 'pubsweet-component-xpub-find-reviewers/src/components'
+// SimpleJ-specific components
+import Dashboard from './components/component-xpub-dashboard/src/components/Dashboard'
+import SubmitPage from './components/component-xpub-submit/src/components/SubmitPage'
+import ManuscriptPage from './components/component-xpub-manuscript/src/components/ManuscriptPage'
+import ReviewersPage from './components/component-xpub-review/src/components/ReviewersPage'
+import ReviewPage from './components/component-xpub-review/src/components/ReviewPage'
+import TeamPage from './components/component-xpub-teams-manager/src/components/TeamsManagerPage'
+import DecisionPage from './components/component-xpub-review/src/components/DecisionPage'
+import FormBuilderPage from './components/component-xpub-formbuilder/src/components/FormBuilderPage'
 
-import Dashboard from 'pubsweet-component-xpub-dashboard/src/components/Dashboard'
-import SubmitPage from 'pubsweet-component-xpub-submit/src/components/SubmitPage'
-import ManuscriptPage from 'pubsweet-component-xpub-manuscript/src/components/ManuscriptPage'
-import ReviewersPage from 'pubsweet-component-xpub-review/src/components/ReviewersPage'
-import ReviewPage from 'pubsweet-component-xpub-review/src/components/ReviewPage'
-import TeamPage from 'pubsweet-component-xpub-teams-manager/src/components/TeamsManagerPage'
-import DecisionPage from 'pubsweet-component-xpub-review/src/components/DecisionPage'
-import UsersManager from 'pubsweet-component-users-manager'
-import FormBuilderPage from 'pubsweet-component-xpub-formbuilder/src/components/FormBuilderPage'
-// import SimpleFormBuilderPage from 'pubsweet-component-xpub-simple-formbuilder/src/components/SimpleFormBuilderPage'
 import App from './components/App'
 
 const createReturnUrl = ({ pathname, search = '' }) => pathname + search
diff --git a/config/components.json b/config/components.json
index 661c1de018b13613094bb692486333d8034df89a..a772f9a5afc228cc27e61edc612e9b0acaa1ba89 100644
--- a/config/components.json
+++ b/config/components.json
@@ -1,13 +1,7 @@
 [
-  "pubsweet-component-xpub-dashboard",
-  "pubsweet-component-xpub-manuscript",
-  "pubsweet-component-xpub-review",
-  "pubsweet-component-xpub-review-backend",
-  "pubsweet-component-xpub-submit",
-  "pubsweet-component-xpub-teams-manager",
-  "pubsweet-component-xpub-formbuilder",
   "@pubsweet/model-team",
   "@pubsweet/model-user",
+  "./server/component-xpub-review-backend/src/",
   "./server/journal/src/",
   "./server/manuscript/src/",
   "./server/review/src/",
diff --git a/package.json b/package.json
index 25f8ea61baceb15ae059bde8e80cbff550b6b769..b5f80bf1a01c21c94453b555eece91c255669c26 100644
--- a/package.json
+++ b/package.json
@@ -42,14 +42,6 @@
     "pubsweet-component-login": "^3.0.16",
     "pubsweet-component-signup": "^2.1.10",
     "pubsweet-component-users-manager": "^3.0.21",
-    "pubsweet-component-xpub-dashboard": "^5.0.21",
-    "pubsweet-component-xpub-find-reviewers": "^0.0.24",
-    "pubsweet-component-xpub-formbuilder": "^1.0.7",
-    "pubsweet-component-xpub-manuscript": "^2.0.2",
-    "pubsweet-component-xpub-review": "^4.0.7",
-    "pubsweet-component-xpub-review-backend": "^1.0.24",
-    "pubsweet-component-xpub-submit": "^6.0.21",
-    "pubsweet-component-xpub-teams-manager": "^3.0.2",
     "pubsweet-server": "13.7.2",
     "react": "^16.3.2",
     "react-dom": "^16.3.2",
@@ -59,7 +51,6 @@
     "supertest": "^3.0.0",
     "winston": "^2.4.0",
     "xpub-journal": "^0.1.0",
-    "xpub-selectors": "^0.2.0",
     "xpub-theme": "^0.0.23",
     "xpub-with-context": "^0.2.0"
   },
diff --git a/server/component-xpub-review-backend/package.json b/server/component-xpub-review-backend/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..da79cf01d823e422b742ebf8fae9466b988b1b53
--- /dev/null
+++ b/server/component-xpub-review-backend/package.json
@@ -0,0 +1,21 @@
+{
+  "name": "pubsweet-component-xpub-review-backend",
+  "version": "1.1.0",
+  "main": "src",
+  "keywords": [],
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "dependencies": {
+    "@pubsweet/component-send-email": "^0.3.0",
+    "@pubsweet/errors": "^2.0.25",
+    "@pubsweet/models": "^0.3.0",
+    "lodash": "^4.17.11",
+    "passport": "^0.4.0"
+  },
+  "peerDependencies": {
+    "@pubsweet/logger": ">=0.0.1",
+    "config": ">=3.0.1",
+    "pubsweet-server": ">=1.0.0"
+  },
+  "gitHead": "6b100b76f21785e5e50fca082a2743d3d0b1c88a"
+}
diff --git a/server/component-xpub-review-backend/src/index.js b/server/component-xpub-review-backend/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..fbe594893158dd11c269424d39446ab4ee66933f
--- /dev/null
+++ b/server/component-xpub-review-backend/src/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+  server: () => require('./reviewBackend.js'),
+}
diff --git a/server/component-xpub-review-backend/src/reviewBackend.js b/server/component-xpub-review-backend/src/reviewBackend.js
new file mode 100644
index 0000000000000000000000000000000000000000..bdee8d7b59b3f6fdd5a205cde4a0fcd44f2c967e
--- /dev/null
+++ b/server/component-xpub-review-backend/src/reviewBackend.js
@@ -0,0 +1,246 @@
+const { pick } = require('lodash')
+const config = require('config')
+const passport = require('passport')
+const logger = require('@pubsweet/logger')
+const emailer = require('@pubsweet/component-send-email')
+
+const authsome = require('pubsweet-server/src/helpers/authsome')
+const { AuthorizationError } = require('@pubsweet/errors')
+
+const authBearer = passport.authenticate('bearer', { session: false })
+
+module.exports = app => {
+  app.patch('/api/make-invitation', authBearer, async (req, res, next) => {
+    const { User, Fragment, Team, Collection } = require('@pubsweet/models')
+
+    try {
+      const version = await Fragment.find(req.body.versionId)
+      const project = await Collection.find(req.body.projectId)
+
+      const reviewer = await Promise.all(
+        project.reviewers
+          .filter(user => user.user === req.body.reviewerId)
+          .map(({ user }) => User.find(user)),
+      )
+
+      const currentAndUpdate = {
+        current: version,
+        update: {
+          id: req.body.versionId,
+          reviewers: req.body.reviewers,
+        },
+      }
+
+      const canViewVersion = await authsome.can(req.user, 'GET', version)
+      const canPatchVersion = await authsome.can(
+        req.user,
+        'PATCH',
+        currentAndUpdate,
+      )
+
+      if (!canPatchVersion || !canViewVersion) throw new AuthorizationError()
+      let versionUpdateData = req.body.reviewers
+      if (canPatchVersion.filter) {
+        versionUpdateData = canPatchVersion.filter(versionUpdateData)
+      }
+      await version.updateProperties({ reviewers: versionUpdateData })
+      await version.save()
+
+      logger.info(`Sending invitation email to ${reviewer[0].email}`)
+
+      let message = `<p>${version.metadata.title}</p>`
+      message += `<p>${version.metadata.abstract}</p>`
+      message += `<p><a href='${
+        config.get('pubsweet-server').baseUrl
+      }'>Click here to navigate to the Dashboard</a></p>`
+
+      emailer
+        .send({
+          from: config.get('mailer.from'),
+          to: reviewer[0].email,
+          subject: 'Review Invitation',
+          html: message,
+        })
+        .catch(err => {
+          logger.error(err.response)
+        })
+
+      const currentAndUpdateProject = {
+        current: project,
+        update: req.body,
+      }
+
+      const canViewProject = await authsome.can(req.user, 'GET', project)
+      const canPatchProject = await authsome.can(
+        req.user,
+        'PATCH',
+        currentAndUpdateProject,
+      )
+      if (!canPatchProject || !canViewProject) throw new AuthorizationError()
+
+      await Team.query().upsertGraphAndFetch(
+        {
+          role: 'reviewer',
+          name: 'Reviewer',
+          objectId: version.id,
+          objectType: 'fragment',
+          members: [{ user: { id: reviewer[0].id } }],
+        },
+        { relate: true },
+      )
+
+      res.send({
+        version: canViewVersion.filter
+          ? canViewVersion.filter(version)
+          : version,
+        project: canViewProject.filter
+          ? canViewProject.filter(project)
+          : project,
+      })
+    } catch (err) {
+      next(err)
+    }
+  })
+
+  app.patch('/api/make-decision', authBearer, async (req, res, next) => {
+    try {
+      const { User, Fragment, Collection } = require('@pubsweet/models')
+
+      const version = await Fragment.find(req.body.versionId)
+      const project = await Collection.find(req.body.projectId)
+      const authors = await Promise.all(version.owners.map(id => User.find(id)))
+
+      let currentAndUpdate = {
+        current: version,
+        update: {
+          id: req.body.versionId,
+          decision: req.body.decision,
+        },
+      }
+
+      const canViewVersion = await authsome.can(req.user, 'GET', version)
+      const canPatchVersion = await authsome.can(
+        req.user,
+        'PATCH',
+        currentAndUpdate,
+      )
+
+      if (!canPatchVersion || !canViewVersion) throw new AuthorizationError()
+      let versionUpdateData = { decision: req.body.decision }
+      if (canPatchVersion.filter) {
+        versionUpdateData = canPatchVersion.filter(versionUpdateData)
+      }
+      await version.updateProperties(versionUpdateData)
+
+      let nextVersionData
+      let projectUpdateData = {}
+      let message
+      switch (version.decision.recommendation) {
+        case 'accept':
+          projectUpdateData.status = 'accepted'
+          message = '<p>Your manuscript has been accepted</p>'
+          break
+
+        case 'reject':
+          projectUpdateData.status = 'rejected'
+          message = '<p>Your manuscript has been rejected</p>'
+          break
+
+        case 'revise': {
+          projectUpdateData.status = 'revising'
+          message = '<p>Revisions to your manuscript have been requested</p>'
+
+          const cloned = pick(version, [
+            'collections',
+            'owners',
+            'source',
+            'metadata',
+            'declarations',
+            'suggestions',
+            'files',
+            'notes',
+          ])
+          nextVersionData = {
+            fragmentType: 'version',
+            created: new Date(),
+            ...cloned,
+            version: version.version + 1,
+          }
+
+          break
+        }
+
+        default:
+          throw new Error('Unknown decision type')
+      }
+
+      message += version.decision.note.content
+
+      let nextVersion
+      let canViewNextVersion
+      if (nextVersionData) {
+        const canCreateVersion = await authsome.can(req.user, 'POST', {
+          path: '/collections/:collectionId/fragments',
+          collection: project,
+          fragment: nextVersionData,
+        })
+        if (!canCreateVersion) throw new AuthorizationError()
+        if (canCreateVersion.filter) {
+          nextVersionData = canCreateVersion.filter(nextVersionData)
+        }
+        nextVersion = new Fragment(nextVersionData)
+
+        canViewNextVersion = await authsome.can(req.user, 'GET', nextVersion)
+      }
+
+      currentAndUpdate = { current: project, update: req.body }
+
+      const canViewProject = await authsome.can(req.user, 'GET', project)
+      const canPatchProject = await authsome.can(
+        req.user,
+        'PATCH',
+        currentAndUpdate,
+      )
+      if (!canPatchProject || !canViewProject) throw new AuthorizationError()
+      if (canPatchProject.filter) {
+        projectUpdateData = canPatchProject.filter(projectUpdateData)
+      }
+      await project.updateProperties(projectUpdateData)
+
+      await Promise.all([
+        version.save(),
+        nextVersion && nextVersion.save(),
+        nextVersion && project.addFragment(nextVersion),
+        project.save(),
+      ])
+
+      const authorEmails = authors.map(user => user.email)
+      logger.info(`Sending decision email to ${authorEmails}`)
+      emailer
+        .send({
+          from: config.get('mailer.from'),
+          to: authorEmails,
+          subject: 'Decision made',
+          html: message,
+        })
+        .catch(err => {
+          logger.error(err.response)
+        })
+
+      res.send({
+        version: canViewVersion.filter
+          ? canViewVersion.filter(version)
+          : version,
+        project: canViewProject.filter
+          ? canViewProject.filter(project)
+          : project,
+        nextVersion:
+          canViewNextVersion && canViewNextVersion.filter
+            ? canViewNextVersion.filter(nextVersion)
+            : nextVersion,
+      })
+    } catch (err) {
+      next(err)
+    }
+  })
+}
diff --git a/server/component-xpub-review-backend/src/reviewBackend.test.js b/server/component-xpub-review-backend/src/reviewBackend.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd0539fa3b1939d05df5f98827c1d01bc24342a3
--- /dev/null
+++ b/server/component-xpub-review-backend/src/reviewBackend.test.js
@@ -0,0 +1,171 @@
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+process.env.NODE_CONFIG = '{"mailer":{"from":"sender@example.com"}}'
+
+const express = require('express')
+const supertest = require('supertest')
+const bodyParser = require('body-parser')
+
+// mocks
+jest.mock('@pubsweet/component-send-email', () => ({
+  send: jest.fn().mockImplementation(() => Promise.resolve({})),
+}))
+
+jest.mock('@pubsweet/db-manager', () => ({
+  models: {
+    Team: () => ({
+      find: jest.fn(() => ({
+        id: '9555530a-ca92-4e74-a48c-b21ccc109ca8',
+        teams: ['08888b14-8b64-420d-898f-b2bdd9fbd57c'],
+        email: 'author@example.org',
+      })),
+      save: () => {},
+    }),
+    User: {
+      find: jest.fn(() => ({
+        id: '9555530a-ca92-4e74-a48c-b21ccc109ca8',
+        teams: ['08888b14-8b64-420d-898f-b2bdd9fbd57c'],
+        email: 'author@example.org',
+        save: () => {},
+      })),
+    },
+    Fragment: {
+      find: jest.fn(() => ({
+        version: 1,
+        owners: [{}],
+        metadata: {
+          title: 'title',
+          abstract: 'abstract',
+        },
+        updateProperties(update) {
+          Object.assign(this, update)
+        },
+        save: () => {},
+      })),
+    },
+    Collection: {
+      find: jest.fn(() => ({
+        updateProperties: () => ({}),
+        reviewers: [
+          {
+            user: '9555530a-ca92-4e74-a48c-b21ccc109ca8',
+          },
+        ],
+        save: () => {},
+      })),
+    },
+  },
+}))
+
+jest.mock('pubsweet-server/src/helpers/authsome', () => ({
+  can: jest.fn(() => true),
+}))
+
+jest.mock('passport', () => ({
+  authenticate: jest.fn((AuthBear, data) =>
+    jest.fn((req, res, next) => next()),
+  ),
+}))
+
+const authsome = require('pubsweet-server/src/helpers/authsome')
+const transport = require('@pubsweet/component-send-email')
+const component = require('./reviewBackend')
+
+function makeApp() {
+  const app = express()
+  app.use(bodyParser.json())
+  component(app)
+  return supertest(app)
+}
+
+describe('/api/make-decision route', () => {
+  beforeEach(() => {
+    jest.clearAllMocks()
+  })
+  it('sends email on acceptance', async () => {
+    const app = makeApp()
+    const response = await app.patch('/api/make-decision').send({
+      decision: { recommendation: 'accept', note: { content: 'blah blah' } },
+      versionId: 1,
+      projectId: 2,
+    })
+    expect(response.body.version).toBeDefined()
+    expect(response.body.project).toBeDefined()
+    expect(response.body.nextVersion).not.toBeDefined()
+    expect(transport.send).toHaveBeenCalledWith(
+      expect.objectContaining({
+        from: 'sender@example.com',
+        to: ['author@example.org'],
+        subject: 'Decision made',
+      }),
+    )
+  })
+
+  it('rejects if not authorised', async () => {
+    authsome.can.mockReturnValue(false)
+    const app = makeApp()
+    const response = await app.patch('/api/make-decision').send({
+      decision: { recommendation: 'accept', note: { content: 'blah blah' } },
+      versionId: 1,
+      projectId: 2,
+    })
+    expect(response.status).toBe(403)
+    expect(transport.send).not.toHaveBeenCalled()
+  })
+})
+
+describe('/api/make-invitation route', () => {
+  beforeEach(() => {
+    jest.clearAllMocks()
+  })
+  it('sends invitation email', async () => {
+    authsome.can.mockReturnValue(true)
+    const app = makeApp()
+    const response = await app.patch('/api/make-invitation').send({
+      versionId: '1',
+      projectId: '2',
+      reviewerId: '9555530a-ca92-4e74-a48c-b21ccc109ca8',
+      reviewers: [
+        {
+          events: {
+            invited: 'Mon Apr 02 2018 14:58:06 GMT+0300 (EEST)',
+          },
+          email: 'author@example.org',
+          status: 'invited',
+        },
+      ],
+    })
+
+    expect(response.body.version.reviewers).toBeDefined()
+    expect(transport.send).toHaveBeenCalledWith(
+      expect.objectContaining({
+        from: 'sender@example.com',
+        to: 'author@example.org',
+        subject: 'Review Invitation',
+        html:
+          "<p>title</p><p>abstract</p><p><a href='http://example.com'>Click here to navigate to the Dashboard</a></p>",
+      }),
+    )
+  })
+
+  it('rejects if not authorised', async () => {
+    authsome.can.mockReturnValue(false)
+    const app = makeApp()
+    const response = await app.patch('/api/make-invitation').send({
+      versionId: '1',
+      projectId: '2',
+      reviewerId: '9555530a-ca92-4e74-a48c-b21ccc109ca8',
+      reviewers: [
+        {
+          events: {
+            invited: 'Mon Apr 02 2018 14:58:06 GMT+0300 (EEST)',
+          },
+          email: 'author@example.org',
+          status: 'invited',
+        },
+      ],
+    })
+
+    expect(response.status).toBe(403)
+    expect(transport.send).not.toHaveBeenCalled()
+  })
+})