diff --git a/package-lock.json b/package-lock.json index a1d10dff0..258d77fef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2795,6 +2795,11 @@ "source-map": "^0.5.7" }, "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5126,6 +5131,11 @@ "domelementtype": "1" } }, + "dompurify": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.2.tgz", + "integrity": "sha512-BsGR4nDLaC5CNBnyT5I+d5pOeaoWvgVeg6Gq/aqmKYWMPR07131u60I80BvExLAJ0FQEIBQ1BTicw+C5+jOyrg==" + }, "domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", @@ -13113,6 +13123,15 @@ "prop-types": "^15.5.8" } }, + "react-input-mask": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-input-mask/-/react-input-mask-2.0.4.tgz", + "integrity": "sha512-1hwzMr/aO9tXfiroiVCx5EtKohKwLk/NT8QlJXHQ4N+yJJFyUuMT+zfTpLBwX/lK3PkuMlievIffncpMZ3HGRQ==", + "requires": { + "invariant": "^2.2.4", + "warning": "^4.0.2" + } + }, "react-is": { "version": "16.13.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", @@ -15832,6 +15851,14 @@ "makeerror": "1.0.x" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", diff --git a/package.json b/package.json index 8661d731e..743c722d2 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "bootstrap": "^4.3.1", "cheerio": "^1.0.0-rc.3", "csp-parse": "0.0.2", + "dompurify": "^2.2.2", "easymde": "^2.8.0", "escape-string-regexp": "^4.0.0", "immutability-helper": "^3.0.1", @@ -22,6 +23,7 @@ "react-beautiful-dnd": "^12.0.0", "react-color": "^2.17.3", "react-dom": "^16.10.2", + "react-input-mask": "^2.0.4", "react-router-dom": "^5.1.2", "react-scripts": "3.4.0", "react-select": "^3.1.0", diff --git a/src/components/Dropdown.jsx b/src/components/Dropdown.jsx index ac573dd8a..87ba48c26 100644 --- a/src/components/Dropdown.jsx +++ b/src/components/Dropdown.jsx @@ -19,12 +19,12 @@ const Dropdown = ({ onChange={onFieldChange} > { defaultOption && - + + } + { options + .filter((option) => option !== defaultOption) + .map((option) => ( )) } - { options.map((option) => { - if (option === defaultOption) return // skip option if already included in default option - return - })} ); diff --git a/src/components/FolderCard.jsx b/src/components/FolderCard.jsx index d673ec19b..6dc5572d3 100644 --- a/src/components/FolderCard.jsx +++ b/src/components/FolderCard.jsx @@ -107,7 +107,7 @@ const FolderCard = ({ {displayText} { - pageType === 'homepage' + pageType === 'homepage' || pageType === 'contact-us' ? '' : (
diff --git a/src/components/InputMaskFormField.jsx b/src/components/InputMaskFormField.jsx new file mode 100644 index 000000000..eba3200ca --- /dev/null +++ b/src/components/InputMaskFormField.jsx @@ -0,0 +1,61 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import elementStyles from '../styles/isomer-cms/Elements.module.scss'; +import InputMask from 'react-input-mask'; + +const InputMaskFormField = ({ + title, + value, + mask, + maskChar, + alwaysShowMask, + id, + hasError, + errorMessage, + onFieldChange, + style, + disabled, + fixedMessage, + maxWidth, +}) => ( + <> + { title && } +
+ { fixedMessage &&

{fixedMessage}

} + +
+ { errorMessage && {errorMessage} } + +); + +export default InputMaskFormField; + +InputMaskFormField.propTypes = { + title: PropTypes.string, + value: PropTypes.string.isRequired, + mask: PropTypes.string.isRequired, + maskChar: PropTypes.string, + alwaysShowMask: PropTypes.bool, + id: PropTypes.string.isRequired, + hasError: PropTypes.bool, + errorMessage: PropTypes.string, + onFieldChange: PropTypes.func.isRequired, + isRequired: PropTypes.bool, + style: PropTypes.string, + maxWidth: PropTypes.bool, +}; + +InputMaskFormField.defaultProps = { + style: undefined, + errorMessage: null, +}; diff --git a/src/components/contact-us/ContactCard.jsx b/src/components/contact-us/ContactCard.jsx index 1ff02b5c5..2cd7f1b5d 100644 --- a/src/components/contact-us/ContactCard.jsx +++ b/src/components/contact-us/ContactCard.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import elementStyles from '../../styles/isomer-cms/Elements.module.scss'; import FormField from '../FormField'; import ContactFields from './ContactFields'; +import { isEmpty } from '../../utils'; /* eslint react/no-array-index-key: 0 @@ -17,15 +18,16 @@ const EditorContactCard = ({ shouldDisplay, displayHandler, cardErrors, + sectionId, }) => { return ( -
+

{title}

-
{ shouldDisplay @@ -34,7 +36,7 @@ const EditorContactCard = ({
- +
) @@ -88,5 +91,6 @@ EditorContactCard.propTypes = { other: PropTypes.string, }), ), - }) + }), + sectionId: PropTypes.string, }; diff --git a/src/components/contact-us/ContactFields.jsx b/src/components/contact-us/ContactFields.jsx index fb7bf9e90..45385c7eb 100644 --- a/src/components/contact-us/ContactFields.jsx +++ b/src/components/contact-us/ContactFields.jsx @@ -1,32 +1,47 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import FormField from '../FormField'; +import InputMaskFormField from '../InputMaskFormField'; +import Dropdown from '../Dropdown'; +import _ from 'lodash'; const ContactFields = ({ cardIndex, content, onFieldChange, errors, + sectionId, }) => { + const [phoneFieldType, setPhoneFieldType] = useState(content[0].phone[0] === '1' ? 'tollfree' : 'local') + return (
- + setPhoneFieldType(e.target.value)} + /> ( -
+

{title}

-
{ shouldDisplay @@ -34,7 +37,7 @@ const EditorLocationSection = ({
+ + Note: If left blank, map url is automatically generated from Address fields +
- +
) @@ -98,5 +106,6 @@ EditorLocationSection.propTypes = { }), ), mapUrl: PropTypes.string, - }) + }), + sectionId: PropTypes.string, }; diff --git a/src/components/contact-us/LocationFields.jsx b/src/components/contact-us/LocationFields.jsx index 1540c0429..74ba51c39 100644 --- a/src/components/contact-us/LocationFields.jsx +++ b/src/components/contact-us/LocationFields.jsx @@ -12,6 +12,7 @@ const LocationHoursFields = ({ cardIndex, onFieldChange, errors, + sectionId, }) => { return ( @@ -20,19 +21,19 @@ const LocationHoursFields = ({ { operatingHours && operatingHours.map( (operations, operationsIndex) => (
-
+
- ))}
{ operatingHours.length < DEFAULT_NUM_OPERATING_FIELDS - ? + ? Add operating hours :

Maximum 5 operating hours fields

@@ -71,6 +72,7 @@ const LocationAddressFields = ({ cardIndex, onFieldChange, errors, + sectionId, }) => { const [ errorMessage, setErrorMessage ] = useState('') @@ -94,7 +96,7 @@ const LocationAddressFields = ({
( -
+
-

{`${_.upperFirst(sectionType)} section`}

-
{shouldDisplay ? ( <> - + {(droppableProvided) => ( /* eslint-disable react/jsx-props-no-spreading */
{ cards.map((card, cardIndex) => ( {(draggableProvided) => ( @@ -57,17 +57,18 @@ const EditorSection = ({ {...draggableProvided.dragHandleProps} ref={draggableProvided.innerRef} > - { sectionType === 'contact' + { sectionId=== 'contacts' ? deleteHandler(event, sectionId)} onFieldChange={onFieldChange} shouldDisplay={displayCards[cardIndex]} displayHandler={displayHandler} cardErrors={errors[cardIndex]} + sectionId={sectionId} /> : deleteHandler(event, sectionId)} onFieldChange={onFieldChange} shouldDisplay={displayCards[cardIndex]} displayHandler={displayHandler} cardErrors={errors[cardIndex]} + sectionId={sectionId} /> }
@@ -94,7 +96,7 @@ const EditorSection = ({ )}
- +
) @@ -138,7 +140,7 @@ EditorSection.propTypes = { onFieldChange: PropTypes.func.isRequired, createHandler: PropTypes.func.isRequired, deleteHandler: PropTypes.func.isRequired, - sectionType: PropTypes.string.isRequired, + sectionId: PropTypes.string.isRequired, shouldDisplay: PropTypes.bool.isRequired, displayCards: PropTypes.arrayOf(PropTypes.bool.isRequired).isRequired, displayHandler: PropTypes.func.isRequired, diff --git a/src/components/homepage/HeroSection.jsx b/src/components/homepage/HeroSection.jsx index 74f3258a7..7c91f5bb9 100644 --- a/src/components/homepage/HeroSection.jsx +++ b/src/components/homepage/HeroSection.jsx @@ -73,8 +73,8 @@ const EditorHeroSection = ({ siteName={siteName} type="image" /> - - Note: you can only have either Key Highlights+Hero button or a Hero Dropdown + + Note: you can only have either Key Highlights+Hero button or a Hero Dropdown
{dropdown diff --git a/src/layouts/EditContactUs.jsx b/src/layouts/EditContactUs.jsx index fc0cee893..c34a68dfd 100644 --- a/src/layouts/EditContactUs.jsx +++ b/src/layouts/EditContactUs.jsx @@ -1,4 +1,3 @@ -// TODO: Error handling and validation (csp check) // TODO: Clean up formatting, semi-colons, PropTypes etc import React, { Component } from 'react'; import axios from 'axios'; @@ -27,6 +26,7 @@ import TemplateLocationsSection from '../templates/contact-us/LocationsSection' import TemplateContactsSection from '../templates/contact-us/ContactsSection' import TemplateFeedbackSection from '../templates/contact-us/FeedbackSection'; +import DeleteWarningModal from '../components/DeleteWarningModal'; /* eslint-disable react/jsx-props-no-spreading */ /* eslint-disable react/no-array-index-key */ @@ -62,9 +62,9 @@ const LocationSectionConstructor = (operatingHoursLength) => ({ const enumSection = (type, args) => { switch (type) { - case 'contact': + case 'contacts': return ContactSectionConstructor(); - case 'location': + case 'locations': return LocationSectionConstructor(args?.operatingHoursLength); case 'contact_field': return ContactFieldConstructor(); @@ -77,28 +77,38 @@ export default class EditContactUs extends Component { constructor(props) { super(props); this.scrollRefs = { - header: null, - feedback: null, - contact: null, - location: null, + sectionsScrollRefs: { + locations: null, + contacts: null, + header: null, + feedback: null, + }, + contacts: [], + locations: [], }; this.state = { frontMatter: {}, + originalFrontMatter: {}, frontMatterSha: null, footerContent: {}, + originalFooterContent: {}, footerSha: null, displaySections: { sectionsDisplay: { - location: false, - contact: false, + locations: false, + contacts: false, }, - contactCardsDisplay: [], - locationCardsDisplay: [], + contacts: [], + locations: [], }, errors: { contacts: [], locations: [], - } + }, + itemPendingForDelete: { + id: null, + type: '', + }, }; } @@ -121,37 +131,51 @@ export default class EditContactUs extends Component { const { contacts, locations } = sanitisedFrontMatter - const contactsErrors = [] - const locationsErrors = [] - const contactCardsDisplay = [] - const locationCardsDisplay = [] + const contactsErrors = [], locationsErrors = [] + const contactsDisplay = [], locationsDisplay = [] + const contactsScrollRefs = [], locationsScrollRefs = [] + const sectionsDisplay = { - contact: false, - location:false + contacts: false, + locations: false } + const sectionsScrollRefs = { + header: React.createRef(), + feedback: React.createRef(), + contacts: React.createRef(), + locations: React.createRef(), + } + contacts.forEach(_ => { - contactsErrors.push(enumSection('contact')) - contactCardsDisplay.push(false) + contactsErrors.push(enumSection('contacts')) + contactsDisplay.push(false) + contactsScrollRefs.push(React.createRef()) }) locations.forEach(location => { - const args = { - operatingHoursLength: location.operating_hours.length - } - locationsErrors.push(enumSection('location', args)) - locationCardsDisplay.push(false) + locationsErrors.push(enumSection('locations', { operatingHoursLength: location.operating_hours.length })) + locationsDisplay.push(false) + locationsScrollRefs.push(React.createRef()) }) + this.scrollRefs = { + sectionsScrollRefs, + contacts: contactsScrollRefs, + locations: locationsScrollRefs, + } + this.setState({ + originalFooterContent: _.cloneDeep(footerContent), footerContent, footerSha, + originalFrontMatter: _.cloneDeep(frontMatter), frontMatter: sanitisedFrontMatter, frontMatterSha: sha, displaySections: { sectionsDisplay, - contactCardsDisplay, - locationCardsDisplay, + contacts: contactsDisplay, + locations: locationsDisplay, }, errors: { contacts: contactsErrors, @@ -164,8 +188,9 @@ export default class EditContactUs extends Component { } onDragEnd = (result) => { + const { scrollRefs, state } = this; const { source, destination, type } = result; - const { frontMatter, displaySections, errors } = this.state; + const { frontMatter, displaySections, errors } = state; // If the user dropped the draggable to no known droppable if (!destination) return; @@ -176,72 +201,47 @@ export default class EditContactUs extends Component { && destination.index === source.index ) return; - let newFrontMatter, newErrors, newDisplaySections; + const elem = frontMatter[type][source.index]; + const elemError = errors[type][source.index]; + const elemDisplay = displaySections[type][source.index]; + const elemScrollRef = scrollRefs[type][source.index]; - switch (type) { - case 'contact': { - const elem = frontMatter.contacts[source.index]; - newFrontMatter = update(frontMatter, { - contacts: { - $splice: [ - [source.index, 1], // Remove elem from its original position - [destination.index, 0, elem], // Splice elem into its new position - ], - }, - }); - const elemError = errors.contacts[source.index]; - newErrors = update(errors, { - contacts: { - $splice: [ - [source.index, 1], // Remove elem from its original position - [destination.index, 0, elemError], // Splice elem into its new position - ], - }, - }); - const elemDisplay = displaySections.contactCardsDisplay[source.index]; - newDisplaySections = update(displaySections, { - contactCardsDisplay: { - $splice: [ - [source.index, 1], - [destination.index, 0, elemDisplay], - ], - }, - }); - break; - } - case 'location': { - const elem = frontMatter.locations[source.index]; - newFrontMatter = update(frontMatter, { - locations: { - $splice: [ - [source.index, 1], // Remove elem from its original position - [destination.index, 0, elem], // Splice elem into its new position - ], - }, - }); - const elemError = errors.locations[source.index]; - newErrors = update(errors, { - locations: { - $splice: [ - [source.index, 1], // Remove elem from its original position - [destination.index, 0, elemError], // Splice elem into its new position - ], - }, - }); - const elemDisplay = displaySections.locationCardsDisplay[source.index]; - newDisplaySections = update(displaySections, { - locationCardsDisplay: { - $splice: [ - [source.index, 1], - [destination.index, 0, elemDisplay], - ], - }, - }); - break; - } - default: { - } - } + const newFrontMatter = update(frontMatter, { + [type]: { + $splice: [ + [source.index, 1], // Remove elem from its original position + [destination.index, 0, elem], // Splice elem into its new position + ], + }, + }); + const newErrors = update(errors, { + [type]: { + $splice: [ + [source.index, 1], // Remove elem from its original position + [destination.index, 0, elemError], // Splice elem into its new position + ], + }, + }); + const newDisplaySections = update(displaySections, { + [type]: { + $splice: [ + [source.index, 1], + [destination.index, 0, elemDisplay], + ], + }, + }); + const newScrollRefs = update(scrollRefs, { + [type]: { + $splice: [ + [source.index, 1], + [destination.index, 0, elemScrollRef], + ], + }, + }) + + // scroll to new location of dragged element + this.scrollRefs[type][destination.index].current.scrollIntoView() + this.scrollRefs = newScrollRefs this.setState({ frontMatter: newFrontMatter, @@ -252,7 +252,7 @@ export default class EditContactUs extends Component { onFieldChange = async (event) => { try { - const { state } = this; + const { scrollRefs, state } = this; const { frontMatter, footerContent } = state const { errors } = state; const { id, value } = event.target; @@ -265,6 +265,7 @@ export default class EditContactUs extends Component { newFooterContent = update(footerContent, { [elemType]: {$set: value}, }); + scrollRefs.sectionsScrollRefs[elemType].current.scrollIntoView(); break; } case 'header': { @@ -273,9 +274,10 @@ export default class EditContactUs extends Component { newFrontMatter = update(frontMatter, { [field]: {$set: value}, }); + scrollRefs.sectionsScrollRefs[elemType].current.scrollIntoView(); break; } - case 'contact': { + case 'contacts': { const contactIndex = parseInt(idArray[1], RADIX_PARSE_INT); const contactType = idArray[2]; const contentIndex = parseInt(idArray[3], RADIX_PARSE_INT); @@ -283,25 +285,25 @@ export default class EditContactUs extends Component { switch (contactType) { case 'title': newFrontMatter = update(frontMatter, { - contacts: {[contactIndex]: {[contactType]: {$set: value }}}, + [elemType]: {[contactIndex]: {[contactType]: {$set: value }}}, }); newErrors = update(errors, { - contacts: {[contactIndex]: {[contactType]: {$set: validateContact(contactType, value)}}} + [elemType]: {[contactIndex]: {[contactType]: {$set: validateContact(contactType, value)}}} }) break; default: // 'phone', 'email', 'other' newFrontMatter = update(frontMatter, { - contacts: {[contactIndex]: {content : {[contentIndex]: {[contactType]: {$set: value } }}}}, + [elemType]: {[contactIndex]: {content : {[contentIndex]: {[contactType]: {$set: value } }}}}, }); newErrors = update(errors, { - contacts: {[contactIndex]: {content : {[contentIndex]: {[contactType]: {$set: validateContact(contactType, value) } }}}}, + [elemType]: {[contactIndex]: {content : {[contentIndex]: {[contactType]: {$set: validateContact(contactType, value) } }}}}, }); break; } + scrollRefs[elemType][contactIndex].current.scrollIntoView(); break; } - case 'location': { - const { locations } = state.frontMatter; + case 'locations': { const locationIndex = parseInt(idArray[1], RADIX_PARSE_INT); const locationType = idArray[2]; // e.g. "title" or "address" const fieldIndex = parseInt(idArray[3], RADIX_PARSE_INT); @@ -310,7 +312,7 @@ export default class EditContactUs extends Component { switch (locationType) { case 'operating_hours': newFrontMatter = update(frontMatter, { - locations: {[locationIndex]: {[locationType]: {[fieldIndex] : {[fieldType]: { $set: value }}}}}, + [elemType]: {[locationIndex]: {[locationType]: {[fieldIndex] : {[fieldType]: { $set: value }}}}}, }); newErrors = update(errors, { locations: {[locationIndex]: {[locationType]: {[fieldIndex] : {[fieldType]: { $set: validateLocation(fieldType, value) }}}}}, @@ -318,39 +320,40 @@ export default class EditContactUs extends Component { break; case 'add_operating_hours': newFrontMatter = update(frontMatter, { - locations: {[locationIndex]: {operating_hours : {$push: [enumSection('location_hours_field')]}}}, + [elemType]: {[locationIndex]: {operating_hours : {$push: [enumSection('location_hours_field')]}}}, }); newErrors = update(errors, { - locations: {[locationIndex]: {operating_hours : {$push: [enumSection('location_hours_field')]}}}, + [elemType]: {[locationIndex]: {operating_hours : {$push: [enumSection('location_hours_field')]}}}, }); break; case 'remove_operating_hours': newFrontMatter = update(frontMatter, { - locations: {[locationIndex]: {operating_hours : {$splice: [[fieldIndex,1]]}}} + [elemType]: {[locationIndex]: {operating_hours : {$splice: [[fieldIndex,1]]}}} }); newErrors = update(errors, { - locations: {[locationIndex]: {operating_hours : {$splice: [[fieldIndex,1]]}}} + [elemType]: {[locationIndex]: {operating_hours : {$splice: [[fieldIndex,1]]}}} }); break; case 'address': newFrontMatter = update(frontMatter, { - locations: {[locationIndex]: {[locationType]: {[fieldIndex] : { $set: value }}}}, + [elemType]: {[locationIndex]: {[locationType]: {[fieldIndex] : { $set: value }}}}, }); // for address, we validate all address fields together, not the single field const addressFields = newFrontMatter.locations[locationIndex][locationType] newErrors = update(errors, { - locations: {[locationIndex]: {[locationType]: { $set: validateLocation(locationType, addressFields) }}}, + [elemType]: {[locationIndex]: {[locationType]: { $set: validateLocation(locationType, addressFields) }}}, }); break; default: newFrontMatter = update(frontMatter, { - locations: {[locationIndex]: {[locationType]: { $set: value }}}, + [elemType]: {[locationIndex]: {[locationType]: { $set: value }}}, }); newErrors = update(errors, { - locations: {[locationIndex]: {[locationType]: { $set: validateLocation(locationType, value) }}}, + [elemType]: {[locationIndex]: {[locationType]: { $set: validateLocation(locationType, value) }}}, }); break; } + scrollRefs[elemType][locationIndex].current.scrollIntoView(); break; } } @@ -359,7 +362,6 @@ export default class EditContactUs extends Component { footerContent: _.isUndefined(newFooterContent) ? currState.footerContent : newFooterContent, errors: _.isUndefined(newErrors) ? currState.errors : newErrors, })); - this.scrollRefs[elemType].scrollIntoView() } catch (err) { console.log(err); @@ -369,89 +371,72 @@ export default class EditContactUs extends Component { createHandler = async (event) => { const { id } = event.target; try { + const { scrollRefs, state } = this; + const { frontMatter, displaySections, errors } = state; - const { frontMatter, displaySections, errors } = this.state; - - let newFrontMatter, newDisplaySections, newErrors; - switch (id) { - case 'contact': { - newFrontMatter = update(frontMatter, { - contacts: {$push: [enumSection('contact')]}, - }); - newErrors = update(errors, { - contacts: {$push: [enumSection('contact')]}, - }) - newDisplaySections = update(displaySections, { - contactCardsDisplay: {$push: [true]}, - }); - break; - } - case 'location': { - newFrontMatter = update(frontMatter, { - locations: {$push: [enumSection('location')]}, - }); - newErrors = update(errors, { - locations: {$push: [enumSection('location')]}, - }) - newDisplaySections = update(displaySections, { - locationCardsDisplay: {$push: [true]}, - }); - break; - } + const newFrontMatter = update(frontMatter, { + [id]: {$push: [enumSection(id)]}, + }); + const newErrors = update(errors, { + [id]: {$push: [enumSection(id)]}, + }) + const newDisplaySections = update(displaySections, { + [id]: {$push: [true]}, + }); + const newScrollRefs = update(scrollRefs, { + [id]: {$push: [React.createRef()]}, + }); + + if (scrollRefs[id].length) { + // Scroll to an approximation of where the new field will be based on the current last field, calibrated from the bottom of page + _.last(scrollRefs[id]).current.scrollIntoView() + } else { + scrollRefs.sectionsScrollRefs[id].current.scrollIntoView() } + + this.scrollRefs = newScrollRefs; + this.setState({ frontMatter: newFrontMatter, errors: newErrors, displaySections: newDisplaySections, }); - this.scrollRefs[id].scrollIntoView() + } catch (err) { console.log(err); } } - deleteHandler = async (event) => { - const { id } = event.target + deleteHandler = async (id) => { try { + const { scrollRefs, state } = this; + const { frontMatter, displaySections, errors } = state; + const idArray = id.split('-'); const elemType = idArray[0]; const sectionIndex = parseInt(idArray[1], RADIX_PARSE_INT); - const { frontMatter, displaySections, errors } = this.state; - let newFrontMatter, newDisplaySections, newErrors; + const newFrontMatter = update(frontMatter, { + [elemType]: {$splice: [[sectionIndex, 1]]}, + }); + const newErrors = update(errors, { + [elemType]: {$splice: [[sectionIndex, 1]]}, + }); + const newDisplaySections = update(displaySections, { + [elemType]: {$splice: [[sectionIndex, 1]]}, + }); + const newScrollRefs = update(scrollRefs, { + [elemType]: {$splice: [[sectionIndex, 1]]}, + }); + + this.scrollRefs = newScrollRefs; - switch (elemType) { - case 'contact': { // fix - newFrontMatter = update(frontMatter, { - contacts: {$splice: [[sectionIndex, 1]]}, - }); - newErrors = update(errors, { - contacts: {$splice: [[sectionIndex, 1]]}, - }); - newDisplaySections = update(displaySections, { - contactCardsDisplay: {$splice: [[sectionIndex, 1]]}, - }); - break; - } - case 'location': { - newFrontMatter = update(frontMatter, { - locations: {$splice: [[sectionIndex, 1]]}, - }); - newErrors = update(errors, { - locations: {$splice: [[sectionIndex, 1]]}, - }); - newDisplaySections = update(displaySections, { - locationCardsDisplay: {$splice: [[sectionIndex, 1]]}, - }); - break; - } - } this.setState({ frontMatter: newFrontMatter, errors: newErrors, displaySections: newDisplaySections, }); - this.scrollRefs[elemType].scrollIntoView() + } catch (err) { console.log(err); } @@ -459,50 +444,50 @@ export default class EditContactUs extends Component { displayHandler = async (event) => { try { + const { state, scrollRefs } = this; + const { displaySections } = state; + const { contacts: contactsDisplay, locations: locationsDisplay } = displaySections; + const { id } = event.target; const idArray = id.split('-'); const elemType = idArray[0]; - const { displaySections } = this.state; - const { sectionsDisplay, contactCardsDisplay, locationCardsDisplay } = displaySections; const sectionIndex = parseInt(idArray[1], RADIX_PARSE_INT) || idArray[1]; - let newSectionDisplay = { contact: false, location: false } - let newContactCardsDisplay = _.fill(Array(contactCardsDisplay.length), false); - let newLocationCardsDisplay = _.fill(Array(locationCardsDisplay.length), false); + let resetDisplaySections = { + sectionsDisplay: { + contacts: false, + locations: false, + }, + contacts: _.fill(Array(contactsDisplay.length), false), + locations: _.fill(Array(locationsDisplay.length), false), + } let newDisplaySections; switch (elemType) { case 'section': { - const currDisplayValue = sectionsDisplay[sectionIndex]; - newSectionDisplay[sectionIndex] = !currDisplayValue; + const currDisplayValue = displaySections.sectionsDisplay[sectionIndex]; + resetDisplaySections.sectionsDisplay[sectionIndex] = !currDisplayValue; newDisplaySections = update(displaySections, { - sectionsDisplay: {$set: newSectionDisplay}, - contactCardsDisplay: {$set: newContactCardsDisplay}, - locationCardsDisplay: {$set: newLocationCardsDisplay}, + $set : resetDisplaySections, }); - this.scrollRefs[sectionIndex].scrollIntoView() + scrollRefs.sectionsScrollRefs[sectionIndex].current.scrollIntoView(); break; } - case 'contact': { - const currDisplayValue = contactCardsDisplay[sectionIndex]; - newContactCardsDisplay[sectionIndex] = !currDisplayValue; + default: { + const currDisplayValue = displaySections[elemType][sectionIndex]; + resetDisplaySections[elemType][sectionIndex] = !currDisplayValue; newDisplaySections = update(displaySections, { - contactCardsDisplay: {$set: newContactCardsDisplay}, + [elemType]: {$set: resetDisplaySections[elemType]}, }); + scrollRefs[elemType][sectionIndex].current.scrollIntoView(); break; } - case 'location': { - const currDisplayValue = locationCardsDisplay[sectionIndex] - newLocationCardsDisplay[sectionIndex] = !currDisplayValue; - newDisplaySections = update(displaySections, { - locationCardsDisplay: {$set: newLocationCardsDisplay}, - }); - break; - } } + this.setState({ displaySections: newDisplaySections, }); + } catch (err) { console.log(err); } @@ -524,8 +509,6 @@ export default class EditContactUs extends Component { newContacts.push(_.cloneDeep(contact)) } }) - filteredFrontMatter.contacts = newContacts; - let newLocations = []; state.frontMatter.locations.forEach((location) => { if ( !isEmpty(location) ) { @@ -540,7 +523,13 @@ export default class EditContactUs extends Component { newLocations.push(newLocation); } }) + + filteredFrontMatter.contacts = newContacts; filteredFrontMatter.locations = newLocations; + + // If array is empty, delete the object + if (!filteredFrontMatter.contacts.length) delete filteredFrontMatter.contacts + if (!filteredFrontMatter.locations.length) delete filteredFrontMatter.locations const content = concatFrontMatterMdBody(filteredFrontMatter, ''); const base64EncodedContent = Base64.encode(content); @@ -554,7 +543,7 @@ export default class EditContactUs extends Component { withCredentials: true, }); - // Update settings + // // Update settings let updatedFooterContents = _.cloneDeep(state.footerContent) const footerParams = { @@ -573,30 +562,45 @@ export default class EditContactUs extends Component { } render() { + const { state, scrollRefs } = this const { footerContent, + originalFooterContent, frontMatter, + originalFrontMatter, displaySections, frontMatterSha, footerSha, errors, - } = this.state; + itemPendingForDelete, + } = state; const { match } = this.props; const { siteName } = match.params; const { agency_name: agencyName, contacts, locations } = frontMatter const { feedback } = footerContent - const { sectionsDisplay, contactCardsDisplay, locationCardsDisplay } = displaySections - - const hasContactErrors = !isEmpty(errors.contacts) - const hasLocationErrors = !isEmpty(errors.locations) + const { sectionsDisplay } = displaySections + const { sectionsScrollRefs } = scrollRefs - const hasErrors = hasContactErrors || hasLocationErrors; + const hasErrors = !isEmpty(errors.contacts) || !isEmpty(errors.locations); + const hasChanges = JSON.stringify(originalFrontMatter) === JSON.stringify(frontMatter) && JSON.stringify(footerContent) === JSON.stringify(originalFooterContent); return ( <> + { + itemPendingForDelete.id + && ( + this.setState({ itemPendingForDelete: { id: null, type: '' } })} + onDelete={() => { this.deleteHandler(itemPendingForDelete.id); this.setState({ itemPendingForDelete: { id: null, type: '' } }); }} + type={itemPendingForDelete.type} + /> + ) + }
@@ -627,53 +631,54 @@ export default class EditContactUs extends Component { cards={locations} onFieldChange={this.onFieldChange} createHandler={this.createHandler} - deleteHandler={this.deleteHandler} - shouldDisplay={sectionsDisplay.location} - displayCards={locationCardsDisplay} - sectionType={'location'} + deleteHandler={(event, type) => this.setState({ itemPendingForDelete: { id: event.target.id, type } })} + shouldDisplay={sectionsDisplay.locations} + displayCards={displaySections.locations} displayHandler={this.displayHandler} errors={errors.locations} + sectionId={'locations'} /> this.setState({ itemPendingForDelete: { id: event.target.id, type } })} + shouldDisplay={sectionsDisplay.contacts} + displayCards={displaySections.contacts} displayHandler={this.displayHandler} errors={errors.contacts} + sectionId={'contacts'} />
-
+
{/* contact-us header */} -
this.scrollRefs.header = ref}> - -
+ {/* contact-us content */}
- -
{ this.scrollRefs.location = ref;} }> - -
- - {/* contacts section */} -
{ this.scrollRefs.contact = ref;} }> - -
- - {/* feedback url section */} -
{ this.scrollRefs.feedback = ref;} }> - -
+ + +
@@ -682,9 +687,9 @@ export default class EditContactUs extends Component {
diff --git a/src/layouts/EditHomepage.jsx b/src/layouts/EditHomepage.jsx index 376058f28..a3995f897 100644 --- a/src/layouts/EditHomepage.jsx +++ b/src/layouts/EditHomepage.jsx @@ -993,8 +993,8 @@ export default class EditHomepage extends Component { value={frontMatter.notification} id="site-notification" onChange={this.onFieldChange} /> - - Note: Leave text field empty if you don’t need this notification bar + + Note: Leave text field empty if you don’t need this notification bar
diff --git a/src/styles/isomer-cms/elements/base.scss b/src/styles/isomer-cms/elements/base.scss index 2b79d3454..b4faa9fbf 100644 --- a/src/styles/isomer-cms/elements/base.scss +++ b/src/styles/isomer-cms/elements/base.scss @@ -50,8 +50,10 @@ i { .error { color: $error-red; + font-size: 14px; } -span { +.info { + color: $isomer-blue; font-size: 14px; -} +} \ No newline at end of file diff --git a/src/styles/isomer-cms/elements/card.scss b/src/styles/isomer-cms/elements/card.scss index 54e355c73..e47b5c153 100644 --- a/src/styles/isomer-cms/elements/card.scss +++ b/src/styles/isomer-cms/elements/card.scss @@ -18,6 +18,7 @@ h2{ font-size: 20px; font-weight: normal; + overflow-wrap: anywhere; color: darken($isomer-blue,20%); } @@ -27,6 +28,10 @@ } } + &.error{ + border: 1px solid $error-red; + } + &.dragging{ background: white; box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2); diff --git a/src/styles/isomer-cms/pages/Editor.module.scss b/src/styles/isomer-cms/pages/Editor.module.scss index d477cbf41..6bd900bc4 100644 --- a/src/styles/isomer-cms/pages/Editor.module.scss +++ b/src/styles/isomer-cms/pages/Editor.module.scss @@ -19,6 +19,15 @@ position: relative; } +.contactUsEditorMain{ + width: calc(100vw - #{$homepage-editor-sidebar-width}); + height: calc(100vh - #{$header-height} - #{$footer-height}); + box-sizing: border-box; + overflow-y: scroll; + position: relative; + background-color: white; +} + .pageEditorSidebar{ width: $page-editor-sidebar-width; background: $base-background-light; diff --git a/src/styles/isomer-template.scss b/src/styles/isomer-template.scss index 9b4973c50..152978597 100644 --- a/src/styles/isomer-template.scss +++ b/src/styles/isomer-template.scss @@ -356,7 +356,8 @@ body { a { color: #4372d6; cursor: pointer; - text-decoration: none + text-decoration: none; + font-size: inherit; } a strong { @@ -400,7 +401,8 @@ small { span { font-style: inherit; - font-weight: inherit + font-weight: inherit; + font-size: inherit; } strong { diff --git a/src/templates/contact-us/ContactUsHeader.jsx b/src/templates/contact-us/ContactUsHeader.jsx index 514b31a8f..6c68d0ed4 100644 --- a/src/templates/contact-us/ContactUsHeader.jsx +++ b/src/templates/contact-us/ContactUsHeader.jsx @@ -3,10 +3,8 @@ import PropTypes from 'prop-types'; import Breadcrumb from '../pageComponents/Breadcrumb'; -const TemplateContactUsHeader = ({ - agencyName, -}) => ( -
+const TemplateContactUsHeader = React.forwardRef(( { agencyName }, ref ) => ( +
@@ -22,7 +20,7 @@ const TemplateContactUsHeader = ({
-); +)); TemplateContactUsHeader.propTypes = { diff --git a/src/templates/contact-us/ContactsSection.jsx b/src/templates/contact-us/ContactsSection.jsx index 03ccde6fd..5701fd914 100644 --- a/src/templates/contact-us/ContactsSection.jsx +++ b/src/templates/contact-us/ContactsSection.jsx @@ -1,8 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; +import DOMPurify from 'dompurify'; -const Contact = ({ contact } ) => ( -
+const Contact = React.forwardRef(( { contact }, ref ) => ( +

{contact.title}

{ contact.content.map( (d, i) => { @@ -11,46 +12,48 @@ const Contact = ({ contact } ) => ( switch (key) { case 'phone': { return ( - -

+

+ {d[key]} -

- + +

+ ) } case 'email': { return ( - -

+

+ {d[key]} -

- + +

) } default: { // others return ( /* TODO: CSP validation should be done on html elements before rendering */ -
+
${DOMPurify.sanitize(d[key])}

` }} key={i}/> ) } } }) }
-); +)); -const TemplateContactsSection = ({ contacts }) => ( - <> - { contacts && -
-
-
Contact Us
+const TemplateContactsSection = React.forwardRef(( { contacts, scrollRefs }, ref ) => ( +
+ { contacts && contacts.length + ?
+
+
Contact Us
+
+ { contacts.map((contact, i) => )}
- { contacts.map( (contact, i) => ) } -
+ : null } - -); +
+)); Contact.propTypes = { title: PropTypes.string, diff --git a/src/templates/contact-us/FeedbackSection.jsx b/src/templates/contact-us/FeedbackSection.jsx index 5055bc532..cf856fcc2 100644 --- a/src/templates/contact-us/FeedbackSection.jsx +++ b/src/templates/contact-us/FeedbackSection.jsx @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -const TemplateFeedbackSection = ({ feedback }) => ( - <> +const TemplateFeedbackSection = React.forwardRef(( { feedback }, ref ) => ( +
{ feedback &&
@@ -17,8 +17,8 @@ const TemplateFeedbackSection = ({ feedback }) => (
} - -); +
+)); TemplateFeedbackSection.propTypes = { feedback: PropTypes.string, diff --git a/src/templates/contact-us/LocationsSection.jsx b/src/templates/contact-us/LocationsSection.jsx index f121b15d1..2211ebd4c 100644 --- a/src/templates/contact-us/LocationsSection.jsx +++ b/src/templates/contact-us/LocationsSection.jsx @@ -18,7 +18,12 @@ const LocationAddress = ({ location } ) => (
{ location.address.map((value, i) =>

{value}

) } - +
VIEW MAP @@ -28,8 +33,8 @@ const LocationAddress = ({ location } ) => (
); -const Location = ({ location }) => ( -
+const Location = React.forwardRef( ( { location }, ref ) => ( +
{ location.address && location.title &&
{location.title}
@@ -45,17 +50,17 @@ const Location = ({ location }) => (
-); +)); -const TemplateLocationsSection = ({ locations }) => ( - <> +const TemplateLocationsSection = React.forwardRef(( { locations, scrollRefs }, ref) => ( +
{ locations && <> - { locations.map( (location, i) => ) } + { locations.map( (location, i) => )} } - -); +
+)); LocationAddress.propTypes = { location: PropTypes.shape({ diff --git a/src/utils/dataSanitisers.js b/src/utils/dataSanitisers.js index ecf6a40a0..429513ee3 100644 --- a/src/utils/dataSanitisers.js +++ b/src/utils/dataSanitisers.js @@ -4,59 +4,76 @@ import _ from 'lodash'; const DEFAULT_ADDRESS_FIELD_LENGTH = 3; const DEFAULT_NUM_OPERATING_FIELDS = 5; +function getContentDataField(content, dataType) { + const dataObj = _.find(content, (obj) => { + return dataType in obj && _.isString(obj[dataType]) + }) + return _.cloneDeep(dataObj) || {[dataType]: ''} +} + function sanitiseContent(content) { let sanitisedContent = []; // sanitisedContent should be an array of 3 objects, [{phone: }, {email: }, {other: }] - // we find the first object of each of type (phone, email, other) and push it - sanitisedContent.push( _.find(content, obj => 'phone' in obj) || {phone: ''}); - sanitisedContent.push( _.find(content, obj => 'email' in obj) || {email: ''}); - sanitisedContent.push( _.find(content, obj => 'other' in obj) || {other: ''}); + // we find the first object of each of type (phone, email, other) and push a deep clone, else return initialized objects + sanitisedContent.push( getContentDataField(content, 'phone') ); + sanitisedContent.push( getContentDataField(content, 'email') ); + sanitisedContent.push( getContentDataField(content, 'other') ); return sanitisedContent; } function sanitiseAddress(address) { let sanitisedAddress = []; // sanitisedAddress should be an array of strings of length DEFAULT_ADDRESS_FIELD_LENGTH + // we find the first DEFAULT_ADDRESS_FIELD_LENGTH strings and push a deep clone if they exist, else return empty strings _.range(DEFAULT_ADDRESS_FIELD_LENGTH).forEach( (index) => { - sanitisedAddress.push( (address && address[index] ) ? address[index] : '') + sanitisedAddress.push( (address && address[index] ) ? _.cloneDeep(address[index]) : '') }) return sanitisedAddress; } function sanitiseOperatingHours(operatingHours) { - let sanitisedOperatingHours = []; + let sanitisedOperatingHours = {}; + sanitisedOperatingHours.days = _.cloneDeep(operatingHours.days) || ''; + sanitisedOperatingHours.time = _.cloneDeep(operatingHours.time) || ''; + sanitisedOperatingHours.description = _.cloneDeep(operatingHours.description) || ''; + return sanitisedOperatingHours +} + +function sanitiseOperatingHoursArr(operatingHoursArr) { + let sanitisedOperatingHoursArr = []; // sanitisedOperatingHours should be an array of objects of maximum length DEFAULT_NUM_OPERATING_FIELDS + // we find the first DEFAULT_NUM_OPERATING_FIELDS objects and push a deep clone if they exist _.range(DEFAULT_NUM_OPERATING_FIELDS).forEach( (index) => { - if (operatingHours && operatingHours[index]) { - sanitisedOperatingHours.push(operatingHours[index]) - } + sanitisedOperatingHoursArr.push( (operatingHoursArr && operatingHoursArr[index]) ? sanitiseOperatingHours(operatingHoursArr[index]) : undefined) }) - return sanitisedOperatingHours; + return sanitisedOperatingHoursArr.filter(elem => elem); } function sanitiseContact(contact) { // rearrange - const { content } = contact - const sanitisedContent = sanitiseContent(content) - return { - ...contact, - content: sanitisedContent, - } + const { title, content } = contact + + let sanitisedContact = {} + sanitisedContact.content = sanitiseContent(content) + sanitisedContact.title = _.cloneDeep(title) || '' + + return sanitisedContact } function sanitiseLocation(location) { - const { address, operating_hours: operatingHours } = location - const sanitisedAddress = sanitiseAddress(address) - const sanitisedOperatingHours = sanitiseOperatingHours(operatingHours) - - return { - ...location, - address: sanitisedAddress, - operating_hours: sanitisedOperatingHours, - } + const { title, address, operating_hours: operatingHours, maps_link: mapUrl } = location + + let sanitisedLocation = {} + sanitisedLocation.address = sanitiseAddress(address) + sanitisedLocation.operating_hours = sanitiseOperatingHoursArr(operatingHours) + sanitisedLocation.maps_link = _.cloneDeep(mapUrl) || '' + sanitisedLocation.title = _.cloneDeep(title) || '' + return sanitisedLocation } export function sanitiseFrontMatter(frontMatter) { const { contacts, locations } = frontMatter; + + let sanitisedFrontMatter = _.cloneDeep(frontMatter) let sanitisedContacts = []; let sanitisedLocations = []; @@ -70,9 +87,8 @@ export function sanitiseFrontMatter(frontMatter) { sanitisedLocations.push(sanitiseLocation(location)) ) } - return { - ...frontMatter, - contacts: sanitisedContacts, - locations: sanitisedLocations, - } + + sanitisedFrontMatter.contacts = sanitisedContacts + sanitisedFrontMatter.locations = sanitisedLocations + return sanitisedFrontMatter } \ No newline at end of file diff --git a/src/utils/validators.js b/src/utils/validators.js index ecbb4b448..a5556b7d2 100644 --- a/src/utils/validators.js +++ b/src/utils/validators.js @@ -1,19 +1,19 @@ +const _ = require('lodash'); + // Common regexes and constants // ============== const PERMALINK_REGEX = '^(([a-z0-9]+([-][a-z0-9]+)*)+)$'; const URL_REGEX_PART_1 = '^(https://)?(www.)?('; const URL_REGEX_PART_2 = '.com/)([a-zA-Z0-9_-]+(/)?)+$'; const PHONE_REGEX = '^\\+65(6|8|9)[0-9]{7}$' -const TOLLFREE_PHONE_REGEX = '^1800[0-9]{7}$' -const EMAIL_REGEX = '^[a-z0-9-_\.]+@[a-z0-9-]+\.[a-z]{2,4}$'; +const EMAIL_REGEX = '^(([^<>()\\[\\]\\.,;:\\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 DATE_REGEX = '^([0-9]{4}-[0-9]{2}-[0-9]{2})$'; -const ALPHABETS_ONLY_REGEX = '^[a-zA-Z" "\._-]+$' -const ALPHANUMERICS_ONLY_REGEX = '^[a-zA-Z0-9" "\._-]+$' +const ALPHABETS_ONLY_REGEX = '^[a-zA-Z" "\\._-]+$' +const ALPHANUMERICS_ONLY_REGEX = '^[a-zA-Z0-9" "\\._-]+$' const permalinkRegexTest = RegExp(PERMALINK_REGEX); const phoneRegexTest = RegExp(PHONE_REGEX); -const tollfreePhoneRegexTest = RegExp(TOLLFREE_PHONE_REGEX); const emailRegexTest = RegExp(EMAIL_REGEX); const dateRegexTest = RegExp(DATE_REGEX); const alphabetsRegexTest = RegExp(ALPHABETS_ONLY_REGEX); @@ -76,12 +76,12 @@ const HERO_DROPDOWN_MAX_LENGTH = 30; // Contact Us Editor // =============== // Contacts -const CONTACT_TITLE_MIN_LENGTH = 2; +const CONTACT_TITLE_MIN_LENGTH = 1; const CONTACT_TITLE_MAX_LENGTH = 30; const CONTACT_DESCRIPTION_MAX_LENGTH = 400; // Locations -const LOCATION_TITLE_MIN_LENGTH = 2; +const LOCATION_TITLE_MIN_LENGTH = 1; const LOCATION_TITLE_MAX_LENGTH = 30; const LOCATION_ADDRESS_MIN_LENGTH = 2; const LOCATION_ADDRESS_MAX_LENGTH = 30; @@ -468,15 +468,21 @@ const validateContact = (contactType, value) => { switch (contactType) { case 'title': if (value.length < CONTACT_TITLE_MIN_LENGTH) { - errorMessage = `Title should be longer than ${CONTACT_TITLE_MIN_LENGTH} characters.`; + errorMessage = `Title cannot be empty.`; }; if (value.length > CONTACT_TITLE_MAX_LENGTH) { errorMessage = `Title should be shorter than ${CONTACT_TITLE_MAX_LENGTH} characters.`; }; break; case 'phone': - if ( value && ! (phoneRegexTest.test(value) || tollfreePhoneRegexTest.test(value)) ) { - errorMessage = `Local numbers should start with +65 followed by 8 digits starting with 6, 8 or 9. Toll free numbers should start with 1800 followed by 7 digits.` + const strippedValue = value.replace(/\s/g, '') + if ( strippedValue.includes('_')) { + errorMessage = `Field not completed` + } + if (_.startsWith(strippedValue, '+65')) { + if (! strippedValue.includes('_') && !phoneRegexTest.test(strippedValue) ) { + errorMessage = `Local numbers should start with 6, 8 or 9.` + } } break; case 'email': @@ -489,6 +495,8 @@ const validateContact = (contactType, value) => { errorMessage = `Description should be shorter than ${CONTACT_DESCRIPTION_MAX_LENGTH} characters.`; } break; + default: + break; } return errorMessage } @@ -501,13 +509,16 @@ const validateLocation = (locationType, value) => { case 'title': // Title is too short if (value.length < LOCATION_TITLE_MIN_LENGTH) { - errorMessage = `Title should be longer than ${LOCATION_TITLE_MIN_LENGTH} characters.`; + errorMessage = `Title cannot be empty.`; }; // Title is too long if (value.length > LOCATION_TITLE_MAX_LENGTH) { errorMessage = `Title should be shorter than ${LOCATION_TITLE_MAX_LENGTH} characters.`; }; break; + case 'maps_link': { + break; + } case 'address': let errors = []; // check if in-between fields are empty e.g. field 3 is filled but field 2 is empty @@ -544,10 +555,10 @@ const validateLocation = (locationType, value) => { if (value && !alphanumericRegexTest.test(value)) { errorMessage += `Field should only contain alphanumeric characters. ` } - if (value && !value.length < LOCATION_OPERATING_HOURS_MIN_LENGTH) { + if (value && value.length < LOCATION_OPERATING_HOURS_MIN_LENGTH) { errorMessage += `Field should be longer than ${LOCATION_OPERATING_HOURS_MIN_LENGTH} characters.` } - if (value && !value.length > LOCATION_OPERATING_HOURS_MAX_LENGTH) { + if (value && value.length > LOCATION_OPERATING_HOURS_MAX_LENGTH) { errorMessage += `Field should be shorter than ${LOCATION_OPERATING_HOURS_MAX_LENGTH} characters.` } break;