From 952a1340c40337594d8390c3861640b176686f4f Mon Sep 17 00:00:00 2001
From: Alf Eaton <eaton.alf@gmail.com>
Date: Thu, 17 Aug 2017 14:12:09 +0100
Subject: [PATCH] Restart as a monorepo

---
 .envrc                                        |   6 +-
 .eslintrc                                     |  19 +-
 .gitignore                                    |   6 +-
 .nvmrc                                        |   2 +-
 README.md                                     |  17 +-
 app/app.js                                    |  33 --
 app/components/App.js                         |  18 -
 app/components/Navigation.js                  |  37 --
 app/components/NavigationContainer.js         |  32 --
 app/components/WaxContainer.js                |  78 ----
 app/routes.js                                 |  39 --
 app/styles.js                                 |   5 -
 app/styles/_base.scss                         |  26 --
 app/styles/_bootstrap.scss                    |  24 -
 app/styles/_bootstrap_buttons.scss            |  12 -
 app/styles/_bootstrap_form.scss               |  31 --
 app/styles/_fonts.scss                        |  12 -
 app/styles/_icons.scss                        |   2 -
 app/styles/_modal.scss                        |  68 ---
 app/styles/_navbar.scss                       |  73 ---
 app/styles/_reset.scss                        | 136 ------
 app/styles/_table.scss                        |  98 ----
 app/styles/_variables.scss                    |  10 -
 app/styles/main.scss                          |  18 -
 config/shared.js                              |  66 ---
 docs/schema-outline.ftml                      | 419 ------------------
 docs/workflow-outline.ftml                    | 121 -----
 lerna.json                                    |   7 +
 package.json                                  |  92 +---
 packages/collabra/.gitignore                  |   6 +
 packages/collabra/app/app.js                  |  33 ++
 packages/collabra/app/components/App.js       |  77 ++++
 .../collabra/app/components/App.local.css     |   3 +
 .../app/components/AuthenticatedPage.js       |  30 +-
 packages/collabra/app/components/Logout.js    |  37 ++
 .../collabra/app/components/SubmitPage.js     |  18 +
 .../collabra/app/config/journal/decisions.js  |  22 +
 .../app/config/journal/declarations.js        |  81 ++++
 packages/collabra/app/config/journal/index.js |   4 +
 .../collabra/app/config/journal/metadata.js   |   4 +
 .../collabra/app/config/journal/sections.js   |   6 +
 {app => packages/collabra/app}/index.ejs      |   0
 {app => packages/collabra/app}/index.html     |   0
 packages/collabra/app/routes.js               |  37 ++
 packages/collabra/config/authsome.js          |   8 +
 .../collabra/config}/components.json          |   5 +-
 {config => packages/collabra/config}/dev.js   |   0
 .../collabra/config}/development.js           |   0
 .../collabra/config}/production.js            |   0
 packages/collabra/config/shared.js            |  32 ++
 {config => packages/collabra/config}/test.js  |   0
 packages/collabra/config/validations.js       |  15 +
 packages/collabra/package.json                |  67 +++
 .../collabra/static}/pubsweet-rgb-small.jpg   | Bin
 .../collabra/static}/pubsweet.jpg             | Bin
 packages/collabra/webpack/common-rules.js     | 105 +++++
 .../collabra/webpack}/webpack.dev.config.js   |  16 +-
 .../webpack}/webpack.production.config.js     |  15 +-
 .../collabra/webpack}/webpack.test.config.js  |  12 +-
 packages/component-dashboard/package.json     |  26 ++
 .../src/components/Dashboard.js               |  24 +
 .../src/components/Dashboard.local.css        |   4 +
 .../src/components/DashboardItem.js           |   9 +
 .../src/components/DashboardPage.js           |  22 +
 .../src/components/UploadManuscript.js        |  94 ++++
 .../src/components/UploadManuscript.local.css |  26 ++
 .../src/components/index.js                   |   1 +
 packages/component-dashboard/src/index.js     |   7 +
 packages/component-manuscript/package.json    |  43 ++
 .../src/components/Manuscript.js              |  26 ++
 .../src/components/Manuscript.local.css       |   9 +
 .../src/components/ManuscriptPage.js          |  19 +
 .../src/components/index.js                   |   1 +
 packages/component-manuscript/src/index.js    |   7 +
 .../component-manuscript/webpack.config.js    |  86 ++++
 packages/component-submit/package.json        |  25 ++
 .../component-submit/src/components/Submit.js |   9 +
 .../src/components/Submit.local.css           |   3 +
 .../src/components/SubmitPage.js              |  17 +
 .../component-submit/src/components/index.js  |   1 +
 packages/component-submit/src/index.js        |   7 +
 packages/component-submit/src/lib/date.js     |   3 +
 packages/component-submit/src/lib/sort.js     |   3 +
 packages/component-submit/src/lib/text.js     |   1 +
 packages/xpub-fonts/package.json              |  12 +
 packages/xpub-fonts/src/index.js              |   3 +
 packages/xpub-selectors/package.json          |   7 +
 packages/xpub-selectors/src/index.js          |  14 +
 packages/xpub-ui/.eslintrc                    |   7 +
 .../lib/styleguide/StyleGuideRenderer.js      |  25 ++
 .../styleguide/StyleGuideRenderer.local.css   |  37 ++
 packages/xpub-ui/lib/styleguide/Wrapper.js    |  21 +
 .../xpub-ui/lib/styleguide/Wrapper.local.css  |   3 +
 packages/xpub-ui/lib/styleguide/Wrapper.scss  |   4 +
 packages/xpub-ui/package.json                 |  34 ++
 packages/xpub-ui/src/AppBar.js                |  27 ++
 packages/xpub-ui/src/AppBar.local.css         |  16 +
 packages/xpub-ui/src/AppBar.md                |  20 +
 packages/xpub-ui/src/Radio.js                 |  18 +
 packages/xpub-ui/src/Radio.local.css          |  13 +
 packages/xpub-ui/src/Radio.md                 |  25 ++
 packages/xpub-ui/src/RadioGroup.js            |  19 +
 packages/xpub-ui/src/RadioGroup.md            |  26 ++
 packages/xpub-ui/src/YesOrNo.js               |  25 ++
 packages/xpub-ui/src/YesOrNo.local.css        |   3 +
 packages/xpub-ui/src/YesOrNo.md               |  10 +
 packages/xpub-ui/src/index.js                 |   2 +
 packages/xpub-ui/styleguide.config.js         |  23 +
 packages/xpub-ui/webpack.config.js            |  78 ++++
 permissions/index.js                          |   3 -
 webpack/common-rules.js                       | 114 -----
 webpack/components.js                         |   8 -
 112 files changed, 1592 insertions(+), 1618 deletions(-)
 delete mode 100644 app/app.js
 delete mode 100644 app/components/App.js
 delete mode 100644 app/components/Navigation.js
 delete mode 100644 app/components/NavigationContainer.js
 delete mode 100644 app/components/WaxContainer.js
 delete mode 100644 app/routes.js
 delete mode 100644 app/styles.js
 delete mode 100644 app/styles/_base.scss
 delete mode 100644 app/styles/_bootstrap.scss
 delete mode 100644 app/styles/_bootstrap_buttons.scss
 delete mode 100644 app/styles/_bootstrap_form.scss
 delete mode 100644 app/styles/_fonts.scss
 delete mode 100644 app/styles/_icons.scss
 delete mode 100644 app/styles/_modal.scss
 delete mode 100644 app/styles/_navbar.scss
 delete mode 100644 app/styles/_reset.scss
 delete mode 100644 app/styles/_table.scss
 delete mode 100644 app/styles/_variables.scss
 delete mode 100644 app/styles/main.scss
 delete mode 100644 config/shared.js
 delete mode 100644 docs/schema-outline.ftml
 delete mode 100644 docs/workflow-outline.ftml
 create mode 100644 lerna.json
 create mode 100644 packages/collabra/.gitignore
 create mode 100644 packages/collabra/app/app.js
 create mode 100644 packages/collabra/app/components/App.js
 create mode 100644 packages/collabra/app/components/App.local.css
 rename app/components/AuthenticatedContainer.js => packages/collabra/app/components/AuthenticatedPage.js (69%)
 create mode 100644 packages/collabra/app/components/Logout.js
 create mode 100644 packages/collabra/app/components/SubmitPage.js
 create mode 100644 packages/collabra/app/config/journal/decisions.js
 create mode 100644 packages/collabra/app/config/journal/declarations.js
 create mode 100644 packages/collabra/app/config/journal/index.js
 create mode 100644 packages/collabra/app/config/journal/metadata.js
 create mode 100644 packages/collabra/app/config/journal/sections.js
 rename {app => packages/collabra/app}/index.ejs (100%)
 rename {app => packages/collabra/app}/index.html (100%)
 create mode 100644 packages/collabra/app/routes.js
 create mode 100644 packages/collabra/config/authsome.js
 rename {config => packages/collabra/config}/components.json (58%)
 rename {config => packages/collabra/config}/dev.js (100%)
 rename {config => packages/collabra/config}/development.js (100%)
 rename {config => packages/collabra/config}/production.js (100%)
 create mode 100644 packages/collabra/config/shared.js
 rename {config => packages/collabra/config}/test.js (100%)
 create mode 100644 packages/collabra/config/validations.js
 create mode 100644 packages/collabra/package.json
 rename {static => packages/collabra/static}/pubsweet-rgb-small.jpg (100%)
 rename {static => packages/collabra/static}/pubsweet.jpg (100%)
 create mode 100644 packages/collabra/webpack/common-rules.js
 rename {webpack => packages/collabra/webpack}/webpack.dev.config.js (81%)
 rename {webpack => packages/collabra/webpack}/webpack.production.config.js (87%)
 rename {webpack => packages/collabra/webpack}/webpack.test.config.js (90%)
 create mode 100644 packages/component-dashboard/package.json
 create mode 100644 packages/component-dashboard/src/components/Dashboard.js
 create mode 100644 packages/component-dashboard/src/components/Dashboard.local.css
 create mode 100644 packages/component-dashboard/src/components/DashboardItem.js
 create mode 100644 packages/component-dashboard/src/components/DashboardPage.js
 create mode 100644 packages/component-dashboard/src/components/UploadManuscript.js
 create mode 100644 packages/component-dashboard/src/components/UploadManuscript.local.css
 create mode 100644 packages/component-dashboard/src/components/index.js
 create mode 100644 packages/component-dashboard/src/index.js
 create mode 100644 packages/component-manuscript/package.json
 create mode 100644 packages/component-manuscript/src/components/Manuscript.js
 create mode 100644 packages/component-manuscript/src/components/Manuscript.local.css
 create mode 100644 packages/component-manuscript/src/components/ManuscriptPage.js
 create mode 100644 packages/component-manuscript/src/components/index.js
 create mode 100644 packages/component-manuscript/src/index.js
 create mode 100644 packages/component-manuscript/webpack.config.js
 create mode 100644 packages/component-submit/package.json
 create mode 100644 packages/component-submit/src/components/Submit.js
 create mode 100644 packages/component-submit/src/components/Submit.local.css
 create mode 100644 packages/component-submit/src/components/SubmitPage.js
 create mode 100644 packages/component-submit/src/components/index.js
 create mode 100644 packages/component-submit/src/index.js
 create mode 100644 packages/component-submit/src/lib/date.js
 create mode 100644 packages/component-submit/src/lib/sort.js
 create mode 100644 packages/component-submit/src/lib/text.js
 create mode 100644 packages/xpub-fonts/package.json
 create mode 100644 packages/xpub-fonts/src/index.js
 create mode 100644 packages/xpub-selectors/package.json
 create mode 100644 packages/xpub-selectors/src/index.js
 create mode 100644 packages/xpub-ui/.eslintrc
 create mode 100644 packages/xpub-ui/lib/styleguide/StyleGuideRenderer.js
 create mode 100644 packages/xpub-ui/lib/styleguide/StyleGuideRenderer.local.css
 create mode 100644 packages/xpub-ui/lib/styleguide/Wrapper.js
 create mode 100644 packages/xpub-ui/lib/styleguide/Wrapper.local.css
 create mode 100644 packages/xpub-ui/lib/styleguide/Wrapper.scss
 create mode 100644 packages/xpub-ui/package.json
 create mode 100644 packages/xpub-ui/src/AppBar.js
 create mode 100644 packages/xpub-ui/src/AppBar.local.css
 create mode 100644 packages/xpub-ui/src/AppBar.md
 create mode 100644 packages/xpub-ui/src/Radio.js
 create mode 100644 packages/xpub-ui/src/Radio.local.css
 create mode 100644 packages/xpub-ui/src/Radio.md
 create mode 100644 packages/xpub-ui/src/RadioGroup.js
 create mode 100644 packages/xpub-ui/src/RadioGroup.md
 create mode 100644 packages/xpub-ui/src/YesOrNo.js
 create mode 100644 packages/xpub-ui/src/YesOrNo.local.css
 create mode 100644 packages/xpub-ui/src/YesOrNo.md
 create mode 100644 packages/xpub-ui/src/index.js
 create mode 100644 packages/xpub-ui/styleguide.config.js
 create mode 100644 packages/xpub-ui/webpack.config.js
 delete mode 100644 permissions/index.js
 delete mode 100644 webpack/common-rules.js
 delete mode 100644 webpack/components.js

diff --git a/.envrc b/.envrc
index 41c57fe011..7e21fc2f6d 100644
--- a/.envrc
+++ b/.envrc
@@ -1,3 +1,3 @@
-if declare -Ff use_nvm >/dev/null; then
-  use nvm 
-fi
+#https://github.com/direnv/direnv/wiki/Node#load-nodejs-version-from-a-node-version-or-nvmrc-file
+set -e
+use node
diff --git a/.eslintrc b/.eslintrc
index e8d0389584..5e603ecd19 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,20 +1,3 @@
 {
-  "parser": "babel-eslint",
-  "extends": [
-    "standard",
-    "plugin:react/recommended"
-  ],
-  "plugins": [
-    "react"
-  ],
-  "parserOptions": {
-    "ecmaVersion": 7,
-    "ecmaFeatures": {
-      "jsx": true
-    }
-  },
-  "env": {
-    "es6": true,
-    "browser": true
-  }
+  "extends": "react-app"
 }
diff --git a/.gitignore b/.gitignore
index 1be6079c9d..2053ddd9dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,3 @@
-api
-logs
-node_modules
+*.log
+.env
 .env.*
-_build
diff --git a/.nvmrc b/.nvmrc
index c8357abfec..7f8f011eb7 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-7.9
\ No newline at end of file
+7
diff --git a/README.md b/README.md
index 5a01649695..f5b8ddc9b8 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,16 @@
-Note: xpub is still _very_ new. This repository contains components for uploading and submitting a manuscript, but is not yet ready for use.
+Note: xpub is still _very_ new. This repository contains an initial set of components but is not yet ready for use.
+
+## Installing
+
+In the root directory, run `npm install` then `npm run hoist` to install all the dependencies.
+
+## Contents
+
+* `collabra`: a PubSweet application that provides configuration and routing.
+* `component-dashboard`: a PubSweet component that provides a Dashboard page.
+* `component-submit`: a PubSweet component that provides a Submit page.
+* `component-manuscript`: a PubSweet component that provides a Manuscript page.
+* `xpub-fonts`: an index that imports the fonts for xpub applications
+* `xpub-selectors`: some useful redux selectors
+* `xpub-ui`: a library of user interface elements for use in PubSweet components.
+
diff --git a/app/app.js b/app/app.js
deleted file mode 100644
index 1f2aeaad23..0000000000
--- a/app/app.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import { AppContainer } from 'react-hot-loader'
-import { browserHistory } from 'react-router'
-import { syncHistoryWithStore } from 'react-router-redux'
-import { configureStore, Root } from 'pubsweet-client'
-
-import './styles'
-
-let store = configureStore(browserHistory, {})
-let history = syncHistoryWithStore(browserHistory, store)
-
-const rootEl = document.getElementById('root')
-
-ReactDOM.render(
-  <AppContainer>
-    <Root store={store} history={history} />
-  </AppContainer>,
-  rootEl
-)
-
-if (module.hot) {
-  module.hot.accept('pubsweet-client/src/components/Root', () => {
-    const NextRoot = require('pubsweet-client/src/components/Root').default
-
-    ReactDOM.render(
-      <AppContainer>
-        <NextRoot store={store} history={history} />
-      </AppContainer>,
-      rootEl
-    )
-  })
-}
diff --git a/app/components/App.js b/app/components/App.js
deleted file mode 100644
index d8de2cc1d5..0000000000
--- a/app/components/App.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import NavigationContainer from './NavigationContainer'
-
-const App = ({ children }) => (
-  <div>
-    <NavigationContainer/>
-    <div style={{marginTop: 50}}>
-      {children}
-    </div>
-  </div>
-)
-
-App.propTypes = {
-  children: PropTypes.node
-}
-
-export default App
diff --git a/app/components/Navigation.js b/app/components/Navigation.js
deleted file mode 100644
index e6c4bf913b..0000000000
--- a/app/components/Navigation.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Nav, Navbar, NavbarBrand, NavItem } from 'react-bootstrap'
-
-const Navigation = ({ appLink, appName, logout, currentUser, updateSubscriber }) => (
-  <Navbar fluid fixedTop style={{ minHeight: 0 }}>
-    <Navbar.Header>
-      <NavbarBrand>
-        <Navbar.Link href={appLink}>{appName}</Navbar.Link>
-      </NavbarBrand>
-    </Navbar.Header>
-
-    {currentUser ? (
-      <Nav pullRight>
-        {updateSubscriber && <NavItem>{updateSubscriber}</NavItem>}
-        <NavItem>logged in as {currentUser.username}</NavItem>
-        <NavItem onClick={logout}>logout</NavItem>
-      </Nav>
-    ) : (
-      <Nav pullRight>
-        <NavItem>
-          <Navbar.Link href="/signin">login</Navbar.Link>
-        </NavItem>
-      </Nav>
-    )}
-  </Navbar>
-)
-
-Navigation.propTypes = {
-  appLink: PropTypes.string.isRequired,
-  appName: PropTypes.string.isRequired,
-  currentUser: PropTypes.object,
-  logout: PropTypes.func.isRequired,
-  updateSubscriber: PropTypes.node
-}
-
-export default Navigation
diff --git a/app/components/NavigationContainer.js b/app/components/NavigationContainer.js
deleted file mode 100644
index 729b694d8d..0000000000
--- a/app/components/NavigationContainer.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import UpdateSubscriber from 'pubsweet-client/src/components/UpdateSubscriber'
-import { connect } from 'react-redux'
-import { logoutUser } from 'pubsweet-component-login/actions'
-import Navigation from './Navigation'
-
-const NavigationContainer = ({ logoutUser, currentUser }) => (
-  <Navigation appName="xpub" // TODO: make configurable
-              appLink="/projects" // TODO: make configurable
-              currentUser={currentUser}
-              logout={logoutUser}
-              updateSubscriber={UpdateSubscriber}/>
-)
-
-NavigationContainer.propTypes = {
-  currentUser: PropTypes.object,
-  logoutUser: PropTypes.func.isRequired
-}
-
-const selectCurrentUser = (state) => state.currentUser.isAuthenticated
-  ? state.currentUser.user
-  : null
-
-export default connect(
-  state => ({
-    currentUser: selectCurrentUser(state)
-  }),
-  {
-    logoutUser
-  }
-)(NavigationContainer)
diff --git a/app/components/WaxContainer.js b/app/components/WaxContainer.js
deleted file mode 100644
index f85531fc43..0000000000
--- a/app/components/WaxContainer.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'react-redux'
-import { browserHistory } from 'react-router'
-import { fileUpload } from 'pubsweet-client/src/actions/fileUpload'
-import { getCollection } from 'pubsweet-client/src/actions/collections'
-import { getFragment, updateFragment } from 'pubsweet-client/src/actions/fragments'
-import SimpleEditor from 'pubsweet-component-wax/src/SimpleEditor'
-
-const fullscreenStyle = {
-  position: 'fixed',
-  top: 50, // leave room for the navbar
-  left: 0,
-  right: 0,
-  bottom: 0,
-  background: 'white',
-  overflow: 'hidden'
-}
-
-class WaxContainer extends React.Component {
-  componentDidMount () {
-    const { getCollection, getFragment, params } = this.props
-
-    getCollection({ id: params.project })
-    getFragment({ id: params.project }, { id: params.version })
-  }
-
-  render () {
-    const { project, version, fileUpload, updateFragment, currentUser } = this.props
-
-    if (!version || !project) return null
-
-    return (
-      <SimpleEditor
-        book={project}
-        fileUpload={fileUpload}
-        fragment={version}
-        history={browserHistory}
-        onSave={({ source }) => updateFragment(project, { id: version.id, source })}
-        update={data => updateFragment(project, { id: version.id, ...data })}
-        user={currentUser}
-        style={fullscreenStyle}
-      />
-    )
-  }
-}
-
-WaxContainer.propTypes = {
-  fileUpload: PropTypes.func.isRequired,
-  getCollection: PropTypes.func.isRequired,
-  getFragment: PropTypes.func.isRequired,
-  updateFragment: PropTypes.func.isRequired,
-  currentUser: PropTypes.object,
-  params: PropTypes.object.isRequired,
-  project: PropTypes.object,
-  version: PropTypes.object
-}
-
-const selectCollection = (state, id) => state.collections
-  .find(collection => collection.id === id)
-
-const selectCurrentUser = (state) => state.currentUser.isAuthenticated
-  ? state.currentUser.user
-  : null
-
-export default connect(
-  (state, ownProps) => ({
-    currentUser: selectCurrentUser(state),
-    project: selectCollection(state, ownProps.params.project),
-    version: state.fragments[ownProps.params.version]
-  }),
-  {
-    fileUpload,
-    getCollection,
-    getFragment,
-    updateFragment
-  }
-)(WaxContainer)
diff --git a/app/routes.js b/app/routes.js
deleted file mode 100644
index a2010fc666..0000000000
--- a/app/routes.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react'
-import { IndexRoute, Redirect, Route } from 'react-router'
-
-import App from './components/app'
-import AuthenticatedContainer from './components/AuthenticatedContainer'
-import WaxContainer from './components/WaxContainer'
-
-import Signup from 'pubsweet-component-signup/Signup'
-import Login from 'pubsweet-component-login/Login'
-import PasswordReset from 'pubsweet-component-password-reset-frontend/PasswordReset'
-
-import ProjectListContainer from 'pubsweet-component-xpub-dashboard/components/ProjectListContainer'
-import ProjectContainer from 'pubsweet-component-xpub-submission/components/ProjectContainer'
-import VersionsListContainer from 'pubsweet-component-xpub-submission/components/VersionsListContainer'
-import DeclarationsContainer from 'pubsweet-component-xpub-submission/components/DeclarationsContainer'
-
-export default (
-  <Route>
-    <Redirect from="/" to="/projects"/>
-
-    <Route path="/" component={App}>
-      <Route component={AuthenticatedContainer}>
-        <Route path="projects" component={ProjectListContainer}/>
-
-        <Route path="projects/:project" component={ProjectContainer}>
-          <IndexRoute component={VersionsListContainer}/>
-
-          <Route path="declarations" component={DeclarationsContainer}/>
-        </Route>
-
-        <Route path="editor/:project/:version" component={WaxContainer}/>
-      </Route>
-
-      <Route path="signup" component={Signup}/>
-      <Route path="login" component={Login}/>
-      <Route path="password-reset" component={PasswordReset}/>
-    </Route>
-  </Route>
-)
diff --git a/app/styles.js b/app/styles.js
deleted file mode 100644
index 3d3a5ba984..0000000000
--- a/app/styles.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import 'pubsweet-fira'
-import 'typeface-fira-sans-condensed'
-import 'typeface-vollkorn'
-
-import './styles/main.scss'
diff --git a/app/styles/_base.scss b/app/styles/_base.scss
deleted file mode 100644
index a21a82fc39..0000000000
--- a/app/styles/_base.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-@import 'variables';
-
-a {
-  color: $dark-grey;
-  text-decoration: underline;
-}
-
-h1,
-h2,
-h3 {
-  color: $dark-grey;
-  margin-bottom: 5px;
-  margin-top: 5px;
-  padding-bottom: 0;
-  padding-top: 0;
-}
-
-h2 {
-  font-size: 25px;
-  font-weight: bold;
-}
-
-h3 {
-  color: lighten($dark-grey, 10%);
-  font-size: 20px;
-}
diff --git a/app/styles/_bootstrap.scss b/app/styles/_bootstrap.scss
deleted file mode 100644
index 47f859b74c..0000000000
--- a/app/styles/_bootstrap.scss
+++ /dev/null
@@ -1,24 +0,0 @@
-@import 'variables';
-
-// override bootstrap-sass default variables
-$border-radius-base: 0;
-$icon-font-path: '~bootstrap-sass/assets/fonts/bootstrap/';
-
-@import '~bootstrap-sass/assets/stylesheets/bootstrap';
-@import '~bootstrap-sass/assets/stylesheets/bootstrap-sprockets';
-
-@import 'base';
-@import 'bootstrap_buttons';
-@import 'bootstrap_form';
-@import 'navbar';
-
-.alert.alert-warning {
-  background-color: $very-light-grey;
-  border: 0;
-  color: $black;
-  margin-top: 50px;
-}
-
-.error {
-  color: $brown;
-}
diff --git a/app/styles/_bootstrap_buttons.scss b/app/styles/_bootstrap_buttons.scss
deleted file mode 100644
index b2ba600896..0000000000
--- a/app/styles/_bootstrap_buttons.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-@import 'variables';
-
-.btn-primary {
-  background-color: $blue;
-  border-color: transparent;
-
-  &:hover,
-  &:focus {
-    background-color: darken($blue, 10%);
-    border-color: darken($blue, 20%);
-  }
-}
diff --git a/app/styles/_bootstrap_form.scss b/app/styles/_bootstrap_form.scss
deleted file mode 100644
index 5a9692e12c..0000000000
--- a/app/styles/_bootstrap_form.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-@import 'variables';
-
-legend {
-  border: 0;
-  color: $blue;
-  font-weight: bold;
-}
-
-label {
-  color: $blue;
-  font-weight: normal;
-}
-
-input[type='text'],
-input[type='text'].form-control,
-input[type='password'].form-control {
-  border: 2px solid $medium-light-gray;
-
-  &.error {
-    border-right: 4px solid $main-grey;
-  }
-
-  &.success {
-    border-right: 4px solid $dark-grey;
-  }
-}
-
-.form-group {
-  margin-bottom: 15px;
-  margin-top: 15px;
-}
diff --git a/app/styles/_fonts.scss b/app/styles/_fonts.scss
deleted file mode 100644
index 4e748c8352..0000000000
--- a/app/styles/_fonts.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-//@import '~pubsweet-fira/src/fira';
-//@import '~typeface-fira-sans-condensed/index';
-//@import '~typeface-vollkorn/index';
-
-.content-text {
-  font-family: 'Vollkorn', serif;
-}
-
-.content-metadata,
-.content-interactive {
-  font-family: 'Fira Sans Condensed', sans-serif;
-}
diff --git a/app/styles/_icons.scss b/app/styles/_icons.scss
deleted file mode 100644
index cd0d29f878..0000000000
--- a/app/styles/_icons.scss
+++ /dev/null
@@ -1,2 +0,0 @@
-$fa-font-path: "~font-awesome/fonts";
-@import "~font-awesome/scss/font-awesome";
diff --git a/app/styles/_modal.scss b/app/styles/_modal.scss
deleted file mode 100644
index eb51544a35..0000000000
--- a/app/styles/_modal.scss
+++ /dev/null
@@ -1,68 +0,0 @@
-$brown: #a52a2a;
-$button-background: #d3d3d3;
-$button-text: #000;
-$grey: #808080;
-$white: #fff;
-
-.modal-content {
-  border: 2px solid $grey;
-  border-radius: 0;
-  font-style: italic;
-  font-weight: 500;
-  padding: 1em;
-}
-
-.modal-header {
-  padding: 0;
-}
-
-.modal-title {
-  font-size: 24px;
-  line-height: 32px;
-}
-
-.modal-body {
-  border: 0;
-  padding: 20px 0;
-}
-
-.modal-footer {
-  border: 0;
-  font-style: normal;
-  font-weight: normal;
-  padding-right: 0;
-  text-align: center;
-}
-
-.modal-buttons-container {
-  float: right;
-}
-
-.modal-button {
-  background-color: $button-background;
-  border: 3px solid transparent;
-  border-radius: 3px;
-  color: $button-text;
-  display: inline-block;
-  margin-bottom: .5em;
-  padding: 4px 40px;
-  text-align: center;
-  text-decoration: none;
-  text-transform: uppercase;
-
-  &:hover {
-    background-color: $grey;
-    color: $white;
-    cursor: pointer;
-    text-decoration: none;
-  }
-}
-
-.modal-discard {
-  color: $brown;
-  margin-right: 20px;
-
-  &:hover {
-    color: $brown;
-  }
-}
diff --git a/app/styles/_navbar.scss b/app/styles/_navbar.scss
deleted file mode 100644
index cc27e0cdf8..0000000000
--- a/app/styles/_navbar.scss
+++ /dev/null
@@ -1,73 +0,0 @@
-@import 'variables';
-
-@media (min-width: 768px) {
-  .navbar-brand {
-    margin-left: 0;
-  }
-}
-
-.navbar-link,
-.navbar-default .navbar-link {
-  color: #4990E2;
-  text-decoration: none;
-  font-weight: 800;
-}
-
-.navbar-default {
-  border: 0;
-  font-weight: bold;
-  font-style: italic;
-  background-color: white;
-
-  .container-fluid {
-    padding-left: 20px;
-    padding-right: 20px;
-  }
-
-  .navbar-brand {
-    color: $blue;
-    height: auto;
-    padding: 0;
-    margin-top: 15px;
-    margin-left: 0 !important;
-    text-decoration: none;
-    display: inline-block;
-
-    img {
-      height: 100%;
-    }
-  }
-
-  .navbar-header {
-    font-style: italic;
-  }
-
-  .navbar-nav {
-    margin-top: 0;
-
-    > .active {
-      > a {
-        background-color: transparent;
-        color: #000;
-        font-weight: bold;
-        text-decoration: none;
-
-        &:focus {
-          background-color: transparent;
-        }
-
-        &:hover {
-          background-color: transparent;
-        }
-      }
-    }
-
-    > li {
-      > a {
-        color: $blue;
-        font-style: italic;
-        text-decoration: none;
-      }
-    }
-  }
-}
diff --git a/app/styles/_reset.scss b/app/styles/_reset.scss
deleted file mode 100644
index ef308c355e..0000000000
--- a/app/styles/_reset.scss
+++ /dev/null
@@ -1,136 +0,0 @@
-// http://meyerweb.com/eric/tools/css/reset/
-//    v2.0 | 20110126
-//    License: none (public domain)
-
-html,
-body,
-div,
-span,
-applet,
-object,
-iframe,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-p,
-blockquote,
-pre,
-a,
-abbr,
-acronym,
-address,
-big,
-cite,
-code,
-del,
-dfn,
-em,
-img,
-ins,
-kbd,
-q,
-s,
-samp,
-small,
-strike,
-strong,
-sub,
-sup,
-tt,
-var,
-b,
-u,
-i,
-center,
-dl,
-dt,
-dd,
-ol,
-ul,
-li,
-fieldset,
-form,
-label,
-legend,
-table,
-caption,
-tbody,
-tfoot,
-thead,
-tr,
-th,
-td,
-article,
-aside,
-canvas,
-details,
-embed,
-figure,
-figcaption,
-footer,
-header,
-hgroup,
-menu,
-nav,
-output,
-ruby,
-section,
-summary,
-time,
-mark,
-audio,
-video {
-  border: 0;
-  font: inherit;
-  font-size: 100%;
-  margin: 0;
-  padding: 0;
-  vertical-align: baseline;
-}
-
-
-// HTML5 display-role reset for older browsers
-
-article,
-aside,
-details,
-figcaption,
-figure,
-footer,
-header,
-hgroup,
-menu,
-nav,
-section {
-  display: block;
-}
-
-body {
-  line-height: 1;
-}
-
-ol,
-ul {
-  list-style: none;
-}
-
-blockquote,
-q {
-  quotes: none;
-}
-
-blockquote::before,
-blockquote::after,
-q::before,
-q::after {
-  // content: '';
-  content: none;
-}
-
-table {
-  border-collapse: collapse;
-  border-spacing: 0;
-}
diff --git a/app/styles/_table.scss b/app/styles/_table.scss
deleted file mode 100644
index 8f8c040f2d..0000000000
--- a/app/styles/_table.scss
+++ /dev/null
@@ -1,98 +0,0 @@
-@import 'variables';
-
-.table {
-  margin-bottom: 10px;
-
-  > thead {
-    > tr {
-      > th {
-        background-color: transparent;
-        border: 0;
-        color: $dark-grey;
-        text-align: left;
-      }
-    }
-  }
-
-  > tbody {
-    > tr:first-child {
-      > td {
-        border: 0;
-      }
-    }
-
-    > tr {
-      > td {
-        border: 0;
-        border-top: 1px $dark-grey solid;
-        padding-bottom: 20px;
-        padding-top: 20px;
-      }
-    }
-  }
-
-  td.index,
-  th.index {
-    width: 5%;
-  }
-
-  td.main,
-  th.main {
-    width: 50%;
-  }
-
-  .btn-primary {
-    background-color: $main-grey;
-    border: 0;
-    margin-left: 5px;
-
-    i {
-      margin-left: -2px;
-      margin-top: 2px;
-    }
-  }
-
-  .btn-success {
-    background-color: #4B8AC9;
-    border: 0;
-    margin-left: 5px;
-  }
-
-  .btn-danger {
-    background-color: $brown;
-
-    border: 0;
-    margin-left: 5px;
-
-    i {
-      margin-left: -1px;
-    }
-  }
-
-  .btn-warning {
-    border: 0;
-    margin-left: 5px;
-  }
-
-  .Select-value {
-    background-color: $main-grey;
-    border: 1px solid transparent;
-    color: $dark-grey;
-
-    .Select-value-icon {
-      border-right: 1px solid $dark-grey;
-
-      &:hover {
-        background-color: $medium-light-gray;
-        color: $white;
-      }
-    }
-
-  }
-
-  .Select-clear-zone {
-    &:hover {
-      color: $brown
-    }
-  }
-}
diff --git a/app/styles/_variables.scss b/app/styles/_variables.scss
deleted file mode 100644
index ff845a0707..0000000000
--- a/app/styles/_variables.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-$black: rgba(0, 0, 0, .75);
-$brown: #a52a2a;
-$white: #fff;
-$blue: #4990e2;
-
-$dark-grey: #404040;
-$light-grey: #cdcdcd;
-$main-grey: #d8d8d8;
-$medium-light-gray: #bab8b8;
-$very-light-grey: #f2f2f2;
diff --git a/app/styles/main.scss b/app/styles/main.scss
deleted file mode 100644
index a6f5d8fe62..0000000000
--- a/app/styles/main.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-@import 'reset';
-@import 'bootstrap';
-@import 'variables';
-@import 'fonts';
-@import 'icons';
-
-html,
-body {
-  color: $black;
-  font-family: 'Fira Sans', sans-serif;
-  line-height: 24px;
-  margin: 0;
-}
-
-.separator {
-  clear: both;
-  height: 1px;
-}
diff --git a/config/shared.js b/config/shared.js
deleted file mode 100644
index 2261e133de..0000000000
--- a/config/shared.js
+++ /dev/null
@@ -1,66 +0,0 @@
-const path = require('path')
-const Joi = require('joi')
-const permissions = require('../permissions/index')
-
-module.exports = {
-  'pubsweet-server': {
-    dbPath: path.join(__dirname, '..', 'api', 'db'),
-    API_ENDPOINT: 'http://localhost:3000/api'
-  },
-  'pubsweet-client': {
-    'login-redirect': '/',
-    theme: process.env.PUBSWEET_THEME
-  },
-  'mail-transport': {
-    sendmail: true
-  },
-  'password-reset': {
-    url: process.env.PUBSWEET_PASSWORD_RESET_URL || 'http://localhost:3000/password-reset',
-    sender: process.env.PUBSWEET_PASSWORD_RESET_SENDER || 'dev@example.com'
-  },
-  authsome: {
-    mode: permissions,
-    teams: {
-      // TODO
-    }
-  },
-  pubsweet: {
-    components: require('./components.json')
-  },
-  'pubsweet-component-ink-backend': {
-    inkEndpoint: process.env.INK_ENDPOINT || 'http://ink-api.coko.foundation',
-    email: process.env.INK_USERNAME,
-    password: process.env.INK_PASSWORD,
-    maxRetries: 500
-  },
-  validations: {
-    collection: { // project
-      declarations: Joi.object(),
-      events: Joi.object(),
-      roles: Joi.object(),
-      status: Joi.string().required(), // TODO: use the latest workflow event?
-      statusDate: Joi.date().timestamp().required(), // TODO: use the latest workflow event?
-      title: Joi.string().required()
-    },
-    fragment: { // version
-      comments: Joi.object(), // wax
-      declarations: Joi.object(),
-      events: Joi.object(),
-      files: Joi.object(),
-      lock: Joi.object().allow(null), // wax
-      metadata: Joi.object(),
-      progress: Joi.object(), // wax
-      published: Joi.date().timestamp(),
-      roles: Joi.object(),
-      source: Joi.string().required(), // wax (TODO: move to file)
-      status: Joi.string(),
-      submitted: Joi.date().timestamp(),
-      trackChanges: Joi.boolean(), // wax
-      version: Joi.number().required()
-    },
-    user: {
-      name: Joi.string(), // TODO: add "name" to the login form
-      editor: Joi.boolean()
-    }
-  }
-}
diff --git a/docs/schema-outline.ftml b/docs/schema-outline.ftml
deleted file mode 100644
index 04b937b4d2..0000000000
--- a/docs/schema-outline.ftml
+++ /dev/null
@@ -1,419 +0,0 @@
-<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <meta name="expandedItems" content="X1GX6h6Cm X1xlTqp07 mJ24-iaRX X1kNqp6C7 QJo4qpp07 mJkGp3aRQ QJXEp5aA7 Qy2j-sTCQ X1j5caTRX Xyi2566CQ X1r-Tn60m mykgbs6C7 mkmksTTAm XJqCtpaRm XkmY5a6AX QkCDUAaCm QJv3boaAm myvUFC6C7 Q1N8FRTCm mkFWf1CAX mkGzz1CC7 Q1d1wThaCm X1opC2pR7 mkAe36TC7 Xy6TAnT0Q" />
-    <meta charset="UTF-8" />
-  </head>
-  <body>
-    <ul id="FoldingText">
-      <li id="X1GX6h6Cm">
-        <p>journals</p>
-        <ul>
-          <li id="X1xlTqp07">
-            <p><b>Journal</b></p>
-            <ul>
-              <li id="QknM6qaCQ">
-                <p>metadata</p>
-                <ul>
-                  <li id="QyLu-opA7">
-                    <p>title</p>
-                  </li>
-                  <li id="my_dbiaA7">
-                    <p>issn</p>
-                  </li>
-                  <li id="XkltZi6CQ">
-                    <p>url</p>
-                  </li>
-                </ul>
-              </li>
-              <li id="XkMKZoT0m">
-                <p>defaults</p>
-                <ul>
-                  <li id="Q1rqWiTR7">
-                    <p>review deadline</p>
-                  </li>
-                </ul>
-              </li>
-              <li id="mJ24-iaRX">
-                <p>roles</p>
-                <ul>
-                  <li id="XkfsWjTRX">
-                    <p>staff</p>
-                  </li>
-                  <li id="XyUsbipCQ">
-                    <p>editor</p>
-                  </li>
-                </ul>
-              </li>
-              <li id="X1kNqp6C7">
-                <p>declarations</p>
-                <ul>
-                  <li id="QJo4qpp07">
-                    <p><b>Declaration Question</b></p>
-                    <ul>
-                      <li id="QyySc66RX">
-                        <p>key</p>
-                      </li>
-                      <li id="Q1irqTp0Q">
-                        <p>question UI component</p>
-                      </li>
-                      <li id="QJPP9TpRQ">
-                        <p>answer UI component</p>
-                      </li>
-                    </ul>
-                  </li>
-                </ul>
-              </li>
-            </ul>
-          </li>
-        </ul>
-      </li>
-      <li id="mJkGp3aRQ">
-        <p>projects</p>
-        <ul>
-          <li id="QJXEp5aA7">
-            <p><b>Project</b></p>
-            <ul>
-              <li id="X1ZEh6TR7">
-                <p>title</p>
-              </li>
-              <li id="Q19r6qpRX">
-                <p>roles</p>
-                <ul>
-                  <li id="Qy2j-sTCQ">
-                    <p>owner</p>
-                    <ul>
-                      <li id="X1j5caTRX">
-                        <p><b>Role</b></p>
-                        <ul>
-                          <li id="XkJjcTaAX">
-                            <p>type</p>
-                          </li>
-                          <li id="XksXoapCX">
-                            <p>user</p>
-                          </li>
-                          <li id="Xyi2566CQ">
-                            <p>events</p>
-                            <ul>
-                              <li id="XkhgiTaRm">
-                                <p>started</p>
-                              </li>
-                              <li id="myCgsp6A7">
-                                <p>ended</p>
-                              </li>
-                            </ul>
-                          </li>
-                        </ul>
-                      </li>
-                    </ul>
-                  </li>
-                  <li id="Qypi-jpAQ">
-                    <p>editor</p>
-                  </li>
-                  <li id="myJnZspCQ">
-                    <p>reviewer</p>
-                  </li>
-                </ul>
-              </li>
-              <li id="XkMDp560X">
-                <p>events</p>
-                <ul>
-                  <li id="XJylGipCm">
-                    <p>created</p>
-                  </li>
-                  <li id="Xy7xGop0X">
-                    <p>title changed</p>
-                  </li>
-                  <li id="XkFeGoaA7">
-                    <p>role added/removed</p>
-                  </li>
-                </ul>
-              </li>
-              <li id="X1r-Tn60m">
-                <p>versions</p>
-                <ul>
-                  <li id="mykgbs6C7">
-                    <p><b>Version</b></p>
-                    <ul>
-                      <li id="XkkksT60X">
-                        <p>files</p>
-                        <ul>
-                          <li id="mkmksTTAm">
-                            <p><b>File</b></p>
-                            <ul>
-                              <li id="Q1wU1zk1V">
-                                <p>type (e.g. "manuscript", "figure")</p>
-                              </li>
-                              <li id="mknwkG11V">
-                                <p>key</p>
-                              </li>
-                              <li id="my-wyz1kN">
-                                <p>blob id</p>
-                              </li>
-                              <li id="Q1J0qaTAX">
-                                <p>filename</p>
-                              </li>
-                              <li id="QkgC9TTR7">
-                                <p>title</p>
-                              </li>
-                              <li id="mkZR9pa0X">
-                                <p>size</p>
-                              </li>
-                              <li id="XJzCca60X">
-                                <p>MIME type</p>
-                              </li>
-                              <li id="QymAc66R7">
-                                <p>description</p>
-                              </li>
-                              <li id="XJqCtpaRm">
-                                <p>events</p>
-                                <ul>
-                                  <li id="Q1nRF660m">
-                                    <p>added</p>
-                                  </li>
-                                  <li id="myybcp6CX">
-                                    <p>updated</p>
-                                  </li>
-                                </ul>
-                              </li>
-                            </ul>
-                          </li>
-                        </ul>
-                      </li>
-                      <li id="myRzMsTRX">
-                        <p>declarations</p>
-                        <ul>
-                          <li id="XkmY5a6AX">
-                            <p><b>Declaration Answer</b></p>
-                            <ul>
-                              <li id="QkhYqp6R7">
-                                <p>question key</p>
-                              </li>
-                              <li id="QkmvLRTR7">
-                                <p>question text?</p>
-                              </li>
-                              <li id="mJ0Y5ap0m">
-                                <p>answer</p>
-                              </li>
-                              <li id="QkCDUAaCm">
-                                <p>events</p>
-                                <ul>
-                                  <li id="X1Zd8CTA7">
-                                    <p>answered</p>
-                                  </li>
-                                </ul>
-                              </li>
-                            </ul>
-                          </li>
-                        </ul>
-                      </li>
-                      <li id="X14_op6RQ">
-                        <p>metadata</p>
-                        <ul>
-                          <li id="myBB2pa0Q">
-                            <p>title (with markup)</p>
-                          </li>
-                          <li id="X19_opp0m">
-                            <p>authors/contributors</p>
-                          </li>
-                          <li id="mJsuoTaAQ">
-                            <p>affiliations</p>
-                          </li>
-                          <li id="XJCdsTpCm">
-                            <p>funders</p>
-                          </li>
-                          <li id="XJ7Ks66AQ">
-                            <p>contributions</p>
-                          </li>
-                        </ul>
-                      </li>
-                      <li id="mykfZiaC7">
-                        <p>roles</p>
-                        <ul>
-                          <li id="QJv3boaAm">
-                            <p>reviewer</p>
-                            <ul>
-                              <li id="myvUFC6C7">
-                                <p><b>Role</b></p>
-                                <ul>
-                                  <li id="QJZA-ja0X">
-                                    <p>invitation</p>
-                                  </li>
-                                  <li id="QJGzYApR7">
-                                    <p>project role id</p>
-                                  </li>
-                                  <li id="Q1N8FRTCm">
-                                    <p>events</p>
-                                    <ul>
-                                      <li id="Xk2IFA6RQ">
-                                        <p>invited</p>
-                                      </li>
-                                      <li id="XJA8KApRQ">
-                                        <p>accepted</p>
-                                      </li>
-                                      <li id="QyxPFCTR7">
-                                        <p>declined</p>
-                                      </li>
-                                      <li id="QJUwF0aCm">
-                                        <p>submitted review</p>
-                                      </li>
-                                    </ul>
-                                  </li>
-                                </ul>
-                              </li>
-                            </ul>
-                          </li>
-                        </ul>
-                      </li>
-                      <li id="XyiMWjaRm">
-                        <p>notes/messages/tasks</p>
-                        <ul>
-                          <li id="mkFWf1CAX">
-                            <p><b>Note</b></p>
-                            <ul>
-                              <li id="mkGzz1CC7">
-                                <p>type</p>
-                                <ul>
-                                  <li id="QypTopa07">
-                                    <p>changes needed</p>
-                                  </li>
-                                  <li id="mkH0ip6Am">
-                                    <p>notes for the editor</p>
-                                  </li>
-                                  <li id="Xyl1h6a0m">
-                                    <p>notes for the reviewers</p>
-                                  </li>
-                                  <li id="QkKNpT6CX">
-                                    <p>review</p>
-                                  </li>
-                                  <li id="m1jE66pA7">
-                                    <p>decision</p>
-                                  </li>
-                                  <li id="mynNTpaC7">
-                                    <p>author response</p>
-                                  </li>
-                                </ul>
-                              </li>
-                              <li id="QkPfzk00m">
-                                <p>attachments (or inline links to uploaded files?)</p>
-                              </li>
-                            </ul>
-                          </li>
-                        </ul>
-                      </li>
-                      <li id="Q1d1wThaCm">
-                        <p>events</p>
-                        <ul>
-                          <li id="XkJwahaAQ">
-                            <p>created</p>
-                          </li>
-                          <li id="m1kC1Gyy4">
-                            <p>file added/removed/updated</p>
-                          </li>
-                          <li id="XkG1J100Q">
-                            <p>declarations saved/updated</p>
-                          </li>
-                          <li id="Q11myJ0AX">
-                            <p>submitted for preprint</p>
-                          </li>
-                          <li id="QJVkDa2pCm">
-                            <p>submitted for peer review</p>
-                          </li>
-                          <li id="XyM2Fpa07">
-                            <p>checked</p>
-                          </li>
-                          <li id="XJ31k1RA7">
-                            <p>editor assigned</p>
-                          </li>
-                          <li id="QyyeykAR7">
-                            <p>reviewers invited</p>
-                          </li>
-                          <li id="X17xyJR0Q">
-                            <p>reviewers accepted</p>
-                          </li>
-                          <li id="m1leMkA0m">
-                            <p>reviews submitted</p>
-                          </li>
-                          <li id="XyVgz1AA7">
-                            <p>decision sent</p>
-                          </li>
-                          <li id="mkdxM1CCm">
-                            <p>author response received</p>
-                          </li>
-                          <li id="QkHyvTn6RQ">
-                            <p>metadata updated</p>
-                          </li>
-                          <li id="myvyw62TCQ">
-                            <p>preprint approved</p>
-                          </li>
-                          <li id="Qk8yPp3a07">
-                            <p>publication approved</p>
-                          </li>
-                          <li id="XJ3SG1ACX">
-                            <p>published</p>
-                          </li>
-                        </ul>
-                      </li>
-                      <li id="XkE0p3T0m">
-                        <p>links</p>
-                        <ul>
-                          <li id="QyDAT2aCm">
-                            <p>previous published version</p>
-                          </li>
-                          <li id="QysRpnaCQ">
-                            <p>next published version</p>
-                          </li>
-                          <li id="mkTCph6RQ">
-                            <p>latest published version</p>
-                          </li>
-                        </ul>
-                      </li>
-                    </ul>
-                  </li>
-                </ul>
-              </li>
-            </ul>
-          </li>
-        </ul>
-      </li>
-      <li id="X1opC2pR7">
-        <p>users</p>
-        <ul>
-          <li id="mk-4jTTRm">
-            <p>name (given + family)</p>
-          </li>
-          <li id="Qy4EiaT0X">
-            <p>email address</p>
-          </li>
-          <li id="mkAe36TC7">
-            <p>identifiers</p>
-            <ul>
-              <li id="mk8WnaaCX">
-                <p>ORCID</p>
-              </li>
-              <li id="QJ5Wha6Cm">
-                <p>Twitter</p>
-              </li>
-              <li id="m1yzhTpAQ">
-                <p>Google Scholar</p>
-              </li>
-              <li id="mkMMhppAm">
-                <p>URL</p>
-              </li>
-            </ul>
-          </li>
-          <li id="Xy6TAnT0Q">
-            <p>roles (on journal, project or version)</p>
-            <ul>
-              <li id="myg0RnpA7">
-                <p>staff</p>
-              </li>
-              <li id="Q1M00nT07">
-                <p>editor</p>
-              </li>
-            </ul>
-          </li>
-        </ul>
-      </li>
-    </ul>
-  </body>
-</html>
diff --git a/docs/workflow-outline.ftml b/docs/workflow-outline.ftml
deleted file mode 100644
index d74b165db4..0000000000
--- a/docs/workflow-outline.ftml
+++ /dev/null
@@ -1,121 +0,0 @@
-<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <meta name="expandedItems" content="mJPQMgARQ QkRBMlCA7 QJqIzeRAX" />
-    <meta charset="UTF-8" />
-  </head>
-  <body>
-    <ul id="FoldingText">
-      <li id="mJPQMgARQ">
-        <p>create project</p>
-        <ul>
-          <li id="XyWXf21RRQ">
-            <p>upload manuscript</p>
-          </li>
-          <li id="QJo7zeC0m">
-            <p>extract title</p>
-          </li>
-        </ul>
-      </li>
-      <li id="mJmDfxCAQ">
-        <p>edit title</p>
-      </li>
-      <li id="Q1gNGxCR7">
-        <p>edit manuscript</p>
-      </li>
-      <li id="QJzXBxAAm">
-        <p>upload supporting files</p>
-      </li>
-      <li id="QkRBMlCA7">
-        <p>submit project for peer review</p>
-        <ul>
-          <li id="XkHIMeCC7">
-            <p>answer declarations</p>
-          </li>
-          <li id="Xk4OMg0R7">
-            <p>accept terms and conditions</p>
-          </li>
-          <li id="XJ6FGgA0m">
-            <p>create version</p>
-          </li>
-          <li id="Qkgf5XlACX">
-            <p>check manuscript</p>
-          </li>
-          <li id="m1WG5QxCCX">
-            <p>check declarations</p>
-          </li>
-          <li id="QJTMBlRC7">
-            <p>check files</p>
-          </li>
-          <li id="myOpmeACQ">
-            <p>assign editor to project</p>
-          </li>
-          <li id="mJjpQxRCX">
-            <p>assign reviewers to project</p>
-          </li>
-          <li id="mkxRQe0CQ">
-            <p>invite reviewers to version</p>
-          </li>
-          <li id="Qy3AQxC0m">
-            <p>accept/decline reviewer invitation</p>
-          </li>
-          <li id="QkGkVg0Cm">
-            <p>submit reviews</p>
-          </li>
-          <li id="X1T1EeRCm">
-            <p>send decision</p>
-          </li>
-          <li id="XyxxNeRCX">
-            <p>submit author response</p>
-          </li>
-          <li id="X1Lrre007">
-            <p>[repeat if necessary]</p>
-          </li>
-          <li id="myBLSeRAX">
-            <p>upload high-resolution figures</p>
-          </li>
-          <li id="XyMOSx007">
-            <p>check figure files</p>
-          </li>
-          <li id="mJzGqQx0RX">
-            <p>extract metadata from manuscript</p>
-          </li>
-          <li id="QJXfcXlR07">
-            <p>check/enter/correct metadata</p>
-          </li>
-          <li id="QkH2170A7">
-            <p>publish</p>
-          </li>
-        </ul>
-      </li>
-      <li id="QJqIzeRAX">
-        <p>submit project for preprint</p>
-        <ul>
-          <li id="Xy-qGgAC7">
-            <p>answer declarations</p>
-          </li>
-          <li id="XJU5MeRCX">
-            <p>accept terms and conditions</p>
-          </li>
-          <li id="mJ55zlA0X">
-            <p>create version</p>
-          </li>
-          <li id="Qy12MlA0m">
-            <p>check manuscript</p>
-          </li>
-          <li id="X1dCfxRRm">
-            <p>check declarations</p>
-          </li>
-          <li id="XJj0fxC0m">
-            <p>extract metadata from manuscript</p>
-          </li>
-          <li id="XyklQg0A7">
-            <p>check/enter/correct metadata</p>
-          </li>
-          <li id="XkDqme0A7">
-            <p>publish preprint</p>
-          </li>
-        </ul>
-      </li>
-    </ul>
-  </body>
-</html>
\ No newline at end of file
diff --git a/lerna.json b/lerna.json
new file mode 100644
index 0000000000..8ac1b113ec
--- /dev/null
+++ b/lerna.json
@@ -0,0 +1,7 @@
+{
+  "lerna": "2.0.0",
+  "version": "independent",
+  "packages": [
+    "packages/*"
+  ]
+}
diff --git a/package.json b/package.json
index 4675067b98..652c268a0b 100644
--- a/package.json
+++ b/package.json
@@ -1,82 +1,28 @@
 {
   "name": "xpub",
-  "version": "0.0.1",
-  "description": "xpub",
+  "version": "0.0.0",
+  "private": true,
   "license": "MIT",
-  "repository": {
-    "type": "git",
-    "url": "https://gitlab.coko.foundation/xpub/xpub"
-  },
-  "dependencies": {
-    "pubsweet": "1.0.0-alpha.4",
-    "font-awesome": "^4.7.0",
-    "joi": "^10.4.1",
-    "lodash": "^4.17.4",
-    "moment": "^2.18.1",
-    "prop-types": "^15.5.10",
-    "pubsweet-client": "^1.0.0-alpha.1",
-    "pubsweet-component-ink-backend": "0.0.10",
-    "pubsweet-component-ink-frontend": "^0.1.0",
-    "pubsweet-component-login": "^0.3.0",
-    "pubsweet-component-password-reset-backend": "^0.1.0",
-    "pubsweet-component-password-reset-frontend": "^0.1.0",
-    "pubsweet-component-signup": "^0.2.0",
-    "pubsweet-component-wax": "0.1.0",
-    "pubsweet-component-xpub-dashboard": "*",
-    "pubsweet-component-xpub-submission": "*",
-    "pubsweet-fira": "^0.0.3",
-    "pubsweet-server": "^1.0.0-alpha.1",
-    "pubsweet-theme-plugin": "^0.0.1",
-    "react": "^15.4.4",
-    "react-bootstrap": "^0.31.0",
-    "react-dom": "^15.5.4",
-    "react-loadable": "^4.0.3",
-    "react-redux": "^5.0.2",
-    "react-router": "^3.0.5",
-    "react-router-bootstrap": "^0.23.3",
-    "react-router-redux": "^4.0.7",
-    "redux": "^3.6.0",
-    "redux-logger": "^3.0.1",
-    "typeface-fira-sans-condensed": "0.0.31",
-    "typeface-vollkorn": "0.0.31"
-  },
   "devDependencies": {
-    "app-module-path": "^2.2.0",
-    "babel-core": "^6.14.0",
     "babel-eslint": "^7.2.3",
-    "babel-loader": "^7.0.0",
-    "babel-plugin-transform-class-properties": "^6.24.1",
-    "babel-preset-babili": "0.1.4",
-    "babel-preset-env": "^1.6.0",
-    "babel-preset-react": "^6.11.1",
-    "babel-preset-stage-2": "^6.13.0",
-    "babili-webpack-plugin": "^0.1.2",
-    "bootstrap-sass": "^3.3.7",
-    "compression-webpack-plugin": "^0.4.0",
-    "copy-webpack-plugin": "^4.0.1",
-    "css-loader": "^0.28.1",
-    "eslint": "^3.19.0",
-    "eslint-loader": "^1.6.0",
-    "extract-text-webpack-plugin": "^2.0.0-beta.4",
-    "file-loader": "^0.11.1",
-    "html-webpack-plugin": "^2.24.0",
-    "joi-browser": "^10.0.6",
-    "json-loader": "^0.5.4",
-    "node-sass": "^4.5.2",
-    "pouchdb-adapter-memory": "^6.1.1",
-    "react-hot-loader": "^3.0.0-beta.6",
-    "sass-loader": "^6.0.3",
-    "script-loader": "^0.7.0",
-    "standard": "^10.0.2",
-    "string-replace-loader": "^1.3.0",
-    "style-loader": "^0.18.2",
-    "url-loader": "^0.5.8",
-    "webpack": "^2.6.1",
-    "webpack-dev-middleware": "^1.10.2",
-    "webpack-hot-middleware": "^2.18.1"
+    "eslint": "^4.4.1",
+    "eslint-config-react-app": "^2.0.0",
+    "eslint-plugin-flowtype": "^2.34.1",
+    "eslint-plugin-import": "^2.6.0",
+    "eslint-plugin-jsx-a11y": "^5.1.1",
+    "eslint-plugin-react": "^7.1.0",
+    "lerna": "^2.0.0"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@gitlab.coko.foundation:xpub/xpub.git"
   },
   "scripts": {
-    "setupdb": "pubsweet setupdb --dev ./",
-    "start": "pubsweet run --dev"
+    "bootstrap": "lerna bootstrap --concurrency=1",
+    "hoist": "lerna bootstrap --hoist --concurrency=1",
+    "clean": "lerna clean"
+  },
+  "engines": {
+    "node": ">=7.9"
   }
 }
diff --git a/packages/collabra/.gitignore b/packages/collabra/.gitignore
new file mode 100644
index 0000000000..1468569887
--- /dev/null
+++ b/packages/collabra/.gitignore
@@ -0,0 +1,6 @@
+_build/
+api/
+logs/
+node_modules/
+.env.*
+.env
diff --git a/packages/collabra/app/app.js b/packages/collabra/app/app.js
new file mode 100644
index 0000000000..b0d7270919
--- /dev/null
+++ b/packages/collabra/app/app.js
@@ -0,0 +1,33 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Provider } from 'react-redux'
+import { AppContainer } from 'react-hot-loader'
+import { Router, browserHistory } from 'react-router'
+import { syncHistoryWithStore } from 'react-router-redux'
+import { configureStore } from 'pubsweet-client'
+import routes from './routes'
+import 'xpub-fonts'
+
+const store = configureStore(browserHistory, {})
+const history = syncHistoryWithStore(browserHistory, store)
+
+const render = routes => {
+  ReactDOM.render(
+    <AppContainer>
+      <Provider store={store}>
+        <Router history={history}>
+          {routes}
+        </Router>
+      </Provider>
+    </AppContainer>,
+    document.getElementById('root')
+  )
+}
+
+render(routes)
+
+if (module.hot) {
+  module.hot.accept('./routes', () => {
+    render(routes)
+  })
+}
diff --git a/packages/collabra/app/components/App.js b/packages/collabra/app/components/App.js
new file mode 100644
index 0000000000..c6fc2af467
--- /dev/null
+++ b/packages/collabra/app/components/App.js
@@ -0,0 +1,77 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { compose, withContext } from 'recompose'
+import { connect } from 'react-redux'
+import { AppBar } from 'xpub-ui'
+import { selectCurrentUser } from 'xpub-selectors'
+import { actions } from 'pubsweet-client'
+import classes from './App.local.css'
+import * as journal from '../config/journal'
+
+// NOTE: currently loading all collections and fragments into the store on startup!
+// TODO: dispatch loading actions from components, via HOC
+
+class App extends React.Component  {
+  componentDidMount () {
+    const { getProjects, getVersions } = this.props
+
+    getProjects().then(({ collections: projects }) => {
+      projects.forEach(project => getVersions(project))
+    })
+  }
+
+  render () {
+    const { children, currentUser } = this.props
+
+    return (
+      <div className={classes.root}>
+        <AppBar
+          appName={journal.metadata.name}
+          appLink="/projects" // TODO: make configurable
+          userName={currentUser ? currentUser.username : null}
+          loginLink="/signin"
+          logoutLink="/signout"/>
+
+        <div className={classes.main}>
+          {children}
+        </div>
+      </div>
+    )
+  }
+}
+
+/*const App = ({ children, currentUser }) => (
+  <div className={classes.root}>
+    <AppBar
+      appName={journal.metadata.name}
+      appLink="/projects" // TODO: make configurable
+      userName={currentUser ? currentUser.username : null}
+      loginLink="/signin"
+      logoutLink="/signout"/>
+
+    <div className={classes.main}>
+      {children}
+    </div>
+  </div>
+)*/
+
+App.propTypes = {
+  children: PropTypes.node,
+  currentUser: PropTypes.object
+}
+
+export default compose(
+  connect(
+    state => ({
+      currentUser: selectCurrentUser(state)
+    }),
+    {
+      getProjects: actions.getCollections,
+      getVersions: actions.getFragments
+    }
+  ),
+  withContext(
+    { journal: PropTypes.object },
+    () => ({ journal })
+  ),
+)(App)
diff --git a/packages/collabra/app/components/App.local.css b/packages/collabra/app/components/App.local.css
new file mode 100644
index 0000000000..d15b54fd71
--- /dev/null
+++ b/packages/collabra/app/components/App.local.css
@@ -0,0 +1,3 @@
+.main {
+  margin-top: 50px;
+}
diff --git a/app/components/AuthenticatedContainer.js b/packages/collabra/app/components/AuthenticatedPage.js
similarity index 69%
rename from app/components/AuthenticatedContainer.js
rename to packages/collabra/app/components/AuthenticatedPage.js
index a9d210e9b1..d7446d1884 100644
--- a/app/components/AuthenticatedContainer.js
+++ b/packages/collabra/app/components/AuthenticatedPage.js
@@ -1,11 +1,12 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import { compose } from 'recompose'
 import { connect } from 'react-redux'
 import { push } from 'react-router-redux'
-import { getCurrentUser } from 'pubsweet-client/src/actions/currentUser'
+import { actions } from 'pubsweet-client'
 import { withRouter } from 'react-router'
 
-class AuthenticatedContainer extends React.Component {
+class AuthenticatedPage extends React.Component {
   componentDidMount () {
     const { isAuthenticated, getCurrentUser } = this.props
 
@@ -35,7 +36,7 @@ class AuthenticatedContainer extends React.Component {
   }
 }
 
-AuthenticatedContainer.propTypes = {
+AuthenticatedPage.propTypes = {
   children: PropTypes.node.isRequired,
   getCurrentUser: PropTypes.func.isRequired,
   isAuthenticated: PropTypes.bool.isRequired,
@@ -44,13 +45,16 @@ AuthenticatedContainer.propTypes = {
   push: PropTypes.func.isRequired
 }
 
-export default withRouter(connect(
-  state => ({
-    isAuthenticated: state.currentUser.isAuthenticated,
-    isFetching: state.currentUser.isFetching
-  }),
-  {
-    getCurrentUser,
-    push
-  }
-)(AuthenticatedContainer))
+export default compose(
+  connect(
+    state => ({
+      isAuthenticated: state.currentUser.isAuthenticated,
+      isFetching: state.currentUser.isFetching
+    }),
+    {
+      getCurrentUser: actions.getCurrentUser,
+      push
+    }
+  ),
+  withRouter
+)(AuthenticatedPage)
diff --git a/packages/collabra/app/components/Logout.js b/packages/collabra/app/components/Logout.js
new file mode 100644
index 0000000000..64d48f0c87
--- /dev/null
+++ b/packages/collabra/app/components/Logout.js
@@ -0,0 +1,37 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { logoutUser } from 'pubsweet-component-login/actions'
+
+class Logout extends React.Component {
+  componentDidMount () {
+    const { isAuthenticated, logoutUser } = this.props
+
+    if (isAuthenticated)  {
+      logoutUser()
+    }
+  }
+
+  render () {
+    const { isAuthenticated } = this.props
+
+    return isAuthenticated ? 'Signed out' : 'Signing out…'
+  }
+}
+
+Logout.propTypes = {
+  logoutUser: PropTypes.func.isRequired,
+  isAuthenticated: PropTypes.bool.isRequired,
+}
+
+export default compose(
+  connect(
+    state => ({
+      isAuthenticated: state.currentUser.isAuthenticated,
+    }),
+    {
+      logoutUser
+    }
+  ),
+)(Logout)
diff --git a/packages/collabra/app/components/SubmitPage.js b/packages/collabra/app/components/SubmitPage.js
new file mode 100644
index 0000000000..1decd05384
--- /dev/null
+++ b/packages/collabra/app/components/SubmitPage.js
@@ -0,0 +1,18 @@
+import { compose } from 'recompose'
+import { connect } from 'redux'
+import { actions } from 'pubsweet-client'
+import { selectCollection } from 'xpub-selectors'
+
+import Submit from 'pubsweet-component-xpub-submit'
+
+export default compose(
+  connect(
+    (state, ownProps) => ({
+      project: selectCollection(state, ownProps.params.project)
+    }),
+    {
+      getVersions: actions.getFragments,
+      getProject: actions.getCollection,
+    }
+  )
+)(Submit)
diff --git a/packages/collabra/app/config/journal/decisions.js b/packages/collabra/app/config/journal/decisions.js
new file mode 100644
index 0000000000..7b9e6dabf6
--- /dev/null
+++ b/packages/collabra/app/config/journal/decisions.js
@@ -0,0 +1,22 @@
+export default {
+  accept: {
+    label: 'Accept',
+    color: 'green',
+    message: 'The submission has been accepted for publication'
+  },
+  minor: {
+    label: 'Minor revisions',
+    color: 'orange',
+    message: 'The submission will be accepted for publication after minor changes'
+  },
+  major: {
+    label: 'Major revisions',
+    color: 'yellow',
+    message: 'The requested changes must be made before submission for review'
+  },
+  reject: {
+    label: 'Reject',
+    color: 'red',
+    message: 'The submission is not acceptable for publication'
+  },
+}
diff --git a/packages/collabra/app/config/journal/declarations.js b/packages/collabra/app/config/journal/declarations.js
new file mode 100644
index 0000000000..6c7ea1741f
--- /dev/null
+++ b/packages/collabra/app/config/journal/declarations.js
@@ -0,0 +1,81 @@
+import React from 'react'
+
+export default {
+  valid: model => !!model.accept, // TODO: use form validation instead?
+  sections: [
+    {
+      name: 'Ethics',
+      items: [
+        {
+          name: 'human',
+          type: 'checkbox',
+          label: 'This research included study of human participants or human tissue',
+          render: value => (
+            <div>This research {value ? 'included' : 'did not include'} study of human participants or human tissue.</div>
+          )
+        },
+        {
+          name: 'humanReview',
+          type: 'text',
+          // label: 'Institutional review board',
+          helperText: 'Please name the Institutional Review Board which approved this research',
+          condition: model => model.human,
+          required: true,
+          render: value => (
+            <div>
+              <div>The Institutional Review Board which approved this research:</div>
+              <div>{value}</div>
+            </div>
+          )
+        }
+      ]
+    },
+    {
+      name: 'Influence',
+      items: [
+        {
+          name: 'financialDisclosure',
+          type: 'text',
+          // label: 'Funding disclosure',
+          helperText: 'Please disclose any role that the funders played in your research and/or manuscript',
+          render: value => (
+            <div>
+              <div>Roles that the funders played in the research and/or manuscript:</div>
+              <div>{value}</div>
+            </div>
+          )
+        }
+      ]
+    },
+    {
+      name: 'Discoveries',
+      items: [
+        {
+          name: 'newTaxon',
+          type: 'checkbox',
+          label: 'This paper describes a new taxon',
+          content: value => (
+            value && <div>Please review <a href="https://submit.elifesciences.org/html/elife_author_instructions.html" target="_blank" rel="noopener noreferrer" style={{textDecoration:'underline'}}>our policies on new taxon nomenclature.</a></div>
+          ),
+          render: value => (
+            <div>This research {value ? 'describes' : 'does not describe'} a new taxon.</div>
+          )
+        }
+      ]
+    },
+    {
+      name: 'Terms & Conditions',
+      items: [
+        {
+          name: 'accept',
+          type: 'checkbox',
+          required: true,
+          label: 'By checking this box, I accept the terms and conditions', // TODO: link
+          render: value => (
+            <div>The terms and conditions {value ? 'were' : 'were not'} accepted.</div>
+          )
+        }
+      ]
+    },
+  ]
+}
diff --git a/packages/collabra/app/config/journal/index.js b/packages/collabra/app/config/journal/index.js
new file mode 100644
index 0000000000..62d90b4a11
--- /dev/null
+++ b/packages/collabra/app/config/journal/index.js
@@ -0,0 +1,4 @@
+export { default as metadata } from './metadata'
+export { default as declarations } from './declarations'
+export { default as decisions } from './decisions'
+export { default as sections } from './sections'
diff --git a/packages/collabra/app/config/journal/metadata.js b/packages/collabra/app/config/journal/metadata.js
new file mode 100644
index 0000000000..0040ca7d9e
--- /dev/null
+++ b/packages/collabra/app/config/journal/metadata.js
@@ -0,0 +1,4 @@
+export default {
+  name: 'Collabra: Psychology',
+  issn: '2474-7394'
+}
diff --git a/packages/collabra/app/config/journal/sections.js b/packages/collabra/app/config/journal/sections.js
new file mode 100644
index 0000000000..419171898b
--- /dev/null
+++ b/packages/collabra/app/config/journal/sections.js
@@ -0,0 +1,6 @@
+export default [
+  {
+    id: 'submissions',
+    label: 'My Submissions'
+  }
+]
diff --git a/app/index.ejs b/packages/collabra/app/index.ejs
similarity index 100%
rename from app/index.ejs
rename to packages/collabra/app/index.ejs
diff --git a/app/index.html b/packages/collabra/app/index.html
similarity index 100%
rename from app/index.html
rename to packages/collabra/app/index.html
diff --git a/packages/collabra/app/routes.js b/packages/collabra/app/routes.js
new file mode 100644
index 0000000000..0642f98adf
--- /dev/null
+++ b/packages/collabra/app/routes.js
@@ -0,0 +1,37 @@
+import React from 'react'
+import { Redirect, Route } from 'react-router'
+// import loadable from 'loadable-components'
+
+import App from './components/app'
+import AuthenticatedPage from './components/AuthenticatedPage'
+
+import Signup from 'pubsweet-component-signup/Signup'
+import Login from 'pubsweet-component-login/Login'
+import Logout from './components/Logout'
+import PasswordReset from 'pubsweet-component-password-reset-frontend/PasswordReset'
+
+import DashboardPage from 'pubsweet-component-xpub-dashboard/src/components'
+
+import SubmitPage from 'pubsweet-component-xpub-submit/src/components'
+
+import ManuscriptPage from 'pubsweet-component-xpub-manuscript/src/components'
+// const ManuscriptPage = loadable(() => import('pubsweet-component-xpub-manuscript/src/components'))
+
+export default (
+  <Route>
+    <Redirect from="/" to="/projects"/>
+
+    <Route path="/" component={App}>
+      <Route component={AuthenticatedPage}>
+        <Route path="dashboard" component={DashboardPage}/>
+        <Route path="projects/:project/submit" component={SubmitPage}/>
+        <Route path="projects/:project/manuscript" component={ManuscriptPage}/>
+      </Route>
+
+      <Route path="signup" component={Signup}/>
+      <Route path="login" component={Login}/>
+      <Route path="logout" component={Logout}/>
+      <Route path="password-reset" component={PasswordReset}/>
+    </Route>
+  </Route>
+)
diff --git a/packages/collabra/config/authsome.js b/packages/collabra/config/authsome.js
new file mode 100644
index 0000000000..5d6ee0dc29
--- /dev/null
+++ b/packages/collabra/config/authsome.js
@@ -0,0 +1,8 @@
+module.exports = {
+  mode: (user, operation, project, version) => {
+    return true // TODO
+  },
+  teams: {
+    // TODO
+  }
+}
diff --git a/config/components.json b/packages/collabra/config/components.json
similarity index 58%
rename from config/components.json
rename to packages/collabra/config/components.json
index bf2d69f628..4e0f0b2f82 100644
--- a/config/components.json
+++ b/packages/collabra/config/components.json
@@ -4,8 +4,5 @@
   "pubsweet-component-password-reset-frontend",
   "pubsweet-component-password-reset-backend",
   "pubsweet-component-ink-frontend",
-  "pubsweet-component-ink-backend",
-  "pubsweet-component-wax",
-  "pubsweet-component-xpub-dashboard",
-  "pubsweet-component-xpub-submission"
+  "pubsweet-component-ink-backend"
 ]
diff --git a/config/dev.js b/packages/collabra/config/dev.js
similarity index 100%
rename from config/dev.js
rename to packages/collabra/config/dev.js
diff --git a/config/development.js b/packages/collabra/config/development.js
similarity index 100%
rename from config/development.js
rename to packages/collabra/config/development.js
diff --git a/config/production.js b/packages/collabra/config/production.js
similarity index 100%
rename from config/production.js
rename to packages/collabra/config/production.js
diff --git a/packages/collabra/config/shared.js b/packages/collabra/config/shared.js
new file mode 100644
index 0000000000..ffa8fa26de
--- /dev/null
+++ b/packages/collabra/config/shared.js
@@ -0,0 +1,32 @@
+const authsome = require('./authsome')
+const components = require('./components.json')
+const validations = require('./validations')
+
+module.exports = {
+  authsome,
+  validations,
+  pubsweet: {
+    components
+  },
+  'pubsweet-server': {
+    dbPath: 'http://localhost:5984/',
+    API_ENDPOINT: 'http://localhost:3000/api'
+  },
+  'pubsweet-client': {
+    'login-redirect': '/',
+    theme: process.env.PUBSWEET_THEME
+  },
+  'mail-transport': {
+    sendmail: true
+  },
+  'password-reset': {
+    url: process.env.PUBSWEET_PASSWORD_RESET_URL || 'http://localhost:3000/password-reset',
+    sender: process.env.PUBSWEET_PASSWORD_RESET_SENDER || 'dev@example.com'
+  },
+  'pubsweet-component-ink-backend': {
+    inkEndpoint: process.env.INK_ENDPOINT || 'http://ink-api.coko.foundation',
+    email: process.env.INK_USERNAME,
+    password: process.env.INK_PASSWORD,
+    maxRetries: 500
+  },
+}
diff --git a/config/test.js b/packages/collabra/config/test.js
similarity index 100%
rename from config/test.js
rename to packages/collabra/config/test.js
diff --git a/packages/collabra/config/validations.js b/packages/collabra/config/validations.js
new file mode 100644
index 0000000000..fdf4839e8e
--- /dev/null
+++ b/packages/collabra/config/validations.js
@@ -0,0 +1,15 @@
+const Joi = require('joi')
+
+module.exports = {
+  collection: { // project
+  },
+  fragment: { // version
+    version: Joi.number().required(),
+    title: Joi.string(),
+    abstract: Joi.string(),
+    authors: Joi.string(),
+  },
+  user: {
+    name: Joi.string(), // TODO: add "name" to the login form
+  }
+}
diff --git a/packages/collabra/package.json b/packages/collabra/package.json
new file mode 100644
index 0000000000..b5430471d2
--- /dev/null
+++ b/packages/collabra/package.json
@@ -0,0 +1,67 @@
+{
+  "name": "xpub-collabra",
+  "version": "0.0.1",
+  "description": "xpub configured for Collabra",
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://gitlab.coko.foundation/xpub/xpub"
+  },
+  "dependencies": {
+    "font-awesome": "^4.7.0",
+    "joi": "^10.4.1",
+    "loadable-components": "^0.2.1",
+    "lodash": "^4.17.4",
+    "moment": "^2.18.1",
+    "prop-types": "^15.5.10",
+    "pubsweet": "1.0.0-alpha.4",
+    "pubsweet-client": "^1.0.0-alpha.1",
+    "pubsweet-component-ink-backend": "0.0.10",
+    "pubsweet-component-ink-frontend": "^0.1.0",
+    "pubsweet-component-login": "^0.3.0",
+    "pubsweet-component-password-reset-backend": "^0.1.0",
+    "pubsweet-component-password-reset-frontend": "^0.1.0",
+    "pubsweet-component-signup": "^0.2.0",
+    "pubsweet-component-xpub-dashboard": "^0.0.2",
+    "pubsweet-component-xpub-manuscript": "^0.0.2",
+    "pubsweet-component-xpub-submit": "^0.0.2",
+    "pubsweet-server": "^1.0.0-alpha.1",
+    "pubsweet-theme-plugin": "^0.0.1",
+    "react": "^15.6.1",
+    "react-dom": "^15.6.1",
+    "react-loadable": "^4.0.3",
+    "react-redux": "^5.0.2",
+    "react-router": "^3.0.5",
+    "react-router-redux": "^4.0.7",
+    "recompose": "^0.25.0",
+    "redux": "^3.6.0",
+    "redux-logger": "^3.0.1",
+    "xpub-fonts": "^0.0.2",
+    "xpub-selectors": "^0.0.2",
+    "xpub-ui": "^0.0.2"
+  },
+  "devDependencies": {
+    "babel-loader": "^7.1.1",
+    "babel-preset-react-app": "^3.0.2",
+    "compression-webpack-plugin": "^1.0.0",
+    "copy-webpack-plugin": "^4.0.1",
+    "css-loader": "^0.28.4",
+    "extract-text-webpack-plugin": "^3.0.0",
+    "file-loader": "^0.11.2",
+    "html-webpack-plugin": "^2.24.0",
+    "joi-browser": "^10.0.6",
+    "node-sass": "^4.5.3",
+    "react-hot-loader": "^3.0.0-beta.7",
+    "sass-loader": "^6.0.6",
+    "string-replace-loader": "^1.3.0",
+    "style-loader": "^0.18.2",
+    "webpack": "^3.5.5",
+    "webpack-dev-middleware": "^1.11.0",
+    "webpack-hot-middleware": "^2.18.1"
+  },
+  "scripts": {
+    "setupdb": "pubsweet setupdb --dev ./",
+    "start": "pubsweet run --dev",
+    "debug": "node $NODE_DEBUG_OPTION ./node_modules/pubsweet/bin/pubsweet-run.js --dev"
+  }
+}
diff --git a/static/pubsweet-rgb-small.jpg b/packages/collabra/static/pubsweet-rgb-small.jpg
similarity index 100%
rename from static/pubsweet-rgb-small.jpg
rename to packages/collabra/static/pubsweet-rgb-small.jpg
diff --git a/static/pubsweet.jpg b/packages/collabra/static/pubsweet.jpg
similarity index 100%
rename from static/pubsweet.jpg
rename to packages/collabra/static/pubsweet.jpg
diff --git a/packages/collabra/webpack/common-rules.js b/packages/collabra/webpack/common-rules.js
new file mode 100644
index 0000000000..d48e5473c4
--- /dev/null
+++ b/packages/collabra/webpack/common-rules.js
@@ -0,0 +1,105 @@
+const path = require('path')
+const components = require('../config/components.json')
+
+const requireComponentsString = components
+  .filter(name => {
+    const component = require(name)
+
+    // "client" or "frontend" for backwards compatibility
+    return component.client || component.frontend
+  })
+  .map(name => "require('" + name + "')")
+  .join(', ')
+
+// paths that use ES6 scripts and CSS modules
+// TODO: compile components to ES5 for distribution
+const include = [
+  path.join(__dirname, '..', 'app'),
+  /pubsweet-[^/]+\/src/,
+  /xpub-[^/]+\/src/,
+  /component-[^/]+\/src/,
+]
+
+module.exports = [
+  // replace "PUBSWEET_COMPONENTS" string in pubsweet-client
+  {
+    test: /\.js$/,
+    enforce: 'pre',
+    // include: /pubsweet-client\/src\/components/,
+    loader: 'string-replace-loader',
+    options: {
+      search: 'PUBSWEET_COMPONENTS',
+      replace: '[' + requireComponentsString + ']'
+    }
+  },
+
+  // loaders
+  {
+    oneOf: [
+      // ES6 JS
+      {
+        test: /\.js?$/,
+        include,
+        loader: 'babel-loader',
+        options: {
+          presets: [
+            'env',
+            'react-app'
+          ],
+          plugins: [
+            'react-hot-loader/babel',
+          ]
+        }
+      },
+
+      // CSS Modules
+      {
+        test: /\.local\.css$/,
+        include,
+        use: [
+          'style-loader',
+          {
+            loader: 'css-loader',
+            options: {
+              modules: true,
+            }
+          }
+        ]
+      },
+
+      // global CSS
+      {
+        test: /\.css$/,
+        use: [
+          'style-loader',
+          'css-loader',
+        ]
+      },
+
+      // global SCSS
+      /*{
+        test: /\.scss$/,
+        use: [
+          'style-loader',
+          'css-loader',
+          'sass-loader'
+        ]
+      },*/
+
+      // HTML (needed?)
+      {
+        test: /\.html$/,
+        use: 'html-loader'
+      },
+
+      // files
+      {
+        exclude: [/\.js$/, /\.html$/, /\.json$/],
+        loader: 'file-loader',
+        options: {
+          name: 'static/media/[name].[hash:8].[ext]',
+        }
+      }
+    ]
+  },
+]
diff --git a/webpack/webpack.dev.config.js b/packages/collabra/webpack/webpack.dev.config.js
similarity index 81%
rename from webpack/webpack.dev.config.js
rename to packages/collabra/webpack/webpack.dev.config.js
index bac8c4e598..e0e9685e83 100644
--- a/webpack/webpack.dev.config.js
+++ b/packages/collabra/webpack/webpack.dev.config.js
@@ -1,3 +1,6 @@
+process.env.NODE_ENV = "development"
+process.env.BABEL_ENV = "development"
+
 const path = require('path')
 const webpack = require('webpack')
 const ThemePlugin = require('pubsweet-theme-plugin')
@@ -23,13 +26,13 @@ module.exports = [
       filename: '[name].js',
       publicPath: '/assets/'
     },
-    devtool: 'cheap-module-source-map',
+    devtool: 'eval', // 'cheap-module-source-map',
     module: {
       rules
     },
     resolve: {
-      symlinks: false,
-      modules: [
+      // symlinks: false,
+      modules: [ // needed for resolving app/routes
         path.resolve(__dirname, '..'),
         path.resolve(__dirname, '..', 'node_modules'),
         'node_modules'
@@ -37,9 +40,10 @@ module.exports = [
       alias: {
         joi: 'joi-browser'
       },
-      plugins: [new ThemePlugin(config['pubsweet-client'].theme)],
-      extensions: ['.js', '.jsx', '.json', '.scss'],
-      enforceExtension: false
+      plugins: [
+        new ThemePlugin(config['pubsweet-client'].theme)
+      ],
+      extensions: ['.js', '.jsx'],
     },
     plugins: [
       new webpack.HotModuleReplacementPlugin(),
diff --git a/webpack/webpack.production.config.js b/packages/collabra/webpack/webpack.production.config.js
similarity index 87%
rename from webpack/webpack.production.config.js
rename to packages/collabra/webpack/webpack.production.config.js
index f1ab7bb1db..c0fd1a9c80 100644
--- a/webpack/webpack.production.config.js
+++ b/packages/collabra/webpack/webpack.production.config.js
@@ -1,3 +1,6 @@
+process.env.NODE_ENV = "production"
+process.env.BABEL_ENV = "production"
+
 const path = require('path')
 const webpack = require('webpack')
 const ExtractTextPlugin = require('extract-text-webpack-plugin')
@@ -26,12 +29,12 @@ module.exports = [
       rules
     },
     resolve: {
-      symlinks: false,
-      modules: [
-        path.resolve(__dirname, '..'),
-        path.join(__dirname, '..', 'node_modules'),
-        'node_modules'
-      ],
+      // symlinks: false,
+      // modules: [
+      //   path.resolve(__dirname, '..'),
+      //   path.join(__dirname, '..', 'node_modules'),
+      //   'node_modules'
+      // ],
       alias: {
         joi: 'joi-browser'
       },
diff --git a/webpack/webpack.test.config.js b/packages/collabra/webpack/webpack.test.config.js
similarity index 90%
rename from webpack/webpack.test.config.js
rename to packages/collabra/webpack/webpack.test.config.js
index a76deb1b98..3449be76df 100644
--- a/webpack/webpack.test.config.js
+++ b/packages/collabra/webpack/webpack.test.config.js
@@ -28,12 +28,12 @@ module.exports = [
       rules
     },
     resolve: {
-      symlinks: false,
-      modules: [
-        path.resolve(__dirname, '..'),
-        path.resolve(__dirname, '..', 'node_modules'),
-        'node_modules'
-      ],
+      // symlinks: false,
+      // modules: [
+      //   path.resolve(__dirname, '..'),
+      //   path.resolve(__dirname, '..', 'node_modules'),
+      //   'node_modules'
+      // ],
       alias: {
         joi: 'joi-browser'
       },
diff --git a/packages/component-dashboard/package.json b/packages/component-dashboard/package.json
new file mode 100644
index 0000000000..5b502a3d71
--- /dev/null
+++ b/packages/component-dashboard/package.json
@@ -0,0 +1,26 @@
+{
+  "name": "pubsweet-component-xpub-dashboard",
+  "version": "0.0.2",
+  "main": "src",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "dependencies": {
+    "classnames": "^2.2.5",
+    "pubsweet-component-ink-frontend": "^0.1.0",
+    "react-dropzone": "^3.13.3",
+    "react-moment": "^0.6.1",
+    "xpub-selectors": "^0.0.2"
+  },
+  "peerDependencies": {
+    "prop-types": "^15.5.10",
+    "pubsweet-client": "^1.0.0-alpha.1",
+    "react": "^15.6.1",
+    "react-dom": "^15.6.1",
+    "react-redux": "^5.0.2",
+    "react-router": "^3.0.5"
+  }
+}
diff --git a/packages/component-dashboard/src/components/Dashboard.js b/packages/component-dashboard/src/components/Dashboard.js
new file mode 100644
index 0000000000..cd0660bdd2
--- /dev/null
+++ b/packages/component-dashboard/src/components/Dashboard.js
@@ -0,0 +1,24 @@
+import React from 'react'
+import classes from './Dashboard.local.css'
+import UploadManuscript from './UploadManuscript'
+import DashboardItem from './DashboardItem'
+
+const Dashboard = ({ projects, createProject, createVersion, convertToHTML, isConverting }) => (
+  <div className={classes.root}>
+    <div className={classes.upload}>
+      <UploadManuscript
+        createProject={createProject}
+        createVersion={createVersion}
+        convertToHTML={convertToHTML}/>
+    </div>
+
+    {projects.map(project => (
+      <DashboardItem
+        className={classes.item}
+        key={project.id}
+        project={project}/>
+    ))}
+  </div>
+)
+
+export default Dashboard
diff --git a/packages/component-dashboard/src/components/Dashboard.local.css b/packages/component-dashboard/src/components/Dashboard.local.css
new file mode 100644
index 0000000000..7a25f58f9e
--- /dev/null
+++ b/packages/component-dashboard/src/components/Dashboard.local.css
@@ -0,0 +1,4 @@
+.root {
+  max-width: 60em;
+  margin: auto;
+}
diff --git a/packages/component-dashboard/src/components/DashboardItem.js b/packages/component-dashboard/src/components/DashboardItem.js
new file mode 100644
index 0000000000..87295ea761
--- /dev/null
+++ b/packages/component-dashboard/src/components/DashboardItem.js
@@ -0,0 +1,9 @@
+import React from 'react'
+
+const DashboardItem = ({ project }) => (
+  <div>
+    <div>{ project.title || 'Untitled' }</div>
+  </div>
+)
+
+export default DashboardItem
diff --git a/packages/component-dashboard/src/components/DashboardPage.js b/packages/component-dashboard/src/components/DashboardPage.js
new file mode 100644
index 0000000000..dba8d4d69b
--- /dev/null
+++ b/packages/component-dashboard/src/components/DashboardPage.js
@@ -0,0 +1,22 @@
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { orderBy } from 'lodash'
+import { actions } from 'pubsweet-client'
+import { selectCurrentUser } from 'xpub-selectors'
+import { ink } from 'pubsweet-component-ink-frontend/actions'
+import Dashboard from './Dashboard'
+
+export default compose(
+  connect(
+    state => ({
+      projects: orderBy(state.collections, 'created', 'desc'),
+      currentUser: selectCurrentUser(state),
+      isConverting: state.ink.isFetching
+    }),
+    {
+      createProject: actions.createCollection,
+      createVersion: actions.createFragment,
+      convertToHTML: ink
+    }
+  )
+)(Dashboard)
diff --git a/packages/component-dashboard/src/components/UploadManuscript.js b/packages/component-dashboard/src/components/UploadManuscript.js
new file mode 100644
index 0000000000..c06fee84d3
--- /dev/null
+++ b/packages/component-dashboard/src/components/UploadManuscript.js
@@ -0,0 +1,94 @@
+import React from 'react'
+import Dropzone from 'react-dropzone'
+import classnames from 'classnames'
+import classes from './UploadManuscript.local.css'
+
+// TODO: move isConverting from global state to local state
+
+const generateTitle = (name) => {
+  return name
+    .replace(/[_-]+/g, ' ') // convert hyphens/underscores to space
+    .replace(/\.[^.]+$/, '') // remove file extension
+}
+
+// TODO: preserve italics (use parse5?)
+const extractTitle = (source) => {
+  const doc = new DOMParser().parseFromString(source, 'text/html')
+  const heading = doc.querySelector('h1')
+
+  return heading ? heading.textContent : null
+}
+
+class UploadManuscript extends React.Component {
+  state = {
+    converting: false,
+    complete: undefined,
+    error: undefined
+  }
+
+  onDrop = acceptedFiles => {
+    const { convertToHTML, createProject, createVersion } = this.props
+
+    const inputFile = acceptedFiles[0]
+
+    this.setState({
+      converting: true,
+      complete: false,
+      error: undefined
+    })
+
+    convertToHTML(inputFile).then(response => {
+      if (!response.converted) {
+        throw new Error('The file was not converted')
+      }
+
+      const source = response.converted
+      const title = extractTitle(source) || generateTitle(inputFile.name)
+
+      return createProject({
+        type: 'project',
+      }).then(({ collection: project }) => {
+        if (!project.id) {
+          throw new Error('Failed to create a project')
+        }
+
+        return createVersion(project, {
+          type: 'version',
+          version: 1,
+          source,
+          title
+        }).then(() => {
+          this.setState({ complete: true })
+        })
+      })
+    }).catch(error => {
+      this.setState({ error: error.message })
+    })
+  }
+
+  render () {
+    const { converting, complete, error } = this.props
+
+    return (
+      <Dropzone
+        onDrop={this.onDrop}
+        accept="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+        className={classes.dropzone}>
+        <div className={classes.main}>
+          <div className={classnames(classes.icon, {
+            [classes.converting]: converting,
+            [classes.complete]: complete
+          })}>
+            <span>+</span>
+          </div>
+
+          <div className={classes.info}>
+            {error ? error : 'Start a new submission'}
+          </div>
+        </div>
+      </Dropzone>
+    )
+  }
+}
+
+export default UploadManuscript
diff --git a/packages/component-dashboard/src/components/UploadManuscript.local.css b/packages/component-dashboard/src/components/UploadManuscript.local.css
new file mode 100644
index 0000000000..a916460723
--- /dev/null
+++ b/packages/component-dashboard/src/components/UploadManuscript.local.css
@@ -0,0 +1,26 @@
+.dropzone {
+  border: none;
+  display: inline-block;
+  cursor: pointer;
+}
+
+.main {
+  font-weight: 200;
+  display: flex;
+  padding-top: 10px;
+  padding-bottom: 10px;
+}
+
+.icon {
+  color: cornflowerblue;
+}
+
+.converting {
+  color: orange;
+}
+
+.info {
+  text-transform: uppercase;
+  font-size: 200%;
+  color: cornflowerblue;
+}
diff --git a/packages/component-dashboard/src/components/index.js b/packages/component-dashboard/src/components/index.js
new file mode 100644
index 0000000000..28b6c656bd
--- /dev/null
+++ b/packages/component-dashboard/src/components/index.js
@@ -0,0 +1 @@
+module.exports = require('./DashboardPage')
diff --git a/packages/component-dashboard/src/index.js b/packages/component-dashboard/src/index.js
new file mode 100644
index 0000000000..243220c285
--- /dev/null
+++ b/packages/component-dashboard/src/index.js
@@ -0,0 +1,7 @@
+module.exports = {
+  frontend: {
+    components: [
+      () => require('./components')
+    ]
+  },
+}
diff --git a/packages/component-manuscript/package.json b/packages/component-manuscript/package.json
new file mode 100644
index 0000000000..cfd77ba020
--- /dev/null
+++ b/packages/component-manuscript/package.json
@@ -0,0 +1,43 @@
+{
+  "name": "pubsweet-component-xpub-manuscript",
+  "version": "0.0.2",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "main": "src",
+  "dependencies": {
+    "pubsweet-component-wax": "^0.1.0",
+    "xpub-selectors": "^0.0.2"
+  },
+  "peerDependencies": {
+    "prop-types": "^15.5.10",
+    "pubsweet-client": "^1.0.0-alpha.1",
+    "react": "^15.6.1",
+    "react-dom": "^15.6.1",
+    "react-redux": "^5.0.2",
+    "react-router": "^3.0.5"
+  },
+  "devDependencies": {
+    "babel-loader": "^7.1.1",
+    "babel-preset-env": "^1.6.0",
+    "babel-preset-minify": "^0.2.0",
+    "babel-preset-react-app": "^3.0.2",
+    "css-loader": "^0.28.4",
+    "eslint": "^4.4.1",
+    "extract-text-webpack-plugin": "^3.0.0",
+    "file-loader": "^0.11.2",
+    "node-sass": "^4.5.3",
+    "rimraf": "^2.6.1",
+    "sass-loader": "^6.0.6",
+    "style-loader": "^0.18.2",
+    "webpack": "^3.5.5",
+    "webpack-node-externals": "^1.6.0"
+  },
+  "scripts": {
+    "prebuild": "npm run clean && npm run lint",
+    "lint": "eslint src",
+    "clean": "rimraf dist",
+    "build": "NODE_ENV=production webpack --progress --profile"
+  }
+}
diff --git a/packages/component-manuscript/src/components/Manuscript.js b/packages/component-manuscript/src/components/Manuscript.js
new file mode 100644
index 0000000000..3480db693b
--- /dev/null
+++ b/packages/component-manuscript/src/components/Manuscript.js
@@ -0,0 +1,26 @@
+import React from 'react'
+import { browserHistory } from 'react-router'
+import SimpleEditor from 'pubsweet-component-wax/src/SimpleEditor'
+import 'pubsweet-component-wax/src/SimpleEditor.scss'
+import classes from './Manuscript.local.css'
+
+// TODO: convert user teams to roles (see SimpleEditorWrapper)?
+
+const Manuscript = ({ project, version, currentUser, fileUpload, updateVersion }) => (
+  <SimpleEditor
+    classes={classes.fullscreen}
+    book={project}
+    fragment={version}
+    user={currentUser}
+    fileUpload={fileUpload}
+    history={browserHistory}
+    update={data => (
+      updateVersion(project, { id: version.id, ...data })
+    )}
+    onSave={source => (
+      updateVersion(project, { id: version.id, source })
+    )}
+  />
+)
+
+export default Manuscript
diff --git a/packages/component-manuscript/src/components/Manuscript.local.css b/packages/component-manuscript/src/components/Manuscript.local.css
new file mode 100644
index 0000000000..6b2a948fa3
--- /dev/null
+++ b/packages/component-manuscript/src/components/Manuscript.local.css
@@ -0,0 +1,9 @@
+.fullscreen {
+  position: fixed;
+  top: 50px;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: white;
+  overflow: hidden;
+}
diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js
new file mode 100644
index 0000000000..8f8a3441a3
--- /dev/null
+++ b/packages/component-manuscript/src/components/ManuscriptPage.js
@@ -0,0 +1,19 @@
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { actions } from 'pubsweet-client'
+import { selectCurrentUser, selectCollection, selectFragment } from 'xpub-selectors'
+import Manuscript from './Manuscript'
+
+export default compose(
+  connect(
+    (state, ownProps) => ({
+      currentUser: selectCurrentUser(state),
+      project: selectCollection(state, ownProps.params.project),
+      version: selectFragment(state, ownProps.params.version)
+    }),
+    {
+      fileUpload: actions.fileUpload,
+      updateVersion: actions.updateFragment
+    }
+  )
+)(Manuscript)
diff --git a/packages/component-manuscript/src/components/index.js b/packages/component-manuscript/src/components/index.js
new file mode 100644
index 0000000000..4d5baac5a2
--- /dev/null
+++ b/packages/component-manuscript/src/components/index.js
@@ -0,0 +1 @@
+module.exports = require('./ManuscriptPage')
diff --git a/packages/component-manuscript/src/index.js b/packages/component-manuscript/src/index.js
new file mode 100644
index 0000000000..0225ff41f4
--- /dev/null
+++ b/packages/component-manuscript/src/index.js
@@ -0,0 +1,7 @@
+module.exports = {
+  frontend: {
+    components: [
+      () => require('./components')
+    ]
+  }
+}
diff --git a/packages/component-manuscript/webpack.config.js b/packages/component-manuscript/webpack.config.js
new file mode 100644
index 0000000000..2fb4f5258d
--- /dev/null
+++ b/packages/component-manuscript/webpack.config.js
@@ -0,0 +1,86 @@
+process.env.BABEL_ENV = 'production'
+process.env.NODE_ENV = 'production'
+
+const path = require('path')
+const nodeExternals = require('webpack-node-externals')
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+
+module.exports = {
+  entry: './src/index.js',
+  output: {
+    filename: 'index.js',
+    path: path.join(__dirname, 'dist'),
+    library: 'WaxEditor',
+    libraryTarget: 'commonjs2'
+  },
+  devtool: 'cheap-module-source-map',
+  externals: [nodeExternals({
+    whitelist: [/\.(?!js$).{1,5}$/i]
+  })],
+  resolve: {
+    extensions: ['.js', '.jsx'], // needed because pubsweet-component-wax uses jsx
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            test: /\.jsx?$/,
+            include: [
+              path.join(__dirname, 'src'),
+              /pubsweet-[^/]+\/src/,
+            ],
+            loader: 'babel-loader',
+            options: {
+              presets: ['minify', 'react-app']
+            }
+          },
+
+          // CSS modules
+          {
+            test: /\.local\.css$/,
+            use: [
+              'style-loader',
+              {
+                loader: 'css-loader',
+                options: {
+                  modules: true
+                }
+              }
+            ]
+          },
+
+          // CSS
+          {
+            test: /\.css$/,
+            use: ExtractTextPlugin.extract({
+              fallback: 'style-loader',
+              use: 'css-loader'
+            })
+          },
+
+          // SCSS
+          {
+            test: /\.scss$/,
+            use: ExtractTextPlugin.extract({
+              fallback: 'style-loader',
+              use: ['css-loader', 'sass-loader'],
+            }),
+          },
+
+          // other files
+          {
+            exclude: [/\.jsx?$/, /\.html$/, /\.json$/],
+            loader: 'file-loader',
+            options: {
+              name: 'static/[name].[hash:8].[ext]',
+            }
+          }
+        ]
+      }
+    ]
+  },
+  plugins: [
+    new ExtractTextPlugin('styles.css'),
+  ]
+}
diff --git a/packages/component-submit/package.json b/packages/component-submit/package.json
new file mode 100644
index 0000000000..9f2e5ec491
--- /dev/null
+++ b/packages/component-submit/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "pubsweet-component-xpub-submit",
+  "version": "0.0.2",
+  "main": "src",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "dependencies": {
+    "formsy-react": "^0.19.2",
+    "formsy-react-components": "^0.10.1",
+    "uuid": "^3.1.0",
+    "xpub-selectors": "^0.0.2"
+  },
+  "peerDependencies": {
+    "prop-types": "^15.5.10",
+    "pubsweet-client": "^1.0.0-alpha.1",
+    "react": "^15.6.1",
+    "react-dom": "^15.6.1",
+    "react-redux": "^5.0.2",
+    "react-router": "^3.0.5"
+  }
+}
diff --git a/packages/component-submit/src/components/Submit.js b/packages/component-submit/src/components/Submit.js
new file mode 100644
index 0000000000..ef389e9e53
--- /dev/null
+++ b/packages/component-submit/src/components/Submit.js
@@ -0,0 +1,9 @@
+import React from 'react'
+import classes from './Submit.local.css'
+
+const Submit = ({ project, version, updateVersion }) => (
+  <div className={classes.root}>
+  </div>
+)
+
+export default Submit
diff --git a/packages/component-submit/src/components/Submit.local.css b/packages/component-submit/src/components/Submit.local.css
new file mode 100644
index 0000000000..bedeb67b49
--- /dev/null
+++ b/packages/component-submit/src/components/Submit.local.css
@@ -0,0 +1,3 @@
+.root {
+
+}
diff --git a/packages/component-submit/src/components/SubmitPage.js b/packages/component-submit/src/components/SubmitPage.js
new file mode 100644
index 0000000000..555531b738
--- /dev/null
+++ b/packages/component-submit/src/components/SubmitPage.js
@@ -0,0 +1,17 @@
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { actions } from 'pubsweet-client'
+import { selectCollection, selectFragment } from 'xpub-selectors'
+import Submit from './Submit'
+
+export default compose(
+  connect(
+    (state, ownProps) => ({
+      project: selectCollection(state, ownProps.params.project),
+      version: selectFragment(state, ownProps.params.version)
+    }),
+    {
+      updateVersion: actions.updateFragment
+    }
+  )
+)(Submit)
diff --git a/packages/component-submit/src/components/index.js b/packages/component-submit/src/components/index.js
new file mode 100644
index 0000000000..38dfee8241
--- /dev/null
+++ b/packages/component-submit/src/components/index.js
@@ -0,0 +1 @@
+module.exports = require('./SubmitPage')
diff --git a/packages/component-submit/src/index.js b/packages/component-submit/src/index.js
new file mode 100644
index 0000000000..0225ff41f4
--- /dev/null
+++ b/packages/component-submit/src/index.js
@@ -0,0 +1,7 @@
+module.exports = {
+  frontend: {
+    components: [
+      () => require('./components')
+    ]
+  }
+}
diff --git a/packages/component-submit/src/lib/date.js b/packages/component-submit/src/lib/date.js
new file mode 100644
index 0000000000..fa2d161cc8
--- /dev/null
+++ b/packages/component-submit/src/lib/date.js
@@ -0,0 +1,3 @@
+import moment from 'moment'
+
+export const format = date => moment(date).format('YYYY-MM-DD')
diff --git a/packages/component-submit/src/lib/sort.js b/packages/component-submit/src/lib/sort.js
new file mode 100644
index 0000000000..1ffa02a35b
--- /dev/null
+++ b/packages/component-submit/src/lib/sort.js
@@ -0,0 +1,3 @@
+export const ascending = field => (a, b) => a[field] - b[field]
+
+export const descending = field => (a, b) => b[field] - a[field]
diff --git a/packages/component-submit/src/lib/text.js b/packages/component-submit/src/lib/text.js
new file mode 100644
index 0000000000..e2e4b4ac34
--- /dev/null
+++ b/packages/component-submit/src/lib/text.js
@@ -0,0 +1 @@
+export const ucfirst = (text) => text.substr(0, 1).toUpperCase() + text.substr(1)
diff --git a/packages/xpub-fonts/package.json b/packages/xpub-fonts/package.json
new file mode 100644
index 0000000000..a948108d27
--- /dev/null
+++ b/packages/xpub-fonts/package.json
@@ -0,0 +1,12 @@
+{
+  "name": "xpub-fonts",
+  "version": "0.0.2",
+  "description": "Fonts for use in xpub clients",
+  "main": "src",
+  "license": "MIT",
+  "dependencies": {
+    "pubsweet-fira": "^0.0.3",
+    "typeface-fira-sans-condensed": "^0.0.35",
+    "typeface-vollkorn": "^0.0.35"
+  }
+}
diff --git a/packages/xpub-fonts/src/index.js b/packages/xpub-fonts/src/index.js
new file mode 100644
index 0000000000..d8d4231df8
--- /dev/null
+++ b/packages/xpub-fonts/src/index.js
@@ -0,0 +1,3 @@
+require('typeface-fira-sans-condensed')
+require('typeface-vollkorn')
+require('pubsweet-fira')
diff --git a/packages/xpub-selectors/package.json b/packages/xpub-selectors/package.json
new file mode 100644
index 0000000000..97a7f5d0bd
--- /dev/null
+++ b/packages/xpub-selectors/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "xpub-selectors",
+  "version": "0.0.2",
+  "description": "Redux selectors for use in xpub components",
+  "main": "src",
+  "license": "MIT"
+}
diff --git a/packages/xpub-selectors/src/index.js b/packages/xpub-selectors/src/index.js
new file mode 100644
index 0000000000..561a5e3b5c
--- /dev/null
+++ b/packages/xpub-selectors/src/index.js
@@ -0,0 +1,14 @@
+export const selectCurrentUser = state => state.currentUser.isAuthenticated
+  ? state.currentUser.user
+  : null
+
+// TODO: collections should be keyed by id
+export const selectCollection = (state, id) => state.collections
+  .find(collection => collection.id === id)
+
+// TODO: there shouldn't be any missing
+export const selectFragments = (state, ids) => ids
+  .map(id => state.fragments[id])
+  .filter(fragment => fragment)
+
+export const selectFragment = (state, id) => state.fragments[id]
diff --git a/packages/xpub-ui/.eslintrc b/packages/xpub-ui/.eslintrc
new file mode 100644
index 0000000000..db7f6d5a86
--- /dev/null
+++ b/packages/xpub-ui/.eslintrc
@@ -0,0 +1,7 @@
+{
+  "globals": {
+    "initialState": true,
+    "state": false,
+    "setState": false
+  }
+}
diff --git a/packages/xpub-ui/lib/styleguide/StyleGuideRenderer.js b/packages/xpub-ui/lib/styleguide/StyleGuideRenderer.js
new file mode 100644
index 0000000000..3a7d1428ca
--- /dev/null
+++ b/packages/xpub-ui/lib/styleguide/StyleGuideRenderer.js
@@ -0,0 +1,25 @@
+import React from 'react'
+import 'xpub-fonts'
+import classes from './StyleGuideRenderer.local.css'
+
+const StyleGuideRenderer = ({ title, children, toc }) => {
+  return (
+    <div className={classes.root}>
+      <div className={classes.sidebar}>
+        <header className={classes.header}>
+          <h1 className={classes.title}>{title}</h1>
+        </header>
+
+        <nav className={classes.nav}>
+          {toc}
+        </nav>
+      </div>
+
+      <div className={classes.content}>
+        {children}
+      </div>
+    </div>
+  )
+}
+
+export default StyleGuideRenderer
diff --git a/packages/xpub-ui/lib/styleguide/StyleGuideRenderer.local.css b/packages/xpub-ui/lib/styleguide/StyleGuideRenderer.local.css
new file mode 100644
index 0000000000..ddcdbb3307
--- /dev/null
+++ b/packages/xpub-ui/lib/styleguide/StyleGuideRenderer.local.css
@@ -0,0 +1,37 @@
+.root {
+  display: grid;
+  grid-template-columns: 1fr 3fr;
+  grid-template-areas: "side content";
+  height: 100vh;
+  width: 100vw;
+}
+
+.sidebar {
+  grid-area: side;
+  display: flex;
+  flex-direction: column;
+  overflow-y: hidden;
+}
+
+.content {
+  grid-area: content;
+  padding: 1rem;
+  overflow-y: auto;
+}
+
+.header {
+  padding: 0.5rem;
+}
+
+.nav {
+  flex: 1;
+  overflow-y: auto;
+  padding: 0.5rem;
+}
+
+.title {
+  font-family: "Fira Sans", serif;
+  font-size: 1rem;
+  margin-bottom: 0;
+  padding: 0 1rem;
+}
diff --git a/packages/xpub-ui/lib/styleguide/Wrapper.js b/packages/xpub-ui/lib/styleguide/Wrapper.js
new file mode 100644
index 0000000000..c6be0fff02
--- /dev/null
+++ b/packages/xpub-ui/lib/styleguide/Wrapper.js
@@ -0,0 +1,21 @@
+import React from 'react'
+// import { Router, createMemoryHistory } from 'react-router'
+import 'xpub-fonts'
+import './Wrapper.scss'
+import classes from './Wrapper.local.css'
+
+/*const Wrapper = ({ children }) => (
+  <Router history={createMemoryHistory()}>
+    <div className={classes.root}>
+      {children}
+    </div>
+  </Router>
+)*/
+
+const Wrapper = ({ children }) => (
+  <div className={classes.root}>
+    {children}
+  </div>
+)
+
+export default Wrapper
diff --git a/packages/xpub-ui/lib/styleguide/Wrapper.local.css b/packages/xpub-ui/lib/styleguide/Wrapper.local.css
new file mode 100644
index 0000000000..a72adc6609
--- /dev/null
+++ b/packages/xpub-ui/lib/styleguide/Wrapper.local.css
@@ -0,0 +1,3 @@
+.root {
+  font-family: "Fira Sans", sans-serif;
+}
diff --git a/packages/xpub-ui/lib/styleguide/Wrapper.scss b/packages/xpub-ui/lib/styleguide/Wrapper.scss
new file mode 100644
index 0000000000..b5321f7d70
--- /dev/null
+++ b/packages/xpub-ui/lib/styleguide/Wrapper.scss
@@ -0,0 +1,4 @@
+// TODO: variables
+
+@import '~bootstrap-sass/assets/stylesheets/_bootstrap';
+
diff --git a/packages/xpub-ui/package.json b/packages/xpub-ui/package.json
new file mode 100644
index 0000000000..0bffb1552b
--- /dev/null
+++ b/packages/xpub-ui/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "xpub-ui",
+  "version": "0.0.2",
+  "files": [
+    "docs",
+    "dist",
+    "src"
+  ],
+  "main": "src",
+  "jsnext:main": "src",
+  "dependencies": {
+    "xpub-fonts": "^0.0.2",
+    "classnames": "^2.2.5",
+    "lodash": "^4.17.4",
+    "prop-types": "^15.5.10",
+    "react": "^15.6.1",
+    "react-dom": "^15.6.1",
+    "react-router": "^3.0.5"
+  },
+  "devDependencies": {
+    "babel-preset-react-app": "^3.0.2",
+    "faker": "^4.1.0",
+    "react-styleguidist": "^6.0.8",
+    "webpack": "^3.5.5"
+  },
+  "scripts": {
+    "styleguide": "styleguidist server",
+    "styleguide:build": "styleguidist build",
+    "clean": "rimraf dist",
+    "lint": "eslint src",
+    "prebuild": "npm run clean && npm run lint",
+    "build": "webpack --progress --profile"
+  }
+}
diff --git a/packages/xpub-ui/src/AppBar.js b/packages/xpub-ui/src/AppBar.js
new file mode 100644
index 0000000000..1aa41a8911
--- /dev/null
+++ b/packages/xpub-ui/src/AppBar.js
@@ -0,0 +1,27 @@
+import React from 'react'
+import { Link } from 'react-router'
+import classnames from 'classnames'
+import classes from './AppBar.local.css'
+
+const AppBar = ({ brandLink, brandName, loginLink, logoutLink, userName }) => (
+  <div className={classes.root}>
+    <Link to={brandLink || '/'}
+          className={classes.link}>{brandName}</Link>
+
+    <div>
+      {userName && (
+        <span className={classes.item}>{userName}</span>
+      )}
+
+      {userName ? (
+        <Link to={logoutLink}
+              className={classnames(classes.item, classes.link)}>logout</Link>
+      ) : (
+        <Link to={loginLink}
+              className={classnames(classes.item, classes.link)}>login</Link>
+      )}
+    </div>
+  </div>
+)
+
+export default AppBar
diff --git a/packages/xpub-ui/src/AppBar.local.css b/packages/xpub-ui/src/AppBar.local.css
new file mode 100644
index 0000000000..40bc1c5482
--- /dev/null
+++ b/packages/xpub-ui/src/AppBar.local.css
@@ -0,0 +1,16 @@
+.root {
+  display: flex;
+  justify-content: space-between;
+}
+
+.link {
+  color: cornflowerblue;
+}
+
+.link:hover {
+  text-decoration: underline;
+}
+
+.item {
+  padding: 1rem;
+}
diff --git a/packages/xpub-ui/src/AppBar.md b/packages/xpub-ui/src/AppBar.md
new file mode 100644
index 0000000000..a2b7acebcb
--- /dev/null
+++ b/packages/xpub-ui/src/AppBar.md
@@ -0,0 +1,20 @@
+The app bar appears at the top of every page of the application.
+
+It displays the name of the application (as a link to the home page), the username of the current user, and a link to sign out. 
+
+```js
+<AppBar
+  brandName="xpub"
+  loginLink="/login"
+  logoutLink="/logout"
+  userName="foo"/>
+```
+
+When the user is not signed in, only the login link is displayed.
+
+```js
+<AppBar
+  brandName="xpub"
+  loginLink="/login"
+  logoutLink="/logout"/>
+```
diff --git a/packages/xpub-ui/src/Radio.js b/packages/xpub-ui/src/Radio.js
new file mode 100644
index 0000000000..ac5b499fad
--- /dev/null
+++ b/packages/xpub-ui/src/Radio.js
@@ -0,0 +1,18 @@
+import React from 'react'
+import classes from './Radio.local.css'
+
+const Radio = ({ name, value, label, checked, required, handleChange }) => (
+  <label className={classes.root}>
+    <input
+      className={classes.input}
+      type="radio"
+      name={name}
+      value={value}
+      checked={checked}
+      required={required}
+      onChange={event => handleChange(event.target.value)}/>
+    {label}
+  </label>
+)
+
+export default Radio
diff --git a/packages/xpub-ui/src/Radio.local.css b/packages/xpub-ui/src/Radio.local.css
new file mode 100644
index 0000000000..5a5e4d035c
--- /dev/null
+++ b/packages/xpub-ui/src/Radio.local.css
@@ -0,0 +1,13 @@
+.root {
+  display: inline-flex;
+  align-items: center;
+  cursor: pointer;
+}
+
+.root:not(:last-child) {
+  margin-right: 1rem;
+}
+
+.input {
+  margin-right: 0.25rem;
+}
diff --git a/packages/xpub-ui/src/Radio.md b/packages/xpub-ui/src/Radio.md
new file mode 100644
index 0000000000..ef4d99f262
--- /dev/null
+++ b/packages/xpub-ui/src/Radio.md
@@ -0,0 +1,25 @@
+A radio button.
+
+```js
+<Radio 
+  name="foo" 
+  handleChange={event => console.log(event)}/>
+```
+
+A checked radio button.
+
+```js
+<Radio 
+  name="bar" 
+  checked 
+  handleChange={event => console.log(event)}/>
+```
+
+A radio button with a label.
+
+```js
+<Radio 
+  name="foo" 
+  label="Foo"
+  handleChange={event => console.log(event)}/>
+```
diff --git a/packages/xpub-ui/src/RadioGroup.js b/packages/xpub-ui/src/RadioGroup.js
new file mode 100644
index 0000000000..fd9fb27ecb
--- /dev/null
+++ b/packages/xpub-ui/src/RadioGroup.js
@@ -0,0 +1,19 @@
+import React from 'react'
+import Radio from './Radio'
+
+const RadioGroup = ({ name, value, options, required, handleChange }) => (
+  <div>
+    {options.map(option => (
+      <Radio
+        key={option.value}
+        name={name}
+        required={required}
+        value={option.value}
+        label={option.label}
+        checked={option.value === value}
+        handleChange={handleChange}/>
+    ))}
+  </div>
+)
+
+export default RadioGroup
diff --git a/packages/xpub-ui/src/RadioGroup.md b/packages/xpub-ui/src/RadioGroup.md
new file mode 100644
index 0000000000..91611a87c5
--- /dev/null
+++ b/packages/xpub-ui/src/RadioGroup.md
@@ -0,0 +1,26 @@
+A group of radio buttons.
+
+```js
+initialState = { foo: undefined };
+
+const options = [
+  {
+    value: 'one',
+    label: 'One'
+  },
+  {
+    value: 'two',
+    label: 'Two'
+  },
+  {
+    value: 'three',
+    label: 'Three'
+  }
+];
+
+<RadioGroup 
+  options={options} 
+  name="foo"
+  value={state.foo} 
+  handleChange={foo => setState({ foo })}/>
+```
diff --git a/packages/xpub-ui/src/YesOrNo.js b/packages/xpub-ui/src/YesOrNo.js
new file mode 100644
index 0000000000..d8e36bce2d
--- /dev/null
+++ b/packages/xpub-ui/src/YesOrNo.js
@@ -0,0 +1,25 @@
+import React from 'react'
+import RadioGroup from './RadioGroup'
+import classes from './YesOrNo.local.css'
+
+const options = [
+  {
+    value: 'yes',
+    label: 'Yes'
+  },
+  {
+    value: 'no',
+    label: 'No'
+  }
+]
+
+const YesOrNo = (name, value, handleChange) => (
+  <RadioGroup
+    className={classes.root}
+    name={name}
+    options={options}
+    value={value}
+    handleChange={handleChange}/>
+)
+
+export default YesOrNo
diff --git a/packages/xpub-ui/src/YesOrNo.local.css b/packages/xpub-ui/src/YesOrNo.local.css
new file mode 100644
index 0000000000..402cdb661c
--- /dev/null
+++ b/packages/xpub-ui/src/YesOrNo.local.css
@@ -0,0 +1,3 @@
+:root {
+  color: darkgreen;
+}
diff --git a/packages/xpub-ui/src/YesOrNo.md b/packages/xpub-ui/src/YesOrNo.md
new file mode 100644
index 0000000000..4f6cb070e9
--- /dev/null
+++ b/packages/xpub-ui/src/YesOrNo.md
@@ -0,0 +1,10 @@
+A group of radio buttons that provides just two options: "Yes" or "No"
+
+```js
+initialState = { foo: undefined };
+
+<YesOrNo
+  name="foo" 
+  value={state.foo} 
+  onChange={event => setState({ foo: event.target.value })}/>
+```
diff --git a/packages/xpub-ui/src/index.js b/packages/xpub-ui/src/index.js
new file mode 100644
index 0000000000..051f5df2b0
--- /dev/null
+++ b/packages/xpub-ui/src/index.js
@@ -0,0 +1,2 @@
+export { default as AppBar } from './AppBar'
+export { default as YesOrNo } from './YesOrNo'
diff --git a/packages/xpub-ui/styleguide.config.js b/packages/xpub-ui/styleguide.config.js
new file mode 100644
index 0000000000..1361625af6
--- /dev/null
+++ b/packages/xpub-ui/styleguide.config.js
@@ -0,0 +1,23 @@
+const path = require('path')
+
+module.exports = {
+  title: 'xpub-ui style guide',
+  styleguideComponents: {
+    StyleGuideRenderer: path.join(__dirname, 'lib/styleguide/StyleGuideRenderer'),
+    Wrapper: path.join(__dirname, 'lib/styleguide/Wrapper'),
+  },
+  context: {
+    faker: 'faker',
+  },
+  components: './src/*.js',
+  skipComponentsWithoutExample: true,
+  webpackConfig: require('./webpack.config.js'),
+  theme: {
+    fontFamily: {
+      base: '"Fira Sans", sans-serif'
+    },
+    color: {
+      link: 'cornflowerblue'
+    }
+  },
+}
diff --git a/packages/xpub-ui/webpack.config.js b/packages/xpub-ui/webpack.config.js
new file mode 100644
index 0000000000..eb924224b6
--- /dev/null
+++ b/packages/xpub-ui/webpack.config.js
@@ -0,0 +1,78 @@
+process.env.BABEL_ENV = 'development'
+process.env.NODE_ENV = 'development'
+
+const path = require('path')
+const nodeExternals = require('webpack-node-externals')
+
+module.exports = {
+  entry: './src/index.js',
+  output: {
+    filename: 'index.js',
+    path: path.join(__dirname, 'dist'),
+    library: 'xpub-ui',
+    libraryTarget: 'commonjs2'
+  },
+  devtool: 'cheap-module-source-map',
+  externals: [nodeExternals({
+    whitelist: [/\.(?!js$).{1,5}$/i]
+  })],
+  module: {
+    rules: [
+      {
+        oneOf: [
+          // ES6 modules
+          {
+            test: /\.js$/,
+            include: [
+              path.join(__dirname, 'src'),
+              path.join(__dirname, 'lib'),
+            ],
+            loader: 'babel-loader',
+            options: {
+              presets: [
+                'react-app',
+              ],
+              cacheDirectory: true,
+            },
+          },
+
+          // CSS modules
+          {
+            test: /\.local\.css$/,
+            include: [
+              path.join(__dirname, 'src'),
+              path.join(__dirname, 'lib'),
+            ],
+            use: [
+              'style-loader',
+              {
+                loader: 'css-loader',
+                options: {
+                  modules: true,
+                }
+              }
+            ],
+          },
+
+          // CSS
+          {
+            test: /\.css$/,
+            use: [
+              'style-loader',
+              'css-loader'
+            ],
+          },
+
+          // Files
+          {
+            exclude: [/\.js$/, /\.html$/, /\.json$/],
+            loader: 'file-loader',
+            options: {
+              name: 'static/media/[name].[hash:8].[ext]',
+            }
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/permissions/index.js b/permissions/index.js
deleted file mode 100644
index 1f51be4d31..0000000000
--- a/permissions/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = (user, operation, project, version) => {
-  return true // TODO
-}
diff --git a/webpack/common-rules.js b/webpack/common-rules.js
deleted file mode 100644
index d0ef41b351..0000000000
--- a/webpack/common-rules.js
+++ /dev/null
@@ -1,114 +0,0 @@
-const path = require('path')
-const components = require('./components')
-
-const modulesPath = path.join(__dirname, '..', 'node_modules')
-
-module.exports = [
-  {
-    test: /\.(js|jsx)$/,
-    exclude: /node_modules\/(?!pubsweet-)/,
-    use: {
-      loader: 'babel-loader',
-      options: {
-        presets: [
-          require.resolve('babel-preset-stage-2'),
-          require.resolve('babel-preset-react'),
-          [require.resolve('babel-preset-env'), {
-            modules: false
-          }]
-        ],
-        plugins: [
-          'react-hot-loader/babel',
-          require.resolve('babel-plugin-transform-class-properties')
-        ],
-        env: {
-          production: {
-            presets: ['babili']
-          }
-        }
-      }
-    }
-  },
-  {
-    test: /\.png$/,
-    use: {
-      loader: 'url-loader'
-    }
-  },
-  {
-    test: /\.(woff|woff2|svg|eot|ttf)$/,
-    use: {
-      loader: 'url-loader',
-      options: {
-        prefix: 'font',
-        limit: 1000
-      }
-    }
-  },
-  {
-    test: /\.html$/,
-    use: {
-      loader: 'html-loader'
-    }
-  },
-  {
-    test: /\.json$/,
-    use: {
-      loader: 'json-loader'
-    }
-  },
-  {
-    test: /\.(css|scss)$/,
-    exclude: /\.local\.s?css$/, // Exclude local styles from global
-    use: [
-      {
-        loader: 'style-loader'
-      },
-      {
-        loader: 'css-loader'
-      },
-      {
-        loader: 'sass-loader',
-        options: {
-          includePaths: [modulesPath]
-        }
-      }
-    ]
-  },
-  {
-    test: /\.(css|scss)$/,
-    include: /\.local\.s?css$/, // Local styles
-    use: [
-      {
-        loader: 'style-loader'
-      },
-      {
-        loader: 'css-loader',
-        options: {
-          modules: true,
-          importLoaders: 1
-        }
-      },
-      {
-        loader: 'sass-loader',
-        options: {
-          includePaths: [modulesPath]
-        }
-      }
-    ]
-  },
-  {
-    test: /\.(js)$/,
-    enforce: 'pre',
-    exclude: /node_modules\/(?!pubsweet-)/,
-    use: {
-      loader: 'string-replace-loader',
-      options: {
-        search: 'PUBSWEET_COMPONENTS',
-        replace: '[' + components.frontend
-          .map(component => `require('${component}')`)
-          .join(', ') + ']' // TODO: move the brackets into pubsweet-client
-      }
-    }
-  }
-]
diff --git a/webpack/components.js b/webpack/components.js
deleted file mode 100644
index fbe479b4f4..0000000000
--- a/webpack/components.js
+++ /dev/null
@@ -1,8 +0,0 @@
-const config = require('../config/shared.js')
-
-const components = config.pubsweet.components
-
-module.exports = {
-  frontend: components.filter(component => require(component).frontend),
-  backend: components.filter(component => require(component).backend)
-}
-- 
GitLab