abmp-npm 1.0.0 → 1.1.1

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
+ }
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # abmp-npm
@@ -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
+ };
@@ -0,0 +1,18 @@
1
+ const ELEVATED_QUERY_OPTIONS = {
2
+ suppressAuth: true,
3
+ };
4
+
5
+ /**
6
+ * Valid configuration keys for getSiteConfigs function
7
+ * @readonly
8
+ * @enum {string}
9
+ */
10
+ const CONFIG_KEYS = {
11
+ AUTOMATION_EMAIL_TRIGGER_ID: 'AUTOMATION_EMAIL_TRIGGER_ID',
12
+ SITE_ASSOCIATION: 'SITE_ASSOCIATION',
13
+ };
14
+
15
+ module.exports = {
16
+ ELEVATED_QUERY_OPTIONS,
17
+ CONFIG_KEYS,
18
+ };
@@ -0,0 +1,54 @@
1
+ const { items: wixData } = require('@wix/data');
2
+ const { webMethod, Permissions } = require('@wix/web-methods');
3
+
4
+ const { COLLECTIONS } = require('../public');
5
+
6
+ const { triggerAutomation } = require('./automations-methods');
7
+ const { CONFIG_KEYS, ELEVATED_QUERY_OPTIONS } = require('./consts');
8
+ const { findMemberByWixDataId, createContactAndMemberIfNew } = require('./members-data-methods');
9
+ const { getSiteConfigs } = require('./utils');
10
+
11
+ const contactSubmission = webMethod(Permissions.Anyone, async (data, memberDataId) => {
12
+ const { firstName, lastName, email, phone, message } = data;
13
+ const [memberData, automationEmailTriggerId] = await Promise.all([
14
+ findMemberByWixDataId(memberDataId),
15
+ getSiteConfigs(CONFIG_KEYS.AUTOMATION_EMAIL_TRIGGER_ID),
16
+ ]);
17
+ if (!memberData.showContactForm) {
18
+ console.log('Member contact form is not enabled for user, skipping contact submission!');
19
+ return;
20
+ }
21
+ let memberContactId = memberData.contactId;
22
+ if (!memberContactId) {
23
+ /**
24
+ * Create a member contact here since some members may have never logged in
25
+ * and therefore don’t have a contact ID. However, these members can still
26
+ * appear in the directory, and site visitors can reach out to them if they’ve
27
+ * enabled their contact form. To handle this, we ensure a contact is created for
28
+ * them during this flow if it doesn’t already exist.
29
+ */
30
+ console.info('Member contact id not found for user, creating new contact!');
31
+ const member = await createContactAndMemberIfNew(memberData);
32
+ memberContactId = member.contactId;
33
+ }
34
+ console.log('memberContactId', memberContactId);
35
+ const emailTriggered = await triggerAutomation(automationEmailTriggerId, {
36
+ contactId: memberContactId,
37
+ name: `${firstName} ${lastName}`,
38
+ email: email,
39
+ phone: phone,
40
+ message: message,
41
+ });
42
+ data = {
43
+ ...data,
44
+ phone: Number(data.phone),
45
+ memberContactId: memberContactId,
46
+ memberEmail: memberData.contactFormEmail,
47
+ };
48
+ await wixData.insert(COLLECTIONS.CONTACT_US_SUBMISSIONS, data, ELEVATED_QUERY_OPTIONS);
49
+ return emailTriggered;
50
+ });
51
+
52
+ module.exports = {
53
+ contactSubmission,
54
+ };
@@ -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,61 @@
1
+ const { items: wixData } = require('@wix/data');
2
+
3
+ const { COLLECTIONS } = require('../public');
4
+
5
+ const { ELEVATED_QUERY_OPTIONS } = require('./consts');
6
+ const { createSiteMember } = require('./members-area-methods');
7
+
8
+ /**
9
+ * Retrieves member data by member ID
10
+ * @param {string} memberId - The member ID to search for
11
+ * @returns {Promise<Object|null>} - Member data or null if not found
12
+ */
13
+ async function findMemberByWixDataId(memberId) {
14
+ if (!memberId) {
15
+ throw new Error('Member ID is required');
16
+ }
17
+ try {
18
+ const member = await wixData.get(COLLECTIONS.MEMBERS_DATA, memberId, ELEVATED_QUERY_OPTIONS);
19
+ return member;
20
+ } catch (error) {
21
+ throw new Error(`Failed to retrieve member data: ${error.message}`);
22
+ }
23
+ }
24
+
25
+ async function createContactAndMemberIfNew(memberData) {
26
+ if (!memberData) {
27
+ throw new Error('Member data is required');
28
+ }
29
+ try {
30
+ const toCreateMemberData = {
31
+ firstName: memberData.firstName,
32
+ lastName: memberData.lastName,
33
+ email: memberData.email,
34
+ phones: memberData.phones,
35
+ contactFormEmail: memberData.contactFormEmail || memberData.email,
36
+ };
37
+ const contactId = await createSiteMember(toCreateMemberData);
38
+ let memberDataWithContactId = {
39
+ ...memberData,
40
+ contactId,
41
+ };
42
+ const updatedResult = await wixData.update(
43
+ COLLECTIONS.MEMBERS_DATA,
44
+ memberDataWithContactId,
45
+ ELEVATED_QUERY_OPTIONS
46
+ );
47
+ memberDataWithContactId = {
48
+ ...memberDataWithContactId,
49
+ ...updatedResult,
50
+ };
51
+ return memberDataWithContactId;
52
+ } catch (error) {
53
+ console.error('Error creating contact and member if new:', error);
54
+ throw new Error(`Failed to create contact and member if new: ${error.message}`);
55
+ }
56
+ }
57
+
58
+ module.exports = {
59
+ findMemberByWixDataId,
60
+ createContactAndMemberIfNew,
61
+ };
@@ -0,0 +1,39 @@
1
+ const { items: wixData } = require('@wix/data');
2
+
3
+ const { COLLECTIONS } = require('../public/consts');
4
+
5
+ const { ELEVATED_QUERY_OPTIONS, CONFIG_KEYS } = require('./consts');
6
+
7
+ /**
8
+ * Retrieves site configuration values from the database
9
+ * @param {string} [configKey] - The configuration key to retrieve. Must be one of:
10
+ * - 'AUTOMATION_EMAIL_TRIGGER_ID' - Email template ID for triggered emails
11
+ * - 'SITE_ASSOCIATION' - Site association configuration
12
+ * @returns {Promise<any>} The configuration value for the specified key, or all configs if no key provided
13
+ * @example
14
+ * // Get specific config
15
+ * const emailTemplateId = await getSiteConfigs('AUTOMATION_EMAIL_TRIGGER_ID');
16
+ *
17
+ * // Get all configs
18
+ * const allConfigs = await getSiteConfigs();
19
+ */
20
+ const getSiteConfigs = async configKey => {
21
+ if (configKey && !Object.values(CONFIG_KEYS).includes(configKey)) {
22
+ throw new Error(
23
+ `Invalid configKey: ${configKey}. Must be one of: ${Object.values(CONFIG_KEYS).join(', ')}`
24
+ );
25
+ }
26
+ const siteConfigs = await wixData.get(
27
+ COLLECTIONS.SITE_CONFIGS,
28
+ 'SINGLE_ITEM_ID',
29
+ ELEVATED_QUERY_OPTIONS
30
+ );
31
+ if (configKey) {
32
+ return siteConfigs[configKey];
33
+ }
34
+ return siteConfigs;
35
+ };
36
+
37
+ module.exports = {
38
+ getSiteConfigs,
39
+ };
@@ -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
- function helloNpm() {
2
- return "hello NPM"
3
- }
4
-
5
- module.exports = helloNpm
1
+ module.exports = {
2
+ ...require('./pages'),
3
+ ...require('./backend'),
4
+ ...require('./public'),
5
+ };
package/package.json CHANGED
@@ -1,11 +1,37 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
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
+ "@wix/web-methods": "^1.0.11",
35
+ "phone": "^3.1.67"
36
+ }
11
37
  }
@@ -0,0 +1,131 @@
1
+ const { lightbox } = require('@wix/site-window');
2
+ const { phone } = require('phone');
3
+
4
+ const { contactSubmission } = require('../backend/forms-methods.web.js');
5
+ const { VALIDATION_MESSAGES, REGEX } = require('../public');
6
+
7
+ async function contactUsOnReady(_$w) {
8
+ _$w('#submitButton').disable();
9
+ const receivedData = await lightbox.getContext();
10
+ const formFieldsSelectors = ['#firstName', '#lastName', '#email', '#phone', '#message'];
11
+ const inputOnCustomValidation = ({ value, reject, message, pattern }) => {
12
+ const isValid = typeof value === 'string' && pattern.test(value.trim());
13
+ if (!isValid) reject(message);
14
+ };
15
+ async function validateAllFields() {
16
+ let allValid = true;
17
+ for (const selector of formFieldsSelectors) {
18
+ const isValid = await _$w(selector).valid;
19
+ _$w(selector).updateValidityIndication();
20
+ if (!isValid) {
21
+ allValid = false;
22
+ }
23
+ }
24
+ return allValid;
25
+ }
26
+
27
+ function resetForm() {
28
+ formFieldsSelectors.forEach(selector => {
29
+ const field = _$w(selector);
30
+ if (field && field.reset) {
31
+ field.reset();
32
+ } else {
33
+ field.value = '';
34
+ }
35
+ if (field && field.resetValidityIndication) {
36
+ field.resetValidityIndication();
37
+ }
38
+ });
39
+ }
40
+
41
+ function setAlertMessage(message) {
42
+ const $message = _$w('#successMessage');
43
+ $message.text = message;
44
+ $message.expand();
45
+
46
+ setTimeout(() => {
47
+ $message.collapse();
48
+ }, 8000);
49
+ }
50
+ // First Name
51
+ _$w('#firstName').onCustomValidation((value, reject) => {
52
+ inputOnCustomValidation({
53
+ value,
54
+ reject,
55
+ message: VALIDATION_MESSAGES.CONTACT_US.FIRST_NAME,
56
+ pattern: REGEX.NAME,
57
+ });
58
+ });
59
+ // Last Name
60
+ _$w('#lastName').onCustomValidation((value, reject) => {
61
+ inputOnCustomValidation({
62
+ value,
63
+ reject,
64
+ message: VALIDATION_MESSAGES.CONTACT_US.LAST_NAME,
65
+ pattern: REGEX.NAME,
66
+ });
67
+ });
68
+ // Message
69
+ _$w('#message').onCustomValidation((value, reject) => {
70
+ inputOnCustomValidation({
71
+ value,
72
+ reject,
73
+ message: VALIDATION_MESSAGES.CONTACT_US.MESSAGE,
74
+ pattern: REGEX.MESSAGE,
75
+ });
76
+ });
77
+
78
+ // Email validation uses native input validation
79
+ // No custom validation needed as email input has built-in validation
80
+
81
+ // Phone (US format)
82
+ _$w('#phone').onCustomValidation((value, reject) => {
83
+ const { isValid } = phone(value, { country: 'US' });
84
+ if (!isValid) {
85
+ reject(VALIDATION_MESSAGES.CONTACT_US.PHONE);
86
+ }
87
+ });
88
+ _$w('#captchaInput').onVerified(async () => {
89
+ const allValid = await validateAllFields();
90
+ if (allValid) {
91
+ _$w('#submitButton').enable();
92
+ return;
93
+ }
94
+ _$w('#submitButton').disable();
95
+ setAlertMessage(VALIDATION_MESSAGES.CONTACT_US.CAPTCHA);
96
+ _$w('#captchaInput').reset();
97
+ });
98
+
99
+ _$w('#captchaInput').onTimeout(() => {
100
+ _$w('#submitButton').disable();
101
+ });
102
+ _$w('#submitButton').onClick(async () => {
103
+ const allValid = await validateAllFields();
104
+ if (!allValid) {
105
+ setAlertMessage(VALIDATION_MESSAGES.CONTACT_US.INVALID_FIELDS);
106
+ return;
107
+ }
108
+
109
+ try {
110
+ const formData = {
111
+ firstName: _$w('#firstName').value,
112
+ lastName: _$w('#lastName').value,
113
+ email: _$w('#email').value,
114
+ phone: _$w('#phone').value,
115
+ message: _$w('#message').value,
116
+ };
117
+
118
+ await contactSubmission(formData, receivedData._id);
119
+ await resetForm();
120
+
121
+ setAlertMessage(VALIDATION_MESSAGES.CONTACT_US.SUBMISSION_SUCCESS);
122
+ } catch (error) {
123
+ console.error('Submission failed:', error);
124
+ setAlertMessage(VALIDATION_MESSAGES.CONTACT_US.SUBMISSION_FAILED);
125
+ }
126
+ });
127
+ }
128
+
129
+ module.exports = {
130
+ contactUsOnReady,
131
+ };
package/pages/index.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ ...require('./ContactUs.js'),
3
+ };
@@ -0,0 +1,15 @@
1
+ const REGEX = {
2
+ NAME: /^[a-zA-Z\s'-]{2,}$/,
3
+ MESSAGE: /^[A-Za-z0-9\s.,!?'"-]{2,}$/,
4
+ };
5
+
6
+ const COLLECTIONS = {
7
+ MEMBERS_DATA: 'MembersDataLatest',
8
+ CONTACT_US_SUBMISSIONS: 'contactUsSubmissions',
9
+ SITE_CONFIGS: 'SiteConfigs',
10
+ };
11
+
12
+ module.exports = {
13
+ REGEX,
14
+ COLLECTIONS,
15
+ };
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ ...require('./consts'),
3
+ ...require('./messages'),
4
+ };
@@ -0,0 +1,16 @@
1
+ const VALIDATION_MESSAGES = {
2
+ CONTACT_US: {
3
+ FIRST_NAME: 'Please enter a valid first name.',
4
+ LAST_NAME: 'Please enter a valid last name.',
5
+ MESSAGE: 'Please enter a valid message.',
6
+ PHONE: 'Please enter a valid US phone number.',
7
+ CAPTCHA: 'Please fix the highlighted fields before captcha verification.',
8
+ INVALID_FIELDS: 'Please fix the highlighted fields before submitting.',
9
+ SUBMISSION_FAILED: 'An error occurred. Please try again.',
10
+ SUBMISSION_SUCCESS: 'Contact submitted successfully',
11
+ },
12
+ };
13
+
14
+ module.exports = {
15
+ VALIDATION_MESSAGES,
16
+ };