diff --git a/src/main/frontend/src/components/AddCatalogPriceForm.js b/src/main/frontend/src/components/AddCatalogPriceForm.js index dfb9d92065..3269bf07e3 100644 --- a/src/main/frontend/src/components/AddCatalogPriceForm.js +++ b/src/main/frontend/src/components/AddCatalogPriceForm.js @@ -2,12 +2,162 @@ // IMPORTANT: // You must update ResourceUrl.RESOURCES_VERSION each time whenever you're modified this file! // +// @todo #1342 AddCatalogPriceForm: show a currency class AddCatalogPriceForm extends React.Component { - + constructor(props) { + super(props); + this.state = { + price: null, + catalog: 'michel', + hasServerError: false, + validationErrors: [], + isDisabled: false + }; + this.handleSubmit = this.handleSubmit.bind(this); + this.handleChangePrice = this.handleChangePrice.bind(this); + this.handleChangeCatalog = this.handleChangeCatalog.bind(this); + } + + handleChangePrice(event) { + event.preventDefault(); + this.setState({ + price: event.target.value + }); + } + + handleChangeCatalog(event) { + event.preventDefault(); + this.setState({ + catalog: event.target.value + }); + } + + handleSubmit(event) { + event.preventDefault(); + + this.setState({ + isDisabled: true, + hasServerError: false, + validationErrors: [] + }); + + axios.patch( + this.props.url, + [ + { + op: 'add', + path: `/${this.state.catalog}_price`, + value: this.state.price + } + ], + { + headers: { + [this.props.csrfHeaderName]: this.props.csrfTokenValue, + 'Cache-Control': 'no-store' + }, + validateStatus: status => { + return status == 204 || status == 400; + } + } + ) + .then(response => { + const data = response.data; + if (data.hasOwnProperty('fieldErrors')) { + const fieldErrors = []; + if (data.fieldErrors.price) { + fieldErrors.push(...data.fieldErrors.price); + } + + this.setState({ + isDisabled: false, + validationErrors: fieldErrors + }); + return; + } + + // no need to reset the state as page will be reloaded + window.location.reload(); + }) + .catch(error => { + console.error(error); + this.setState({ isDisabled: false, hasServerError: true }); + }); + } render() { + const hasValidationErrors = this.state.validationErrors.length > 0; return ( -
- ) +
+
+
+ { this.props.l10n['t_server_error'] || 'Server error' } +
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ + { this.state.validationErrors.join(', ') } + + +
+
+
+ ); } } diff --git a/src/main/frontend/src/components/AddCommentForm.js b/src/main/frontend/src/components/AddCommentForm.js index ed94008e32..1671fd2da6 100644 --- a/src/main/frontend/src/components/AddCommentForm.js +++ b/src/main/frontend/src/components/AddCommentForm.js @@ -4,10 +4,114 @@ // class AddCommentForm extends React.Component { - + constructor(props) { + super(props); + this.state = { + comment: '', + validationErrors: [], + hasServerError: false, + isDisabled: false + }; + this.handleSubmit = this.handleSubmit.bind(this); + this.handleChange = this.handleChange.bind(this); + } + + handleChange(event) { + event.preventDefault(); + this.setState({ + comment: event.target.value + }); + } + + handleSubmit(event) { + event.preventDefault(); + + this.setState({ + isDisabled: true, + hasServerError: false, + validationErrors: [] + }); + + axios.patch( + this.props.url, [ + { + op: 'add', + path: '/comment', + value: this.state.comment + } + ], + { + headers: { + [this.props.csrfHeaderName]: this.props.csrfTokenValue, + 'Cache-Control': 'no-store' + }, + validateStatus: status => { + return status == 204 || status == 400; + } + }) + .then(response => { + const data = response.data; + + if (data.hasOwnProperty('fieldErrors')) { + const fieldErrors = []; + if (data.fieldErrors.comment) { + fieldErrors.push(...data.fieldErrors.comment); + } + this.setState({ + isDisabled: false, + validationErrors: fieldErrors + }); + return; + } + + // no need to reset the state as page will be reloaded + window.location.reload(); + }) + .catch(error => { + console.error(error); + this.setState({ isDisabled: false, hasServerError: true }); + }); + } render() { + const hasValidationErrors = this.state.validationErrors.length > 0; return ( -
- ) +
+
+
+ { this.props.l10n['t_server_error'] || 'Server error' } +
+
+ +
+ +
+
+
+ + { this.state.validationErrors.join(', ') } + + +
+
+
+ ); } } diff --git a/src/main/frontend/src/components/AddReleaseYearForm.js b/src/main/frontend/src/components/AddReleaseYearForm.js index bd83d58bc5..dfc238a05e 100644 --- a/src/main/frontend/src/components/AddReleaseYearForm.js +++ b/src/main/frontend/src/components/AddReleaseYearForm.js @@ -4,10 +4,131 @@ // class AddReleaseYearForm extends React.Component { - + constructor(props) { + super(props); + this.state = { + year: null, + hasServerError: false, + validationErrors: [], + isDisabled: false, + }; + this.handleSubmit = this.handleSubmit.bind(this); + this.handleChange = this.handleChange.bind(this); + } + + generateRange(start, end) { + return new Array(end - start + 1) + .fill() + .map((_, idx) => start + idx); + } + + handleChange(event) { + event.preventDefault(); + this.setState({ + year: event.target.value + }); + } + + handleSubmit(event) { + event.preventDefault(); + + this.setState({ + isDisabled: true, + hasServerError: false, + validationErrors: [] + }); + + axios.patch( + this.props.url, + [ + { + op: 'add', + path: '/release_year', + value: this.state.year + } + ], + { + headers: { + [this.props.csrfHeaderName]: this.props.csrfTokenValue, + 'Cache-Control': 'no-store' + }, + validateStatus: status => { + return status == 204 || status == 400; + } + } + ) + .then(response => { + const data = response.data; + if (data.hasOwnProperty('fieldErrors')) { + const fieldErrors = []; + if (data.fieldErrors.year) { + fieldErrors.push(...data.fieldErrors.year); + } + this.setState({ + isDisabled: false, + validationErrors: fieldErrors + }); + return; + } + + // no need to reset the state as page will be reloaded + window.location.reload(); + }) + .catch(error => { + console.error(error); + this.setState({ isDisabled: false, hasServerError: true }); + }); + } + render() { + const rangeOfYears = this.generateRange( + this.props.sinceYear, + this.props.tillYear + ); + const hasValidationErrors = this.state.validationErrors.length > 0; return ( -
- ) +
+
+
+ { this.props.l10n['t_server_error'] || 'Server error' } +
+
+ +
+ +
+
+
+ + { this.state.validationErrors.join(', ') } + + +
+
+
+ ); } } diff --git a/src/main/java/ru/mystamps/web/feature/site/ResourceUrl.java b/src/main/java/ru/mystamps/web/feature/site/ResourceUrl.java index f84db39dc6..483fb0505e 100644 --- a/src/main/java/ru/mystamps/web/feature/site/ResourceUrl.java +++ b/src/main/java/ru/mystamps/web/feature/site/ResourceUrl.java @@ -32,7 +32,7 @@ public final class ResourceUrl { public static final String STATIC_RESOURCES_URL = "https://stamps.filezz.ru"; // MUST be updated when any of our resources were modified - public static final String RESOURCES_VERSION = "v0.4.3.1"; + public static final String RESOURCES_VERSION = "v0.4.3.2"; // CheckStyle: ignore LineLength for next 14 lines private static final String CATALOG_UTILS_JS = "/public/js/" + RESOURCES_VERSION + "/CatalogUtils.min.js"; diff --git a/src/main/webapp/WEB-INF/views/series/info.html b/src/main/webapp/WEB-INF/views/series/info.html index 1b48373903..30296d257e 100644 --- a/src/main/webapp/WEB-INF/views/series/info.html +++ b/src/main/webapp/WEB-INF/views/series/info.html @@ -894,6 +894,51 @@
Add info about selling/buying thi 'altCurrency' : 'CZK' } } + } + }; + var stubResponse; + + switch (outcome) { + case 'success': + case 'failOnField': + stubResponse = possibleResponses[url][outcome]; + break; + case 'failOnForm': + default: + stubResponse = { + status: 500, + statusText: 'Fake Server Error' + }; + } + + responseCount++; + + return new Promise(function delayExecution(resolve) { + setTimeout(resolve, 500 /* 0.5 second */); + + }).then(function returnResponse() { + return stubResponse.status == 500 ? Promise.reject(stubResponse) : Promise.resolve(stubResponse); + }); + }, + patch: function (url) { + var possibleOutcomes = ['failOnForm', 'failOnField', 'success']; + var outcome = possibleOutcomes[responseCount % possibleOutcomes.length]; + var possibleResponses = { + '/series/100': { + 'failOnField': { + status: 400, + data: { + 'fieldErrors': { + 'comment': ['Comment error'], + 'year': ['Year error'], + 'price': ['Price error'] + } + } + }, + 'success': { + status: 200, + data: {} + } } }; var stubResponse; @@ -937,8 +982,9 @@
Add info about selling/buying thi var addReleaseYearProps = { 'csrfHeaderName': [[ ${_csrf.headerName} ]], 'csrfTokenValue': [[ ${_csrf.token} ]], - 'l10n': { - } + 'l10n': {}, + 'sinceYear': 1840, + 'tillYear': new Date().getFullYear() }; var addCatalogPriceProps = { 'csrfHeaderName': [[ ${_csrf.headerName} ]], @@ -951,7 +997,9 @@
Add info about selling/buying thi /*[- */ var addReleaseYearProps = { 'url': '/series/100', - 'l10n': {} + 'l10n': {}, + 'sinceYear': 1840, + 'tillYear': new Date().getFullYear() }; var addCatalogPriceProps = { 'url': '/series/100',