abmp-npm 1.7.3 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ echo "Running ESLint check..."
5
+ npm run lint
6
+
7
+ if [ $? -ne 0 ]; then
8
+ echo "❌ ESLint check failed. Please fix the linting errors before committing."
9
+ exit 1
10
+ fi
11
+
12
+ echo "Running Prettier check..."
13
+ npm run format:check
14
+
15
+ if [ $? -ne 0 ]; then
16
+ echo "❌ Prettier check failed. Please format your code before committing."
17
+ echo "Run 'npm run format' to automatically format your files."
18
+ exit 1
19
+ fi
20
+
21
+ echo "✅ All checks passed!"
22
+
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ dist
3
+ build
4
+ coverage
5
+ *.min.js
6
+ package-lock.json
7
+
@@ -0,0 +1,16 @@
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "es5",
4
+ "printWidth": 100,
5
+ "tabWidth": 2,
6
+ "semi": true,
7
+ "bracketSpacing": true,
8
+ "bracketSameLine": false,
9
+ "arrowParens": "avoid",
10
+ "endOfLine": "lf",
11
+ "quoteProps": "as-needed",
12
+ "jsxSingleQuote": false,
13
+ "requirePragma": false,
14
+ "insertPragma": false,
15
+ "proseWrap": "preserve"
16
+ }
@@ -0,0 +1,13 @@
1
+ const { customTrigger } = require('@wix/automations');
2
+ const { auth } = require('@wix/essentials');
3
+ const triggerMethod = auth.elevate(customTrigger.runTrigger);
4
+
5
+ const triggerAutomation = async (triggerId, payload) =>
6
+ await triggerMethod({
7
+ triggerId,
8
+ payload,
9
+ });
10
+
11
+ module.exports = {
12
+ triggerAutomation,
13
+ };
package/backend/consts.js CHANGED
@@ -1,52 +1,13 @@
1
1
  /**
2
- * Default display settings for member profiles
2
+ * Valid configuration keys for getSiteConfigs function
3
+ * @readonly
4
+ * @enum {string}
3
5
  */
4
- const DEFAULT_MEMBER_DISPLAY_SETTINGS = {
5
- showLicenseNo: true,
6
- showName: true,
7
- showBookingUrl: false,
8
- showWebsite: false,
9
- showWixUrl: true,
10
- };
11
-
12
- /**
13
- * Address display status types
14
- */
15
- const ADDRESS_STATUS_TYPES = {
16
- FULL_ADDRESS: 'full_address',
17
- STATE_CITY_ZIP: 'state_city_zip',
18
- DONT_SHOW: 'dont_show',
19
- };
20
-
21
- /**
22
- * Address visibility configuration options
23
- */
24
- const ADDRESS_VISIBILITY_OPTIONS = {
25
- ALL: 'all',
26
- NONE: 'none',
27
- };
28
-
29
- const COLLECTIONS = {
30
- MEMBERS_DATA: 'MembersDataLatest',
31
- };
32
-
33
-
6
+ const CONFIG_KEYS = {
7
+ AUTOMATION_EMAIL_TRIGGER_ID: 'AUTOMATION_EMAIL_TRIGGER_ID',
8
+ SITE_ASSOCIATION: 'SITE_ASSOCIATION',
9
+ };
34
10
 
35
- const MEMBER_ACTIONS = {
36
- UPDATE: 'update',
37
- NEW: 'new',
38
- DROP: 'drop',
39
- NONE: 'none'
11
+ module.exports = {
12
+ CONFIG_KEYS,
40
13
  };
41
-
42
- const PRECISION = 3;
43
-
44
- module.exports = {
45
- DEFAULT_MEMBER_DISPLAY_SETTINGS,
46
- ADDRESS_STATUS_TYPES,
47
- ADDRESS_VISIBILITY_OPTIONS,
48
- PRECISION,
49
- COLLECTIONS,
50
- MEMBER_ACTIONS,
51
- };
52
-
@@ -0,0 +1,16 @@
1
+ const { items } = require('@wix/data');
2
+ const { auth } = require('@wix/essentials');
3
+
4
+ // @wix/data does not support suppressAuth currently, so we need to elevate it
5
+ const wixData = {
6
+ insert: auth.elevate(items.insert),
7
+ update: auth.elevate(items.update),
8
+ bulkInsert: auth.elevate(items.bulkInsert),
9
+ query: auth.elevate(items.query),
10
+ save: auth.elevate(items.save),
11
+ remove: auth.elevate(items.remove),
12
+ get: auth.elevate(items.get),
13
+ //TODO: add other methods here as needed
14
+ };
15
+
16
+ module.exports = { wixData };
@@ -0,0 +1,52 @@
1
+ const { COLLECTIONS } = require('../public');
2
+
3
+ const { triggerAutomation } = require('./automations-methods');
4
+ const { CONFIG_KEYS } = require('./consts');
5
+ const { wixData } = require('./elevated-modules');
6
+ const { findMemberByWixDataId, createContactAndMemberIfNew } = require('./members-data-methods');
7
+ const { getSiteConfigs } = require('./utils');
8
+
9
+ const contactSubmission = async (data, memberDataId) => {
10
+ const { firstName, lastName, email, phone, message } = data;
11
+ const [memberData, automationEmailTriggerId] = await Promise.all([
12
+ findMemberByWixDataId(memberDataId),
13
+ getSiteConfigs(CONFIG_KEYS.AUTOMATION_EMAIL_TRIGGER_ID),
14
+ ]);
15
+ if (!memberData.showContactForm) {
16
+ console.log('Member contact form is not enabled for user, skipping contact submission!');
17
+ return;
18
+ }
19
+ let memberContactId = memberData.contactId;
20
+ if (!memberContactId) {
21
+ /**
22
+ * Create a member contact here since some members may have never logged in
23
+ * and therefore don’t have a contact ID. However, these members can still
24
+ * appear in the directory, and site visitors can reach out to them if they’ve
25
+ * enabled their contact form. To handle this, we ensure a contact is created for
26
+ * them during this flow if it doesn’t already exist.
27
+ */
28
+ console.info('Member contact id not found for user, creating new contact!');
29
+ const member = await createContactAndMemberIfNew(memberData);
30
+ memberContactId = member.contactId;
31
+ }
32
+ console.log('memberContactId', memberContactId);
33
+ const emailTriggered = await triggerAutomation(automationEmailTriggerId, {
34
+ contactId: memberContactId,
35
+ name: `${firstName} ${lastName}`,
36
+ email: email,
37
+ phone: phone,
38
+ message: message,
39
+ });
40
+ data = {
41
+ ...data,
42
+ phone: Number(data.phone),
43
+ memberContactId: memberContactId,
44
+ memberEmail: memberData.contactFormEmail,
45
+ };
46
+ await wixData.insert(COLLECTIONS.CONTACT_US_SUBMISSIONS, data);
47
+ return emailTriggered;
48
+ };
49
+
50
+ module.exports = {
51
+ contactSubmission,
52
+ };
package/backend/index.js CHANGED
@@ -1,6 +1,3 @@
1
1
  module.exports = {
2
- ...require('./consts'),
3
- ...require('./utils'),
4
- ...require('./updateMemberData'),
5
- };
6
-
2
+ ...require('./forms-methods'),
3
+ };
@@ -0,0 +1,36 @@
1
+ const { auth } = require('@wix/essentials');
2
+ const { members } = require('@wix/members');
3
+ const elevatedCreateMember = auth.elevate(members.createMember);
4
+
5
+ function prepareContactData(partner) {
6
+ const phones = Array.isArray(partner.phones) ? partner.phones : []; //some users don't have phones
7
+ const options = {
8
+ member: {
9
+ contact: {
10
+ ...partner,
11
+ phones,
12
+ emails: [partner.contactFormEmail || partner.email],
13
+ },
14
+ loginEmail: partner.email,
15
+ },
16
+ };
17
+ return options;
18
+ }
19
+ async function createMemberFunction(member) {
20
+ const newMember = await elevatedCreateMember(member);
21
+ return newMember._id;
22
+ }
23
+ const createSiteMember = async memberDetails => {
24
+ try {
25
+ const options = prepareContactData(memberDetails);
26
+ const contactId = await createMemberFunction(options);
27
+ return contactId;
28
+ } catch (error) {
29
+ console.error(`Error in createSiteMember ${error.message}`);
30
+ throw error;
31
+ }
32
+ };
33
+
34
+ module.exports = {
35
+ createSiteMember,
36
+ };
@@ -0,0 +1,55 @@
1
+ const { COLLECTIONS } = require('../public');
2
+
3
+ const { wixData } = require('./elevated-modules');
4
+ const { createSiteMember } = require('./members-area-methods');
5
+
6
+ /**
7
+ * Retrieves member data by member ID
8
+ * @param {string} memberId - The member ID to search for
9
+ * @returns {Promise<Object|null>} - Member data or null if not found
10
+ */
11
+ async function findMemberByWixDataId(memberId) {
12
+ if (!memberId) {
13
+ throw new Error('Member ID is required');
14
+ }
15
+ try {
16
+ const member = await wixData.get(COLLECTIONS.MEMBERS_DATA, memberId);
17
+ return member;
18
+ } catch (error) {
19
+ throw new Error(`Failed to retrieve member data: ${error.message}`);
20
+ }
21
+ }
22
+
23
+ async function createContactAndMemberIfNew(memberData) {
24
+ if (!memberData) {
25
+ throw new Error('Member data is required');
26
+ }
27
+ try {
28
+ const toCreateMemberData = {
29
+ firstName: memberData.firstName,
30
+ lastName: memberData.lastName,
31
+ email: memberData.email,
32
+ phones: memberData.phones,
33
+ contactFormEmail: memberData.contactFormEmail || memberData.email,
34
+ };
35
+ const contactId = await createSiteMember(toCreateMemberData);
36
+ let memberDataWithContactId = {
37
+ ...memberData,
38
+ contactId,
39
+ };
40
+ const updatedResult = await wixData.update(COLLECTIONS.MEMBERS_DATA, memberDataWithContactId);
41
+ memberDataWithContactId = {
42
+ ...memberDataWithContactId,
43
+ ...updatedResult,
44
+ };
45
+ return memberDataWithContactId;
46
+ } catch (error) {
47
+ console.error('Error creating contact and member if new:', error);
48
+ throw new Error(`Failed to create contact and member if new: ${error.message}`);
49
+ }
50
+ }
51
+
52
+ module.exports = {
53
+ findMemberByWixDataId,
54
+ createContactAndMemberIfNew,
55
+ };
package/backend/utils.js CHANGED
@@ -1,134 +1,34 @@
1
- const {
2
- ADDRESS_STATUS_TYPES,
3
- ADDRESS_VISIBILITY_OPTIONS,
4
- COLLECTIONS,
5
- } = require('./consts.js');
6
-
7
- /**
8
- * Safely adds optional properties from source to target object
9
- * @param {Object} targetObject - Object to add properties to
10
- * @param {Object} sourceObject - Object to get properties from
11
- * @param {string} sourcePropertyKey - Key to get from source
12
- * @param {string} targetPropertyKey - Key to set on target (defaults to sourcePropertyKey)
13
- */
14
- const addOptionalProperty = (
15
- targetObject,
16
- sourceObject,
17
- sourcePropertyKey,
18
- targetPropertyKey = sourcePropertyKey
19
- ) => {
20
- if (sourceObject && sourceObject[sourcePropertyKey]) {
21
- targetObject[targetPropertyKey] = sourceObject[sourcePropertyKey];
22
- }
23
- };
24
-
25
- /**
26
- * Safely trims a string value with fallback
27
- * @param {string} value - The string to trim
28
- * @param {string} fallback - Fallback value if input is invalid
29
- * @returns {string} - The trimmed string or fallback
30
- */
31
- const safeTrim = (value, fallback = '') => {
32
- return value ? value.toString().trim() : fallback;
33
- };
34
-
35
- /**
36
- * Determines address display status based on visibility settings
37
- * @param {string} visibilityValue - The address visibility value from migration data
38
- * @returns {string} - The corresponding address status
39
- */
40
- const determineAddressDisplayStatus = (visibilityValue) => {
41
- if (!visibilityValue) {
42
- return ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
43
- }
44
-
45
- const normalizedValue = visibilityValue.trim().toLowerCase();
46
-
47
- switch (normalizedValue) {
48
- case ADDRESS_VISIBILITY_OPTIONS.ALL:
49
- return ADDRESS_STATUS_TYPES.FULL_ADDRESS;
50
- case ADDRESS_VISIBILITY_OPTIONS.NONE:
51
- return ADDRESS_STATUS_TYPES.DONT_SHOW;
52
- default:
53
- return ADDRESS_STATUS_TYPES.STATE_CITY_ZIP;
54
- }
55
- };
56
-
57
- /**
58
- * Validates if input is a non-empty array
59
- * @param {*} input - Input to validate
60
- * @returns {boolean} - True if input is a non-empty array
61
- */
62
- const isValidArray = (input) => {
63
- return Array.isArray(input) && input.length > 0;
64
- };
65
-
66
- /**
67
- * Processes interests string into clean array
68
- * @param {string} interestsString - Comma-separated interests string
69
- * @returns {Array} - Array of trimmed, non-empty interests
70
- */
71
- const processInterests = (interestsString) => {
72
- if (!interestsString) return [];
73
-
74
- return interestsString
75
- .split(',')
76
- .map((interest) => interest.trim())
77
- .filter((interest) => interest.length > 0);
78
- };
79
-
80
- /**
81
- * Creates a full name from first and last name components
82
- * @param {string} firstName - First name
83
- * @param {string} lastName - Last name
84
- * @returns {string} - Combined full name
85
- */
86
- const createFullName = (firstName, lastName) => {
87
- const trimmedFirst = firstName?.trim() || '';
88
- const trimmedLast = lastName?.trim() || '';
89
- return `${trimmedFirst} ${trimmedLast}`.trim();
90
- };
1
+ const { COLLECTIONS } = require('../public/consts');
91
2
 
92
- function generateGeoHash(addresses) {
93
- const geohash = addresses
94
- ?.filter((address) =>
95
- isNaN(address?.latitude) && isNaN(address?.longitude) ? false : address
96
- )
97
- ?.map((address) =>
98
- ngeohash.encode(address.latitude, address.longitude, PRECISION)
99
- );
100
- return geohash && geohash.length > 0 ? geohash : [];
101
- }
3
+ const { CONFIG_KEYS } = require('./consts');
4
+ const { wixData } = require('./elevated-modules');
102
5
 
103
- /**
104
- * Retrieves member data by member ID
105
- * @param {string} memberId - The member ID to search for
106
- * @returns {Promise<Object|null>} - Member data or null if not found
6
+ /**
7
+ * Retrieves site configuration values from the database
8
+ * @param {string} [configKey] - The configuration key to retrieve. Must be one of:
9
+ * - 'AUTOMATION_EMAIL_TRIGGER_ID' - Email template ID for triggered emails
10
+ * - 'SITE_ASSOCIATION' - Site association configuration
11
+ * @returns {Promise<any>} The configuration value for the specified key, or all configs if no key provided
12
+ * @example
13
+ * // Get specific config
14
+ * const emailTemplateId = await getSiteConfigs('AUTOMATION_EMAIL_TRIGGER_ID');
15
+ *
16
+ * // Get all configs
17
+ * const allConfigs = await getSiteConfigs();
107
18
  */
108
- const findMemberById = async (memberId) => {
109
- if (!memberId) {
110
- throw new Error('Member ID is required');
111
- }
19
+ const getSiteConfigs = async configKey => {
20
+ if (configKey && !Object.values(CONFIG_KEYS).includes(configKey)) {
21
+ throw new Error(
22
+ `Invalid configKey: ${configKey}. Must be one of: ${Object.values(CONFIG_KEYS).join(', ')}`
23
+ );
24
+ }
25
+ const siteConfigs = await wixData.get(COLLECTIONS.SITE_CONFIGS, 'SINGLE_ITEM_ID');
26
+ if (configKey) {
27
+ return siteConfigs[configKey];
28
+ }
29
+ return siteConfigs;
30
+ };
112
31
 
113
- try {
114
- const queryResult = await wixData.query(COLLECTIONS.MEMBERS_DATA)
115
- .eq("memberId", memberId)
116
- .find({ suppressAuth: true });
117
-
118
- return queryResult.items.length > 0 ? queryResult.items[0] : null;
119
- } catch (error) {
120
- throw new Error(`Failed to retrieve member data: ${error.message}`);
121
- }
122
- }
123
-
124
- module.exports = {
125
- addOptionalProperty,
126
- safeTrim,
127
- determineAddressDisplayStatus,
128
- isValidArray,
129
- processInterests,
130
- createFullName,
131
- generateGeoHash,
132
- findMemberById,
133
- };
134
-
32
+ module.exports = {
33
+ getSiteConfigs,
34
+ };
@@ -0,0 +1,113 @@
1
+ const js = require('@eslint/js');
2
+ const prettierConfig = require('eslint-config-prettier');
3
+ const importPlugin = require('eslint-plugin-import');
4
+ const prettier = require('eslint-plugin-prettier');
5
+ const promisePlugin = require('eslint-plugin-promise');
6
+ const globals = require('globals');
7
+
8
+ module.exports = [
9
+ // Recommended base configurations
10
+ js.configs.recommended,
11
+ prettierConfig,
12
+
13
+ // Main configuration
14
+ {
15
+ files: ['**/*.js'],
16
+ languageOptions: {
17
+ ecmaVersion: 2021,
18
+ sourceType: 'commonjs',
19
+ globals: {
20
+ ...globals.node,
21
+ ...globals.es2021,
22
+ ...globals.jest,
23
+ },
24
+ },
25
+ plugins: {
26
+ prettier,
27
+ import: importPlugin,
28
+ promise: promisePlugin,
29
+ },
30
+ rules: {
31
+ // Error prevention
32
+ 'no-var': 'error',
33
+ 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
34
+ 'no-console': ['warn', { allow: ['warn', 'error', 'log', 'info'] }],
35
+ 'no-debugger': 'warn',
36
+ 'no-duplicate-imports': 'error',
37
+ 'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
38
+ 'no-use-before-define': ['error', { functions: false }],
39
+
40
+ // Best practices
41
+ 'prefer-const': 'error',
42
+ 'no-const-assign': 'error',
43
+ 'prefer-arrow-callback': 'error',
44
+ 'arrow-body-style': ['error', 'as-needed'],
45
+ 'no-return-await': 'off',
46
+ 'require-await': 'warn',
47
+
48
+ // Import rules
49
+ 'import/order': [
50
+ 'error',
51
+ {
52
+ groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
53
+ 'newlines-between': 'always',
54
+ alphabetize: { order: 'asc', caseInsensitive: true },
55
+ },
56
+ ],
57
+ 'import/no-unresolved': 'error',
58
+ 'import/no-duplicates': 'error',
59
+ 'import/no-commonjs': 'off',
60
+ 'import/no-dynamic-require': 'off',
61
+
62
+ // Promise rules
63
+ 'promise/always-return': 'off',
64
+ 'promise/no-return-wrap': 'error',
65
+ 'promise/param-names': 'error',
66
+ 'promise/catch-or-return': 'error',
67
+ 'promise/no-native': 'off',
68
+ 'promise/no-callback-in-promise': 'warn',
69
+ 'promise/no-promise-in-callback': 'warn',
70
+ 'promise/no-nesting': 'warn',
71
+
72
+ // Prettier rules
73
+ 'prettier/prettier': [
74
+ 'error',
75
+ {
76
+ singleQuote: true,
77
+ trailingComma: 'es5',
78
+ printWidth: 100,
79
+ tabWidth: 2,
80
+ semi: true,
81
+ bracketSpacing: true,
82
+ arrowParens: 'avoid',
83
+ endOfLine: 'lf',
84
+ },
85
+ ],
86
+ },
87
+ settings: {
88
+ 'import/resolver': {
89
+ node: {
90
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
91
+ },
92
+ },
93
+ },
94
+ },
95
+
96
+ // Test files override
97
+ {
98
+ files: ['**/*.test.js', '**/*.spec.js'],
99
+ languageOptions: {
100
+ globals: {
101
+ ...globals.jest,
102
+ },
103
+ },
104
+ rules: {
105
+ 'no-console': 'off',
106
+ },
107
+ },
108
+
109
+ // Ignore patterns
110
+ {
111
+ ignores: ['node_modules/**', 'dist/**', 'build/**', 'coverage/**', '*.min.js'],
112
+ },
113
+ ];
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- ...require('./public'),
3
2
  ...require('./pages'),
4
3
  ...require('./backend'),
4
+ ...require('./public'),
5
5
  };
package/package.json CHANGED
@@ -1,11 +1,36 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "1.7.3",
3
+ "version": "1.8.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
6
+ "test": "echo \"Error: no test specified\" && exit 1",
7
+ "lint": "eslint .",
8
+ "lint:fix": "eslint . --fix",
9
+ "format": "prettier --write \"**/*.{js,json,md}\"",
10
+ "format:check": "prettier --check \"**/*.{js,json,md}\"",
11
+ "prepare": "husky"
7
12
  },
8
13
  "author": "",
9
14
  "license": "ISC",
10
- "description": ""
15
+ "description": "",
16
+ "devDependencies": {
17
+ "@eslint/js": "^9.12.0",
18
+ "eslint": "^9.12.0",
19
+ "eslint-config-prettier": "^9.1.0",
20
+ "eslint-plugin-import": "^2.30.0",
21
+ "eslint-plugin-prettier": "^5.2.1",
22
+ "eslint-plugin-promise": "^7.1.0",
23
+ "globals": "^15.10.0",
24
+ "husky": "^9.1.6",
25
+ "prettier": "^3.3.3"
26
+ },
27
+ "dependencies": {
28
+ "@wix/automations": "^1.0.261",
29
+ "@wix/crm": "^1.0.1061",
30
+ "@wix/data": "^1.0.303",
31
+ "@wix/essentials": "^0.1.28",
32
+ "@wix/members": "^1.0.330",
33
+ "@wix/site-window": "^1.44.0",
34
+ "phone": "^3.1.67"
35
+ }
11
36
  }