From e3f59ca3b59acccc50532a1f8262c4cc63e18df2 Mon Sep 17 00:00:00 2001 From: john <johnbarlas39@gmail.com> Date: Fri, 14 Apr 2017 20:51:29 +0300 Subject: [PATCH] refactor booklist into dashboard and give delete book action its own modal --- app/components/BookBuilder/BookList.jsx | 106 ---------------- .../{BookBuilder => Dashboard}/AddBook.jsx | 3 + app/components/Dashboard/Book.jsx | 115 ++++++++++++++++++ app/components/Dashboard/BookList.jsx | 44 +++++++ app/components/Dashboard/Dashboard.jsx | 97 +++++++++++++++ app/components/Dashboard/DashboardHeader.jsx | 31 +++++ app/components/Dashboard/RemoveBookModal.jsx | 46 +++++++ .../dashboard.local.scss} | 0 app/components/common/AbstractModal.jsx | 14 ++- app/routes.jsx | 4 +- 10 files changed, 351 insertions(+), 109 deletions(-) delete mode 100644 app/components/BookBuilder/BookList.jsx rename app/components/{BookBuilder => Dashboard}/AddBook.jsx (97%) create mode 100644 app/components/Dashboard/Book.jsx create mode 100644 app/components/Dashboard/BookList.jsx create mode 100644 app/components/Dashboard/Dashboard.jsx create mode 100644 app/components/Dashboard/DashboardHeader.jsx create mode 100644 app/components/Dashboard/RemoveBookModal.jsx rename app/components/{BookBuilder/styles/bookList.local.scss => Dashboard/dashboard.local.scss} (100%) diff --git a/app/components/BookBuilder/BookList.jsx b/app/components/BookBuilder/BookList.jsx deleted file mode 100644 index 2ea56f1..0000000 --- a/app/components/BookBuilder/BookList.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import Actions from 'pubsweet-client/src/actions' -import React from 'react' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import { Link } from 'react-router' - -import AddBook from './AddBook' -import styles from './styles/bookList.local.scss' - -export class BookList extends React.Component { - constructor (props) { - super(props) - - this.toggleModal = this.toggleModal.bind(this) - - this.state = { - showModal: false - } - } - - toggleModal () { - this.setState({ showModal: !this.state.showModal }) - } - - createCollection = newTitle => { - const { createCollection } = this.props.actions - const collection = { title: newTitle || 'Untitled' } - - createCollection(collection) - this.toggleModal() - } - - _removeCollection = (collection) => { - if (confirm('Remove this book?')) { - this.props.actions.deleteCollection(collection) - } - } - - _sortNewestFirst = (a, b) => { - return (b.created < a.created) ? -1 : ((b.created > a.created) ? 1 : 0) - } - - render () { - const { books } = this.props - const { showModal } = this.state - - return ( - <div className={styles.bookList + ' bootstrap pubsweet-component pubsweet-component-scroll'}> - <div className='container col-lg-offset-2 col-lg-8'> - - <div className='col-lg-12'> - <h1 className={styles.bookTitle}> - Books - <div className={styles.addBookBtn} onClick={this.toggleModal}> - <a>add book</a> - </div> - </h1> - </div> - - <div className='col-lg-12'> - {books ? books.sort(this._sortNewestFirst).map(book => ( - <div key={book.id} className={styles.bookContainer}> - <h2>{book.title}</h2> - - <div className={styles.bookActions}> - <Link to={`/books/${book.id}/book-builder`} className={styles.editBook}>Edit</Link> - - <a href='#' onClick={() => this._removeCollection(book)} className={styles.editBook}>Remove</a> - </div> - </div> - )) : 'Fetching…' } - </div> - </div> - - <AddBook - container={this} - create={this.createCollection} - show={showModal} - toggle={this.toggleModal} - /> - </div> - ) - } -} - -BookList.propTypes = { - books: React.PropTypes.arrayOf(React.PropTypes.object), - actions: React.PropTypes.object.isRequired -} - -function mapStateToProps (state, { params }) { - return { - books: state.collections - } -} - -function mapDispatchToProps (dispatch) { - return { - actions: bindActionCreators(Actions, dispatch) - } -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BookList) diff --git a/app/components/BookBuilder/AddBook.jsx b/app/components/Dashboard/AddBook.jsx similarity index 97% rename from app/components/BookBuilder/AddBook.jsx rename to app/components/Dashboard/AddBook.jsx index a0746d0..6450107 100644 --- a/app/components/BookBuilder/AddBook.jsx +++ b/app/components/Dashboard/AddBook.jsx @@ -20,7 +20,10 @@ export default class AddBook extends React.Component { handleKeyOnInput (event) { if (event.charCode !== 13) return + + const { toggle } = this.props this.onCreate() + toggle() } onCreate () { diff --git a/app/components/Dashboard/Book.jsx b/app/components/Dashboard/Book.jsx new file mode 100644 index 0000000..2e0b91e --- /dev/null +++ b/app/components/Dashboard/Book.jsx @@ -0,0 +1,115 @@ +import React from 'react' +import { Link } from 'react-router' + +import RemoveBookModal from './RemoveBookModal' +import styles from './dashboard.local.scss' + +class Book extends React.Component { + constructor (props) { + super(props) + + this.removeBook = this.removeBook.bind(this) + this.toggleModal = this.toggleModal.bind(this) + + this.state = { + showModal: false + } + } + + toggleModal () { + this.setState({ + showModal: !this.state.showModal + }) + } + + removeBook () { + const { book, remove } = this.props + remove(book) + } + + renderTitle () { + const { book } = this.props + + return ( + <h2> { book.title } </h2> + ) + } + + renderEdit () { + const { book } = this.props + + return ( + <Link + className={styles.editBook} + to={`/books/${book.id}/book-builder`} + > + Edit + </Link> + ) + } + + renderRemove () { + return ( + <a + className={styles.editBook} + href='#' + onClick={this.toggleModal} + > + Remove + </a> + ) + } + + renderButtons () { + const { book } = this.props + const edit = this.renderEdit(book) + const remove = this.renderRemove(book) + + return ( + <div className={styles.bookActions}> + { edit } + { remove } + </div> + ) + } + + renderRemoveModal () { + const { book, container } = this.props + const { showModal } = this.state + if (!showModal) return null + + return ( + <RemoveBookModal + book={book} + container={container} + remove={this.removeBook} + show={showModal} + toggle={this.toggleModal} + /> + ) + } + + render () { + const { book } = this.props + + const title = this.renderTitle(book) + const buttons = this.renderButtons(book) + const removeModal = this.renderRemoveModal() + + return ( + <div className={styles.bookContainer}> + { title } + { buttons } + { removeModal } + </div> + ) + } +} + +Book.propTypes = { + book: React.PropTypes.object.isRequired, + container: React.PropTypes.object.isRequired, + remove: React.PropTypes.func.isRequired +} + +export default Book diff --git a/app/components/Dashboard/BookList.jsx b/app/components/Dashboard/BookList.jsx new file mode 100644 index 0000000..7bacc75 --- /dev/null +++ b/app/components/Dashboard/BookList.jsx @@ -0,0 +1,44 @@ +import { map, reverse, sortBy } from 'lodash' +import React from 'react' + +import Book from './Book' + +class BookList extends React.Component { + renderBookList () { + const { books, container, remove } = this.props + if (!books) return 'Fetching...' + + const items = reverse(sortBy(books, 'created')) + + const bookComponents = map(items, book => { + return ( + <Book + book={book} + container={container} + key={book.id} + remove={remove} + /> + ) + }) + + return bookComponents + } + + render () { + const bookList = this.renderBookList() + + return ( + <div className='col-lg-12'> + { bookList } + </div> + ) + } +} + +BookList.propTypes = { + books: React.PropTypes.array.isRequired, + container: React.PropTypes.object.isRequired, + remove: React.PropTypes.func.isRequired +} + +export default BookList diff --git a/app/components/Dashboard/Dashboard.jsx b/app/components/Dashboard/Dashboard.jsx new file mode 100644 index 0000000..0d1a1c4 --- /dev/null +++ b/app/components/Dashboard/Dashboard.jsx @@ -0,0 +1,97 @@ +import Actions from 'pubsweet-client/src/actions' +import React from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +// import { Link } from 'react-router' + +import AddBook from './AddBook' +import BookList from './BookList' +import DashboardHeader from './DashboardHeader' +import styles from './dashboard.local.scss' + +export class Dashboard extends React.Component { + constructor (props) { + super(props) + + this.createCollection = this.createCollection.bind(this) + this.removeCollection = this.removeCollection.bind(this) + this.toggleModal = this.toggleModal.bind(this) + + this.state = { + showModal: false + } + } + + toggleModal () { + this.setState({ + showModal: !this.state.showModal + }) + } + + createCollection (newTitle) { + const { createCollection } = this.props.actions + + const collection = { + title: newTitle || 'Untitled' + } + + createCollection(collection) + } + + removeCollection (collection) { + const { deleteCollection } = this.props.actions + deleteCollection(collection) + } + + render () { + const { books } = this.props + const { showModal } = this.state + + const className = styles.bookList + + ' bootstrap pubsweet-component pubsweet-component-scroll' + + return ( + <div className={className}> + <div className='container col-lg-offset-2 col-lg-8'> + + <DashboardHeader toggle={this.toggleModal} /> + + <BookList + books={books} + container={this} + remove={this.removeCollection} + /> + </div> + + <AddBook + container={this} + create={this.createCollection} + show={showModal} + toggle={this.toggleModal} + /> + </div> + ) + } +} + +Dashboard.propTypes = { + books: React.PropTypes.arrayOf(React.PropTypes.object), + actions: React.PropTypes.object.isRequired +} + +function mapStateToProps (state, { params }) { + return { + books: state.collections + } +} + +function mapDispatchToProps (dispatch) { + return { + actions: bindActionCreators(Actions, dispatch) + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Dashboard) diff --git a/app/components/Dashboard/DashboardHeader.jsx b/app/components/Dashboard/DashboardHeader.jsx new file mode 100644 index 0000000..0ae63e3 --- /dev/null +++ b/app/components/Dashboard/DashboardHeader.jsx @@ -0,0 +1,31 @@ +import React from 'react' + +import styles from './dashboard.local.scss' + +class DashboardHeader extends React.Component { + render () { + const { toggle } = this.props + + return ( + <div className='col-lg-12'> + <h1 className={styles.bookTitle}> + Books + + <div + className={styles.addBookBtn} + onClick={toggle} + > + <a>add book</a> + </div> + + </h1> + </div> + ) + } +} + +DashboardHeader.propTypes = { + toggle: React.PropTypes.func.isRequired +} + +export default DashboardHeader diff --git a/app/components/Dashboard/RemoveBookModal.jsx b/app/components/Dashboard/RemoveBookModal.jsx new file mode 100644 index 0000000..a1f1e1e --- /dev/null +++ b/app/components/Dashboard/RemoveBookModal.jsx @@ -0,0 +1,46 @@ +import React from 'react' + +import AbstractModal from '../common/AbstractModal' + +class RemoveBookModal extends React.Component { + renderBody () { + const { book } = this.props + + return ( + <span> + Are you sure you want to permanently delete { book.title }? + </span> + ) + } + + render () { + const { container, remove, show, toggle } = this.props + + const title = 'Delete Book' + const successText = 'Delete' + + const body = this.renderBody() + + return ( + <AbstractModal + body={body} + container={container} + show={show} + successAction={remove} + successText={successText} + title={title} + toggle={toggle} + /> + ) + } +} + +RemoveBookModal.propTypes = { + book: React.PropTypes.object.isRequired, + container: React.PropTypes.object.isRequired, + remove: React.PropTypes.func.isRequired, + show: React.PropTypes.bool.isRequired, + toggle: React.PropTypes.func.isRequired +} + +export default RemoveBookModal diff --git a/app/components/BookBuilder/styles/bookList.local.scss b/app/components/Dashboard/dashboard.local.scss similarity index 100% rename from app/components/BookBuilder/styles/bookList.local.scss rename to app/components/Dashboard/dashboard.local.scss diff --git a/app/components/common/AbstractModal.jsx b/app/components/common/AbstractModal.jsx index 491e07c..2bf9cc2 100644 --- a/app/components/common/AbstractModal.jsx +++ b/app/components/common/AbstractModal.jsx @@ -2,6 +2,18 @@ import React from 'react' import { Modal } from 'react-bootstrap' export class BookBuilderModal extends React.Component { + constructor (props) { + super(props) + this.performAction = this.performAction.bind(this) + } + + performAction () { + const { successAction, toggle } = this.props + + successAction() + toggle() + } + renderHeader () { const { title } = this.props @@ -28,7 +40,7 @@ export class BookBuilderModal extends React.Component { const { successAction, successText, toggle } = this.props const success = successAction ? <a className='modal-button bb-modal-act' - onClick={successAction}> + onClick={this.performAction}> { successText } </a> : null diff --git a/app/routes.jsx b/app/routes.jsx index 6acdba9..48cecfd 100644 --- a/app/routes.jsx +++ b/app/routes.jsx @@ -11,7 +11,7 @@ import Blog from 'pubsweet-component-blog/Blog' // Editoria import BookBuilder from './components/BookBuilder/BookBuilder' -import BookList from './components/BookBuilder/BookList' +import Dashboard from './components/Dashboard/Dashboard' import SimpleEditorWrapper from './components/SimpleEditor/SimpleEditorWrapper' // Authentication @@ -38,7 +38,7 @@ export default ( <Redirect from='/manage/posts' to='books' /> <Route path='/' component={AuthenticatedManage}> - <Route path='books' component={BookList} /> + <Route path='books' component={Dashboard} /> <Route path='blog' component={Blog} /> <Route path='books/:id/book-builder' component={BookBuilder} /> <Route path='books/:bookId/fragments/:fragmentId' component={SimpleEditorWrapper} /> -- GitLab