diff --git a/src/control/tests/spec/categories.spec.js b/src/control/tests/spec/categories.spec.js
index 6947ec9e..6bd1ab0d 100644
--- a/src/control/tests/spec/categories.spec.js
+++ b/src/control/tests/spec/categories.spec.js
@@ -2,9 +2,9 @@ import "../lib/jasmine";
import "../lib/jasmine-html";
import "../lib/boot";
-import Categories from "../../../repository/Categories";
+import Categories from "../../../widget/js/global/repository/Categories";
import authManager from "../../../UserAccessControl/authManager";
-import Category from "../../../entities/Category";
+import Category from "../../../widget/js/global/data/Category";
const run = () => {
describe("Categories", () => {
diff --git a/src/control/tests/spec/locations.spec.js b/src/control/tests/spec/locations.spec.js
index 4b569ff9..6040b167 100644
--- a/src/control/tests/spec/locations.spec.js
+++ b/src/control/tests/spec/locations.spec.js
@@ -2,10 +2,10 @@ import "../lib/jasmine";
import "../lib/jasmine-html";
import "../lib/boot";
-import Categories from "../../../repository/Categories";
-import Locations from "../../../repository/Locations";
+import Categories from "../../../widget/js/global/repository/Categories";
+import Locations from "../../../widget/js/global/repository/Locations";
import authManager from "../../../UserAccessControl/authManager";
-import Location from "../../../entities/Location";
+import Location from "../../../widget/js/global/data/Location";
const run = () => {
describe("Locations", () => {
diff --git a/src/control/tests/spec/settings.spec.js b/src/control/tests/spec/settings.spec.js
index 04904d2f..2628eb77 100644
--- a/src/control/tests/spec/settings.spec.js
+++ b/src/control/tests/spec/settings.spec.js
@@ -2,7 +2,7 @@ import "../lib/jasmine";
import "../lib/jasmine-html";
import "../lib/boot";
-import Settings from "../../../repository/Settings";
+import Settings from "../../../widget/js/global/repository/Settings";
const run = () => {
describe("Settings", () => {
diff --git a/src/shared/baseDropdown.js b/src/shared/baseDropdown.js
new file mode 100644
index 00000000..cabe5543
--- /dev/null
+++ b/src/shared/baseDropdown.js
@@ -0,0 +1,133 @@
+export default class BaseDropdown {
+ constructor(container, props) {
+ this.container = typeof container === 'string' ? document.querySelector(container) : container;
+ this.props = props || {};
+ this.selectedItem = null;
+ this.placeholder = this.props.placeholder || "";
+ this.dropdownRef = null;
+ this._handleDocumentClick = this._handleDocumentClick.bind(this);
+
+ if (this.props.selectedId) {
+ this.selectedItem = this.props.items.find(item => item.id == this.props.selectedId);
+ } else {
+ const defaultItem = this.props.items && this.props.items.find(item => !item.id);
+ if (defaultItem) this.selectedItem = defaultItem;
+ else this.selectedItem = null;
+ }
+
+ this.render();
+ document.addEventListener('click', this._handleDocumentClick);
+ }
+
+ _handleDocumentClick(event) {
+ if (this.dropdownRef && !this.dropdownRef.contains(event.target) && this.dropdownRef.classList.contains('open')) {
+ this.hideDropdown();
+ }
+ }
+
+ hideDropdown() {
+ const dialogBody = document.querySelector('.dialog-body');
+ if (dialogBody) dialogBody.style.overflowY = 'auto';
+ if (this.dropdownRef) this.dropdownRef.classList.remove('open');
+ }
+
+ showDropdown() {
+ const dialogBody = document.querySelector('.dialog-body');
+ if (dialogBody) dialogBody.style.overflowY = 'unset';
+ if (this.dropdownRef) this.dropdownRef.classList.add('open');
+ }
+
+ toggleCategoriesDropDown() {
+ if (this.dropdownRef && this.dropdownRef.classList.contains('open')) {
+ this.hideDropdown();
+ } else {
+ this.showDropdown();
+ }
+ }
+
+ handleSelect(item) {
+ if (this.props.handleSelect) {
+ this.props.handleSelect(item);
+ }
+ this.hideDropdown();
+
+ this.selectedItem = item;
+ this.render();
+ }
+
+ render() {
+ this.container.innerHTML = "";
+
+ const wrapper = document.createElement('div');
+ if (this.props.id) wrapper.id = this.props.id;
+ wrapper.className = `row ${this.props.className ? this.props.className : ""}`;
+
+ if (this.props.label) {
+ const labelEl = document.createElement('label');
+ labelEl.className = "col-md-4 pull-left";
+ labelEl.innerHTML = `${this.props.label} ${this.props.required ? '
' : ''}`;
+ wrapper.appendChild(labelEl);
+ }
+
+ const colDiv = document.createElement('div');
+ colDiv.className = `${this.props.label ? "col-md-8" : "col-md-12"} pull-left`;
+
+ const btnGroup = document.createElement('div');
+ btnGroup.className = "btn-group w-100";
+ this.dropdownRef = btnGroup;
+
+ const button = document.createElement('button');
+ button.type = "button";
+ button.className = "btn btn-default dropdown-toggle text-left flex space-between";
+ button.setAttribute("data-toggle", "dropdown");
+ button.setAttribute("aria-haspopup", "true");
+ button.setAttribute("aria-expanded", "false");
+
+ if (!this.props.items || !this.props.items.length) {
+ button.disabled = true;
+ }
+
+ button.onclick = () => this.toggleCategoriesDropDown();
+
+ const ellipsisSpan = document.createElement('span');
+ ellipsisSpan.className = "ellipsis";
+ ellipsisSpan.textContent = (this.selectedItem && this.selectedItem.label) ? this.selectedItem.label : this.placeholder;
+
+ const caretSpan = document.createElement('span');
+ caretSpan.className = "caret pull-right";
+
+ button.appendChild(ellipsisSpan);
+ button.appendChild(caretSpan);
+
+ btnGroup.appendChild(button);
+
+ const ul = document.createElement('ul');
+ ul.className = "dropdown-menu min-width-unset";
+ if (this.props.dropToTop) {
+ ul.classList.add('dropup');
+ }
+ ul.setAttribute("role", "menu");
+
+ if (this.props.items && this.props.items.length) {
+ this.props.items.forEach(item => {
+ const li = document.createElement('li');
+ li.onclick = () => this.handleSelect(item);
+ const a = document.createElement('a');
+ a.textContent = item.label;
+ li.appendChild(a);
+ ul.appendChild(li);
+ });
+ }
+
+ btnGroup.appendChild(ul);
+ colDiv.appendChild(btnGroup);
+ wrapper.appendChild(colDiv);
+
+ this.container.appendChild(wrapper);
+ }
+
+ destroy() {
+ document.removeEventListener('click', this._handleDocumentClick);
+ this.container.innerHTML = "";
+ }
+}
\ No newline at end of file
diff --git a/src/shared/stringsConfig.js b/src/shared/stringsConfig.js
index f7594a95..f123c8be 100644
--- a/src/shared/stringsConfig.js
+++ b/src/shared/stringsConfig.js
@@ -294,6 +294,20 @@ export default {
required: true,
maxLength: 35,
},
+ additionalDetails: {
+ title: 'Additional Details',
+ placeholder: 'Enter text here',
+ defaultValue: 'Additional Details',
+ required: true,
+ maxLength: 35,
+ },
+ locationCustomLabel: {
+ title: 'Location Custom Label',
+ placeholder: 'Enter text here',
+ defaultValue: 'Label (optional)',
+ required: true,
+ maxLength: 35,
+ },
locationStreetNameAndNumber: {
title: 'Street Name and Number',
placeholder: 'Enter text here',
@@ -517,6 +531,62 @@ export default {
required: true,
maxLength: 15,
},
+ enterEmailAddress: {
+ title: 'Enter email address',
+ placeholder: 'Enter text here',
+ defaultValue: 'Enter email address',
+ required: true,
+ maxLength: 50,
+ },
+ enterPhoneNumber: {
+ title: 'Enter phone number',
+ placeholder: 'Enter text here',
+ defaultValue: 'Enter phone number',
+ required: true,
+ maxLength: 50,
+ },
+ enterURL: {
+ title: 'Enter URL',
+ placeholder: 'Enter text here',
+ defaultValue: 'Enter URL',
+ required: true,
+ maxLength: 50,
+ },
+ addDescription: {
+ title: 'Add description',
+ placeholder: 'Enter text here',
+ defaultValue: 'Add description',
+ required: true,
+ maxLength: 50,
+ },
+ enterDetails: {
+ title: 'Enter details',
+ placeholder: 'Enter text here',
+ defaultValue: 'Enter details',
+ required: true,
+ maxLength: 50,
+ },
+ validEmail: {
+ title: 'Enter a valid email address',
+ placeholder: 'Enter text here',
+ defaultValue: 'Enter a valid email address',
+ required: true,
+ maxLength: 50,
+ },
+ validPhone: {
+ title: 'Enter a valid phone number',
+ placeholder: 'Enter text here',
+ defaultValue: 'Enter a valid phone number',
+ required: true,
+ maxLength: 50,
+ },
+ validURL: {
+ title: 'Enter a valid URL',
+ placeholder: 'Enter text here',
+ defaultValue: 'Enter a valid URL',
+ required: true,
+ maxLength: 50,
+ },
}
},
toast: {
diff --git a/src/widget/css/main.css b/src/widget/css/main.css
index 5b095b93..fceeff53 100644
--- a/src/widget/css/main.css
+++ b/src/widget/css/main.css
@@ -124,6 +124,16 @@ section#categories.overlay {
display: flex;
flex-direction: column;
}
+.gap-2-rem {
+ gap: 2rem;
+}
+
+.gap-rem {
+ gap: 1rem;
+}
+.gap-half-rem {
+ gap: 0.5rem;
+}
.d-block {
display: block;
}
@@ -650,10 +660,26 @@ button span {
.location-detail__actions {
display: flex;
flex-wrap: wrap;
- justify-content: space-evenly;
+ justify-content: flex-start;
margin: 10px -15px 0;
padding: 15px;
box-shadow: rgb(0 0 0 / 12%) 0px 3px 6px -1px;
+ column-gap: 16px;
+}
+.location-detail__actions .action-item {
+ flex: 1;
+ max-width: 30%;
+}
+.location-detail__actions .location-custom_content_container {
+ min-width: 100% !important;
+}
+
+.location-custom_content_item {
+ font-size: 14px;
+}
+
+.location-custom_content_item .custom-content-value>p:first-child {
+ margin: 0;
}
.location-detail__actions .action-item img {
width: 50px;
@@ -1120,8 +1146,10 @@ body section#home {
}
.fixed-footer button {
+ height: 40px;
margin-right: auto;
margin-left: auto;
+ width: 50%;
}
#create .fixed-footer button,
@@ -1425,7 +1453,8 @@ h4.light-text {
text-transform: unset;
}
#locationDescriptionTextField,
-#locationDescriptionContainer {
+#locationDescriptionContainer,
+.custom-wysiwyg-container {
background-color: rgba(150,150,150,.1);
height: 165px;
padding: 4px 16px 6px;
@@ -1436,24 +1465,29 @@ h4.light-text {
position: relative;
}
#locationDescriptionTextField.has-error,
-#locationDescriptionContainer.has-error {
+#locationDescriptionContainer.has-error,
+.custom-wysiwyg-container.has-error {
border-bottom-color: var(--mdc-theme-error, #EB4747) !important;
}
#locationDescriptionTextField *,
-#locationDescriptionContainer * {
+#locationDescriptionContainer *,
+.custom-wysiwyg-container * {
pointer-events: none;
}
#locationDescriptionTextField:not(.disabled),
-#locationDescriptionContainer:not(.disabled) {
+#locationDescriptionContainer:not(.disabled),
+.custom-wysiwyg-container:not(.disabled) {
cursor: text;
}
#locationDescriptionTextField.disabled,
-#locationDescriptionContainer.disabled {
+#locationDescriptionContainer.disabled,
+.custom-wysiwyg-container.disabled {
cursor: not-allowed;
opacity: 0.5;
}
#locationDescriptionTextField .mdc-floating-label,
-#locationDescriptionContainer .mdc-floating-label {
+#locationDescriptionContainer .mdc-floating-label,
+.custom-wysiwyg-container .mdc-floating-label {
left: 16px;
top: 20px;
color: var(--mdc-theme-on-background, #000000);
@@ -1517,6 +1551,31 @@ h4.light-text {
padding: 20px 0;
}
+.custom-field-title {
+ font-size: 14px;
+ font-weight: 400;
+ color: var(--bf-theme-header-text);
+}
+
+.text-32 {
+ font-size: 32px;
+}
+.custom-field-title.required::after {
+ margin-left: 1px;
+ content: "*";
+ font-size: 15px;
+}
.hidden {
display: none !important;
}
+
+.headerTextTheme {
+ color: var(--bf-theme-header-text);
+}
+
+.iconsTheme {
+ color: var(--bf-theme-icons);
+}
+.margin-auto {
+ margin: auto;
+}
\ No newline at end of file
diff --git a/src/widget/js/Views.js b/src/widget/js/Views.js
index d80e8e36..0ce7882d 100644
--- a/src/widget/js/Views.js
+++ b/src/widget/js/Views.js
@@ -5,6 +5,8 @@ import state from "./state";
import detailsView from "./views/detailsView";
import introView from "./views/introView";
import mapView from "./views/mapView";
+import createView from './views/createView';
+import editView from './views/editView';
class Views {
constructor() {
@@ -71,6 +73,12 @@ class Views {
case 'detail':
detailsView.initLocationDetails();
break;
+ case 'create':
+ createView.navigateTo();
+ break;
+ case 'edit':
+ editView.init();
+ break;
}
}
}
diff --git a/src/widget/js/accessManager.js b/src/widget/js/accessManager.js
index de193f38..79f77a06 100644
--- a/src/widget/js/accessManager.js
+++ b/src/widget/js/accessManager.js
@@ -1,4 +1,5 @@
import authManager from '../../UserAccessControl/authManager';
+import constants from './global/constants';
import state from './state';
export default {
@@ -75,4 +76,20 @@ export default {
}
return authed;
},
+ hasIntroScreenAccess() {
+ const { currentUser } = authManager;
+ const { visibilityOptions } = state.settings.introductoryListView;
+
+ if (visibilityOptions.value === constants.IntroViewVisibilityOptions.ALL) return true;
+
+ const appId = buildfire.getContext().appId;
+ if (currentUser && currentUser.tags && currentUser.tags[appId] && visibilityOptions.value === constants.IntroViewVisibilityOptions.TAGS) {
+ const allowedTagNames = visibilityOptions.tags.map((t) => t.tagName);
+
+ const userTagNames = currentUser.tags[appId].map((tag) => tag.tagName);
+
+ return userTagNames.some((tagName) => allowedTagNames.includes(tagName));
+ }
+ return false;
+ }
};
diff --git a/src/widget/js/constants/index.js b/src/widget/js/constants/index.js
deleted file mode 100644
index 3e970855..00000000
--- a/src/widget/js/constants/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import MAP_STYLES from './mapStyles';
-
-const getMapStyle = (name) => {
- let style;
-
- switch (name) {
- case 'nightMode':
- style = MAP_STYLES.NIGHT_MODE;
- break;
- default:
- style = [];
- }
-
- return style;
-};
-
-const getDefaultLocation = () => ({ lat: 32.7182625, lng: -117.1601157 });
-
-const SearchLocationsModes = {
- All: "All",
- UserPosition: "UserPosition",
- AreaRadius: "AreaRadius",
-};
-
-export default { getMapStyle, getDefaultLocation, SearchLocationsModes };
diff --git a/src/widget/js/global/constants/index.js b/src/widget/js/global/constants/index.js
new file mode 100644
index 00000000..862a381a
--- /dev/null
+++ b/src/widget/js/global/constants/index.js
@@ -0,0 +1,52 @@
+import MAP_STYLES from './mapStyles';
+
+const getMapStyle = (name) => {
+ let style;
+
+ switch (name) {
+ case 'nightMode':
+ style = MAP_STYLES.NIGHT_MODE;
+ break;
+ default:
+ style = [];
+ }
+
+ return style;
+};
+
+const getDefaultLocation = () => ({ lat: 32.7182625, lng: -117.1601157 });
+
+const SearchLocationsModes = {
+ All: "All",
+ UserPosition: "UserPosition",
+ AreaRadius: "AreaRadius",
+ MyLocations: "MyLocations",
+};
+
+const SortingOptions = {
+ Distance: "distance",
+ Alphabetical: "alphabetical",
+ Newest: "newest",
+}
+
+const IntroViewVisibilityOptions = {
+ ALL: "ALL",
+ TAGS: "TAGS",
+ NONE: "NONE",
+};
+
+const QuickActionsOptions = {
+ URL: "URL",
+ PHONE: "PHONE",
+ EMAIL: "EMAIL",
+}
+
+const ContentOptions = {
+ TEXT: "TEXT",
+ RICH_TEXT: "RICH_TEXT",
+ URL: "URL",
+ PHONE: "PHONE",
+ EMAIL: "EMAIL",
+}
+
+export default { getMapStyle, getDefaultLocation, SearchLocationsModes, SortingOptions, IntroViewVisibilityOptions, QuickActionsOptions, ContentOptions, QuickActionsOptions, ContentOptions };
diff --git a/src/widget/js/constants/mapStyles.js b/src/widget/js/global/constants/mapStyles.js
similarity index 100%
rename from src/widget/js/constants/mapStyles.js
rename to src/widget/js/global/constants/mapStyles.js
diff --git a/src/entities/Category.js b/src/widget/js/global/data/Category.js
similarity index 100%
rename from src/entities/Category.js
rename to src/widget/js/global/data/Category.js
diff --git a/src/entities/Location.js b/src/widget/js/global/data/Location.js
similarity index 90%
rename from src/entities/Location.js
rename to src/widget/js/global/data/Location.js
index c9a4addf..30ab56bf 100644
--- a/src/entities/Location.js
+++ b/src/widget/js/global/data/Location.js
@@ -54,6 +54,10 @@ export default class Location {
this.deletedOn = data.deletedOn || null;
this.deletedBy = data.deletedBy || null;
this.isActive = [0, 1].includes(data.isActive) ? data.isActive : 1;
+ this.additionalFields = {
+ quickActions: (data.additionalFields?.quickActions || []).map(field => new CustomField(field)),
+ content: (data.additionalFields?.content || []).map(field => new CustomField(field))
+ };
}
get instanceId() {
@@ -93,6 +97,7 @@ export default class Location {
deletedOn: this.deletedOn,
deletedBy: this.deletedBy,
isActive: this.isActive,
+ additionalFields: this.additionalFields,
_buildfire: {
index: {
text: `${this.title.toLowerCase()} ${this.subtitle ? this.subtitle : ''} ${this.address} ${this.formattedAddress} ${this.addressAlias ? this.addressAlias : ''}`,
@@ -115,3 +120,11 @@ export default class Location {
};
}
}
+
+class CustomField {
+ constructor(data = {}) {
+ this.id = data.id || null;
+ this.customLabel = data.customLabel || null;
+ this.value = data.value || null;
+ }
+}
\ No newline at end of file
diff --git a/src/entities/Settings.js b/src/widget/js/global/data/Settings.js
similarity index 71%
rename from src/entities/Settings.js
rename to src/widget/js/global/data/Settings.js
index 251b59f6..cdeb3d6f 100644
--- a/src/entities/Settings.js
+++ b/src/widget/js/global/data/Settings.js
@@ -1,3 +1,5 @@
+import constants from "../constants";
+
/**
* Settings data model
* @class
@@ -8,7 +10,6 @@ export default class Settings {
* @constructor
*/
constructor(data = {}) {
- this.showIntroductoryListView = typeof data.showIntroductoryListView === 'undefined' ? true : data.showIntroductoryListView;
this.subscription = data.subscription || {
enabled: false,
allowCustomNotifications: false,
@@ -17,12 +18,23 @@ export default class Settings {
this.introductoryListView = data.introductoryListView || {
images: [],
description: null,
- sorting: null,
+ sorting: constants.SortingOptions.Distance,
searchOptions: {
- mode: null,
+ mode: constants.SearchLocationsModes.UserPosition,
areaRadiusOptions: {}
- }
+ },
};
+ if (!data.introductoryListView?.visibilityOptions) {
+ this.introductoryListView.visibilityOptions = {
+ tags: [],
+ value: (data.showIntroductoryListView === true || typeof data.showIntroductoryListView === 'undefined') ? constants.IntroViewVisibilityOptions.ALL : constants.IntroViewVisibilityOptions.NONE
+ }
+ } else {
+ this.introductoryListView.visibilityOptions = {
+ tags: data.introductoryListView?.visibilityOptions?.tags || [],
+ value: data.introductoryListView?.visibilityOptions?.value || constants.IntroViewVisibilityOptions.ALL
+ };
+ }
this.sorting = data.sorting || {
defaultSorting: 'distance',
hideSorting: false,
@@ -34,6 +46,10 @@ export default class Settings {
allowSortByRating: true,
allowSortByViews: true,
};
+ this.customFields = {
+ quickActions: (data.customFields?.quickActions || []).map(field => new CustomField(field)),
+ content: (data.customFields?.content || []).map(field => new CustomField(field))
+ };
this.filter = data.filter || {
allowFilterByArea: true,
allowFilterByBookmarks: false,
@@ -53,7 +69,7 @@ export default class Settings {
allowForLocations: true,
allowForFilters: true
};
- this.design = data.design || {
+ this.design = data.design || {
listViewPosition: 'collapsed',
listViewStyle: 'backgroundImage',
defaultMapStyle: 'light',
@@ -67,7 +83,7 @@ export default class Settings {
};
this.globalEntries = data.globalEntries || {
locations: {
- allowAdding: 'none', // all || none || limited
+ allowAdding: 'none', // all || none || limited
tags: [],
},
photos: {
@@ -87,7 +103,7 @@ export default class Settings {
enabled: true,
time: "12H"
};
- this.categoriesSortBy = data.categoriesSortBy || "Asc";
+ this.categoriesSortBy = data.categoriesSortBy || "Asc";
this.createdOn = data.createdOn || new Date();
this.createdBy = data.createdBy || null;
this.lastUpdatedOn = data.lastUpdatedOn || new Date();
@@ -104,13 +120,13 @@ export default class Settings {
toJSON() {
return {
subscription: this.subscription,
- showIntroductoryListView: this.showIntroductoryListView,
measurementUnit: this.measurementUnit,
introductoryListView: this.introductoryListView,
sorting: this.sorting,
filter: this.filter,
map: this.map,
bookmarks: this.bookmarks,
+ customFields: this.customFields,
design: this.design,
globalEntries: this.globalEntries,
globalEditors: this.globalEditors,
@@ -129,3 +145,13 @@ export default class Settings {
};
}
}
+
+class CustomField {
+ constructor(data = {}) {
+ this.id = data.id || null;
+ this.label = data.label || null;
+ this.type = data.type || null;
+ this.required = data.required || false;
+ this.enableCustomLabel = data.enableCustomLabel || false;
+ }
+}
diff --git a/src/widget/js/global/helpers.js b/src/widget/js/global/helpers.js
new file mode 100644
index 00000000..87ebeafc
--- /dev/null
+++ b/src/widget/js/global/helpers.js
@@ -0,0 +1,10 @@
+export const generateUUID = () => {
+ let dt = new Date().getTime();
+ const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
+ const replace = (dt + Math.random() * 16) % 16 | 0;
+ dt = Math.floor(dt / 16);
+ return (c == "x" ? replace : (replace & 0x3) | 0x8).toString(16);
+ });
+
+ return uuid;
+};
\ No newline at end of file
diff --git a/src/repository/Categories.js b/src/widget/js/global/repository/Categories.js
similarity index 97%
rename from src/repository/Categories.js
rename to src/widget/js/global/repository/Categories.js
index 90542ba2..f942ecc2 100644
--- a/src/repository/Categories.js
+++ b/src/widget/js/global/repository/Categories.js
@@ -1,4 +1,4 @@
-import Category from "../entities/Category";
+import Category from "../data/Category";
/**
* Categories data access
* @class
diff --git a/src/repository/Locations.js b/src/widget/js/global/repository/Locations.js
similarity index 98%
rename from src/repository/Locations.js
rename to src/widget/js/global/repository/Locations.js
index 9c11bcab..aeea48b8 100644
--- a/src/repository/Locations.js
+++ b/src/widget/js/global/repository/Locations.js
@@ -1,4 +1,4 @@
-import Location from "../entities/Location";
+import Location from "../data/Location";
/**
* Locations data access
diff --git a/src/repository/Settings.js b/src/widget/js/global/repository/Settings.js
similarity index 94%
rename from src/repository/Settings.js
rename to src/widget/js/global/repository/Settings.js
index 8a9ffe3c..0e741cdd 100644
--- a/src/repository/Settings.js
+++ b/src/widget/js/global/repository/Settings.js
@@ -1,4 +1,5 @@
-import Setting from "../entities/Settings";
+import Setting from "../data/Settings";
+import constants from "../constants";
export default class Settings {
/**
@@ -29,6 +30,7 @@ export default class Settings {
resolve(settings);
return;
}
+
resolve(new Setting(res.data));
});
});
diff --git a/src/repository/searchEngine.js b/src/widget/js/global/repository/searchEngine.js
similarity index 100%
rename from src/repository/searchEngine.js
rename to src/widget/js/global/repository/searchEngine.js
diff --git a/src/widget/js/util/helpers.js b/src/widget/js/util/helpers.js
index 51867520..2fff05c9 100644
--- a/src/widget/js/util/helpers.js
+++ b/src/widget/js/util/helpers.js
@@ -5,7 +5,8 @@ import { convertTimeToDate, getCurrentDayName, openingNowDate } from '../../../u
import state from '../state';
import Analytics from '../../../utils/analytics';
import WidgetController from '../../widget.controller';
-import Location from '../../../entities/Location';
+import Location from '../global/data/Location';
+import { generateUUID } from '../global/helpers';
export const deepObjectDiff = (a, b, reversible) => {
const r = {};
@@ -55,15 +56,6 @@ export const cdnImage = (imageUrl) => {
return `https://buidfire-proxy.imgix.net/app_${appId}/${encodeURIComponent(imageUrl)}`;
};
-export const generateUUID = () => {
- let dt = new Date().getTime();
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
- const replace = (dt + Math.random() * 16) % 16 | 0;
- dt = Math.floor(dt / 16);
- return (c === "x" ? replace : (replace & 0x3) | 0x8).toString(16);
- });
-};
-
export const getDefaultOpeningHours = () => {
const intervals = [{ from: convertTimeToDate("08:00"), to: convertTimeToDate("20:00") }];
return {
@@ -104,7 +96,7 @@ export const cropImage = (url, options) => {
if (!url) {
return '';
}
- return buildfire.imageLib.cropImage(url, options);
+ return buildfire.imageLib.cropImage(url, options) + '&crop=entropy';
};
export const isLocationOpen = (location) => {
diff --git a/src/widget/js/Accordion.js b/src/widget/js/views/components/Accordion.js
similarity index 100%
rename from src/widget/js/Accordion.js
rename to src/widget/js/views/components/Accordion.js
diff --git a/src/widget/js/views/components/customFields.js b/src/widget/js/views/components/customFields.js
new file mode 100644
index 00000000..8f21e14f
--- /dev/null
+++ b/src/widget/js/views/components/customFields.js
@@ -0,0 +1,377 @@
+import state from '../../state';
+import constants from '../../global/constants';
+
+const customFieldsController = {
+ init() {
+ const { settings } = state;
+ const locationCustomFields = document.querySelector('#locationCustomFields');
+
+ if (settings.customFields.quickActions.length || settings.customFields.content.length) {
+ const locationAdditionalDetailsContainer = document.querySelector('#locationAdditionalDetailsContainer');
+ locationAdditionalDetailsContainer.innerHTML = '';
+
+ this._injectCustomFields();
+
+ locationCustomFields.classList.remove('hidden');
+ } else {
+ locationCustomFields.classList.add('hidden');
+ }
+ },
+
+ _getCustomFieldPlaceholder(fieldType) {
+ switch (fieldType) {
+ case constants.QuickActionsOptions.EMAIL:
+ case constants.ContentOptions.EMAIL:
+ return window.strings?.get('locationEditing.enterEmailAddress')?.v || 'Enter email address';
+ case constants.QuickActionsOptions.PHONE:
+ case constants.ContentOptions.PHONE:
+ return window.strings?.get('locationEditing.enterPhoneNumber')?.v || 'Enter phone number';
+ case constants.QuickActionsOptions.URL:
+ case constants.ContentOptions.URL:
+ return window.strings?.get('locationEditing.enterURL')?.v || 'Enter URL';
+ case constants.ContentOptions.RICH_TEXT:
+ return window.strings?.get('locationEditing.addDescription')?.v || 'Add description';
+ default:
+ return window.strings?.get('locationEditing.enterDetails')?.v || 'Enter details';
+ }
+ },
+
+ _createMDCInput(id, title, value = '', type = 'text', maxLength = 150) {
+ const container = document.createElement('div');
+ container.className = 'text-field-container';
+
+ const mdcField = document.createElement('div');
+ mdcField.className = 'mdc-text-field';
+ mdcField.id = `${id}Field`;
+
+ const input = document.createElement('input');
+ input.className = 'mdc-text-field__input mdc-theme--text-primary-on-background';
+ input.id = id;
+ input.maxLength = maxLength;
+ input.type = type;
+ input.value = value;
+ mdcField.appendChild(input);
+
+ const lineRipple = document.createElement('div');
+ lineRipple.className = 'mdc-line-ripple';
+ mdcField.appendChild(lineRipple);
+
+ const floatingLabel = document.createElement('label');
+ floatingLabel.className = 'mdc-floating-label';
+ floatingLabel.setAttribute('for', id);
+ floatingLabel.textContent = title;
+ mdcField.appendChild(floatingLabel);
+
+ container.appendChild(mdcField);
+
+ const helperLine = document.createElement('div');
+ helperLine.className = `mdc-text-field-helper-line ${id}Helper hidden`;
+
+ const helperText = document.createElement('p');
+ helperText.className = 'mdc-theme--text-primary-on-background mdc-text-field-helper-text mdc-text-field-helper-text--persistent mdc-text-field-helper-text--validation-msg error-msg';
+ helperText.textContent = window.strings?.get('locationEditing.required')?.v || 'Required';
+ helperLine.appendChild(helperText);
+ container.appendChild(helperLine);
+
+ return { container, input };
+ },
+
+ _createMDCDialogTrigger(id, title, value = '') {
+ const container = document.createElement('div');
+ container.className = 'text-field-container';
+
+ const descriptionId = `${id}Container`;
+
+ const wysiwygContainer = document.createElement('div');
+ wysiwygContainer.id = descriptionId;
+ wysiwygContainer.className = 'custom-wysiwyg-container';
+
+ if (value) {
+ wysiwygContainer.innerHTML = value;
+ } else {
+ const labelSpan = document.createElement('label');
+ labelSpan.className = 'mdc-floating-label mdc-theme--text-primary-on-background';
+ labelSpan.textContent = title;
+ wysiwygContainer.appendChild(labelSpan);
+ }
+
+ wysiwygContainer.dataset.value = value;
+ container.appendChild(wysiwygContainer);
+
+ const helperLine = document.createElement('div');
+ helperLine.className = `mdc-text-field-helper-line ${id}Helper hidden`;
+
+ const helperText = document.createElement('p');
+ helperText.className = 'mdc-theme--text-primary-on-background mdc-text-field-helper-text mdc-text-field-helper-text--persistent mdc-text-field-helper-text--validation-msg error-msg';
+ helperText.textContent = window.strings?.get('locationEditing.fieldRequired')?.v || 'Required';
+ helperLine.appendChild(helperText);
+ container.appendChild(helperLine);
+
+ wysiwygContainer.onclick = (e) => {
+ if (wysiwygContainer.classList.contains('disabled')) return;
+ wysiwygContainer.classList.add('disabled');
+ buildfire.input.showTextDialog(
+ {
+ placeholder: title,
+ saveText: window.strings?.get('locationEditing.descriptionDialogSave')?.v || 'Save',
+ cancelText: window.strings?.get('locationEditing.descriptionDialogCancel')?.v || 'Cancel',
+ defaultValue: wysiwygContainer.dataset.value || '',
+ wysiwyg: true,
+ },
+ (err, response) => {
+ wysiwygContainer.classList.remove('disabled');
+ if (err) return console.error(err);
+ if (response.cancelled) return;
+
+ wysiwygContainer.dataset.value = response.results[0].wysiwygValue;
+ wysiwygContainer.innerHTML = '';
+
+ if (response.results[0].wysiwygValue) {
+ wysiwygContainer.innerHTML = response.results[0].wysiwygValue;
+ } else {
+ const labelSpan = document.createElement('label');
+ labelSpan.className = 'mdc-floating-label mdc-theme--text-primary-on-background';
+ labelSpan.textContent = title;
+ wysiwygContainer.appendChild(labelSpan);
+ }
+ }
+ );
+ };
+
+ return { container, input: wysiwygContainer };
+ },
+
+ _injectCustomFields() {
+ const { settings, selectedLocation } = state;
+ const locationAdditionalDetailsContainer = document.querySelector('#locationAdditionalDetailsContainer');
+
+ const allFields = [
+ ...(settings.customFields?.quickActions || []),
+ ...(settings.customFields?.content || [])
+ ];
+
+ const activeLocationFields = [
+ ...(selectedLocation?.additionalFields?.quickActions || []),
+ ...(selectedLocation?.additionalFields?.content || [])
+ ];
+
+ allFields.forEach(field => {
+ const activeField = activeLocationFields.find(f => f.id === field.id) || {};
+ const isRichText = field.type === constants.ContentOptions.RICH_TEXT;
+
+ const row = document.createElement('div');
+ row.id = `custom-field-container-${field.id}`;
+ row.className = 'custom-field-wrapper margin-bottom-twenty d-flex-column gap-half-rem';
+
+ const fieldTitle = field.label;
+ const fieldTitleSpan = document.createElement('span');
+ fieldTitleSpan.className = field.required ? 'custom-field-title required' : 'custom-field-title';
+ fieldTitleSpan.textContent = fieldTitle;
+ row.appendChild(fieldTitleSpan);
+ const placeholder = this._getCustomFieldPlaceholder(field.type);
+
+ if (field.enableCustomLabel) {
+ const innerFlex = document.createElement('div');
+ innerFlex.className = 'd-flex-column gap-half-rem';
+
+ const labelCol = document.createElement('div');
+ labelCol.className = 'w-100';
+
+ const { container: labelMdc } = this._createMDCInput(`custom-field-label-${field.id}`, window.strings?.get('locationEditing.locationCustomLabel')?.v || 'Label (optional)', activeField.customLabel || '', 'text', 50);
+ labelCol.appendChild(labelMdc);
+
+ const valueCol = document.createElement('div');
+ valueCol.className = 'w-100';
+
+ if (isRichText) {
+ const { container: valueMdc } = this._createMDCDialogTrigger(`custom-field-input-${field.id}`, placeholder, activeField.value || '');
+ valueCol.appendChild(valueMdc);
+ } else {
+ const { container: valueMdc, input } = this._createMDCInput(`custom-field-input-${field.id}`, placeholder, activeField.value || '', 'text', 150);
+ input.addEventListener('blur', () => this._validateField(field));
+ valueCol.appendChild(valueMdc);
+ }
+
+ innerFlex.appendChild(labelCol);
+ innerFlex.appendChild(valueCol);
+ row.appendChild(innerFlex);
+
+ } else {
+ if (isRichText) {
+ const { container: valueMdc } = this._createMDCDialogTrigger(`custom-field-input-${field.id}`, placeholder, activeField.value || '');
+ row.appendChild(valueMdc);
+ } else {
+ const { container: valueMdc, input } = this._createMDCInput(`custom-field-input-${field.id}`, placeholder, activeField.value || '', 'text', 150);
+ input.addEventListener('blur', () => this._validateField(field));
+ row.appendChild(valueMdc);
+ }
+ }
+
+ locationAdditionalDetailsContainer.appendChild(row);
+
+ row.querySelectorAll('.mdc-text-field').forEach((el) => {
+ new mdc.textField.MDCTextField(el);
+ });
+ });
+ },
+
+ validate() {
+ const { settings } = state;
+ let isValid = true;
+ const allFields = [
+ ...(settings.customFields?.quickActions || []),
+ ...(settings.customFields?.content || [])
+ ];
+
+ allFields.forEach(field => {
+ if (!this._validateField(field)) {
+ isValid = false;
+ }
+ });
+
+ return isValid;
+ },
+
+ _validateField(field) {
+ let isValid = true;
+ const isRichText = field.type === constants.ContentOptions.RICH_TEXT;
+ let inputElement;
+ let valueStr = '';
+
+ if (isRichText) {
+ inputElement = document.querySelector(`#custom-field-input-${field.id}Container`);
+ valueStr = inputElement ? (inputElement.dataset.value || '') : '';
+ } else {
+ inputElement = document.querySelector(`#custom-field-input-${field.id}`);
+ valueStr = inputElement ? (inputElement.value || '') : '';
+ }
+
+ if (!isRichText && inputElement) {
+ valueStr = valueStr.trim();
+ inputElement.value = valueStr;
+ } else if (isRichText && inputElement) {
+ valueStr = valueStr.trim();
+ inputElement.dataset.value = valueStr;
+ }
+
+ const helperLine = document.querySelector(`.custom-field-input-${field.id}Helper`);
+ const helperText = helperLine ? helperLine.querySelector('.error-msg') : null;
+
+ if (helperLine) {
+ helperLine.classList.add('hidden');
+ helperLine.classList.remove('has-error');
+ }
+ if (inputElement && !isRichText) {
+ const mdcField = inputElement.closest('.mdc-text-field');
+ if (mdcField) mdcField.classList.remove('mdc-text-field--invalid');
+ }
+
+ let errorMessage = '';
+
+ if (field.required && !valueStr) {
+ errorMessage = window.strings?.get('locationEditing.fieldRequired')?.v || 'This field is required';
+ } else if (valueStr) {
+ if (field.type === constants.QuickActionsOptions.EMAIL || field.type === constants.ContentOptions.EMAIL) {
+ const emailHasAt = valueStr.includes('@');
+ const emailHasDotAfterAt = emailHasAt && valueStr.indexOf('.', valueStr.indexOf('@')) !== -1;
+ const emailNoSpaces = !valueStr.includes(' ');
+
+ if (!emailHasAt || !emailHasDotAfterAt || !emailNoSpaces) {
+ errorMessage = window.strings?.get('locationEditing.validEmail')?.v || 'Enter a valid email address';
+ }
+ } else if (field.type === constants.QuickActionsOptions.PHONE || field.type === constants.ContentOptions.PHONE) {
+ const phoneRegex = /^[\d\s+\-()]+$/;
+ const hasLetters = /[a-zA-Z]/.test(valueStr);
+ if (!phoneRegex.test(valueStr) || hasLetters) {
+ errorMessage = window.strings?.get('locationEditing.validPhone')?.v || 'Enter a valid phone number';
+ }
+ } else if (field.type === constants.QuickActionsOptions.URL || field.type === constants.ContentOptions.URL) {
+ if (valueStr.includes(' ') || !valueStr.includes('.')) {
+ errorMessage = window.strings?.get('locationEditing.validURL')?.v || 'Enter a valid URL';
+ } else {
+ if (!/^https?:\/\//i.test(valueStr)) {
+ valueStr = 'https://' + valueStr;
+ if (inputElement && !isRichText) {
+ inputElement.value = valueStr;
+ }
+ }
+ }
+ }
+ }
+
+ if (errorMessage) {
+ isValid = false;
+ if (helperLine && helperText) {
+ helperText.textContent = errorMessage;
+ helperLine.classList.remove('hidden');
+ helperLine.classList.add('has-error');
+ }
+ if (inputElement && !isRichText) {
+ const mdcField = inputElement.closest('.mdc-text-field');
+ if (mdcField) mdcField.classList.add('mdc-text-field--invalid');
+ }
+ }
+
+ return isValid;
+ },
+
+ getFieldsValues() {
+ const { settings } = state;
+ const values = {
+ quickActions: [],
+ content: []
+ };
+
+ const allFields = [
+ ...(settings.customFields?.quickActions || []),
+ ...(settings.customFields?.content || [])
+ ];
+
+ allFields.forEach(field => {
+ const isRichText = field.type === constants.ContentOptions.RICH_TEXT;
+ let inputElement;
+ let valueStr = '';
+ let customLabelStr = '';
+
+ if (isRichText) {
+ inputElement = document.querySelector(`#custom-field-input-${field.id}Container`);
+ valueStr = inputElement ? (inputElement.dataset.value || '') : '';
+ } else {
+ inputElement = document.querySelector(`#custom-field-input-${field.id}`);
+ valueStr = inputElement ? (inputElement.value || '') : '';
+ }
+
+ if (field.enableCustomLabel) {
+ const labelInput = document.querySelector(`#custom-field-label-${field.id}`);
+ customLabelStr = labelInput ? (labelInput.value || '') : '';
+ }
+
+ const fieldData = {
+ id: field.id,
+ value: valueStr.trim()
+ };
+
+ if (field.enableCustomLabel) {
+ fieldData.customLabel = customLabelStr.trim();
+ }
+
+ if (settings.customFields?.quickActions?.find(q => q.id === field.id)) {
+ values.quickActions.push(fieldData);
+ } else {
+ values.content.push(fieldData);
+ }
+ });
+
+ return values;
+ }
+};
+
+export default {
+ ui: {
+ init: customFieldsController.init.bind(customFieldsController)
+ },
+ helper: {
+ validate: customFieldsController.validate.bind(customFieldsController),
+ getFieldsValues: customFieldsController.getFieldsValues.bind(customFieldsController)
+ }
+};
\ No newline at end of file
diff --git a/src/widget/js/views/createView.js b/src/widget/js/views/createView.js
index 8efd0e30..26b9ffb5 100644
--- a/src/widget/js/views/createView.js
+++ b/src/widget/js/views/createView.js
@@ -1,9 +1,8 @@
import state from '../state';
-import Location from '../../../entities/Location';
+import Location from '../global/data/Location';
import views from '../Views';
import {
createTemplate,
- generateUUID,
getDefaultOpeningHours,
showToastMessage,
transformCategoriesToText,
@@ -11,8 +10,9 @@ import {
getActiveTemplate,
cropImage
} from '../util/helpers';
-import { navigateTo, resetBodyScroll, showElement } from '../util/ui';
-import Accordion from '../Accordion';
+import { generateUUID } from '../global/helpers';
+import { hideElement, navigateTo, resetBodyScroll, showElement } from '../util/ui';
+import Accordion from './components/Accordion';
import { convertDateToTime, convertTimeToDate } from '../../../utils/datetime';
import mapView from './mapView';
import introView from './introView';
@@ -23,7 +23,8 @@ import {
validateTimeInterval,
uploadImages, toggleFieldError, createImageHolder, validateOpeningHours
} from '../util/forms';
-import constants from '../constants';
+import constants from '../global/constants';
+import customFieldsController from './components/customFields';
export default {
get _defaultFieldsInfo() {
@@ -292,6 +293,10 @@ export default {
isValid = false;
}
+ if (!customFieldsController.helper.validate()) {
+ isValid = false;
+ }
+
return { isValid };
},
submit(e) {
@@ -314,6 +319,8 @@ export default {
return;
}
+ this.payload.additionalFields = customFieldsController.helper.getFieldsValues();
+
widgetController.createLocation(this.payload)
.then((result) => {
showToastMessage('locationSaved', 5000);
@@ -335,6 +342,7 @@ export default {
if (activeTemplate === 'intro') {
introView.clearIntroViewList();
introView.renderIntroductoryLocations(state.listLocations, true);
+ hideElement('#intro div.empty-page');
} else {
mapView.clearMapViewList();
mapView.renderListingLocations(state.listLocations);
@@ -848,7 +856,17 @@ export default {
this.buildEventsHandlers();
this.buildMap();
this.show();
+ customFieldsController.ui.init();
window.strings.inject(document.querySelector('section#create'), false);
+
+ buildfire.device.onKeyboardShow(() => {
+ setTimeout(() => {
+ const currentActiveInput = document.querySelector('.mdc-text-field.mdc-text-field--focused');
+ if (currentActiveInput) {
+ currentActiveInput.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+ }, 100);
+ });
});
},
};
diff --git a/src/widget/js/views/detailsView.js b/src/widget/js/views/detailsView.js
index 78ea10f0..1ecd2d7f 100644
--- a/src/widget/js/views/detailsView.js
+++ b/src/widget/js/views/detailsView.js
@@ -5,7 +5,6 @@ import state from '../state';
import {
shareLocation,
bookmarkLocation,
- generateUUID,
showToastMessage,
getActiveTemplate,
transformCategoriesToText,
@@ -13,20 +12,21 @@ import {
isLocationOpen,
cdnImage
} from '../util/helpers';
+import { generateUUID } from '../global/helpers';
import { uploadImages } from '../util/forms';
import accessManager from '../accessManager';
import reportAbuse from '../reportAbuse';
-import Locations from '../../../repository/Locations';
-import Location from '../../../entities/Location';
+import Locations from '../global/repository/Locations';
+import Location from '../global/data/Location';
import DeepLink from '../../../utils/deeplink';
-import SearchEngine from '../../../repository/searchEngine';
+import SearchEngine from '../global/repository/searchEngine';
import introView from './introView';
import mapView from './mapView';
import authManager from '../../../UserAccessControl/authManager';
import notifications from '../../services/notifications';
import widgetController from '../../widget.controller';
import views from '../Views';
-import constants from '../constants';
+import constants from '../global/constants';
import MainMap from '../map/Map';
let selectors = {};
@@ -37,7 +37,7 @@ export default {
const location = state.selectedLocation;
const carouselContainer = document.querySelector('.location-detail__carousel');
- carouselContainer.innerHTML = location.images.map((n) => `
`).join('\n');
+ carouselContainer.innerHTML = location.images.map((n) => `
`).join('\n');
const indexInList = state.listLocations.findIndex((i) => i.id === location.id);
const indexInPinned = state.pinnedLocations.findIndex((i) => i.id === location.id);
@@ -353,6 +353,119 @@ export default {
}
},
+ buildCustomActions() {
+ const { selectedLocation } = state;
+ const additionalFields = selectedLocation.additionalFields || {};
+ const quickActions = additionalFields.quickActions || [];
+ const content = additionalFields.content || [];
+
+ state.settings?.customFields?.quickActions.forEach(fieldSchema => {
+ const field = quickActions.find(q => q.id === fieldSchema.id);
+ if (!fieldSchema.type || !field || !field.value) return;
+
+ let icon = '';
+ let actionObject = null;
+
+ if (fieldSchema.type === constants.QuickActionsOptions.EMAIL) {
+ icon = '
alternate_email';
+ actionObject = { action: 'sendEmail', email: field.value };
+ } else if (fieldSchema.type === constants.QuickActionsOptions.PHONE) {
+ icon = '
call';
+ actionObject = { action: 'callNumber', phoneNumber: field.value };
+ } else if (fieldSchema.type === constants.QuickActionsOptions.URL) {
+ icon = '
link';
+ actionObject = { action: 'linkToWeb', url: field.value, openIn: '_blank' };
+ }
+
+ let title = field.customLabel;
+ if (!fieldSchema.enableCustomLabel || !field.customLabel) {
+ title = fieldSchema.label;
+ }
+
+ const div = document.createElement('div');
+ div.className = 'action-item';
+ div.innerHTML = `
+ ${icon}
+
+ `;
+
+ div.onclick = (e) => {
+ e.stopPropagation();
+ if (actionObject) {
+ buildfire.actionItems.execute(actionObject, (err) => {
+ if (err) console.error(err);
+ });
+ }
+ };
+
+ selectors.actionItems.appendChild(div);
+ });
+
+ state.settings?.customFields?.content.forEach(fieldSchema => {
+ const field = content.find(q => q.id === fieldSchema.id);
+
+ if (!fieldSchema.type || !field || !field.value) return;
+
+ let descriptionContainer = selectors.actionItems.querySelector('.location-custom_content_container');
+ if (!descriptionContainer) {
+ descriptionContainer = document.createElement('div');
+ descriptionContainer.className = 'location-custom_content_container flex d-flex-column gap-2-rem padded-md';
+ selectors.actionItems.appendChild(descriptionContainer);
+ }
+
+ const fieldContainer = document.createElement('div');
+ fieldContainer.className = 'location-custom_content_item d-flex-column gap-half-rem';
+
+ let title = field.customLabel;
+ if (!fieldSchema.enableCustomLabel || !field.customLabel) {
+ title = fieldSchema.label;
+ }
+
+ const titleEl = document.createElement('p');
+ titleEl.innerHTML = title;
+ titleEl.classList.add('headerTextTheme');
+ titleEl.style.margin = '0';
+
+ const valueEl = document.createElement('div');
+ valueEl.innerHTML = field.value;
+ valueEl.className = 'custom-content-value';
+ let actionObject = null;
+
+ if (fieldSchema.type === constants.ContentOptions.EMAIL) {
+ valueEl.classList.add('primaryTheme', 'pointer');
+ actionObject = { action: 'sendEmail', email: field.value };
+ } else if (fieldSchema.type === constants.ContentOptions.PHONE) {
+ valueEl.classList.add('primaryTheme', 'pointer');
+ actionObject = { action: 'callNumber', phoneNumber: field.value };
+ } else if (fieldSchema.type === constants.ContentOptions.URL) {
+ valueEl.classList.add('primaryTheme', 'pointer');
+ actionObject = { action: 'linkToWeb', url: field.value, openIn: '_blank' };
+ } else {
+ valueEl.classList.add('bodyTextTheme');
+ }
+
+ valueEl.onclick = (e) => {
+ e.stopPropagation();
+ if (actionObject) {
+ buildfire.actionItems.execute(actionObject, (err) => {
+ if (err) console.error(err);
+ });
+ }
+ };
+
+ fieldContainer.appendChild(titleEl);
+ fieldContainer.appendChild(valueEl);
+ descriptionContainer.appendChild(fieldContainer);
+ });
+ },
+
initLocationDetails() {
return new Promise((resolve, reject) => {
const { selectedLocation } = state;
@@ -431,10 +544,10 @@ export default {
if (selectedLocation.images?.length > 0) {
if (pageMapPosition === 'top') {
- selectors.cover.style.backgroundImage = `linear-gradient( rgb(0 0 0 / 0.6), rgb(0 0 0 / 0.6) ),url('${buildfire.imageLib.cropImage(selectedLocation.images[0].imageUrl, { size: "full_width", aspect: "16:9" })}')`;
+ selectors.cover.style.backgroundImage = `linear-gradient( rgb(0 0 0 / 0.6), rgb(0 0 0 / 0.6) ),url('${buildfire.imageLib.cropImage(selectedLocation.images[0].imageUrl, { size: "full_width", aspect: "1:1" }) + '&crop=entropy'}')`;
selectors.cover.style.display = 'block';
} else {
- selectors.main.style.backgroundImage = `linear-gradient( rgb(0 0 0 / 0.6), rgb(0 0 0 / 0.6) ),url('${buildfire.imageLib.cropImage(selectedLocation.images[0].imageUrl, { size: "full_width", aspect: "16:9" })}')`;
+ selectors.main.style.backgroundImage = `linear-gradient( rgb(0 0 0 / 0.6), rgb(0 0 0 / 0.6) ),url('${buildfire.imageLib.cropImage(selectedLocation.images[0].imageUrl, { size: "full_width", aspect: "1:1" }) + '&crop=entropy'}')`;
}
}
@@ -497,7 +610,7 @@ export default {
}
selectors.actionItems.innerHTML = selectedLocation.actionItems.map((a) => `
- ${a.iconUrl ? `
})
` : a.iconClassName ? `
` : ''}
+ ${a.iconUrl ? `
})
` : a.iconClassName ? `
` : ''}
@@ -507,7 +620,9 @@ export default {
`).join('\n');
- selectors.carousel.innerHTML = selectedLocation.images.map((n) => `
`).join('\n');
+ selectors.carousel.innerHTML = selectedLocation.images.map((n) => `
`).join('\n');
+ this.buildCustomActions();
+
resolve();
});
});
diff --git a/src/widget/js/views/editView.js b/src/widget/js/views/editView.js
index 2f3e7b58..d13a88c3 100644
--- a/src/widget/js/views/editView.js
+++ b/src/widget/js/views/editView.js
@@ -1,12 +1,11 @@
import state from '../state';
-import Location from '../../../entities/Location';
-import Locations from '../../../repository/Locations';
+import Location from '../global/data/Location';
+import Locations from '../global/repository/Locations';
import DeepLink from '../../../utils/deeplink';
-import SearchEngine from '../../../repository/searchEngine';
+import SearchEngine from '../global/repository/searchEngine';
import views from '../Views';
import {
createTemplate,
- generateUUID,
getDefaultOpeningHours,
showToastMessage,
transformCategoriesToText,
@@ -14,15 +13,17 @@ import {
getActiveTemplate,
cropImage,
} from '../util/helpers';
+import { generateUUID } from '../global/helpers';
import { uploadImages } from '../util/forms';
import { navigateTo, resetBodyScroll } from '../util/ui';
-import Accordion from '../Accordion';
+import Accordion from './components/Accordion';
import { convertDateToTime, convertTimeToDate } from '../../../utils/datetime';
import mapView from './mapView';
import introView from './introView';
import { validateOpeningHoursDuplication } from '../../../shared/utils';
-import constants from '../constants';
+import constants from '../global/constants';
import accessManager from '../accessManager';
+import customFieldsController from './components/customFields';
const localState = {
pendingLocation: null,
@@ -173,6 +174,10 @@ const _validateLocationSave = () => {
isValid = false;
}
+ if (!customFieldsController.helper.validate()) {
+ isValid = false;
+ }
+
return isValid;
};
@@ -315,6 +320,8 @@ const _saveChanges = (e) => {
if (!_validateLocationSave()) return;
if (!validateOpeningHoursDuplication(pendingLocation.openingHours)) return;
+ pendingLocation.additionalFields = customFieldsController.helper.getFieldsValues();
+
e.target.disabled = true;
updateLocation(pendingLocation.id, pendingLocation)
@@ -887,6 +894,7 @@ const init = () => {
_refreshLocationImages();
_renderOpeningHours();
_initAddressAutocompleteField('locationAddressFieldInput');
+ customFieldsController.ui.init();
editView.addEventListener('click', onViewClick);
editView.querySelectorAll('.mdc-text-field').forEach((i) => {
@@ -940,6 +948,15 @@ const init = () => {
multi: true,
expanded: true
});
+
+ buildfire.device.onKeyboardShow(() => {
+ setTimeout(() => {
+ const currentActiveInput = document.querySelector('.mdc-text-field.mdc-text-field--focused');
+ if (currentActiveInput) {
+ currentActiveInput.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+ }, 100);
+ });
})
.catch((err) => {
console.error(`error initilizing editView ${err}`);
diff --git a/src/widget/js/views/introView.js b/src/widget/js/views/introView.js
index 744d7f38..6070ff9d 100644
--- a/src/widget/js/views/introView.js
+++ b/src/widget/js/views/introView.js
@@ -9,7 +9,7 @@ const renderIntroductoryLocations = (list, includePinned = false) => {
const container = document.querySelector('#introLocationsList');
let reducedLocations = list.reduce((filtered, n) => {
if (n.listImage != null) {
- n.listImage = buildfire.imageLib.cropImage(n.listImage, { size: "full_width", aspect: "1:1" })
+ n.listImage = buildfire.imageLib.cropImage(n.listImage, { size: "full_width", aspect: "1:1" }) + '&crop=entropy'
} else {
n.listImage = "./images/empty_image.PNG"
}
@@ -48,7 +48,7 @@ const renderIntroductoryLocations = (list, includePinned = false) => {
if (includePinned) {
reducedLocations = state.pinnedLocations.map((n) => (`
-

+
${n.subtitle ?? ""}
diff --git a/src/widget/services/search/introSearchService.js b/src/widget/services/search/introSearchService.js
index 9569886c..a5571c8d 100644
--- a/src/widget/services/search/introSearchService.js
+++ b/src/widget/services/search/introSearchService.js
@@ -2,7 +2,8 @@
import { buildOpenNowCriteria, buildSearchCriteria } from "./shared";
import state from "../../js/state";
import WidgetController from "../../widget.controller";
-import constants from "../../js/constants";
+import constants from "../../js/global/constants";
+import authManager from "../../../UserAccessControl/authManager";
const IntroSearchService = {
_getUserCoordinates() {
@@ -52,6 +53,9 @@ const IntroSearchService = {
}
}
};
+ if (state.settings.introductoryListView.searchOptions?.mode === constants.SearchLocationsModes.MyLocations) {
+ $match['createdBy.userId'] = authManager.currentUser?.userId || '';
+ }
pipelines.push({ $geoNear });
pipelines.push({ $match });
@@ -82,6 +86,10 @@ const IntroSearchService = {
"_buildfire.index.string1": { $nin: existLocationStrings },
};
+ if (state.settings.introductoryListView.searchOptions?.mode === constants.SearchLocationsModes.MyLocations) {
+ $match['createdBy.userId'] = authManager.currentUser?.userId || '';
+ }
+
pipelines.push({ $match });
pipelines.push({ $sort });
diff --git a/src/widget/services/search/mapSearchService.js b/src/widget/services/search/mapSearchService.js
index 60d355e4..1ea5e6de 100644
--- a/src/widget/services/search/mapSearchService.js
+++ b/src/widget/services/search/mapSearchService.js
@@ -3,7 +3,7 @@ import WidgetController from '../../widget.controller';
import mapSearchControl from '../../js/map/search-control';
import state from "../../js/state";
import { buildOpenNowCriteria, buildSearchCriteria } from './shared';
-import constants from '../../js/constants';
+import constants from '../../js/global/constants';
const MapSearchService = {
getMapCenterPoint() {
diff --git a/src/widget/templates/create.html b/src/widget/templates/create.html
index 20a4fff8..fd4c41c2 100644
--- a/src/widget/templates/create.html
+++ b/src/widget/templates/create.html
@@ -101,6 +101,26 @@
+
+
+
+
diff --git a/src/widget/templates/edit.html b/src/widget/templates/edit.html
index dd1c12c8..57dabaae 100644
--- a/src/widget/templates/edit.html
+++ b/src/widget/templates/edit.html
@@ -104,6 +104,25 @@
+
+
+