Skip to content

Commit

Permalink
ngrox setup, survey recipents update
Browse files Browse the repository at this point in the history
  • Loading branch information
aomini committed Aug 11, 2019
1 parent 2cd1774 commit 80913c1
Show file tree
Hide file tree
Showing 28 changed files with 1,280 additions and 13 deletions.
44 changes: 44 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"axios": "^0.19.0",
"http-proxy-middleware": "^0.19.1",
"loadash": "^1.0.0",
"materialize-css": "^1.0.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
Expand All @@ -13,6 +14,7 @@
"react-scripts": "3.0.1",
"react-stripe-checkout": "^2.6.3",
"redux": "^4.0.4",
"redux-form": "^8.2.5",
"redux-thunk": "^2.3.0"
},
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Expand Down
8 changes: 7 additions & 1 deletion client/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ const handleToken = token => async dispatch => {
dispatch({ type: FETCH_USER, payload: res.data });
};

export { fetchUser, handleToken };
const sendSurvey = (survey, history) => async dispatch => {
const res = await axios.post("/api/surveys", survey);
history.push("/surveys");
dispatch({ type: FETCH_USER, payload: res.data });
};

export { fetchUser, handleToken, sendSurvey };
7 changes: 3 additions & 4 deletions client/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import * as actions from "../actions";

import Header from "./Header";
import Landing from "./Landing";

const Survey = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
import Dashboard from "./Dashboard";
import SurveyNew from "./Surveys/SurveyNew";

class App extends Component {
componentDidMount() {
Expand All @@ -21,7 +20,7 @@ class App extends Component {
<div>
<Header />
<Route exact path="/" component={Landing} />
<Route exact path="/surveys" component={Survey} />
<Route exact path="/surveys" component={Dashboard} />
<Route path="/surveys/new" component={SurveyNew} />
</div>
</Router>
Expand Down
17 changes: 17 additions & 0 deletions client/src/components/Dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";
import { Link } from "react-router-dom";

const Dashboard = () => {
return (
<div>
Dashboard
<div className="fixed-action-btn">
<Link className="btn-floating btn-large red" to="/surveys/new">
<i className="material-icons">add</i>
</Link>
</div>
</div>
);
};

export default Dashboard;
2 changes: 1 addition & 1 deletion client/src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Header extends Component {
Credits: {this.props.auth.credits}
</li>,
<li key="2">
<a href="/api/logout">Log Out</a>
<a href="/api/logout">Logout</a>
</li>
];
}
Expand Down
13 changes: 13 additions & 0 deletions client/src/components/Surveys/SurveyField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//contains logic to render a single text and label

import React from "react";

export default ({ input, label, meta: { touched, error } }) => {
return (
<div style={{ marginBottom: "10px" }}>
<label>{label}</label>
<input {...input} />
<div className="red-text"> {touched && error} </div>
</div>
);
};
62 changes: 62 additions & 0 deletions client/src/components/Surveys/SurveyForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import _ from "lodash";
import React, { Component } from "react";
import { reduxForm, Field } from "redux-form";
import { Link } from "react-router-dom";

import SurveyField from "./SurveyField";
import formFields from "./formFields";
import { deepFreeze } from "../../helpers/Array";

import validateEmails from "../../utils/validateEmails";

const FIELDS = deepFreeze(formFields);

class SurveyForm extends Component {
renderFields() {
return FIELDS.map(({ label, name }) => (
<Field
component={SurveyField}
type="text"
name={name}
label={label}
key={name}
/>
));
}

render() {
return (
<form
onSubmit={this.props.handleSubmit(() => this.props.onSurveySubmit())}
>
{this.renderFields()}
<Link to="/surveys" className="red btn-flat white-text">
Cancel
</Link>
<button type="submit" className="teal btn-flat right white-text">
Next
<i className="material-icons right">done</i>
</button>
</form>
);
}
}

const validate = fields => values => {
let errors = {};
errors.recipents = values.recipents && validateEmails(values.recipents);

_.each(fields, ({ name }) => {
if (!values[name]) {
errors[name] = `You must provide a ${name}`;
}
});

return errors;
};

export default reduxForm({
validate: validate(FIELDS),
form: "surveyForm",
destroyOnUnmount: false
})(SurveyForm);
33 changes: 33 additions & 0 deletions client/src/components/Surveys/SurveyNew.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Survey new has the responsibility to show survey form and survey review
import React, { Component } from "react";
import { reduxForm } from "redux-form";
import SurveyForm from "./SurveyForm";
import SurveyReview from "./SurveyReview";

class SurveyNew extends Component {
state = {
reviewPage: false
};

renderContent() {
const { reviewPage } = this.state;
if (reviewPage) {
return (
<SurveyReview
onBackReview={() => this.setState({ reviewPage: !reviewPage })}
/>
);
}
return (
<SurveyForm
onSurveySubmit={() => this.setState({ reviewPage: !reviewPage })}
/>
);
}

render() {
return <div>{this.renderContent()}</div>;
}
}

export default reduxForm({ form: "surveyForm" })(SurveyNew);
58 changes: 58 additions & 0 deletions client/src/components/Surveys/SurveyReview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { sendSurvey } from "../../actions";

import formFields from "./formFields";
import { deepFreeze } from "../../helpers/Array";

class SurveyReview extends Component {
renderContent() {
const FIELDS = deepFreeze(formFields);
return FIELDS.map(({ label, name }) => {
return (
<div key={name}>
<label>{label}</label>
<div>{this.props.form[name]}</div>
</div>
);
});
}

render() {
return (
<div>
<h4>Please confirm your entries</h4>
<div>{this.renderContent()}</div>
<button
className="yellow white-text darken-3 btn-flat"
onClick={() => this.props.onBackReview()}
>
Back
</button>
<button
className="right green btn-flat white-text"
onClick={() =>
this.props.sendSurvey(this.props.form, this.props.history)
}
>
Send Survey
<i className="material-icons right">email</i>
</button>
</div>
);
}
}

const mapStateToProps = ({ form }) => {
return { form: form.surveyForm.values };
};

const mapDispatchToProps = action => {
return { action };
};

export default connect(
mapStateToProps,
{ sendSurvey }
)(withRouter(SurveyReview));
6 changes: 6 additions & 0 deletions client/src/components/Surveys/formFields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default [
{ label: "Survey Title", name: "title" },
{ label: "Survey Subject", name: "subject" },
{ label: "Email Body", name: "body" },
{ label: "Recipients List", name: "recipents" }
];
3 changes: 3 additions & 0 deletions client/src/helpers/Array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const freeze = x => Object.freeze(x);
export const mapper = x => y => x.map(y);
export const deepFreeze = array => mapper(array)(freeze);
15 changes: 12 additions & 3 deletions client/src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import "materialize-css/dist/css/materialize.min.css";
import React, { Component } from "react";
import React from "react";
import ReactDom from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import { createStore, applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";

import App from "./components/App.js";
import reducers from "./reducers";

const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
import axios from "axios";
window.axios = axios;

const store = createStore(
reducers,
compose(
applyMiddleware(reduxThunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);

ReactDom.render(
<Provider store={store}>
Expand Down
5 changes: 4 additions & 1 deletion client/src/reducers/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";

import authReducer from "./authReducer";

export default combineReducers({
auth: authReducer
auth: authReducer,
form: formReducer
});
9 changes: 9 additions & 0 deletions client/src/utils/validateEmails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default emails => {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const inValidEmails = emails
.split(",")
.map(email => email.trim())
.filter(email => (email ? !re.test(email) : false));
if (inValidEmails.length) return `These emails are invalid ${inValidEmails}`;
return null;
};
Loading

0 comments on commit 80913c1

Please sign in to comment.