@@ -63,7 +60,7 @@ class RelatedContent extends React.Component {
var publish_date = item.publish_date
var guid = item.guid
return (
-
+
{title}
@@ -76,8 +73,7 @@ class RelatedContent extends React.Component {
)
} else {
- console.log('UNSUPPORTED TYPE: ' + type)
- return
+ return
}
})}
diff --git a/shared/Contributors/Forms/AddContributor.js b/shared/Contributors/Forms/AddContributor.js
new file mode 100644
index 00000000..922b51d5
--- /dev/null
+++ b/shared/Contributors/Forms/AddContributor.js
@@ -0,0 +1,148 @@
+import React from 'react'
+import moment from 'moment'
+import { isEmpty } from 'lodash'
+import { Field, reduxForm } from 'redux-form'
+import FormController from '../../Forms/Components/FormController/FormController'
+import {
+ renderField,
+ renderSelect,
+ renderZip
+} from '../../Forms/Components/Field'
+import RichTextarea from '../../Forms/Components/RichTextarea'
+import ImageUploadField from '../../Forms/Components/ImageUploadField'
+
+const env = process.env.NODE_ENV === 'dev' ? 'dev' : 'prod'
+const config = require('../../../config/config.json')
+const c = config[env]
+
+export const FORM_KEY = 'addContributor'
+
+const validate = values => {
+ let errors = {}
+
+ if (isEmpty(values.prettyname)) {
+ errors.prettyname = 'Pretty Name field is required'
+ }
+
+ if (isEmpty(values.img)) {
+ errors.img = 'Image field is required'
+ }
+
+ if (isEmpty(values.author)) {
+ errors.author = 'Author field is required'
+ }
+
+ if (isEmpty(values.twitter)) {
+ errors.twitter = 'Author field is required'
+ }
+
+ if (isEmpty(values.linkedin)) {
+ errors.linkedin = 'Linkedin field is required'
+ }
+
+ if (isEmpty(values.bio)) {
+ errors.bio = 'Bio field is required'
+ }
+
+ if (isEmpty(values.sort_rank)) {
+ errors.sort_rank = 'Sort rank field is required'
+ } else if (values.sort_rank < 0) {
+ errors.sort_rank = 'Number should be positive'
+ }
+
+ return errors
+}
+
+const AddContributorForm = ({
+ children,
+ handleSubmit,
+ pristine,
+ reset,
+ submitting,
+ allowSubmit,
+ activeStep,
+ errorMessage,
+ ready,
+ recording,
+ stop,
+ review,
+ submit,
+ complete,
+ submittedUrl,
+ error,
+ showSubmit,
+ customError,
+ customSuccess
+}) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)
+
+export default reduxForm({
+ form: FORM_KEY,
+ validate
+})(AddContributorForm)
diff --git a/shared/Forms/Components/Field/Select.js b/shared/Forms/Components/Field/Select.js
index a32be327..1316e4a6 100644
--- a/shared/Forms/Components/Field/Select.js
+++ b/shared/Forms/Components/Field/Select.js
@@ -11,7 +11,8 @@ export const renderSelect = ({
fieldWrapperClasses = '',
labelWrapperClasses = '',
inputWrapperStyles = '',
- options = []
+ options = [],
+ blankOption = false
}) => {
return (
@@ -25,7 +26,7 @@ export const renderSelect = ({
touched && invalid ? 'has-danger' : ''
}`}
>
-
+ {blankOption &&
}
{options.map(op => (
{op.label}
diff --git a/shared/Forms/Components/FilePreview.js b/shared/Forms/Components/FilePreview.js
index 57d49d76..b195c83e 100644
--- a/shared/Forms/Components/FilePreview.js
+++ b/shared/Forms/Components/FilePreview.js
@@ -34,7 +34,11 @@ const Container = styled.div`
height: 100%;
max-width: 100%;
text-align: center;
+<<<<<<< HEAD
+ position: relative
+=======
position: relative;
+>>>>>>> dev
`
const Preview = styled.img`
diff --git a/shared/Forms/Components/RichTextarea.js b/shared/Forms/Components/RichTextarea.js
index e0e80c36..d9d4585f 100644
--- a/shared/Forms/Components/RichTextarea.js
+++ b/shared/Forms/Components/RichTextarea.js
@@ -20,15 +20,27 @@ const DEFAULT_TOOLBAR = {
}
export default class RichTextarea extends Component {
+ defaultProps = {
+ markdown: false
+ }
+
state = {
editorState: EditorState.createEmpty(),
value: this.props.input.value
}
+ encode = rawObject => {
+ return draftToHtml(rawObject)
+ }
+
+ decode = val => {
+ return htmlToDraft(val)
+ }
+
onChange = editorState => {
this.setState({ editorState })
- const value = draftToHtml(convertToRaw(editorState.getCurrentContent()))
+ const value = this.encode(convertToRaw(editorState.getCurrentContent()))
this.setState({
value
@@ -45,22 +57,19 @@ export default class RichTextarea extends Component {
const { input: { value } } = this.props
const editorState = EditorState.createWithContent(
- ContentState.createFromBlockArray(htmlToDraft(value))
+ ContentState.createFromBlockArray(this.decode(value))
)
- this.setState({ value, editorState }, this.updateVal)
+ this.setState({ value, editorState })
}
componentWillReceiveProps(nextProps) {
if (this.props.input.value !== nextProps.input.value) {
const editorState = EditorState.createWithContent(
- ContentState.createFromBlockArray(htmlToDraft(nextProps.input.value))
+ ContentState.createFromBlockArray(this.decode(nextProps.input.value))
)
- this.setState(
- { value: nextProps.input.value, editorState },
- this.updateVal
- )
+ this.setState({ value: nextProps.input.value, editorState })
}
}
@@ -76,6 +85,7 @@ export default class RichTextarea extends Component {
editorClassName="richEditor"
onEditorStateChange={this.onChange}
onBlur={this.onBlur}
+ suppressContentEditableWarning={true}
toolbar={toolbar}
minHeight={minHeight}
/>
diff --git a/shared/Player/Containers/PlayerContainer.js b/shared/Player/Containers/PlayerContainer.js
index c06403e3..e41d843d 100644
--- a/shared/Player/Containers/PlayerContainer.js
+++ b/shared/Player/Containers/PlayerContainer.js
@@ -76,7 +76,6 @@ class PlayerContainer extends Component {
* Return howler pointer
*/
getHowler() {
- console.log("gethowler")
return this.player.howler
}
@@ -87,7 +86,6 @@ class PlayerContainer extends Component {
if (this.player == null) {
return 1
}
- console.log(this.player)
try {
return this.player.duration()
} catch (e) {
@@ -299,8 +297,6 @@ class PlayerContainer extends Component {
let hr = 0
const arr = duration.split(':')
- console.log('arr')
- console.log(arr)
if (arr.length === 2) {
min = +arr[0]
sec = +arr[1]
@@ -312,7 +308,6 @@ class PlayerContainer extends Component {
const d = min * 60 + sec
const p = 1.0 * d * position / 100
- console.log(d, p, hr, min, sec)
return this.formatPosition(p)
}
@@ -398,9 +393,6 @@ class PlayerContainer extends Component {
const episode = oepisode.toJS()
let { title, pubDate } = episode
- console.log('=================================================')
- console.log(episode)
- console.log(episode.related)
const mp3s = episode.related && episode.related.filter(r => r.type === 'mp3')
var mp3 = ""
var duration = 1
@@ -417,8 +409,6 @@ class PlayerContainer extends Component {
pubDate = moment(pubDate).format('MMMM D, YYYY')
}
if (mp3 == undefined) {
- console.log("ERROR")
- console.log(episode)
return (
)
diff --git a/shared/components/admin/AdminCmsAddRelated.js b/shared/components/admin/AdminCmsAddRelated.js
deleted file mode 100644
index a48b576e..00000000
--- a/shared/components/admin/AdminCmsAddRelated.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import React, { Component, PropTypes } from 'react'
-import { connect } from 'react-redux'
-import AdminLayout from './AdminLayout'
-import RelatedContent from './RelatedContent'
-
-class AdminCmsAddRelated extends Component {
- render() {
- const { history } = this.props
-
- return (
-
- Related Content
-
-
-
- )
- }
-}
-export default connect(state => ({}))(AdminCmsAddRelated)
diff --git a/shared/components/admin/AdminCmsBlogUpdate.js b/shared/components/admin/AdminCmsBlogUpdate.js
new file mode 100644
index 00000000..1a82f3c2
--- /dev/null
+++ b/shared/components/admin/AdminCmsBlogUpdate.js
@@ -0,0 +1,24 @@
+import React, { Component, PropTypes } from 'react'
+import { connect } from 'react-redux'
+import AdminLayout from './AdminLayout'
+import CmsBlogUpdate from "./blog/CmsBlogUpdate";
+
+class AdminCmsBlogUpdate extends Component {
+ render() {
+ const { history, location: { pathname } } = this.props
+ const prettyname = pathname.replace('/admin/cms/blog', '')
+
+ return (
+
+ Update Blog Post
+
+
+
+ )
+ }
+}
+export default connect(state => ({
+ admin: state.admin
+}))(AdminCmsBlogUpdate)
diff --git a/shared/components/admin/AdminCmsContributorAdd.js b/shared/components/admin/AdminCmsContributorAdd.js
new file mode 100644
index 00000000..68d4a878
--- /dev/null
+++ b/shared/components/admin/AdminCmsContributorAdd.js
@@ -0,0 +1,22 @@
+import React, { Component, PropTypes } from 'react'
+import { connect } from 'react-redux'
+import AdminLayout from './AdminLayout'
+import ContributorAddFormController from './contributors/ContributorAddFormController'
+
+class AdminCmsContributorAdd extends Component {
+ render() {
+ const { history } = this.props
+
+ return (
+
+ Add Contributor
+
+
+
+ )
+ }
+}
+
+export default connect(state => ({
+ admin: state.admin
+}))(AdminCmsContributorAdd)
diff --git a/shared/components/admin/AdminCmsRecentRelated.js b/shared/components/admin/AdminCmsRecentRelated.js
deleted file mode 100644
index 972f91b8..00000000
--- a/shared/components/admin/AdminCmsRecentRelated.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import React, { Component, PropTypes } from 'react'
-import { connect } from 'react-redux'
-import AdminLayout from './AdminLayout'
-import RelatedContentList from './RelatedContentList'
-
-class AdminCmsRecentRelated extends Component {
- render() {
- const { history } = this.props
- const oadmin = this.props.admin.toJS()
- const relatedcontent = oadmin['relatedcontent'] || []
-
- return (
- relatedcontent && (
-
- Recent Related
-
-
-
- )
- )
- }
-}
-export default connect(state => ({
- admin: state.admin
-}))(AdminCmsRecentRelated)
diff --git a/shared/components/admin/AdminMenu.js b/shared/components/admin/AdminMenu.js
index 4844e496..66f37724 100644
--- a/shared/components/admin/AdminMenu.js
+++ b/shared/components/admin/AdminMenu.js
@@ -19,15 +19,10 @@ class AdminMenu extends Component {
CMS recent
- Homepage
-
-
- Add Related Content
+ Add Contributor
-
- Recent Related Content
-
+ Homepage
diff --git a/shared/components/admin/AdminOld.js b/shared/components/admin/AdminOld.js
index b643f52e..a71aba3c 100644
--- a/shared/components/admin/AdminOld.js
+++ b/shared/components/admin/AdminOld.js
@@ -76,10 +76,7 @@ class Admin extends Component {
-
-
-
-
+
diff --git a/shared/components/admin/BlogList.js b/shared/components/admin/BlogList.js
new file mode 100644
index 00000000..0dd2c20c
--- /dev/null
+++ b/shared/components/admin/BlogList.js
@@ -0,0 +1,142 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import styled from 'styled-components'
+import moment from 'moment'
+
+import BlogListItem from '../../Blog/Components/BlogListItem'
+import RelatedContent from '../../Blog/Components/RelatedContent'
+import { Link } from 'react-router'
+
+const fakeClick = e => {
+ e.preventDefault()
+ e.stopPropagation()
+}
+
+const normalizeDate = d => moment(d).format('YYYY-MM-DD')
+
+class BlogList extends Component {
+ getAuthor = (author = '') => this.props.contributors.get(author.toLowerCase().trim()).toJS()
+
+ renderPreview() {
+ const { blog } = this.props
+ const contributor = this.getAuthor(blog.author) || {}
+
+ return (
+
+
+
+
+ )
+ }
+
+ render() {
+ const { odd, blog } = this.props
+ const { blog_id, prettyname } = blog
+
+ blog.publish_date = normalizeDate(blog.publish_date)
+
+ return (
+
+
+
+
+ ID: {blog_id}
+
+
+ Edit
+
+
+
+ Prettyname:
+
+
+
+
+
+ {this.renderPreview()}
+
+
+ )
+ }
+}
+
+export default connect(state => ({
+ contributors: state.site.get('contributors')
+}))(BlogList)
+
+const Wrapper = styled.div`
+ background: #f9faf9;
+ margin-bottom: 12px;
+ border: 1px solid #e1e3e2;
+ display: flex;
+ flex-direction: column;
+
+ ${props =>
+ props.open &&
+ `
+ background: rgba(240, 217, 67, 0.1);
+ `};
+`
+
+const Inner = styled.div`
+ padding: 20px 30px;
+`
+
+const Row = styled.div`
+ display: flex;
+ align-items: center;
+`
+
+const Label = styled.div`
+ display: flex;
+ align-items: center;
+ margin-right: 10px;
+`
+
+const Value = styled.div`
+ flex: 1;
+
+ > input {
+ width: 100%;
+ }
+`
+
+const Preview = styled.div``
+
+const Heading = Row.extend`
+ font-size: 24px;
+ color: #2d1454;
+
+ height: 60px;
+`
+
+const ID = styled.span`
+ color: #000;
+`
+
+const EditButton = styled(Link)`
+ width: 160px;
+ font-size: 16px;
+ border: none;
+ border-radius: 5px;
+ margin-left: 10px;
+ background: #f0d943;
+ display: inline-block;
+ border: none;
+ line-height: 27px;
+ text-align: center;
+ color: #fff !important;
+
+ &:hover {
+ color: #fff;
+ border: none;
+ }
+`
diff --git a/shared/components/admin/BlogUpdater.js b/shared/components/admin/BlogUpdater.js
deleted file mode 100644
index 0c2d26dc..00000000
--- a/shared/components/admin/BlogUpdater.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import querystring from 'querystring'
-import { connect } from 'react-redux'
-
-class BlogUpdater extends React.Component {
- constructor(props) {
- super(props)
- this.state = props.blog
- }
-
- update(me, title, event) {
- console.log(event)
- console.log(event.target)
- console.log(event.target.value)
- var s = me.state
- s[title] = event.target.value
- me.setState(s)
- }
-
- save(blog_id, title, abstract, author, publish_date, dispatch) {
- console.log('save')
- dispatch({
- type: 'CMS_UPDATE_BLOG',
- payload: {
- blog_id,
- title,
- abstract,
- author,
- publish_date,
- dispatch
- }
- })
- }
-
- delete(blog_id, dispatch) {
- console.log('delete')
- dispatch({ type: 'CMS_DELETE_BLOG', payload: { blog_id, dispatch } })
- }
-
- componentDidMount() {}
-
- render() {
- var dispatch = this.props.dispatch
- var blog = this.state
- var blog_id = blog['blog_id']
- var title = blog['title']
- var abstract = blog['abstract']
- var author = blog['author']
- var publish_date = blog['publish_date']
- var prettyname = blog['prettyname']
- var me = this
- return (
-
-
-
-
Prettyname:
-
- https://dataskeptic.com/blog{prettyname}
-
-
-
-
-
-
Author:
-
-
-
-
Publish date:
-
-
-
-
-
-
-
-
- Save
-
-
-
-
- Delete
-
-
-
-
- )
- }
-}
-export default connect(state => ({}))(BlogUpdater)
diff --git a/shared/components/admin/CMS.js b/shared/components/admin/CMS.js
index 7ef387b2..377e313c 100644
--- a/shared/components/admin/CMS.js
+++ b/shared/components/admin/CMS.js
@@ -3,7 +3,7 @@ import React from 'react'
import ReactDOM from 'react-dom'
import querystring from 'querystring'
import { connect } from 'react-redux'
-import BlogUpdater from './BlogUpdater'
+import BlogList from './BlogList'
class CMS extends React.Component {
constructor(props) {
@@ -61,9 +61,9 @@ class CMS extends React.Component {
CMS {mode}
- {blogs.map(blog => {
- return
- })}
+ {blogs.map((blog, index) => (
+
+ ))}
)
diff --git a/shared/components/admin/HomepageController.js b/shared/components/admin/HomepageController.js
index effdd8cc..9e714b9d 100644
--- a/shared/components/admin/HomepageController.js
+++ b/shared/components/admin/HomepageController.js
@@ -2,7 +2,7 @@ import React from 'react'
import ReactDOM from 'react-dom'
import querystring from 'querystring'
import { connect } from 'react-redux'
-import BlogSearchSelect from './BlogSearchSelect'
+import BlogSearchSelect from './blog/BlogSearchSelect'
class HomepageController extends React.Component {
change = (f, val) => {
diff --git a/shared/components/admin/RelatedContent.js b/shared/components/admin/RelatedContent.js
deleted file mode 100644
index 16c34dc4..00000000
--- a/shared/components/admin/RelatedContent.js
+++ /dev/null
@@ -1,183 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import querystring from 'querystring'
-import { connect } from 'react-redux'
-import BlogSearchSelect from './BlogSearchSelect'
-
-class RelatedContent extends React.Component {
- changeBlog = (id, val) => {
- this.setState({
- [id]: val
- })
- }
-
- constructor(props) {
- super(props)
- this.state = {
- type: '',
- source: '',
- dest: '',
- title: '',
- body: '',
- selected: false
- }
- }
-
- update(me, title, event) {
- var s = me.state
- var val = event.target.value
- s[title] = val
- me.setState(s)
- if (title == 'type') {
- if (val == 'internal-link') {
- me.setState({
- lbl2: 'Related blog_id',
- lbl3: 'Link Anchor Text',
- lbl4: 'Comment',
- dest: '',
- selected: true
- })
- } else if (val == 'external-link') {
- me.setState({
- lbl2: 'Related page url',
- lbl3: 'Link Anchor Text',
- lbl4: 'Comment',
- dest: 'https://',
- selected: true
- })
- } else if (val == 'mp3') {
- me.setState({
- lbl2: 'Media URL',
- lbl3: 'Title of recording',
- lbl4: 'Description',
- dest: 'https://dataskeptic.com/blog/',
- selected: true
- })
- } else if (val == 'person') {
- me.setState({
- lbl2: 'IMG URL',
- lbl3: 'Guest Name',
- lbl4: 'Guest Bio',
- dest: '',
- selected: true
- })
- } else if (val == 'homepage-image') {
- me.setState({
- lbl2: 'Image url (400x400px)',
- lbl3: 'Alt text',
- lbl4: 'Leave Blank',
- dest: '',
- selected: true
- })
- } else if (val == 'blog-header-img') {
- me.setState({
- lbl2: 'Image url (800x150px)',
- lbl3: 'Alt text',
- lbl4: 'Leave Blank',
- dest: '',
- selected: true
- })
- } else {
- me.setState({ selected: false })
- }
- }
- }
-
- save(dispatch) {
- dispatch({
- type: 'RELATED_CONTENT_ADD',
- payload: { data: this.state, dispatch }
- })
- }
-
- render() {
- var oadmin = this.props.admin.toJS()
- var add_related_msg = oadmin['add_related_msg']
- var me = this
- var dispatch = this.props.dispatch
- var bdy =
- if (this.state.selected) {
- bdy = (
-
-
-
-
-
-
- {this.state.type === 'internal-link' ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
{add_related_msg}
-
- )
- }
- return (
-
-
Add Related Content
-
-
- -- [SELECT] --
- internal-link
- external-link
- homepage-image
- blog-header-img
- mp3
- person
-
-
- {bdy}
-
- )
- }
-}
-export default connect(state => ({
- admin: state.admin
-}))(RelatedContent)
diff --git a/shared/components/admin/RelatedContentList.js b/shared/components/admin/RelatedContentList.js
deleted file mode 100644
index f454d58b..00000000
--- a/shared/components/admin/RelatedContentList.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import querystring from 'querystring'
-import { connect } from 'react-redux'
-
-class RelatedContentList extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- related: []
- }
- }
-
- componentDidMount() {
- var dispatch = this.props.dispatch
- dispatch({ type: 'RELATED_CONTENT_LIST', payload: { dispatch } })
- }
-
- delete(content_id, dispatch) {
- dispatch({ type: 'RELATED_CONTENT_DELETE', payload: { content_id } })
- }
-
- render() {
- var me = this
- var related = this.props.items || []
- var dispatch = this.props.dispatch
- if (related.length == 0) {
- return (
-
-
Recent Related Content
-
No related content yet. Add some relationships!
-
- )
- }
- return (
-
-
Recent Related Content
- {related.map(rc => {
- var content_id = rc['content_id']
- var blog_id = rc['blog_id']
- var dest = rc['dest']
- var title = rc['title']
- var body = rc['body']
- return (
-
-
blog_id:
-
{blog_id}
-
title:
-
{title}
-
Destination:
-
{dest}
-
Comment:
-
{body}
-
-
-
- Delete
-
-
-
-
- )
- })}
-
- )
- }
-}
-export default connect(state => ({}))(RelatedContentList)
diff --git a/shared/components/admin/blog/BlogForm.js b/shared/components/admin/blog/BlogForm.js
new file mode 100644
index 00000000..e173f07a
--- /dev/null
+++ b/shared/components/admin/blog/BlogForm.js
@@ -0,0 +1,91 @@
+import React from 'react'
+import { Field, FieldArray, reduxForm } from 'redux-form'
+import FormController from '../../../Forms/Components/FormController/FormController'
+import {
+ renderField,
+ renderSelect,
+ renderZip
+} from '../../../Forms/Components/Field'
+import { renderRelated } from './BlogRelatedFields'
+import BlogSearchSelect from "./BlogSearchSelect";
+import ContributorSelect from "./ContributorSelect";
+
+export const FORM_KEY = 'blogUpdate'
+
+const validate = values => {
+ let errors = {}
+
+ return errors
+}
+
+const BlogForm = ({
+ children,
+ handleSubmit,
+ pristine,
+ reset,
+ submitting,
+ allowSubmit,
+ activeStep,
+ errorMessage,
+ ready,
+ recording,
+ stop,
+ review,
+ submit,
+ complete,
+ submittedUrl,
+ error,
+ showSubmit
+}) => (
+
+
+
+
+
+
+
+
+
+
+
+)
+
+export default reduxForm({
+ form: FORM_KEY,
+ validate
+})(BlogForm)
diff --git a/shared/components/admin/blog/BlogRelatedFields.js b/shared/components/admin/blog/BlogRelatedFields.js
new file mode 100644
index 00000000..220b35c4
--- /dev/null
+++ b/shared/components/admin/blog/BlogRelatedFields.js
@@ -0,0 +1,379 @@
+import React from 'react'
+import { Field, FieldArray, reduxForm } from 'redux-form'
+import {
+ renderCheckbox,
+ renderField,
+ renderSelect
+} from '../../../Forms/Components/Field'
+import styled from 'styled-components'
+import BlogSearchSelect from './BlogSearchSelect'
+import ImageUploadField from '../../../Forms/Components/ImageUploadField'
+
+const env = process.env.NODE_ENV === 'dev' ? 'dev' : 'prod'
+const config = require('../../../../config/config.json')
+const c = config[env]
+
+export const CAREER_CAROUSEL = 'career-carousel'
+export const INTERNAL_LINK = 'internal-link'
+export const EXTERNAL_LINK = 'external-link'
+export const HOME_PAGE_IMAGE = 'homepage-image'
+export const BLOG_HEADER_IMAGE = 'blog-header-img'
+export const MP3 = 'mp3'
+export const PERSON = 'person'
+export const BLANK = 'blank'
+
+const validate = values => {
+ let errors = {}
+
+ return errors
+}
+
+const EMPTY_RELATED_ITEM = {
+ created: true
+}
+
+const renderInternalLinkFields = (member, disabled) => (
+
+
+
+
+
+
+
+)
+
+const renderExternalLinkFields = (member, disabled) => (
+
+
+
+
+
+
+
+)
+
+const renderHomePageImageFields = (member, disabled) => (
+
+
+
+
+
+)
+
+const renderBlogHeaderImageFields = (member, disabled) => (
+
+
+
+
+
+)
+
+const renderMp3Fields = (member, disabled) => (
+
+
+
+
+
+
+
+)
+
+const renderPersonFields = (member, disabled) => (
+
+
+
+
+
+
+
+)
+
+const renderBlankFields = (member, disabled) =>
+
+const renderCareerCarouselFields = (member, disabled) => (
+
+
+
+)
+
+const renderRelatedFields = ({ type, member, disabled }) => {
+ let fieldsRenderer
+
+ switch (type) {
+ case CAREER_CAROUSEL:
+ fieldsRenderer = renderCareerCarouselFields
+ break
+
+ case INTERNAL_LINK:
+ fieldsRenderer = renderInternalLinkFields
+ break
+
+ case EXTERNAL_LINK:
+ fieldsRenderer = renderExternalLinkFields
+ break
+
+ case HOME_PAGE_IMAGE:
+ fieldsRenderer = renderHomePageImageFields
+ break
+
+ case BLOG_HEADER_IMAGE:
+ fieldsRenderer = renderBlogHeaderImageFields
+ break
+
+ case MP3:
+ fieldsRenderer = renderMp3Fields
+ break
+
+ case PERSON:
+ fieldsRenderer = renderPersonFields
+ break
+
+ case BLANK:
+ fieldsRenderer = renderBlankFields
+ break
+
+ default:
+ fieldsRenderer = renderBlankFields
+ }
+
+ const fields = fieldsRenderer(member, disabled)
+
+ return (
+
+
+
+ {fields}
+
+ )
+}
+
+export const renderRelated = ({ fields, meta: { error, submitFailed } }) => (
+
+ {submitFailed && error && {error} }
+
+ {fields.map((member, index) => (
+
+
+ Related #{index + 1}
+
+
+
+ {renderRelatedFields({
+ type: fields.get(index).type,
+ member,
+ disabled: fields.get(index).remove
+ })}
+
+ ))}
+
+ fields.push(EMPTY_RELATED_ITEM)}
+ >
+ + Add Related
+
+
+)
+
+const RelatedEntries = styled.div`
+ margin: 20px 0px;
+`
+
+const RelatedEntry = styled.div`
+ margin-bottom: 2em;
+ border-top: 1px solid #e1e3e2;
+ padding-top: 2em;
+`
+
+const RelatedIndex = styled.div`
+ span {
+ font-weight: bold;
+ margin-right: 10px;
+ }
+
+ .remove {
+ label,
+ p {
+ padding: 0px;
+ margin: 0px;
+ }
+ }
+
+ display: flex;
+ align-items: center;
+
+ ${props =>
+ props.removed &&
+ `
+ text-decoration: line-through;
+ overflow: hidden;
+ `};
+`
+
+const ActionButton = styled.button`
+ font-weight: bold;
+ color: #fff;
+ border: none;
+ border-radius: 5px;
+`
+
+const RelatedAddButton = ActionButton.extend`
+ background: #f0d943;
+ margin-top: 1em;
+`
+
+const RelatedFields = styled.div``
diff --git a/shared/components/admin/BlogSearchSelect.js b/shared/components/admin/blog/BlogSearchSelect.js
similarity index 90%
rename from shared/components/admin/BlogSearchSelect.js
rename to shared/components/admin/blog/BlogSearchSelect.js
index 827626c7..0be8e9b7 100644
--- a/shared/components/admin/BlogSearchSelect.js
+++ b/shared/components/admin/blog/BlogSearchSelect.js
@@ -8,29 +8,30 @@ export default class BlogSearchSelect extends Component {
state = {
firstInit: false,
query: '',
- selectedOption: this.props.value
+ selectedOption: this.props.input.value
}
- componentWillReceiveProps(nextProps) {
- if (this.props.value !== nextProps.value) {
- if (this.state.firstInit) return
+ handleChange = selectedOption => {
+ this.setState({ selectedOption })
- this.setState({
- selectedOption: nextProps.value,
- firstInit: true
- })
- }
+ const blog_id = selectedOption ? selectedOption.blog_id : null
+
+ const { input: { onChange } } = this.props
+ onChange(blog_id)
}
inputChange = query => this.setState({ query })
- handleChange = selectedOption => {
- this.setState({ selectedOption })
+ componentWillReceiveProps(nextProps) {
+ if (this.props.input.value !== nextProps.input.value) {
+ if (this.state.firstInit) return
- const blog_id = selectedOption ? selectedOption.blog_id : null
- const { id, onChange = () => {} } = this.props
+ this.setState({
+ firstInit: true
+ })
- onChange(id, blog_id)
+ this.handleChange(nextProps.input.value)
+ }
}
getOptions = query => {
diff --git a/shared/components/admin/blog/CmsBlogUpdate.js b/shared/components/admin/blog/CmsBlogUpdate.js
new file mode 100644
index 00000000..038d7f7b
--- /dev/null
+++ b/shared/components/admin/blog/CmsBlogUpdate.js
@@ -0,0 +1,236 @@
+import React, { Component } from 'react'
+import BlogUpdateForm, { FORM_KEY } from './BlogForm'
+import { INTERNAL_LINK } from './BlogRelatedFields'
+
+import styled from 'styled-components'
+import { connect } from 'react-redux'
+import { getBlog } from '../../../reducers/CmsReducer'
+import { submit } from 'redux-form'
+import moment from 'moment/moment'
+import Loading from '../../../Common/Components/Loading'
+
+const normalizeDate = d => moment(d).format('YYYY-MM-DD')
+
+const formatRelated = item => {
+ const {
+ dest = '',
+ source = '',
+ title = '',
+ body = '',
+ content_id,
+ blog_id2,
+ blog_id,
+ type
+ } = item
+
+ return { dest, source, title, body, content_id, blog_id2, blog_id, type }
+}
+
+class CmsBlogUpdate extends Component {
+ save = async () => {
+ await this.props.dispatch(submit(FORM_KEY))
+ }
+
+ saveRelated = related => {
+ // manage new related
+ const created = related
+ .filter(item => item.created)
+ .map(item => this.addRelatedItem(item))
+
+ // remove old related
+ const remove = related
+ .filter(item => item.remove)
+ .map(item => this.deleteRelatedItem(item))
+
+ return Promise.all(created.concat(remove))
+ }
+
+ onSave = async data => {
+ const {
+ blog_id,
+ title,
+ abstract,
+ author,
+ publish_date,
+ related = []
+ } = data
+
+ const { dispatch } = this.props
+
+ const blogRelated = related.map(item => ({
+ ...item,
+ blog_id,
+ source: blog_id
+ }))
+
+ return this.saveRelated(blogRelated).then(() =>
+ dispatch({
+ type: 'CMS_UPDATE_BLOG',
+ payload: {
+ blog_id,
+ title,
+ abstract,
+ author,
+ publish_date,
+ dispatch
+ }
+ })
+ )
+ }
+
+ delete = () => {
+ const { dispatch, blog: { blog_id } } = this.props
+
+ dispatch({ type: 'CMS_DELETE_BLOG', payload: { blog_id, dispatch } })
+ }
+
+ addRelatedItem = data => {
+ const { dispatch } = this.props
+ data = formatRelated(data)
+
+ dispatch({
+ type: 'RELATED_CONTENT_ADD',
+ payload: { data, dispatch }
+ })
+ }
+
+ deleteRelatedItem = ({ content_id }) => {
+ return this.props.dispatch({
+ type: 'RELATED_CONTENT_DELETE',
+ payload: { content_id }
+ })
+ }
+
+ componentDidMount() {
+ const dispatch = this.props.dispatch
+ dispatch({ type: 'CMS_LOAD_PENDING_BLOGS', payload: { dispatch } })
+
+ const limit = 20
+ const offset = 0
+ const prefix = ''
+ const payload = { limit, offset, prefix, dispatch }
+ dispatch({ type: 'CMS_LOAD_RECENT_BLOGS', payload })
+ }
+
+ renderNotFound() {
+ return
Not found.
+ }
+
+ render() {
+ const { blog, loaded, error, success } = this.props
+
+ if (!loaded) {
+ return
+ }
+
+ if (!blog) {
+ return this.renderNotFound()
+ }
+
+ const { related = [] } = blog
+
+ const blogFormData = {
+ ...blog,
+ publish_date: normalizeDate(blog.publish_date),
+ related: related.map(item => {
+ if (item.type === INTERNAL_LINK) {
+ item.dest = {
+ blog_id: item.blog_id,
+ title: item.title
+ }
+ }
+ return item
+ })
+ }
+
+ return (
+
+
+
+
+ Delete
+ Save
+
+
+ {error && {error} }
+ {success && Update success }
+
+ )
+ }
+}
+
+export default connect((state, ownProps) => ({
+ admin: state.admin,
+ blog: getBlog(state, ownProps.prettyname),
+ error: state.cms.getIn(['blogs', 'error']),
+ success: state.cms.getIn(['blogs', 'success']),
+ loaded:
+ state.cms.get('recent_blogs_loaded') &&
+ state.cms.get('pending_blogs_loaded')
+}))(CmsBlogUpdate)
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+
+ textarea {
+ min-height: 100px;
+ }
+`
+const BlogForm = styled(BlogUpdateForm)`
+ display: flex;
+ flex-direction: column;
+`
+
+const Row = styled.div`
+ display: flex;
+`
+
+const Buttons = Row.extend`
+ justify-content: flex-end;
+`
+
+const ActionButton = styled.button`
+ font-weight: bold;
+ color: #fff;
+ border: none;
+ border-radius: 5px;
+`
+
+const CancelButton = ActionButton.extend`
+ background: transparent;
+ margin-right: 12px;
+
+ span {
+ color: #333;
+ border-bottom: 1px dotted;
+ }
+`
+
+const SaveButton = ActionButton.extend`
+ background: #2ecb70;
+`
+
+const DeleteButton = ActionButton.extend`
+ margin-right: 12px;
+ background: #e74c3c;
+ opacity: 0.7;
+
+ &:hover {
+ opacity: 1;
+ }
+`
+
+const Error = styled.div`
+ color: red;
+`
+
+const Success = styled.div`
+ color: green;
+`
diff --git a/shared/components/admin/blog/ContributorSelect.js b/shared/components/admin/blog/ContributorSelect.js
new file mode 100644
index 00000000..a33fa136
--- /dev/null
+++ b/shared/components/admin/blog/ContributorSelect.js
@@ -0,0 +1,84 @@
+import React, { Component } from 'react'
+import { Async } from 'react-select'
+import axios from 'axios'
+import styled from 'styled-components'
+import { find, orderBy } from 'lodash'
+
+const normalizeKey = key => key.toLowerCase()
+
+export default class ContributorSelect extends Component {
+ state = {
+ firstInit: false,
+ query: '',
+ selectedOption: normalizeKey(this.props.input.value)
+ }
+
+ handleChange = option => {
+ const selectedOption = normalizeKey(option.id)
+ this.setState({ selectedOption })
+
+ const { input: { onChange } } = this.props
+ onChange(selectedOption)
+ }
+
+ inputChange = query => this.setState({ query })
+
+ getOptions = query => {
+ query = encodeURIComponent(query)
+
+ return axios
+ .get(`/api/v1/contributors?q=${query}`)
+ .then(res => res.data)
+ .then((options = []) => ({ options }))
+ }
+
+ renderOption = ({ prettyname, img }) => (
+
-
+
+ {prettyname}
+
+ )
+
+ render() {
+ const { selectedOption } = this.state
+
+ return (
+
+ )
+ }
+}
+
+const Item = styled.div`
+ display: flex;
+ flex-direction: wrap;
+ align-items: center;
+`
+
+const Title = styled.p`
+ font-weight: bold;
+ margin: 0px;
+ padding: 0px;
+`
+
+const Avatar = styled.img`
+ border-radius: 50%;
+ width: 28px;
+ height: 28px;
+ padding: 2px;
+ background-color: #ffffff;
+ border: 1px solid #dddddd !important;
+ margin-right: 0.3em;
+ overflow: hidden;
+ margin-top: 2px;
+`
diff --git a/shared/components/admin/contributors/ContributorAddFormController.js b/shared/components/admin/contributors/ContributorAddFormController.js
new file mode 100644
index 00000000..46d3979c
--- /dev/null
+++ b/shared/components/admin/contributors/ContributorAddFormController.js
@@ -0,0 +1,93 @@
+import React, { Component } from 'react'
+import styled from 'styled-components'
+import { connect } from 'react-redux'
+import { submit } from 'redux-form'
+import AddContributor, {
+ FORM_KEY
+} from '../../../Contributors/Forms/AddContributor'
+import { strictForm } from '../../../styles'
+import { addContributor } from '../../../reducers/CmsReducer'
+
+class ContributorAddFormController extends Component {
+ state = {
+ defaultValues: {
+ sort_rank: 0
+ }
+ }
+
+ save = () => this.props.dispatch(submit(FORM_KEY))
+
+ submit = data => this.props.dispatch(addContributor(data))
+
+ render() {
+ const { loading, error, success } = this.props
+
+ return (
+
+
+ {JSON.stringify({
+ loading,
+ error,
+ success
+ })}
+
+
+
+
+ {loading ? 'Creating...' : 'Create'}
+
+
+
+ {error && {error} }
+ {success && Success }
+
+ )
+ }
+}
+
+export default connect((state, ownProps) => ({
+ loading: state.cms.getIn(['contributors', 'processing']),
+ error: state.cms.getIn(['contributors', 'error']),
+ success: state.cms.getIn(['contributors', 'success'])
+}))(ContributorAddFormController)
+
+const Container = styled.div`
+ ${strictForm};
+ display: flex;
+ flex-direction: column;
+
+ textarea {
+ min-height: 100px;
+ }
+`
+
+const Row = styled.div`
+ display: flex;
+`
+
+const Buttons = Row.extend`
+ justify-content: flex-end;
+`
+
+const SaveButton = styled.button`
+ border: none;
+ margin-top: 14px;
+ border: 0;
+ background: #f0d943;
+ border-radius: 4px;
+ width: 100%;
+ font-size: 16px;
+ color: #333;
+ padding: 10px 18px;
+`
+
+const Error = styled.div`
+ color: red;
+`
+
+const Success = styled.div`
+ color: green;
+`
diff --git a/shared/reducers/AdminReducer.js b/shared/reducers/AdminReducer.js
index 4a303994..85d3b9f0 100644
--- a/shared/reducers/AdminReducer.js
+++ b/shared/reducers/AdminReducer.js
@@ -321,17 +321,6 @@ export default function adminReducer(state = defaultState, action) {
break
case 'RELATED_CONTENT_DELETE':
var payload = action.payload
- var nlist = []
- var content_id = payload['content_id']
- var i = 0
- while (i < nstate.relatedcontent.length) {
- var rc = nstate.relatedcontent[i]
- if (rc['content_id'] != content_id) {
- nlist.push(rc)
- }
- i += 1
- }
- nstate.relatedcontent = nlist
var url = base_url + '/blog/relatedcontent/delete'
axios
.post(url, payload)
diff --git a/shared/reducers/CmsReducer.js b/shared/reducers/CmsReducer.js
index 3505921c..b8828c0e 100644
--- a/shared/reducers/CmsReducer.js
+++ b/shared/reducers/CmsReducer.js
@@ -4,6 +4,14 @@ import querystring from 'querystring'
import axios from 'axios'
import snserror from '../SnsUtil'
import { load_blogs } from '../daos/serverInit'
+import Request from '../Request'
+import {
+ ADD_JOB,
+ ADD_JOB_FAIL,
+ ADD_JOB_SUCCESS,
+ addJobFail,
+ addJobSuccess
+} from './AdminReducer'
const aws = require('aws-sdk')
var env = process.env.NODE_ENV === 'dev' ? 'dev' : 'prod'
@@ -12,6 +20,10 @@ const config = require('../../config/config.json')
var base_url = config[env]['base_api'] + env
+export const ADD_CONTRIBUTOR = 'ADD_CONTRIBUTOR'
+export const ADD_CONTRIBUTOR_FAIL = 'ADD_CONTRIBUTOR_FAIL'
+export const ADD_CONTRIBUTOR_SUCCESS = 'ADD_CONTRIBUTOR_SUCCESS'
+
const init = {
home_loaded: false,
jobListing: {
@@ -30,6 +42,15 @@ const init = {
pending_blogs_loaded: false,
blog_state: '',
blog_content: {},
+ blogs: {
+ error: null,
+ success: false
+ },
+ contributors: {
+ processing: false,
+ error: null,
+ success: false
+ },
live: 'loading'
}
@@ -153,6 +174,8 @@ export default function cmsReducer(state = defaultState, action) {
var dispatch = payload.dispatch
//payload['title'] = payload['title']
//payload['abstract'] = payload['abstract']
+ nstate.blogs.success = false
+ nstate.blogs.error = null
var url = base_url + '/blog/update'
axios
.post(url, payload)
@@ -160,17 +183,41 @@ export default function cmsReducer(state = defaultState, action) {
var data = result['data']
var error = data['error']
if (error) {
- alert('ERROR: ' + JSON.stringify(result))
+ dispatch({
+ type: 'CMS_UPDATE_BLOG_FAIL',
+ payload: {
+ error
+ }
+ })
} else {
- alert('SUCCESS: ' + JSON.stringify(result))
+ dispatch({
+ type: 'CMS_UPDATE_BLOG_SUCCESS',
+ payload: {
+ data
+ }
+ })
}
})
.catch(err => {
console.log(err)
var errorMsg = JSON.stringify(err)
snserror('CMS_UPDATE_BLOG', errorMsg)
+ dispatch({
+ action: 'CMS_UPDATE_BLOG_FAIL',
+ payload: {
+ error: errorMsg
+ }
+ })
})
break
+ case 'CMS_UPDATE_BLOG_SUCCESS':
+ nstate.blogs.success = true
+ nstate.blogs.error = null
+ break
+ case 'CMS_UPDATE_BLOG_FAIL':
+ nstate.blogs.error = action.payload.error
+ break
+
case 'CMS_SET_HOMEPAGE_FEATURE':
nstate.featured_blog_state = 'submit'
var blog_id = nstate.featured_blog.blog_id
@@ -318,6 +365,62 @@ export default function cmsReducer(state = defaultState, action) {
nstate.jobListing.loaded = true
nstate.jobListing.jobs = action.payload.data
break
+
+ case ADD_CONTRIBUTOR:
+ nstate.contributors.processing = true
+ nstate.contributors.success = false
+ nstate.contributors.error = null
+ break
+
+ case ADD_CONTRIBUTOR_SUCCESS:
+ nstate.contributors.processing = false
+ nstate.contributors.success = true
+ nstate.contributors.error = null
+ break
+
+ case ADD_CONTRIBUTOR_FAIL:
+ nstate.contributors.processing = false
+ nstate.contributors.success = false
+ nstate.contributors.error = action.payload.error
+ break
}
return Immutable.fromJS(nstate)
}
+
+export const getBlog = (state, prettyname) => {
+ const pendingBlog = state.cms
+ .get('pending_blogs')
+ .find(item => item.get('prettyname') === prettyname)
+
+ const recentBlog = state.cms
+ .get('recent_blogs')
+ .find(item => item.get('prettyname') === prettyname)
+
+ const blog = pendingBlog || recentBlog
+
+ return blog ? blog.toJS() : blog
+}
+
+export const addContributor = contributor => {
+ return dispatch => {
+ dispatch(addContributorRequest)
+
+ Request.post(`/api/v1/contributors`, contributor)
+ .then(result => dispatch(addContributorSuccess(result.data)))
+ .catch(err => dispatch(addContributorFail(err.data.error)))
+ }
+}
+
+export const addContributorRequest = () => ({
+ type: ADD_CONTRIBUTOR
+})
+
+export const addContributorFail = error => ({
+ type: ADD_CONTRIBUTOR_FAIL,
+ payload: { error }
+})
+
+export const addContributorSuccess = result => ({
+ type: ADD_CONTRIBUTOR_SUCCESS,
+ payload: { result }
+})
diff --git a/shared/reducers/SiteReducer.js b/shared/reducers/SiteReducer.js
index 16323a40..30083a0a 100644
--- a/shared/reducers/SiteReducer.js
+++ b/shared/reducers/SiteReducer.js
@@ -7,7 +7,7 @@ import { clone } from 'lodash'
* Whenever some reducer initial state have been changed update current schema version
* Unique version number is total count of commits in `master`
*/
-export const SCHEMA_VER = 'v1956'
+export const SCHEMA_VER = 'v1957'
const randomSessionId = () => v4()
diff --git a/shared/routes.jsx b/shared/routes.jsx
index b9b81f84..60d56be7 100644
--- a/shared/routes.jsx
+++ b/shared/routes.jsx
@@ -1,15 +1,6 @@
import React from 'react'
-import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, History } from 'react-router'
import ReactGA from 'react-ga'
-import {
- reduxReactRouter,
- routerStateReducer,
- ReduxRouter
-} from 'redux-react-router'
-import createBrowserHistory from 'history/lib/createBrowserHistory'
-import configureStore from './store'
-
import About from 'components/About'
import Advertising from 'components/Advertising'
import App from 'components/index'
@@ -53,14 +44,14 @@ import ContributorPage from './Contributors/Routes/ContributorPage'
import AdminHome from 'components/admin/AdminHome'
import AdminCmsPending from 'components/admin/AdminCmsPending'
-import AdminCmsAddRelated from 'components/admin/AdminCmsAddRelated'
import AdminCmsRecent from 'components/admin/AdminCmsRecent'
import AdminCmsFeature from 'components/admin/AdminCmsFeature'
-import AdminCmsRecentRelated from 'components/admin/AdminCmsRecentRelated'
+import AdminCmsBlogUpdate from 'components/admin/AdminCmsBlogUpdate'
import AdminEmailsSend from 'components/admin/AdminEmailsSend'
import AdminOrdersNew from 'components/admin/AdminOrdersNew'
import AdminOrdersProcessing from 'components/admin/AdminOrdersProcessing'
import AdminAddJob from 'components/admin/AdminAddJob'
+import AdminCmsContributorAdd from 'components/admin/AdminCmsContributorAdd'
import PublicAddJob from 'components/jobs/PublicAddJob'
import UserPlaylist from 'components/UserPlaylist'
@@ -99,18 +90,23 @@ export default (
+
+
+
+
+
+
+
+
+
+
+
@@ -158,12 +160,15 @@ export default (
+
+
+
@@ -172,18 +177,23 @@ export default (
component={props =>
}
/>
+
+
+
+
+
+
@@ -215,15 +226,19 @@ export default (
+
+
+
+
@@ -247,9 +262,11 @@ export default (
+
+
@@ -266,16 +283,16 @@ export default (
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/shared/styles.js b/shared/styles.js
index d43e4937..60c94bb0 100644
--- a/shared/styles.js
+++ b/shared/styles.js
@@ -35,7 +35,7 @@ export const strictForm = css`
}
.field-input {
- > input {
+ input {
padding: 2px 6px !important;
}
}