diff --git a/frontend/models/Collection.js b/frontend/models/Collection.js index 70036fba..66c207ca 100644 --- a/frontend/models/Collection.js +++ b/frontend/models/Collection.js @@ -26,7 +26,9 @@ class Collection { /* Computed */ @computed get entryUrl(): string { - return this.type === 'atlas' ? this.documents[0].url : this.url; + return this.type === 'atlas' && this.documents.length > 0 + ? this.documents[0].url + : this.url; } /* Actions */ diff --git a/frontend/scenes/Collection/Collection.js b/frontend/scenes/Collection/Collection.js index 7a47bbdb..57184d6a 100644 --- a/frontend/scenes/Collection/Collection.js +++ b/frontend/scenes/Collection/Collection.js @@ -1,14 +1,20 @@ // @flow import React from 'react'; +import { observable } from 'mobx'; import { observer, inject } from 'mobx-react'; import { Redirect } from 'react-router'; +import { Link } from 'react-router-dom'; import _ from 'lodash'; +import styled from 'styled-components'; +import { newDocumentUrl } from 'utils/routeHelpers'; import CollectionsStore from 'stores/CollectionsStore'; -import CollectionStore from './CollectionStore'; import CenteredContent from 'components/CenteredContent'; import LoadingListPlaceholder from 'components/LoadingListPlaceholder'; +import Button from 'components/Button'; +import Flex from 'components/Flex'; +import HelpText from 'components/HelpText'; type Props = { collections: CollectionsStore, @@ -17,24 +23,66 @@ type Props = { @observer class Collection extends React.Component { props: Props; - store: CollectionStore; - - constructor(props) { - super(props); - this.store = new CollectionStore(); - } + collection: ?Collection; + @observable isFetching = true; + @observable redirectUrl; componentDidMount = () => { - const { id } = this.props.match.params; - this.store.fetchCollection(id); + this.fetchDocument(); }; + fetchDocument = async () => { + const { collections } = this.props; + const { id } = this.props.match.params; + + // $FlowIssue not the correct type? + this.collection = await collections.getById(id); + + if (!this.collection) this.redirectUrl = '/404'; + + if (this.collection && this.collection.documents.length > 0) { + this.redirectUrl = this.collection.documents[0].url; + } + this.isFetching = false; + }; + + renderNewDocument() { + return ( + +

Create a document

+ + Publish your first document to start building your collection. + + + + + + +
+ ); + } + render() { - return this.store.redirectUrl - ? - : - - ; + return ( + + {this.redirectUrl && } + {this.isFetching + ? + : this.renderNewDocument()} + + ); } } + +const NewDocumentContainer = styled(Flex)` + padding-top: 70px; +`; + +const Action = styled(Flex)` + margin: 20px 0; +`; + export default inject('collections')(Collection); diff --git a/frontend/scenes/Collection/CollectionStore.js b/frontend/scenes/Collection/CollectionStore.js deleted file mode 100644 index e10870e7..00000000 --- a/frontend/scenes/Collection/CollectionStore.js +++ /dev/null @@ -1,31 +0,0 @@ -// @flow -import { observable, action } from 'mobx'; -import invariant from 'invariant'; -import { client } from 'utils/ApiClient'; -import { notFoundUrl } from 'utils/routeHelpers'; - -class CollectionStore { - @observable redirectUrl: ?string; - @observable isFetching = true; - - /* Actions */ - - @action fetchCollection = async (id: string) => { - this.isFetching = true; - - try { - const res = await client.get('/collections.info', { id }); - invariant(res && res.data, 'Data should be available'); - const { data } = res; - - if (data.type === 'atlas') this.redirectUrl = data.documents[0].url; - else throw new Error('TODO code up non-atlas collections'); - } catch (e) { - console.log(e); - this.redirectUrl = notFoundUrl(); - } - this.isFetching = false; - }; -} - -export default CollectionStore; diff --git a/frontend/stores/CollectionsStore.js b/frontend/stores/CollectionsStore.js index 4914dccc..a0c09491 100644 --- a/frontend/stores/CollectionsStore.js +++ b/frontend/stores/CollectionsStore.js @@ -49,8 +49,25 @@ class CollectionsStore { } }; - getById = (id: string): ?Collection => { - return _.find(this.data, { id }); + @action getById = async (id: string): Promise => { + let collection = _.find(this.data, { id }); + if (!collection) { + try { + const res = await this.client.post('/collections.info', { + id, + }); + invariant(res && res.data, 'Collection not available'); + const { data } = res; + runInAction('CollectionsStore#getById', () => { + collection = new Collection(data); + this.add(collection); + }); + } catch (e) { + // No-op + } + } + + return collection; }; @action add = (collection: Collection): void => { diff --git a/frontend/utils/routeHelpers.js b/frontend/utils/routeHelpers.js index 8c43c22d..6569726d 100644 --- a/frontend/utils/routeHelpers.js +++ b/frontend/utils/routeHelpers.js @@ -1,5 +1,6 @@ // @flow import Document from 'models/Document'; +import Collection from 'models/Collection'; export function homeUrl(): string { return '/dashboard'; @@ -21,6 +22,10 @@ export function documentEditUrl(doc: Document): string { return `${doc.url}/edit`; } +export function newDocumentUrl(collection: Collection): string { + return `${collection.url}/new`; +} + export function searchUrl(query?: string): string { if (query) return `/search/${query}`; return `/search`; diff --git a/server/models/Collection.js b/server/models/Collection.js index 762b3542..bf1713fc 100644 --- a/server/models/Collection.js +++ b/server/models/Collection.js @@ -39,19 +39,29 @@ const Collection = sequelize.define( collection.urlId = collection.urlId || randomstring.generate(10); }, afterCreate: async collection => { + const team = await collection.getTeam(); + const collections = await team.getCollections(); + + // Don't auto-create for journal types, yet if (collection.type !== 'atlas') return; - const document = await Document.create({ - parentDocumentId: null, - atlasId: collection.id, - teamId: collection.teamId, - userId: collection.creatorId, - lastModifiedById: collection.creatorId, - createdById: collection.creatorId, - title: 'Introduction', - text: '# Introduction\n\nLets get started...', - }); - collection.documentStructure = [document.toJSON()]; + if (collections.length === 0) { + // Create intro document if first collection for team + const document = await Document.create({ + parentDocumentId: null, + atlasId: collection.id, + teamId: collection.teamId, + userId: collection.creatorId, + lastModifiedById: collection.creatorId, + createdById: collection.creatorId, + title: 'Introduction', + text: '# Introduction\n\nLets get started...', + }); + collection.documentStructure = [document.toJSON()]; + } else { + // Let user create first document + collection.documentStructure = []; + } await collection.save(); }, }, @@ -65,6 +75,9 @@ Collection.associate = models => { as: 'documents', foreignKey: 'atlasId', }); + Collection.belongsTo(models.Team, { + as: 'team', + }); Collection.addScope('withRecentDocuments', { include: [ { diff --git a/server/models/Team.js b/server/models/Team.js index 5841c0da..bd486117 100644 --- a/server/models/Team.js +++ b/server/models/Team.js @@ -1,3 +1,4 @@ +// @flow import { DataTypes, sequelize } from '../sequelize'; import Collection from './Collection'; @@ -24,7 +25,7 @@ const Team = sequelize.define( ); Team.associate = models => { - Team.hasMany(models.Collection, { as: 'atlases' }); + Team.hasMany(models.Collection, { as: 'collections' }); Team.hasMany(models.Document, { as: 'documents' }); Team.hasMany(models.User, { as: 'users' }); };