diff --git a/api/graphql.js b/api/graphql.js
index 005b997..3c130df 100644
--- a/api/graphql.js
+++ b/api/graphql.js
@@ -28,25 +28,26 @@ class ResumeSchema {
const ResumeType = new GraphQLObjectType({
name: 'resume',
- fields: function () {
- return {
- id: {
- type: GraphQLID
- },
- userid: {
- type: GraphQLFloat
- },
- resumeid: {
- type: GraphQLInt
- },
- cvdata: {
- type: GraphQLString
- },
- template: {
- type: GraphQLString
- }
- };
- }
+ fields: () => ({
+ id: {
+ type: new GraphQLNonNull(GraphQLID)
+ },
+ userid: {
+ type: new GraphQLNonNull(GraphQLFloat)
+ },
+ resumeid: {
+ type: new GraphQLNonNull(GraphQLInt)
+ },
+ cvdata: {
+ type: GraphQLString
+ },
+ template: {
+ type: GraphQLString
+ },
+ share: {
+ type: GraphQLString
+ }
+ })
});
const MutationAdd = {
@@ -72,13 +73,17 @@ class ResumeSchema {
template: {
name: 'Resume template',
type: new GraphQLNonNull(GraphQLString)
+ },
+ share: {
+ name: 'Resume share',
+ type: new GraphQLNonNull(GraphQLString)
}
},
- resolve: (root, {id, userid, resumeid, cvdata, template}) => {
+ resolve: (root, {id, userid, resumeid, cvdata, template, share}) => {
return new Promise((resolve, reject) => {
- db.insert(dbName, {id, userid, resumeid, cvdata, template})
+ db.insert(dbName, {id, userid, resumeid, cvdata, template, share})
.then(() => db.selectAll(dbName))
- .then(resumes => resolve(resumes))
+ .then(() => resolve())
.catch(err => reject(err));
});
}
@@ -97,7 +102,7 @@ class ResumeSchema {
return new Promise((resolve, reject) => {
db.delete(dbName, {id})
.then(() => db.selectAll(dbName))
- .then(resumes => resolve(resumes))
+ .then(() => resolve())
.catch(err => reject(err));
});
}
@@ -108,7 +113,7 @@ class ResumeSchema {
description: 'Update a Resume',
args: {
id: {
- name: 'Id',
+ name: 'Resume Id',
type: new GraphQLNonNull(GraphQLID)
},
cvdata: {
@@ -118,15 +123,20 @@ class ResumeSchema {
template: {
name: 'Resume template',
type: GraphQLString
+ },
+ share: {
+ name: 'Resume share',
+ type: GraphQLString
}
},
- resolve: (root, {id, cvdata, template}) => {
+ resolve: (root, {id, cvdata, template, share}) => {
return new Promise((resolve, reject) => {
const dataToUpdate = {};
if(cvdata) dataToUpdate.cvdata = cvdata;
if(template) dataToUpdate.template = template;
+ if(share) dataToUpdate.share = share;
db.update(dbName, {id}, {$set:dataToUpdate})
- .then(() => db.selectAll(dbName))
+ .then(() => db.find(dbName, {id}))
.then(resumes => resolve(resumes))
.catch(err => reject(err));
});
@@ -143,12 +153,17 @@ class ResumeSchema {
args: {
userid: {
name: 'User ID',
- type: new GraphQLNonNull(GraphQLFloat)
+ type: GraphQLFloat
+ },
+ id: {
+ name: 'Resume ID',
+ type: GraphQLID
}
},
- resolve: (root, {userid}) => {
+ resolve: (root, {id, userid}) => {
return new Promise((resolve, reject) => {
- db.find(dbName, {userid})
+ const idToFind = id ? {id} : {userid};
+ db.find(dbName, idToFind)
.then(resumes => resolve(resumes))
.catch(err => reject(err));
});
diff --git a/package.json b/package.json
index 80fdf8b..7f69c37 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
"draft-js-import-html": "0.3.2",
"es6-promise": "4.1.1",
"express": "4.14.1",
- "express-graphql": "0.6.6",
+ "express-graphql": "0.6.12",
"express-session": "1.15.3",
"extract-text-webpack-plugin": "2.1.2",
"file-loader": "0.9.0",
diff --git a/tools/middlewares/cryptoModule.js b/tools/middlewares/cryptoModule.js
new file mode 100644
index 0000000..0a7d50b
--- /dev/null
+++ b/tools/middlewares/cryptoModule.js
@@ -0,0 +1,20 @@
+import crypto from 'crypto';
+
+const key = process.env.cryptoKey;
+const nonce = process.env.cryptoNonce;
+
+const encrypt = (text) => {
+ const cipher = crypto.createCipheriv('aes256', key, nonce);
+ let ciphertext = cipher.update(text, 'utf8', 'hex');
+ ciphertext += cipher.final('hex');
+ return ciphertext;
+};
+
+const decrypt = (ciphertext) => {
+ const decipher = crypto.createDecipheriv('aes256', key, nonce);
+ let plainText = decipher.update(ciphertext, 'hex', 'utf8');
+ plainText += decipher.final('utf8');
+ return plainText;
+};
+
+export {encrypt, decrypt};
diff --git a/tools/middlewares/graphql-query.js b/tools/middlewares/graphql-query.js
new file mode 100644
index 0000000..c7f7ac5
--- /dev/null
+++ b/tools/middlewares/graphql-query.js
@@ -0,0 +1,24 @@
+import { graphql } from 'graphql';
+
+import ResumeSchema from '../../api/graphql';
+
+const graphqlQuery = (query) => {
+
+ return new Promise((resolve, reject) => {
+ graphql(getSchema(), query).then((result) => {
+ if (result.errors) reject(result.errors);
+ if (result.data.resumes && result.data.resumes.length === 1)
+ resolve(result.data.resumes[0]);
+ if (result.data.resumes && result.data.resumes.length > 1)
+ resolve(result.data.resumes);
+ resolve();
+ }).catch(e => reject(e));
+ });
+};
+
+const getSchema = () => {
+ const resumeSchema = new ResumeSchema().getSchema();
+ return resumeSchema;
+};
+
+export {getSchema, graphqlQuery};
diff --git a/tools/routes/api.js b/tools/routes/api.js
index dc49d7b..caa57ac 100644
--- a/tools/routes/api.js
+++ b/tools/routes/api.js
@@ -1,23 +1,25 @@
-import { graphql } from 'graphql';
import graphqlHTTP from 'express-graphql';
import bodyParser from 'body-parser';
-import ResumeSchema from '../../api/graphql';
+import {getSchema, graphqlQuery} from '../middlewares/graphql-query';
const ApiRouter = (app, express) => {
const router = express.Router();
- const resumeSchema = new ResumeSchema().getSchema();
+ const resumeSchema = getSchema();
- router.use('/graphql', graphqlHTTP(() => ({
- resumeSchema,
- pretty: true
- })));
+ router.use('/graphql', graphqlHTTP({
+ schema: resumeSchema,
+ pretty: true,
+ graphiql: true
+ }));
router.post('/resume', bodyParser.text(), (req, res) => {
- graphql(resumeSchema, req.body).then( function(result) {
- res.send(JSON.stringify(result,null,' '));
- }).catch(e => res.send(e));
+ graphqlQuery(req.body).then( function(result) {
+ res.send({result});
+ }).catch(e => {
+ res.status(502).send({errors: e});
+ });
});
return router;
diff --git a/tools/routes/app.js b/tools/routes/app.js
index 4f523f4..23d05a1 100644
--- a/tools/routes/app.js
+++ b/tools/routes/app.js
@@ -3,6 +3,8 @@ import path from 'path';
import Mailer from '../middlewares/mailer_sendgrid';
import Messager from '../middlewares/messager';
import {generatePDF} from '../middlewares/generate-pdf';
+import {encrypt, decrypt} from '../middlewares/cryptoModule';
+import {graphqlQuery} from '../middlewares/graphql-query';
import * as Templates from '../../web/templates';
const AppRoutes = (app, express) => {
@@ -11,7 +13,7 @@ const AppRoutes = (app, express) => {
const mailService = new Mailer();
const messager = new Messager();
- router.post('/download', bodyParser.json() , function(req, res){
+ router.post('/download', bodyParser.json() , (req, res) => {
let Comp = Templates[`Template${req.body.templateId}`];
const filename = `Resume-${req.body.templateId}-${new Date().getTime()}`;
const filePath = path.join(__dirname,`../generated_files/${filename}.pdf`);
@@ -19,11 +21,11 @@ const AppRoutes = (app, express) => {
.then((response)=> res.send(response))
.catch((error) => {
console.error('Error downloading resume', error);
- res.status(500).send('Error downloading resume. Please try again after some time');
+ res.status(502).send({errors: 'Error downloading resume. Please try again after some time'});
});
});
- router.post('/email', bodyParser.json() , function(req, res){
+ router.post('/email', bodyParser.json() , (req, res) => {
let Comp = Templates[`Template${req.body.templateId}`];
const filename = `Resume-${req.body.templateId}-${new Date().getTime()}`;
const filePath = path.join(__dirname,`../generated_files/${filename}.pdf`);
@@ -32,18 +34,44 @@ const AppRoutes = (app, express) => {
.then(() => res.send({ok: true, statusText: 'Email sent successfully'}))
.catch((error) => {
console.error('Error emailing resume', error);
- res.status(500).send({ok: false, statusText: 'Error sending resume. Please try again after sometime'});
+ res.status(502).send({errors: 'Error sending resume. Please try again after sometime'});
});
});
- router.get('/template/:id', bodyParser.json() , function(req, res){
+ router.post('/share', bodyParser.json() , (req, res) => {
+ const ciphertext = encrypt(req.body.id);
+ const link = `${req.protocol}://${req.headers.host}/resume/${ciphertext}`;
+ const share = JSON.stringify(JSON.stringify({link}));
+ var query = `mutation { update (id: "${req.body.id}", share: ${share}) { id } }`;
+ graphqlQuery(query).then(() => {
+ res.send({result: {link}});
+ }).catch((error) => {
+ console.error('Error sharing resume', error);
+ res.status(502).send({errors: 'Error sharing resume. Please try again after sometime'});
+ });
+ });
+
+ router.get('/resume/:id', (req, res) => {
+ const decryptedId = decrypt(req.params.id);
+ var query = `query { resumes (id: "${decryptedId}") { id, resumeid, cvdata, template } }`;
+ graphqlQuery(query).then((result) => {
+ let Comp = Templates[`Template${JSON.parse(result.template).id}`];
+ const html = app.locals.getComponentAsHTML(Comp, JSON.parse(result.cvdata), JSON.parse(result.template).color);
+ res.send(html);
+ }).catch((error) => {
+ console.error('Error getting resume', error);
+ res.status(502).send({errors: 'Error getting resume. Please try again after sometime'});
+ });
+ });
+
+ router.get('/template/:id', bodyParser.json() , (req, res) => {
const json = require('../../mock/snehajain.json');
let Comp = Templates[`Template${req.params.id}`];
const html = app.locals.getComponentAsHTML(Comp, json);
res.send(html);
});
- router.post('/feedback', bodyParser.json() , function(req, res){
+ router.post('/feedback', bodyParser.json() , (req, res) => {
messager.sendFeedback(req.body);
mailService.sendFeedback(req.body);
res.sendStatus(204);
diff --git a/tools/server.js b/tools/server.js
index bcf7e7b..396dbf6 100644
--- a/tools/server.js
+++ b/tools/server.js
@@ -46,9 +46,9 @@ app.locals.renderIndex = (res, data) => {
}, res);
};
-app.locals.getComponentAsHTML = (Component, cvdata, designColor) => {
+app.locals.getComponentAsHTML = (Component, cvdata, templateColor) => {
try {
- return ReactDOMServer.renderToStaticMarkup();
+ return ReactDOMServer.renderToStaticMarkup();
} catch (e) {
console.error(e);
return '
Some Error Occured
';
diff --git a/web/actions/index.js b/web/actions/index.js
index 0600d6c..f7b5e5e 100644
--- a/web/actions/index.js
+++ b/web/actions/index.js
@@ -2,3 +2,4 @@ export * from './analytics';
export * from './cvform/';
export * from './app';
export * from './template';
+export * from './share';
diff --git a/web/actions/share.js b/web/actions/share.js
new file mode 100644
index 0000000..f2b86a3
--- /dev/null
+++ b/web/actions/share.js
@@ -0,0 +1,6 @@
+const changeShareLink = (link) => ({
+ type: 'CHANGE_SHARE_LINK',
+ payload: link
+});
+
+export {changeShareLink};
diff --git a/web/api/resume.js b/web/api/resume.js
index b6321b7..cadfaaa 100644
--- a/web/api/resume.js
+++ b/web/api/resume.js
@@ -7,77 +7,80 @@ promise.polyfill();
class ResumeService {
- static add(user, resumeid, cvdata, templateid, templatecolor) {
+ handleResponse(res) {
+ return new Promise((resolve, reject) => {
+ if (res.ok || res.status === 502) {
+ res.json().then(data => {
+ if (data.errors) reject(data.errors);
+ else resolve(data.result);
+ });
+ } else reject(res.statusText);
+ });
+ }
+
+ add(user, resumeid, cvdata, templateid, templatecolor, share) {
const template = JSON.stringify(JSON.stringify({id: templateid, color: templatecolor}));
- var query = `mutation { add (id: "${user.id}_${resumeid}", userid: ${user.id}, resumeid: ${resumeid}, cvdata: ${JSON.stringify(JSON.stringify(cvdata))}, template: ${template}) { id } }`;
- return new Promise((resolve) => {
+ var query = `mutation { add (id: "${user.id}_${resumeid}", userid: ${user.id}, resumeid: ${resumeid}, cvdata: ${JSON.stringify(JSON.stringify(cvdata))}, template: ${template}, share: ${JSON.stringify(JSON.stringify(share))}) { id } }`;
+ return new Promise((resolve, reject) => {
fetch('/api/resume', {
method: 'POST',
body: query
- }).then(data => data.json())
- .then((data) => {
- if (data.errors) throw data.errors;
- else resolve({});
- }).catch((err) => {
- window.sendErr('ResumeService add err', err);
- resolve({});
+ }).then(this.handleResponse)
+ .then(() => resolve())
+ .catch((err) => {
+ window.sendErr(`ResumeService add err: ${JSON.stringify(err)}`);
+ reject();
});
});
}
- static get(user) {
- var query = `query { resumes (userid: ${user.id}) { id, resumeid, cvdata, template } }`;
- return new Promise((resolve) => {
+ get(user) {
+ var query = `query { resumes (userid: ${user.id}) { id, resumeid, cvdata, template, share } }`;
+ return new Promise((resolve, reject) => {
fetch('/api/resume', {
method: 'POST',
body: query
- }).then(data => data.json())
- .then((data) => {
- if (data.errors) throw data.errors;
- else resolve(data);
- }).catch((err) => {
- window.sendErr('ResumeService get err:', err);
- resolve({data: {resumes: []}});
+ }).then(this.handleResponse)
+ .then((data) => resolve(data))
+ .catch((err) => {
+ window.sendErr(`ResumeService get err: ${JSON.stringify(err)}`);
+ reject();
});
});
}
- static update(user, resumeid, cvdata) {
- return new Promise((resolve) => {
+ update(user, resumeid, cvdata) {
+ return new Promise((resolve, reject) => {
var query = `mutation { update (id: "${user.id}_${resumeid}", cvdata: ${JSON.stringify(JSON.stringify(cvdata))}) { id } }`;
fetch('/api/resume', {
method: 'POST',
body: query
- }).then(data => data.json())
- .then((res) => {
- if (res.errors) throw res.errors;
- else resolve();
- }).catch((err) => {
- window.sendErr('ResumeService update err:', err);
- resolve();
+ }).then(this.handleResponse)
+ .then(() => resolve())
+ .catch((err) => {
+ window.sendErr(`ResumeService update err: ${JSON.stringify(err)}`);
+ reject();
});
});
}
- static updateTemplate(user, resumeid, templateid, templatecolor) {
+ updateTemplate(user, resumeid, templateid, templatecolor) {
const template = JSON.stringify(JSON.stringify({id: templateid, color: templatecolor}));
- return new Promise((resolve) => {
+ return new Promise((resolve, reject) => {
var query = `mutation { update (id: "${user.id}_${resumeid}", template: ${template}) { id } }`;
fetch('/api/resume', {
method: 'POST',
body: query
- }).then(data => data.json())
- .then((res) => {
- if (res.errors) throw res.errors;
- else resolve();
- }).catch((err) => {
- window.sendErr('ResumeService update template err:', err);
- resolve();
+ }).then(this.handleResponse)
+ .then(() => resolve())
+ .catch((err) => {
+ window.sendErr(`ResumeService updateTemplate err: ${JSON.stringify(err)}`);
+ reject();
});
});
}
- static download(data) {
+ download(data) {
return new Promise((resolve, reject) => {
fetch('/download', {
method: 'POST',
@@ -94,13 +97,13 @@ class ResumeService {
fileSaver.saveAs(blob, 'resume.pdf');
resolve();
}).catch((err) => {
- window.sendErr('ResumeService download err:', err);
+ window.sendErr(`ResumeService download err: ${JSON.stringify(err)}`);
reject(err);
});
});
}
- static email(data) {
+ email(data) {
return new Promise((resolve, reject) => {
fetch('/email', {
method: 'POST',
@@ -108,14 +111,29 @@ class ResumeService {
'Content-Type': 'application/json'
},
body: data
- }).then((res) => {
- if (res.ok)
- resolve();
- else throw res.statusText;
- }).catch((err) => {
- window.sendErr('ResumeService email err:', err);
- reject({message: err});
- });
+ }).then(this.handleResponse)
+ .then(() => resolve())
+ .catch((err) => {
+ window.sendErr(`ResumeService email err: ${JSON.stringify(err)}`);
+ reject();
+ });
+ });
+ }
+
+ share(data) {
+ return new Promise((resolve, reject) => {
+ fetch('/share', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: data
+ }).then(this.handleResponse)
+ .then(({link}) => resolve(link))
+ .catch((err) => {
+ window.sendErr(`ResumeService share err: ${JSON.stringify(err)}`);
+ reject();
+ });
});
}
}
diff --git a/web/clientRenderer.js b/web/clientRenderer.js
index ee7b006..8bf033c 100644
--- a/web/clientRenderer.js
+++ b/web/clientRenderer.js
@@ -77,38 +77,29 @@ if (document) {
};
const main = () => {
- try {
- /* eslint-disable no-underscore-dangle */
- const preloadedState = window.__REDUX_STATE__;
- if (!preloadedState.user) {
- const user_id = UserService.get();
- if (user_id) {
- preloadedState.user = {id: user_id};
- } else {
- preloadedState.user = {id: new Date().getTime()};
- UserService.add(preloadedState.user);
- }
- preloadedState.user.isLoggedIn = false;
+ /* eslint-disable no-underscore-dangle */
+ const preloadedState = window.__REDUX_STATE__;
+ const resumeService = new ResumeService();
+ if (!preloadedState.user) {
+ const user_id = UserService.get();
+ if (user_id) {
+ preloadedState.user = {id: user_id};
+ } else {
+ preloadedState.user = {id: new Date().getTime()};
+ UserService.add(preloadedState.user);
}
- ResumeService.get(preloadedState.user).then(res => {
- if (res.errors) {
- throw `ResumeService get error: ${res.errors}`;
- }
- if (res.data.resumes.length === 0) {
- preloadedState.user.isNew = true;
- } else {
- preloadedState.cvform = htmlToJson(res.data.resumes[0].cvdata);
- preloadedState.template = JSON.parse(res.data.resumes[0].template);
- }
- render(preloadedState);
- }).catch((e) => {
- window.sendErr(e.message);
- showError();
- });
- } catch (err) {
- window.sendErr(err.stack);
- showError();
+ preloadedState.user.isLoggedIn = false;
}
+ resumeService.get(preloadedState.user).then(res => {
+ if (!res) {
+ preloadedState.user.isNew = true;
+ } else {
+ preloadedState.cvform = htmlToJson(res.cvdata);
+ preloadedState.template = JSON.parse(res.template);
+ preloadedState.share = JSON.parse(res.share);
+ }
+ render(preloadedState);
+ }).catch(() => showError());
};
main();
}
diff --git a/web/components/email-dialog/index.js b/web/components/email-dialog/index.js
index 68be9df..5be68ef 100644
--- a/web/components/email-dialog/index.js
+++ b/web/components/email-dialog/index.js
@@ -25,6 +25,7 @@ class EmailDialog extends React.Component{
emailError: null,
emailSucess: false
};
+ this.resumeService = new ResumeService();
this.email = this.email.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleFormChange = this.handleFormChange.bind(this);
@@ -38,7 +39,7 @@ class EmailDialog extends React.Component{
if (this.state.fullname.error || this.state.email.error || this.state.message.error) {
return;
}
- this.props.trackEmail();
+ this.props.fireButtonClick('email');
this.setState({emailing: true});
const data = JSON.stringify({
cvdata: this.props.cvdata,
@@ -50,8 +51,8 @@ class EmailDialog extends React.Component{
message: this.state.message.value
}
});
- ResumeService.updateTemplate(this.props.user, 1, this.props.templateId, this.props.templateColor)
- .then(() => ResumeService.email(data))
+ this.resumeService.updateTemplate(this.props.user, 1, this.props.templateId, this.props.templateColor)
+ .then(() => this.resumeService.email(data))
.then(() => {
this.setState({
fullname: {value: '', error: ''},
@@ -171,11 +172,11 @@ const mapStateToProps = (state) => ({
});
const mapDispatchToProps = dispatch => ({
- trackEmail: () => dispatch(ACTIONS.fireButtonClick('email'))
+ fireButtonClick: (buttonName) => dispatch(ACTIONS.fireButtonClick(buttonName))
});
EmailDialog.propTypes = {
- trackEmail: PropTypes.func.isRequired,
+ fireButtonClick: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
cvdata: PropTypes.object.isRequired,
templateId: PropTypes.number.isRequired,
diff --git a/web/components/form-feedback/index.js b/web/components/form-feedback/index.js
index 5428cc4..8069afc 100644
--- a/web/components/form-feedback/index.js
+++ b/web/components/form-feedback/index.js
@@ -24,6 +24,10 @@ class FormFeedback extends React.Component{
}
submitForm(event) {
event.preventDefault();
+ ['fullname', 'email', 'message'].forEach((property) => {
+ if (!this.state[property].value)
+ this.handleChange({target: {value: '', required: true}}, property);
+ });
if (this.state.fullname.error || this.state.email.error || this.state.message.error) return;
var postData = {
fullname: this.state.fullname.value,
@@ -95,7 +99,7 @@ class FormFeedback extends React.Component{
label="Submit"
primary={true}
onClick={this.submitForm}
- style={{marginTop: '12px'}}
+ style={{marginTop: '28px'}}
/>
);
diff --git a/web/components/page-header/index.js b/web/components/page-header/index.js
index dad3149..894fe94 100644
--- a/web/components/page-header/index.js
+++ b/web/components/page-header/index.js
@@ -33,7 +33,8 @@ class PageHeader extends React.Component {
window.addEventListener('resize', this.handleWidth);
/* global gapi FB*/
window.onGapiLoaded = () => {
- gapi.follow.go();
+ gapi.follow.go('g-follow');
+ gapi.plusone.render('g-plusone');
this.enableLike();
};
window.onFbLoaded = () => {
@@ -48,7 +49,7 @@ class PageHeader extends React.Component {
handleWidth() {
this.props.changeView({
- mobileView: window.innerWidth <= 604
+ mobileView: window.innerWidth <= 768
});
}
@@ -112,14 +113,14 @@ class PageHeader extends React.Component {
- {this.state.displayLike &&
}
diff --git a/web/components/share-dialog/index.js b/web/components/share-dialog/index.js
new file mode 100644
index 0000000..80b45ce
--- /dev/null
+++ b/web/components/share-dialog/index.js
@@ -0,0 +1,105 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+
+import muiThemeable from 'material-ui/styles/muiThemeable';
+import Dialog from 'material-ui/Dialog';
+import RaisedButton from 'material-ui/RaisedButton';
+
+import {ResumeService} from '../../api';
+import {jsonToHtml} from '../../utils/parse-cvform';
+import * as ACTIONS from '../../actions';
+
+import './small.less';
+
+class ShareDialog extends React.Component{
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ sharing: false,
+ shareError: null,
+ emailSucess: false
+ };
+ this.share = this.share.bind(this);
+ this.resumeService = new ResumeService();
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (!this.props.isOpen && nextProps.isOpen && !this.props.share.link) {
+ this.share();
+ }
+ }
+
+ share() {
+ this.props.fireButtonClick('share');
+ this.setState({sharing: true});
+ this.resumeService.updateTemplate(this.props.user, 1, this.props.templateId, this.props.templateColor)
+ .then(() => this.resumeService.share(JSON.stringify({id: `${this.props.user.id}_1`})))
+ .then((link) => {
+ this.setState({
+ sharing: false
+ });
+ this.props.updateLink(link);
+ }).catch(() => {
+ this.setState({sharing: false, shareError: 'An error occured. please try again'});
+ });
+ }
+
+ render() {
+ return (
+
+
]}
+ modal={false}
+ open={this.props.isOpen}
+ onRequestClose={this.props.toggle} >
+
+
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => ({
+ cvdata: jsonToHtml(state.cvform),
+ user: state.user,
+ templateId: state.template.id,
+ templateColor: state.template.color,
+ mobileView: state.app.mobileView,
+ share: state.share
+});
+
+const mapDispatchToProps = dispatch => ({
+ fireButtonClick: (buttonName) => dispatch(ACTIONS.fireButtonClick(buttonName)),
+ updateLink: (link) => dispatch(ACTIONS.changeShareLink(link))
+});
+
+ShareDialog.propTypes = {
+ fireButtonClick: PropTypes.func.isRequired,
+ user: PropTypes.object.isRequired,
+ cvdata: PropTypes.object.isRequired,
+ templateId: PropTypes.number.isRequired,
+ templateColor: PropTypes.string.isRequired,
+ toggle: PropTypes.func.isRequired,
+ isOpen: PropTypes.bool.isRequired,
+ share: PropTypes.object.isRequired,
+ updateLink: PropTypes.func.isRequired
+};
+
+export default muiThemeable()(connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ShareDialog));
diff --git a/web/components/share-dialog/small.less b/web/components/share-dialog/small.less
new file mode 100644
index 0000000..0cd04af
--- /dev/null
+++ b/web/components/share-dialog/small.less
@@ -0,0 +1,16 @@
+@import '../../common/styles/colors.less';
+
+.share-dialog {
+ .email-form {
+ max-width: 435px;
+ margin: auto;
+ }
+ .error {
+ color: @errorColor;
+ font-size: 14px;
+ }
+ a {
+ color: @primary1Color;
+ text-decoration: underline;
+ }
+}
diff --git a/web/index.js b/web/index.js
index de643bc..7012bdc 100644
--- a/web/index.js
+++ b/web/index.js
@@ -13,11 +13,12 @@ class Page extends React.Component {
constructor(props) {
super(props);
+ this.resumeService = new ResumeService();
this.reduxStore = configureStore(props.preloadedState);
this.reduxStore.dispatch(ACTIONS.initState());
const state = this.reduxStore.getState();
if (state.user.isNew)
- ResumeService.add(state.user, 1, jsonToHtml(state.cvform), state.template.id, state.template.color);
+ this.resumeService.add(state.user, 1, jsonToHtml(state.cvform), state.template.id, state.template.color, state.share).catch(e => console.log(e));
}
componentDidMount() {
diff --git a/web/pages/editor/index.js b/web/pages/editor/index.js
index 5e9cc9c..62a8fc1 100644
--- a/web/pages/editor/index.js
+++ b/web/pages/editor/index.js
@@ -30,6 +30,7 @@ class Editor extends React.Component {
this.state = {
stepIndex: 0
};
+ this.resumeService = new ResumeService();
this.stepCount = 6;
this.steps = ['Personal', 'Profile', 'Skill', 'Job', 'Education', 'Misc'];
this.preview = this.preview.bind(this);
@@ -45,7 +46,7 @@ class Editor extends React.Component {
preview() {
this.props.trackPreview();
- ResumeService.update(this.props.user, 1, jsonToHtml(this.props.cvdata))
+ this.resumeService.update(this.props.user, 1, jsonToHtml(this.props.cvdata))
.then(() => browserHistory.push('/preview'))
.catch(() => alert('Some error occured. Please try again'));
}
diff --git a/web/pages/editor/small.less b/web/pages/editor/small.less
index d8e5792..12f30ea 100644
--- a/web/pages/editor/small.less
+++ b/web/pages/editor/small.less
@@ -32,14 +32,14 @@
}
}
.form-section {
- max-width: 604px;
+ max-width: 768px;
padding: 24px;
margin: auto;
padding-bottom: 24px;
}
}
-@media all and (max-width: 604px) {
+@media all and (max-width: 768px) {
.editor {
.tabs {
> div:first-child {
diff --git a/web/pages/preview/index.js b/web/pages/preview/index.js
index 401f6ab..507172c 100644
--- a/web/pages/preview/index.js
+++ b/web/pages/preview/index.js
@@ -6,10 +6,14 @@ import { browserHistory } from 'react-router';
import muiThemeable from 'material-ui/styles/muiThemeable';
import RaisedButton from 'material-ui/RaisedButton';
import {Toolbar} from 'material-ui/Toolbar';
+import IconMenu from 'material-ui/IconMenu';
+import MenuItem from 'material-ui/MenuItem';
import DownloadIcon from 'material-ui/svg-icons/file/cloud-download';
import EmailIcon from 'material-ui/svg-icons/communication/email';
import ChevronLeft from 'material-ui/svg-icons/navigation/chevron-left';
import PrintIcon from 'material-ui/svg-icons/action/print';
+import ShareIcon from 'material-ui/svg-icons/social/share';
+import LinkIcon from 'material-ui/svg-icons/content/link';
import ColorLens from 'material-ui/svg-icons/image/color-lens';
import {ResumeService} from '../../api';
@@ -19,6 +23,7 @@ import * as ACTIONS from '../../actions';
import * as Templates from '../../templates';
import PageHeaderContainer from '../../containers/page-header';
import EmailDialog from '../../components/email-dialog';
+import ShareDialog from '../../components/share-dialog';
import './small.less';
@@ -28,11 +33,14 @@ class Preview extends React.Component {
this.state = {
error: null,
downloading: false,
- emailDialogOpen: false
+ emailDialogOpen: false,
+ shareDialogOpen: false
};
this.download = this.download.bind(this);
this.print = this.print.bind(this);
this.toggleEmailDialog = this.toggleEmailDialog.bind(this);
+ this.toggleShareDialog = this.toggleShareDialog.bind(this);
+ this.resumeService = new ResumeService();
}
choose() {
@@ -43,16 +51,20 @@ class Preview extends React.Component {
this.setState({emailDialogOpen: !this.state.emailDialogOpen});
}
+ toggleShareDialog() {
+ this.setState({shareDialogOpen: !this.state.shareDialogOpen});
+ }
+
download() {
- this.props.trackDownload();
+ this.props.fireButtonClick('download');
this.setState({downloading: true});
const data = JSON.stringify({
cvdata: this.props.cvdata,
templateId: this.props.templateId,
templateColor: this.props.templateColor
});
- ResumeService.updateTemplate(this.props.user, 1, this.props.templateId, this.props.templateColor)
- .then(ResumeService.download(data))
+ this.resumeService.updateTemplate(this.props.user, 1, this.props.templateId, this.props.templateColor)
+ .then(this.resumeService.download(data))
.then(() => this.setState({downloading: false}))
.catch(e => this.setState({downloading: false, error: e.message}));
}
@@ -62,30 +74,42 @@ class Preview extends React.Component {
}
print() {
- this.props.trackPrint();
- ResumeService.updateTemplate(this.props.user, 1, this.props.templateId, this.props.templateColor)
+ this.props.fireButtonClick('print');
+ this.resumeService.updateTemplate(this.props.user, 1, this.props.templateId, this.props.templateColor)
.then(() => window.print())
.catch(e => this.setState({error: e.message}));
}
render() {
let Comp = Templates[`Template${this.props.templateId}`] || Templates['Template1'];
+ const style = {
+ minWidth: '48px',
+ marginLeft: '8px'
+ };
+ const labelStyle = {paddingRight: 8};
+ const buttonStyle = {};
+ const allStyles = {style, labelStyle, buttonStyle};
return (
- }/>
- }
- />
- } />
+ }/>
+ }/>
+ } />
- } />
- } />
- } />
+ } />}
+ open={this.state.openMenu}
+ onRequestChange={this.handleOnRequestChange}
+ >
+ } />
+ }/>
+ }/>
+
+ } />
@@ -94,6 +118,7 @@ class Preview extends React.Component {
+
);
}
@@ -111,8 +136,7 @@ const mapDispatchToProps = dispatch => ({
changeTemplateColor: (e) => {
dispatch(ACTIONS.changeTemplateColor(e.target.value));
},
- trackDownload: () => dispatch(ACTIONS.fireButtonClick('download')),
- trackPrint: () => dispatch(ACTIONS.fireButtonClick('print'))
+ fireButtonClick: (buttonName) => dispatch(ACTIONS.fireButtonClick(buttonName))
});
export default muiThemeable()(connect(
@@ -127,8 +151,7 @@ Preview.propTypes = {
templateId: PropTypes.number.isRequired,
templateColor: PropTypes.string.isRequired,
changeTemplateColor: PropTypes.func.isRequired,
- trackDownload: PropTypes.func.isRequired,
- trackPrint: PropTypes.func.isRequired
+ fireButtonClick: PropTypes.func.isRequired
};
Preview.defaultProps = {};
diff --git a/web/pages/preview/small.less b/web/pages/preview/small.less
index 247e1cf..ac9e943 100644
--- a/web/pages/preview/small.less
+++ b/web/pages/preview/small.less
@@ -14,12 +14,10 @@
justify-content: space-between;
}
.colorpicker {
- margin-top: 0 !important;
- margin-right: 12px;
- height: 40px !important;
+ height: 37px!important;
width: 48px;
padding: 0;
- margin-top: -2px !important;
+ margin-top: 0 !important;
}
}
.error {
diff --git a/web/pages/template/index.js b/web/pages/template/index.js
index 3fe2083..da90790 100644
--- a/web/pages/template/index.js
+++ b/web/pages/template/index.js
@@ -6,7 +6,6 @@ import { connect } from 'react-redux';
import muiThemeable from 'material-ui/styles/muiThemeable';
import {Toolbar} from 'material-ui/Toolbar';
import RaisedButton from 'material-ui/RaisedButton';
-import Avatar from 'material-ui/Avatar';
import PreviewIcon from 'material-ui/svg-icons/action/visibility';
import PageHeaderContainer from '../../containers/page-header';
@@ -22,13 +21,14 @@ class Template extends React.Component {
constructor(props) {
super(props);
this.preview = this.preview.bind(this);
+ this.resumeService = new ResumeService();
}
preview() {
this.props.trackPreview();
const templateid = this.props.templateId;
const templatecolor = tilesData[this.props.templateId - 1].templateColor;
- ResumeService.updateTemplate(this.props.user, 1, templateid, templatecolor)
+ this.resumeService.updateTemplate(this.props.user, 1, templateid, templatecolor)
.then(() => browserHistory.push('/preview'))
.catch(() => alert('Some error occured. Please try again'));
}
diff --git a/web/reducers/index.js b/web/reducers/index.js
index 90d0602..cd23e4c 100644
--- a/web/reducers/index.js
+++ b/web/reducers/index.js
@@ -6,12 +6,14 @@ import build from './build';
import template from './template';
import user from './user';
import app from './app';
+import share from './share';
const reducer = combineReducers({
cvform,
user,
build,
template,
+ share,
app,
routing: routerReducer
});
diff --git a/web/reducers/share.js b/web/reducers/share.js
new file mode 100644
index 0000000..12f2c72
--- /dev/null
+++ b/web/reducers/share.js
@@ -0,0 +1,17 @@
+const initialState = {
+ link: null
+};
+
+const share = (state = initialState, action) => {
+
+ switch (action.type) {
+
+ case 'CHANGE_SHARE_LINK':
+ return {...state, link: action.payload};
+
+ default:
+ return { ...state };
+ }
+};
+
+export default share;