diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 75ab08de4..58e84e4fc 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -32,6 +32,7 @@ const Editor = createClass({ onTextChange : ()=>{}, onStyleChange : ()=>{}, onMetaChange : ()=>{}, + reportError : ()=>{}, renderer : 'legacy' }; @@ -291,7 +292,8 @@ const Editor = createClass({ rerenderParent={this.rerenderParent} /> + onChange={this.props.onMetaChange} + reportError={this.props.reportError}/> ; } }, diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 03f40e7cf..e19f6644a 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -4,7 +4,7 @@ const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); -const request = require('superagent'); +const request = require('../../utils/request-middleware.js'); const Nav = require('naturalcrit/nav/nav.jsx'); const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx'); @@ -37,7 +37,8 @@ const MetadataEditor = createClass({ renderer : 'legacy', theme : '5ePHB' }, - onChange : ()=>{} + onChange : ()=>{}, + reportError : ()=>{} }; }, @@ -121,8 +122,12 @@ const MetadataEditor = createClass({ request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`) .send() - .end(function(err, res){ - window.location.href = '/'; + .end((err, res)=>{ + if(err) { + this.props.reportError(err); + } else { + window.location.href = '/'; + } }); }, diff --git a/client/homebrew/navbar/error-navitem.jsx b/client/homebrew/navbar/error-navitem.jsx new file mode 100644 index 000000000..efee04019 --- /dev/null +++ b/client/homebrew/navbar/error-navitem.jsx @@ -0,0 +1,85 @@ +require('./error-navitem.less'); +const React = require('react'); +const Nav = require('naturalcrit/nav/nav.jsx'); +const createClass = require('create-react-class'); + +const ErrorNavItem = createClass({ + getDefaultProps : function() { + return { + error : '', + parent : null + }; + }, + render : function() { + const clearError = ()=>{ + const state = { + error : null + }; + if(this.props.parent.state.isSaving) { + state.isSaving = false; + } + this.props.parent.setState(state); + }; + + const error = this.props.error; + const response = error.response; + const status = response.status; + const message = response.body?.message; + let errMsg = ''; + try { + errMsg += `${error.toString()}\n\n`; + errMsg += `\`\`\`\n${error.stack}\n`; + errMsg += `${JSON.stringify(response.error, null, ' ')}\n\`\`\``; + console.log(errMsg); + } catch (e){} + + if(status === 409) { + return + Oops! +
+ {message ?? 'Conflict: please refresh to get latest changes'} +
+
; + } else if(status === 412) { + return + Oops! +
+ {message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'} +
+
; + } + + if(response.req.url.match(/^\/api.*Google.*$/m)){ + return + Oops! +
+ Looks like your Google credentials have + expired! Visit our log in page to sign out + and sign back in with Google, + then try saving again! + +
+ Sign In +
+
+
+ Not Now +
+
+
; + } + + return + Oops! +
+ Looks like there was a problem saving.
+ Report the issue + here + . +
+
; + } +}); + +module.exports = ErrorNavItem; \ No newline at end of file diff --git a/client/homebrew/navbar/error-navitem.less b/client/homebrew/navbar/error-navitem.less new file mode 100644 index 000000000..8a7cabb19 --- /dev/null +++ b/client/homebrew/navbar/error-navitem.less @@ -0,0 +1,77 @@ +.navItem { + &.error { + position : relative; + background-color : @red; + } + + .errorContainer{ + animation-name: glideDown; + animation-duration: 0.4s; + position : absolute; + top : 100%; + left : 50%; + z-index : 1000; + width : 140px; + padding : 3px; + color : white; + background-color : #333; + border : 3px solid #444; + border-radius : 5px; + transform : translate(-50% + 3px, 10px); + text-align : center; + font-size : 10px; + font-weight : 800; + text-transform : uppercase; + a{ + color : @teal; + } + &:before { + content: ""; + width: 0px; + height: 0px; + position: absolute; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid transparent; + border-bottom: 10px solid #444; + left: 53px; + top: -23px; + } + &:after { + content: ""; + width: 0px; + height: 0px; + position: absolute; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid transparent; + border-bottom: 10px solid #333; + left: 53px; + top: -19px; + } + .deny { + width : 48%; + margin : 1px; + padding : 5px; + background-color : #333; + display : inline-block; + border-left : 1px solid #666; + .animate(background-color); + &:hover{ + background-color : red; + } + } + .confirm { + width : 48%; + margin : 1px; + padding : 5px; + background-color : #333; + display : inline-block; + color : white; + .animate(background-color); + &:hover{ + background-color : teal; + } + } + } +} \ No newline at end of file diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx index d17c020af..aa2f47431 100644 --- a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx @@ -4,7 +4,7 @@ const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); const moment = require('moment'); -const request = require('superagent'); +const request = require('../../../../utils/request-middleware.js'); const googleDriveIcon = require('../../../../googleDrive.png'); const dedent = require('dedent-tabs').default; @@ -18,7 +18,8 @@ const BrewItem = createClass({ description : '', authors : [], stubbed : true - } + }, + reportError : ()=>{} }; }, @@ -33,8 +34,12 @@ const BrewItem = createClass({ request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`) .send() - .end(function(err, res){ - location.reload(); + .end((err, res)=>{ + if(err) { + this.props.reportError(err); + } else { + location.reload(); + } }); }, diff --git a/client/homebrew/pages/basePages/listPage/listPage.jsx b/client/homebrew/pages/basePages/listPage/listPage.jsx index 162aba5e4..b80b04899 100644 --- a/client/homebrew/pages/basePages/listPage/listPage.jsx +++ b/client/homebrew/pages/basePages/listPage/listPage.jsx @@ -23,7 +23,8 @@ const ListPage = createClass({ brews : [] } ], - navItems : <> + navItems : <>, + reportError : null }; }, getInitialState : function() { @@ -81,7 +82,7 @@ const ListPage = createClass({ if(!brews || !brews.length) return
No Brews.
; return _.map(brews, (brew, idx)=>{ - return ; + return ; }); }, diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 873f5d3d2..87bc7eefb 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -3,7 +3,7 @@ require('./editPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); -const request = require('superagent'); +const request = require('../../utils/request-middleware.js'); const { Meta } = require('vitreum/headtags'); const Nav = require('naturalcrit/nav/nav.jsx'); @@ -12,6 +12,7 @@ const Navbar = require('../../navbar/navbar.jsx'); const NewBrew = require('../../navbar/newbrew.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx'); const PrintLink = require('../../navbar/print.navitem.jsx'); +const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx'); const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; @@ -45,7 +46,7 @@ const EditPage = createClass({ alertLoginToTransfer : false, saveGoogle : this.props.brew.googleId ? true : false, confirmGoogleTransfer : false, - errors : null, + error : null, htmlErrors : Markdown.validate(this.props.brew.text), url : '', autoSave : true, @@ -60,7 +61,6 @@ const EditPage = createClass({ url : window.location.href }); - this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{ @@ -157,7 +157,10 @@ const EditPage = createClass({ this.setState((prevState)=>({ confirmGoogleTransfer : !prevState.confirmGoogleTransfer })); - this.clearErrors(); + this.setState({ + error : null, + isSaving : false + }); }, closeAlerts : function(event){ @@ -173,24 +176,16 @@ const EditPage = createClass({ this.setState((prevState)=>({ saveGoogle : !prevState.saveGoogle, isSaving : false, - errors : null + error : null }), ()=>this.save()); }, - clearErrors : function(){ - this.setState({ - errors : null, - isSaving : false - - }); - }, - save : async function(){ if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); this.setState((prevState)=>({ isSaving : true, - errors : null, + error : null, htmlErrors : Markdown.validate(prevState.brew.text) })); @@ -205,8 +200,9 @@ const EditPage = createClass({ .send(brew) .catch((err)=>{ console.log('Error Updating Local Brew'); - this.setState({ errors: err }); + this.setState({ error: err }); }); + if(!res) return; this.savedBrew = res.body; history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); @@ -266,77 +262,6 @@ const EditPage = createClass({ }, renderSaveButton : function(){ - if(this.state.errors){ - let errMsg = ''; - try { - errMsg += `${this.state.errors.toString()}\n\n`; - errMsg += `\`\`\`\n${this.state.errors.stack}\n`; - errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``; - console.log(errMsg); - } catch (e){} - - // if(this.state.errors.status == '401'){ - // return - // Oops! - //
- // You must be signed in to a Google account - // to save this to
Google Drive!
- // - //
- // Sign In - //
- //
- //
- // Not Now - //
- //
- //
; - // } - - if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){ - return - Oops! -
- Looks like your Google credentials have - expired! Visit our log in page to sign out - and sign back in with Google, - then try saving again! - -
- Sign In -
-
-
- Not Now -
-
-
; - } - - if(this.state.errors.response.error.status === 409) { - const message = this.state.errors.response.body?.message; - return - Oops! -
- {message ? message : 'Conflict: please refresh to get latest changes'} -
-
; - } - - return - Oops! -
- Looks like there was a problem saving.
- Report the issue - here - . -
-
; - } - if(this.state.autoSaveWarning && this.hasChanges()){ this.setAutosaveWarning(); const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60); @@ -380,6 +305,12 @@ const EditPage = createClass({ this.warningTimer; }, + errorReported : function(error) { + this.setState({ + error + }); + }, + renderAutoSaveButton : function(){ return Autosave @@ -424,10 +355,13 @@ const EditPage = createClass({ {this.renderGoogleDriveIcon()} - - {this.renderSaveButton()} - {this.renderAutoSaveButton()} - + {this.state.error ? + : + + {this.renderSaveButton()} + {this.renderAutoSaveButton()} + + } @@ -465,6 +399,7 @@ const EditPage = createClass({ onTextChange={this.handleTextChange} onStyleChange={this.handleStyleChange} onMetaChange={this.handleMetaChange} + reportError={this.errorReported} renderer={this.state.brew.renderer} /> diff --git a/client/homebrew/pages/editPage/editPage.less b/client/homebrew/pages/editPage/editPage.less index 9d3f69221..581e6dfa2 100644 --- a/client/homebrew/pages/editPage/editPage.less +++ b/client/homebrew/pages/editPage/editPage.less @@ -13,10 +13,6 @@ cursor : initial; color : #666; } - &.error{ - position : relative; - background-color : @red; - } } .googleDriveStorage { position : relative; @@ -26,74 +22,4 @@ padding : 0px; margin : -5px; } - .errorContainer{ - animation-name: glideDown; - animation-duration: 0.4s; - position : absolute; - top : 100%; - left : 50%; - z-index : 500; - width : 140px; - padding : 3px; - color : white; - background-color : #333; - border : 3px solid #444; - border-radius : 5px; - transform : translate(-50% + 3px, 10px); - text-align : center; - font-size : 10px; - font-weight : 800; - text-transform : uppercase; - a{ - color : @teal; - } - &:before { - content: ""; - width: 0px; - height: 0px; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid transparent; - border-bottom: 10px solid #444; - left: 53px; - top: -23px; - } - &:after { - content: ""; - width: 0px; - height: 0px; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid transparent; - border-bottom: 10px solid #333; - left: 53px; - top: -19px; - } - .deny { - width : 48%; - margin : 1px; - padding : 5px; - background-color : #333; - display : inline-block; - border-left : 1px solid #666; - .animate(background-color); - &:hover{ - background-color : red; - } - } - .confirm { - width : 48%; - margin : 1px; - padding : 5px; - background-color : #333; - display : inline-block; - color : white; - .animate(background-color); - &:hover{ - background-color : teal; - } - } - } } diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 7e9d9c3b4..9802517b1 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -3,7 +3,7 @@ const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); -const request = require('superagent'); +const request = require('../../utils/request-middleware.js'); const { Meta } = require('vitreum/headtags'); const Nav = require('naturalcrit/nav/nav.jsx'); @@ -12,6 +12,7 @@ const NewBrewItem = require('../../navbar/newbrew.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx'); const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; const AccountNavItem = require('../../navbar/account.navitem.jsx'); +const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); @@ -31,14 +32,18 @@ const HomePage = createClass({ getInitialState : function() { return { brew : this.props.brew, - welcomeText : this.props.brew.text + welcomeText : this.props.brew.text, + error : undefined }; }, handleSave : function(){ request.post('/api') .send(this.state.brew) .end((err, res)=>{ - if(err) return; + if(err) { + this.setState({ error: err }); + return; + } const brew = res.body; window.location = `/edit/${brew.editId}`; }); @@ -54,6 +59,10 @@ const HomePage = createClass({ renderNavbar : function(){ return + {this.state.error ? + : + null + } diff --git a/client/homebrew/pages/homePage/homePage.less b/client/homebrew/pages/homePage/homePage.less index 719e89f22..a7523bd3c 100644 --- a/client/homebrew/pages/homePage/homePage.less +++ b/client/homebrew/pages/homePage/homePage.less @@ -40,4 +40,11 @@ right : 350px; } } + + .navItem.save{ + background-color: @orange; + &:hover{ + background-color: @green; + } + } } diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index cd197f5fb..3e96ff5c0 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -3,13 +3,14 @@ require('./newPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); -const request = require('superagent'); +const request = require('../../utils/request-middleware.js'); const Markdown = require('naturalcrit/markdown.js'); const Nav = require('naturalcrit/nav/nav.jsx'); const Navbar = require('../../navbar/navbar.jsx'); const AccountNavItem = require('../../navbar/account.navitem.jsx'); +const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; const HelpNavItem = require('../../navbar/help.navitem.jsx'); @@ -39,7 +40,7 @@ const NewPage = createClass({ brew : brew, isSaving : false, saveGoogle : (global.account && global.account.googleId ? true : false), - errors : null, + error : null, htmlErrors : Markdown.validate(brew.text) }; }, @@ -122,14 +123,6 @@ const NewPage = createClass({ })); }, - clearErrors : function(){ - this.setState({ - errors : null, - isSaving : false - - }); - }, - save : async function(){ this.setState({ isSaving : true @@ -152,7 +145,7 @@ const NewPage = createClass({ .send(brew) .catch((err)=>{ console.log(err); - this.setState({ isSaving: false, errors: err }); + this.setState({ isSaving: false, error: err }); }); if(!res) return; @@ -164,67 +157,6 @@ const NewPage = createClass({ }, renderSaveButton : function(){ - if(this.state.errors){ - let errMsg = ''; - try { - errMsg += `${this.state.errors.toString()}\n\n`; - errMsg += `\`\`\`\n${this.state.errors.stack}\n`; - errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``; - console.log(errMsg); - } catch (e){} - - // if(this.state.errors.status == '401'){ - // return - // Oops! - //
- // You must be signed in to a Google account - // to save this to
Google Drive!
- // - //
- // Sign In - //
- //
- //
- // Not Now - //
- //
- //
; - // } - - if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){ - return - Oops! -
- Looks like your Google credentials have - expired! Visit our log in page to sign out - and sign back in with Google, - then try saving again! - -
- Sign In -
-
-
- Not Now -
-
-
; - } - - return - Oops! -
- Looks like there was a problem saving.
- Report the issue - here - . -
-
; - } - if(this.state.isSaving){ return save... @@ -254,7 +186,10 @@ const NewPage = createClass({
- {this.renderSaveButton()} + {this.state.error ? + : + this.renderSaveButton() + } {this.renderLocalPrintButton()} diff --git a/client/homebrew/pages/newPage/newPage.less b/client/homebrew/pages/newPage/newPage.less index 4f39e626d..f83827ffb 100644 --- a/client/homebrew/pages/newPage/newPage.less +++ b/client/homebrew/pages/newPage/newPage.less @@ -4,79 +4,5 @@ &:hover{ background-color: @green; } - &.error{ - position : relative; - background-color : @red; - } - } - .errorContainer{ - animation-name: glideDown; - animation-duration: 0.4s; - position : absolute; - top : 100%; - left : 50%; - z-index : 100000; - width : 140px; - padding : 3px; - color : white; - background-color : #333; - border : 3px solid #444; - border-radius : 5px; - transform : translate(-50% + 3px, 10px); - text-align : center; - font-size : 10px; - font-weight : 800; - text-transform : uppercase; - a{ - color : @teal; - } - &:before { - content: ""; - width: 0px; - height: 0px; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid transparent; - border-bottom: 10px solid #444; - left: 53px; - top: -23px; - } - &:after { - content: ""; - width: 0px; - height: 0px; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid transparent; - border-bottom: 10px solid #333; - left: 53px; - top: -19px; - } - .deny { - width : 48%; - margin : 1px; - padding : 5px; - background-color : #333; - display : inline-block; - border-left : 1px solid #666; - .animate(background-color); - &:hover{ - background-color : red; - } - } - .confirm { - width : 48%; - margin : 1px; - padding : 5px; - background-color : #333; - display : inline-block; - color : white; - .animate(background-color); - &:hover{ - background-color : teal; - } - } } } diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx index 7341c61ad..1e051987b 100644 --- a/client/homebrew/pages/userPage/userPage.jsx +++ b/client/homebrew/pages/userPage/userPage.jsx @@ -12,6 +12,7 @@ const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; const Account = require('../../navbar/account.navitem.jsx'); const NewBrew = require('../../navbar/newbrew.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx'); +const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const UserPage = createClass({ displayName : 'UserPage', @@ -19,7 +20,8 @@ const UserPage = createClass({ return { username : '', brews : [], - query : '' + query : '', + error : null }; }, getInitialState : function() { @@ -50,10 +52,19 @@ const UserPage = createClass({ brewCollection : brewCollection }; }, + errorReported : function(error) { + this.setState({ + error + }); + }, navItems : function() { return + {this.state.error ? + : + null + } @@ -63,7 +74,7 @@ const UserPage = createClass({ }, render : function(){ - return ; + return ; } }); diff --git a/client/homebrew/utils/request-middleware.js b/client/homebrew/utils/request-middleware.js new file mode 100644 index 000000000..f6bc2571b --- /dev/null +++ b/client/homebrew/utils/request-middleware.js @@ -0,0 +1,12 @@ +const request = require('superagent'); + +const addHeader = (request)=>request.set('Homebrewery-Version', global.version); + +const requestMiddleware = { + get : (path)=>addHeader(request.get(path)), + put : (path)=>addHeader(request.put(path)), + post : (path)=>addHeader(request.post(path)), + delete : (path)=>addHeader(request.delete(path)), +}; + +module.exports = requestMiddleware; \ No newline at end of file diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 0d3f87a20..f531bdcf9 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -338,6 +338,7 @@ If you believe you should have access to this brew, ask the file owner to invite } }; +router.use('/api', require('./middleware/check-client-version.js')); router.post('/api', asyncHandler(api.newBrew)); router.put('/api/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); router.put('/api/update/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); diff --git a/server/middleware/check-client-version.js b/server/middleware/check-client-version.js new file mode 100644 index 000000000..e9caf6eff --- /dev/null +++ b/server/middleware/check-client-version.js @@ -0,0 +1,12 @@ +module.exports = (req, res, next)=>{ + const userVersion = req.get('Homebrewery-Version'); + const version = require('../../package.json').version; + + if(userVersion != version) { + return res.status(412).send({ + message : `Client version ${userVersion} is out of date. Please save your changes elsewhere and refresh to pick up client version ${version}.` + }); + } + + next(); +};