Skip to content

Commit

Permalink
Added basic Content Types functionality (#3)
Browse files Browse the repository at this point in the history
* Added Sidebar component to private pages

* Fix .main-container in mobile

* Added Settings page

* Added Settings form and functionality for site title and site description

* Added basic Content Types functionality
  • Loading branch information
jamenamcinteer committed Nov 4, 2018
1 parent 5a4ba43 commit afc570f
Show file tree
Hide file tree
Showing 16 changed files with 481 additions and 138 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# ChaiCMS

A React CMS for the JAMstack.

## Roadmap

[ ] Write tests for all components, actions, reducers
[ ] Create site fields functionality and attach to content types
[ ] Create default content types (pages, posts) on init
[ ] Enforce uniqueness of slugs
[ ] Create content functionality
[ ] Add meta data to content types: date created, date modified, author
[ ] Add media functionality - upload, simple image editing
[ ] Allow upload of image for splash screen or solid color
[ ] More robust user authentication with invite links, etc
[ ] Look into using Hooks
[ ] Build a REST API with Express to handle all CRUD actions
87 changes: 87 additions & 0 deletions src/actions/contentTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import uuid from "uuid";
import database from "../firebase/firebase";

// ADD_CONTENT_TYPE
export const addContentType = contentType => ({
type: "ADD_CONTENT_TYPE",
contentType
});

export const startAddContentType = (contentTypeData = {}) => {
return (dispatch, getState) => {
const { title = "", slug = "" } = contentTypeData;
const contentType = { title, slug };

return database
.ref(`content_types`)
.push(contentType)
.then(ref => {
dispatch(
addContentType({
id: ref.key,
...contentType
})
);
});
};
};

// REMOVE_CONTENT_TYPE
export const removeContentType = ({ id } = {}) => ({
type: "REMOVE_CONTENT_TYPE",
id
});

export const startRemoveContentType = ({ id } = {}) => {
return (dispatch, getState) => {
return database
.ref(`content_types/${id}`)
.remove()
.then(() => {
dispatch(removeContentType({ id }));
});
};
};

// EDIT_CONTENT_TYPE
export const editContentType = (id, updates) => ({
type: "EDIT_CONTENT_TYPE",
id,
updates
});

export const startEditContentType = (id, updates) => {
return (dispatch, getState) => {
return database
.ref(`content_types/${id}`)
.update(updates)
.then(() => {
dispatch(editContentType(id, updates));
});
};
};

// SET_CONTENT_TYPES
export const setContentTypes = contentTypes => ({
type: "SET_CONTENT_TYPES",
contentTypes
});

export const startSetContentTypes = () => {
return (dispatch, getState) => {
return database
.ref(`content_types`)
.once("value")
.then(snapshot => {
const contentTypes = [];
snapshot.forEach(childSnapshot => {
contentTypes.push({
id: childSnapshot.key,
...childSnapshot.val()
});
});

dispatch(setContentTypes(contentTypes));
});
};
};
11 changes: 7 additions & 4 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import AppRouter, { history } from "./routers/AppRouter";
import configureStore from "./store/configureStore";
import { login, logout } from "./actions/auth";
import { startSetSettings } from "./actions/settings";
import { startSetContentTypes } from "./actions/contentTypes";
import "normalize.css/normalize.css";
import "./styles/styles.scss";
import "react-dates/lib/css/_datepicker.css";
Expand Down Expand Up @@ -33,10 +34,12 @@ firebase.auth().onAuthStateChanged(user => {
store.dispatch(login(user.uid));

store.dispatch(startSetSettings()).then(() => {
renderApp();
if (history.location.pathname === "/") {
history.push("/dashboard");
}
store.dispatch(startSetContentTypes()).then(() => {
renderApp();
if (history.location.pathname === "/") {
history.push("/dashboard");
}
});
});
} else {
store.dispatch(logout());
Expand Down
34 changes: 34 additions & 0 deletions src/components/AddContentTypePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import { connect } from "react-redux";
import ContentTypeForm from "./ContentTypeForm.js";
import { startAddContentType } from "../actions/contentTypes";

export class AddContentTypePage extends React.Component {
onSubmit = contentType => {
this.props.startAddContentType(contentType);
this.props.history.push("/content-types");
};
render() {
return (
<div>
<div className="page-header">
<div className="content-container">
<h1 className="page-header__title">Add Content Type</h1>
</div>
</div>
<div className="content-container content-container--centered">
<ContentTypeForm onSubmit={this.onSubmit} />
</div>
</div>
);
}
}

const mapDispatchToProps = dispatch => ({
startAddContentType: contentType => dispatch(startAddContentType(contentType))
});

export default connect(
undefined,
mapDispatchToProps
)(AddContentTypePage);
39 changes: 39 additions & 0 deletions src/components/ContentPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
// import ContentList from "./ContentList";

export class ContentPage extends React.Component {
render() {
return (
<div>
<div className="page-header">
<div className="content-container">
<h1 className="page-header__title">
{this.props.contentType.title}
</h1>
<div className="page-header__actions">
<Link
className="button"
to={`/content/${this.props.contentType.slug}/add`}
>
Add {this.props.contentType.title}
</Link>
</div>
</div>
</div>
<div className="content-container">{/* <ContentList /> */}</div>
</div>
);
}
}

const mapStateToProps = (state, props) => {
return {
contentType: state.contentTypes.find(
contentType => contentType.slug === props.match.params.slug
)
};
};

export default connect(mapStateToProps)(ContentPage);
86 changes: 86 additions & 0 deletions src/components/ContentTypeForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from "react";

export default class ContentTypeForm extends React.Component {
constructor(props) {
super(props);

this.state = {
title: props.contentType ? props.contentType.title : "",
slug: props.contentType ? props.contentType.slug : "",
error: ""
};
}
useSlugify = string => {
const a = "àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;";
const b = "aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------";
const p = new RegExp(a.split("").join("|"), "g");
return string
.toString()
.toLowerCase()
.replace(/\s+/g, "-") // Replace spaces with
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, "-and-") // Replace & with ‘and’
.replace(/[^\w\-]+/g, "") // Remove all non-word characters
.replace(/\-\-+/g, "-") // Replace multiple — with single -
.replace(/^-+/, ""); // Trim — from start of text .replace(/-+$/, '') // Trim — from end of text
};
onTitleChange = e => {
const title = e.target.value;
const slug = this.useSlugify(title);
this.setState(() => ({
title,
slug
}));
};
onSlugChange = e => {
const slug = e.target.value;
this.setState(() => ({
slug
}));
};
onSubmit = e => {
e.preventDefault();
// TO DO: Check that slug is unique in db
if (!this.state.title || !this.state.slug) {
const error = "Please provide title and slug";
const success = "";
this.setState(() => ({ error, success }));
} else {
const error = "";
const success = "Content type saved successfully.";
this.setState(() => ({ error, success }));
this.props.onSubmit({
title: this.state.title,
slug: this.state.slug
});
}
};
render() {
return (
<form className="form" onSubmit={this.onSubmit}>
{this.state.error && <p className="form__error">{this.state.error}</p>}
{this.state.success && (
<p className="form__success">{this.state.success}</p>
)}
<input
className="text-input"
type="text"
placeholder="Title"
autoFocus
value={this.state.title}
onChange={this.onTitleChange}
/>
<input
className="text-input"
type="text"
placeholder="Slug"
value={this.state.slug}
onChange={this.onSlugChange}
/>
<div>
<button className="button">Save Content Type</button>
</div>
</form>
);
}
}
14 changes: 14 additions & 0 deletions src/components/ContentTypeListItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import { Link } from "react-router-dom";

const ContentTypeListItem = ({ id, title, slug }) => (
<Link className="list-item" to={`/content-types/edit/${id}`}>
<div>
<h3 className="list-item__title">{title}</h3>
<span className="list-item__sub-title">{slug}</span>
</div>
{/* <h3 className="list-item__data">{slug}</h3> */}
</Link>
);

export default ContentTypeListItem;
33 changes: 33 additions & 0 deletions src/components/ContentTypesList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import { connect } from "react-redux";
import ContentTypeListItem from "./ContentTypeListItem";
import selectContentTypes from "../selectors/contentTypes";

export const ContentTypesList = props => (
<div>
<div className="list-header">
<div className="show-for-mobile">Content Types</div>
<div className="show-for-desktop">Title</div>
{/* <div className="show-for-desktop">Slug</div> */}
</div>
<div className="list-body">
{props.contentTypes.length === 0 ? (
<div className="list-item list-item--message">
<span>No content types</span>
</div>
) : (
props.contentTypes.map(contentType => {
return <ContentTypeListItem {...contentType} key={contentType.id} />;
})
)}
</div>
</div>
);

const mapStateToProps = state => {
return {
contentTypes: selectContentTypes(state.contentTypes)
};
};

export default connect(mapStateToProps)(ContentTypesList);
23 changes: 23 additions & 0 deletions src/components/ContentTypesPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import { Link } from "react-router-dom";
import ContentTypesList from "./ContentTypesList";

const ContentTypesPage = () => (
<div>
<div className="page-header">
<div className="content-container">
<h1 className="page-header__title">Content Types</h1>
<div className="page-header__actions">
<Link className="button" to="/content-types/add">
Add Content Type
</Link>
</div>
</div>
</div>
<div className="content-container">
<ContentTypesList />
</div>
</div>
);

export default ContentTypesPage;
Loading

0 comments on commit afc570f

Please sign in to comment.