@modernman00/shared-js-lib 1.2.14 → 1.2.24

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.
package/Utility.js CHANGED
@@ -131,3 +131,21 @@ export const sanitizeInput = (input) => {
131
131
  .replace(/'/g, ''')
132
132
  .trim();
133
133
  };
134
+
135
+
136
+ /**
137
+ * Binds an event listener to an element with the specified ID.
138
+ *
139
+ * @param {Object} options - An object containing the following properties:
140
+ * - id {string}: The ID of the element to bind the event listener to.
141
+ * - event {string} (optional): The type of event to listen for. Defaults to 'click'.
142
+ * - handler {function}: The function to be called when the event occurs.
143
+ * @throws {Error} Throws an error if the element with the specified ID is not found,
144
+ * or if the handler is not a function.
145
+ */
146
+ export function bindEvent({ id, event = 'click', handler }) {
147
+ const el = document.getElementById(id);
148
+ if (el && typeof handler === 'function') {
149
+ el.addEventListener(event, handler);
150
+ }
151
+ }
@@ -0,0 +1,152 @@
1
+ import { createCodeSubmitHandler } from '../AcctMgt/code.js';
2
+ import { forgotSubmitHandler } from '../AcctMgt/forgot.js';
3
+ import { createAdminLoginHandler } from '../AcctMgt/login.js';
4
+ import { setupPasswordChange } from '../AcctMgt/changePassword.js';
5
+ import { loginSubmission } from '../AcctMgt/processAll.js';
6
+ import { showPassword } from '../AcctMgt/loginUtility.js';
7
+
8
+ jest.mock('../AcctMgt/processAll.js');
9
+ jest.mock('../AcctMgt/loginUtility.js');
10
+
11
+ // Utility to mock DOM elements
12
+ const mockDOM = (ids) => {
13
+ ids.forEach(id => {
14
+ const el = document.createElement('button');
15
+ el.setAttribute('id', id);
16
+ document.body.appendChild(el);
17
+ });
18
+ };
19
+
20
+ afterEach(() => {
21
+ document.body.innerHTML = '';
22
+ jest.clearAllMocks();
23
+ });
24
+
25
+ describe('AcctMgt Handlers', () => {
26
+ test('createCodeSubmitHandler submits code form correctly', () => {
27
+ mockDOM(['button']);
28
+ createCodeSubmitHandler({
29
+ formId: 'codeForm',
30
+ inputId: 'code_id',
31
+ buttonId: 'button',
32
+ route: '/api/code',
33
+ redirect: '/success',
34
+ theme: 'bulma',
35
+ maxLength: 40
36
+ });
37
+
38
+ document.getElementById('button').dispatchEvent(new Event('click'));
39
+
40
+ expect(loginSubmission).toHaveBeenCalledWith(
41
+ 'codeForm',
42
+ '/api/code',
43
+ '/success',
44
+ 'bulma',
45
+ { maxLength: { id: ['code_id'], max: [40] } }
46
+ );
47
+ });
48
+
49
+ test('forgotSubmitHandler submits email form correctly', () => {
50
+ mockDOM(['button']);
51
+ const emailInput = document.createElement('input');
52
+ emailInput.setAttribute('id', 'email_id');
53
+ document.body.appendChild(emailInput);
54
+
55
+ forgotSubmitHandler({
56
+ formId: 'forgotPassword',
57
+ inputId: 'email_id',
58
+ buttonId: 'button',
59
+ route: '/api/forgot',
60
+ redirect: '/reset',
61
+ theme: 'bulma',
62
+ maxLength: 50
63
+ });
64
+
65
+ document.getElementById('button').dispatchEvent(new Event('click'));
66
+
67
+ expect(loginSubmission).toHaveBeenCalledWith(
68
+ 'forgotPassword',
69
+ '/api/forgot',
70
+ '/reset',
71
+ 'bulma',
72
+ { maxLength: { id: ['email_id'], max: [50] } }
73
+ );
74
+ });
75
+
76
+ test('createAdminLoginHandler sets attributes and submits login', () => {
77
+ mockDOM(['button', 'showPassword_id']);
78
+ const passwordInput = document.createElement('input');
79
+ passwordInput.setAttribute('id', 'password_id');
80
+ document.body.appendChild(passwordInput);
81
+
82
+ createAdminLoginHandler({
83
+ formId: 'managed',
84
+ emailId: 'email_id',
85
+ passwordId: 'password_id',
86
+ buttonId: 'button',
87
+ showToggleId: 'showPassword_id',
88
+ route: '/api/login',
89
+ redirect: '/admin',
90
+ theme: 'bootstrap',
91
+ maxLength: [30, 50]
92
+ });
93
+
94
+ document.getElementById('button').dispatchEvent(new Event('click'));
95
+ document.getElementById('showPassword_id').dispatchEvent(new Event('click'));
96
+
97
+ expect(loginSubmission).toHaveBeenCalledWith(
98
+ 'managed',
99
+ '/api/login',
100
+ '/admin',
101
+ 'bootstrap',
102
+ { maxLength: { id: ['password_id', 'email_id'], max: [30, 50] } }
103
+ );
104
+
105
+ expect(passwordInput.getAttribute('autocomplete')).toBe('current-password');
106
+ expect(showPassword).toHaveBeenCalledWith('password_id');
107
+ });
108
+
109
+ test('setupPasswordChange submits when passwords match', () => {
110
+ mockDOM(['button', 'showPassword_id']);
111
+ const passwordInput = document.createElement('input');
112
+ passwordInput.setAttribute('id', 'password_id');
113
+ passwordInput.value = 'Secure123!';
114
+ document.body.appendChild(passwordInput);
115
+
116
+ const confirmInput = document.createElement('input');
117
+ confirmInput.setAttribute('id', 'confirm_password_id');
118
+ confirmInput.value = 'Secure123!';
119
+ document.body.appendChild(confirmInput);
120
+
121
+ const errorEl = document.createElement('div');
122
+ errorEl.setAttribute('id', 'confirm_password_error');
123
+ document.body.appendChild(errorEl);
124
+
125
+ const helpEl = document.createElement('div');
126
+ helpEl.setAttribute('id', 'password_help');
127
+ document.body.appendChild(helpEl);
128
+
129
+ setupPasswordChange({
130
+ buttonId: 'button',
131
+ passwordId: 'password_id',
132
+ confirmId: 'confirm_password_id',
133
+ errorId: 'confirm_password_error',
134
+ helpId: 'password_help',
135
+ showToggleId: 'showPassword_id',
136
+ route: '/api/change-password',
137
+ redirect: '/dashboard',
138
+ theme: 'bulma',
139
+ maxLength: 50
140
+ });
141
+
142
+ document.getElementById('button').dispatchEvent(new Event('click'));
143
+
144
+ expect(loginSubmission).toHaveBeenCalledWith(
145
+ 'changePassword',
146
+ '/api/change-password',
147
+ '/dashboard',
148
+ 'bulma',
149
+ { maxLength: { id: ['password_id', 'confirm_password_id'], max: [50, 50] } }
150
+ );
151
+ });
152
+ });
@@ -0,0 +1,22 @@
1
+ import { getCookieValue } from './Cookie.js';
2
+ import axios from 'axios';
3
+ import axiosRetry from 'axios-retry';
4
+
5
+ axiosRetry(axios, { retries: 3 });
6
+
7
+ const csrfToken = getCookieValue('XSRF-TOKEN');
8
+
9
+ axios.interceptors.request.use(config => {
10
+ if (csrfToken) {
11
+ config.headers['X-Requested-With'] = 'XMLHttpRequest';
12
+ config.headers['Content-Type'] = 'application/json';
13
+ config.headers['Accept'] = 'application/json';
14
+ if (csrfToken) {
15
+ config.headers['X-XSRF-TOKEN'] = csrfToken; // Set CSRF token header
16
+ }
17
+ return config;
18
+ }
19
+ return config;
20
+ });
21
+
22
+ export default axios;
@@ -0,0 +1,3 @@
1
+ export default {
2
+ presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
3
+ };
@@ -0,0 +1,3 @@
1
+ <?php
2
+ define('APP_VERSION', 'v1.2.25');
3
+
package/general.js ADDED
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+ import { id } from './UtilityHtml.js';
3
+
4
+
5
+ /**
6
+ *
7
+ * @param { id of the first element} first
8
+ * @param {* id of the second element} second
9
+ * @param {* error id - if error - where to show it} err
10
+ */
11
+ export const matchInput = (first, second, err) => {
12
+ let error, firstInput, secondInput;
13
+ error = id(err);
14
+ firstInput = id(first);
15
+ secondInput = id(second);
16
+
17
+ secondInput.addEventListener('keyup', () => {
18
+
19
+ if (secondInput.value !== firstInput.value) {
20
+ error.innerHTML = 'Your passwords do not match';
21
+ error.style.color = 'red';
22
+ } else {
23
+ error.innerHTML = 'The password is matched: <i class=\'fa fa-check\' aria-hidden=\'true\'></i>';
24
+ error.style.color = 'green';
25
+ }
26
+
27
+
28
+ });
29
+ };
30
+
31
+
32
+
33
+
34
+ /**
35
+ * Redirects the browser to a new URL after a specified delay.
36
+ *
37
+ * @param {string} url - The URL to redirect to.
38
+ * @param {number} delay - The delay, in milliseconds, before the redirect occurs.
39
+ * @return {void} This function does not return anything.
40
+ */
41
+ export const redirectAfterDelay = (url, delay) => {
42
+ setTimeout(() => {
43
+ window.location.href = url;
44
+ }, delay);
45
+ };
46
+
47
+
48
+ export const parseErrorResponse = (error) => {
49
+ let errorMessage = 'An unknown error occurred';
50
+
51
+ if (Array.isArray(error?.response?.data?.message)) {
52
+ errorMessage = `<ul>${error.response.data.message.map(e => `<li>${e}</li>`).join('')}</ul>`;
53
+ } else if (typeof error?.response?.data?.message === 'string') {
54
+ errorMessage = error.response.data.message;
55
+ } else if (typeof error.message === 'string') {
56
+ errorMessage = error.message;
57
+ }
58
+
59
+ return errorMessage;
60
+ };
61
+
62
+
package/index.js CHANGED
@@ -10,4 +10,11 @@ export * from './FormHelper.js';
10
10
  export * from './Loader.js';
11
11
  export * from './ShowResponse.js';
12
12
  export * from './DateTime.js';
13
- export * from './CountryCode.js'
13
+ export * from './CountryCode.js'
14
+ export * from './AcctMgt/changePassword.js';
15
+ export * from './AcctMgt/code.js';
16
+ export * from './AcctMgt/forgot.js';
17
+ export * from './AcctMgt/login.js';
18
+ export * from './AcctMgt/loginUtility.js';
19
+ export * from './AcctMgt/processAll.js';
20
+ export * from './general.js';
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ export default {
2
+ transform: {
3
+ '^.+\\.js$': 'babel-jest'
4
+ },
5
+ testEnvironment: 'jsdom'
6
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modernman00/shared-js-lib",
3
- "version": "1.2.14",
3
+ "version": "1.2.24",
4
4
  "description": "Reusable JS utilities for numerous js problems",
5
5
  "homepage": "https://github.com/modernman00/shared-js-lib#readme",
6
6
  "keywords": [
@@ -20,7 +20,7 @@
20
20
  "author": "olawale olaogun",
21
21
  "main": "index.js",
22
22
  "scripts": {
23
- "test": "echo \"Error: no test specified\" && exit 1"
23
+ "test": "jest"
24
24
  },
25
25
  "dependencies": {
26
26
  "axios": "^1.9.0",
@@ -28,5 +28,29 @@
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"
31
+ },
32
+ "devDependencies": {
33
+ "@babel/core": "^7.28.3",
34
+ "@babel/preset-env": "^7.28.3",
35
+ "@semantic-release/changelog": "^6.0.3",
36
+ "@semantic-release/git": "^10.0.1",
37
+ "@semantic-release/github": "^11.0.4",
38
+ "@semantic-release/npm": "^12.0.2",
39
+ "auto-changelog": "^2.5.0",
40
+ "babel-jest": "^30.0.5",
41
+ "jest": "^30.0.5",
42
+ "jest-environment-jsdom": "^30.0.5",
43
+ "semantic-release": "^24.2.7"
44
+ },
45
+ "release": {
46
+ "branches": [
47
+ "main"
48
+ ],
49
+ "plugins": [
50
+ "@semantic-release/npm",
51
+ "@semantic-release/changelog",
52
+ "@semantic-release/git",
53
+ "@semantic-release/github"
54
+ ]
31
55
  }
32
56
  }
package/release.sh ADDED
@@ -0,0 +1,161 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # --- Config ---
5
+ VERSION_FILE="config/version.php"
6
+ HISTORY_FILE="release-history.md"
7
+ REPO="origin"
8
+ DEFAULT_BRANCH="main"
9
+
10
+ # --- Utilities ---
11
+ function detect_branch() {
12
+ CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
13
+ echo "🔍 Current branch: $CURRENT_BRANCH"
14
+ if [[ "$CURRENT_BRANCH" != "$DEFAULT_BRANCH" && "$CURRENT_BRANCH" != "master" ]]; then
15
+ echo "⚠️ You're on '$CURRENT_BRANCH'. Switch to '$DEFAULT_BRANCH' or 'master' before releasing."
16
+ exit 1
17
+ fi
18
+ }
19
+
20
+ function ensure_clean_git() {
21
+ if [[ -n $(git status --porcelain) ]]; then
22
+ echo "🛑 Uncommitted changes detected."
23
+ git status
24
+ read -p "❓ Auto-commit all changes before release? (y/n): " choice
25
+ if [[ "$choice" == "y" ]]; then
26
+ git add .
27
+ git commit -m "🧹 Auto-commit before release"
28
+ else
29
+ echo "🚫 Aborting release."
30
+ exit 1
31
+ fi
32
+ fi
33
+ }
34
+
35
+ function run_tests() {
36
+ echo "🧪 Running tests with coverage..."
37
+ npm test -- --coverage || {
38
+ echo "❌ Tests failed. Aborting release."
39
+ exit 1
40
+ }
41
+ echo "✅ Tests passed."
42
+ }
43
+
44
+ function validate_dependencies() {
45
+ command -v gh >/dev/null || {
46
+ echo "❌ GitHub CLI (gh) not found. Install it to publish releases."
47
+ exit 1
48
+ }
49
+ }
50
+
51
+ function extract_version() {
52
+ grep -q "APP_VERSION" "$VERSION_FILE" || {
53
+ echo "❌ APP_VERSION not found in $VERSION_FILE"
54
+ exit 1
55
+ }
56
+ CURRENT=$(grep "APP_VERSION" "$VERSION_FILE" | sed -E "s/.*'([^']+)'.*/\1/")
57
+ IFS='.' read -r MAJOR MINOR PATCH <<< "${CURRENT//v/}"
58
+ }
59
+
60
+ function determine_bump_type() {
61
+ LAST_COMMIT=$(git log -1 --pretty=%s)
62
+ MODE="patch"
63
+ [[ "$LAST_COMMIT" == feat:* ]] && MODE="minor"
64
+ [[ "$LAST_COMMIT" == fix:* ]] && MODE="patch"
65
+ [[ "$LAST_COMMIT" == chore:* || "$LAST_COMMIT" == docs:* ]] && MODE="patch"
66
+ [[ "$LAST_COMMIT" == BREAKING:* ]] && MODE="major"
67
+
68
+ case "$MODE" in
69
+ major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
70
+ minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
71
+ patch) PATCH=$((PATCH + 1)) ;;
72
+ esac
73
+
74
+ SEMVER="${MAJOR}.${MINOR}.${PATCH}"
75
+ NEW="v$SEMVER"
76
+ echo "🔧 Current version: $CURRENT"
77
+ echo "⏫ Detected bump: $MODE → $NEW"
78
+ echo "📦 Last commit: $LAST_COMMIT"
79
+ }
80
+
81
+ function update_version_file() {
82
+ if [[ "$OSTYPE" == "darwin"* ]]; then
83
+ sed -i '' "s/$CURRENT/$NEW/" "$VERSION_FILE"
84
+ else
85
+ sed -i "s/$CURRENT/$NEW/" "$VERSION_FILE"
86
+ fi
87
+ }
88
+
89
+ function commit_and_tag() {
90
+ COMMIT_MSG="🔖 Bump version to $NEW"
91
+ npm version "$SEMVER" --no-git-tag-version
92
+ git add "$VERSION_FILE"
93
+ git commit -m "$COMMIT_MSG"
94
+
95
+ if git rev-parse "$NEW" >/dev/null 2>&1; then
96
+ echo "⚠️ Tag $NEW already exists. Skipping tag creation."
97
+ else
98
+ git tag "$NEW"
99
+ git push "$REPO" "$NEW"
100
+ fi
101
+
102
+ echo "🚀 Pushing changes to $REPO/$DEFAULT_BRANCH..."
103
+ git push "$REPO" "$DEFAULT_BRANCH" || {
104
+ echo "❌ Failed to push to $DEFAULT_BRANCH. Check your Git credentials or branch name."
105
+ exit 1
106
+ }
107
+ }
108
+
109
+ function generate_changelog() {
110
+ LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
111
+ [ "$LAST_TAG" = "$NEW" ] && LAST_TAG=$(git rev-list --tags --max-count=2 | tail -n1 | xargs git describe --tags --abbrev=0)
112
+ NOTES="### Changelog for $NEW\n\n"
113
+ NOTES+=$(git log "$LAST_TAG"..HEAD --pretty=format:"- %s _(by %an)_" --no-merges)
114
+ [ -z "$NOTES" ] && NOTES="Initial release."
115
+ }
116
+
117
+ function publish_github_release() {
118
+ gh release create "$NEW" \
119
+ --title "Release $NEW" \
120
+ --notes "$NOTES" \
121
+ --verify-tag
122
+ echo "✅ Published $NEW with changelog and updated $VERSION_FILE!"
123
+ }
124
+
125
+ function log_release_history() {
126
+ echo -e "\n## $NEW — $(date '+%Y-%m-%d %H:%M')\n$NOTES" >> "$HISTORY_FILE"
127
+ echo "📝 Logged release to $HISTORY_FILE"
128
+ }
129
+
130
+ function publish_to_npm() {
131
+ echo "🚀 Publishing $SEMVER to npm..."
132
+ npm publish --access public || {
133
+ echo "❌ npm publish failed. Check your credentials, registry access, or package.json config."
134
+ exit 1
135
+ }
136
+
137
+ echo "🔍 Verifying publish on npm registry..."
138
+ PACKAGE_NAME=$(node -p "require('./package.json').name")
139
+ PUBLISHED_VERSION=$(npm view "$PACKAGE_NAME" version)
140
+
141
+ if [[ "$PUBLISHED_VERSION" == "$SEMVER" ]]; then
142
+ echo "✅ npm publish confirmed: $PUBLISHED_VERSION is live on npmjs.org"
143
+ else
144
+ echo "⚠️ Publish may not have propagated yet. Expected $SEMVER, got $PUBLISHED_VERSION"
145
+ fi
146
+ }
147
+
148
+
149
+ # --- Execution ---
150
+ detect_branch
151
+ ensure_clean_git
152
+ run_tests
153
+ validate_dependencies
154
+ extract_version
155
+ determine_bump_type
156
+ update_version_file
157
+ commit_and_tag
158
+ generate_changelog
159
+ publish_github_release
160
+ log_release_history
161
+ publish_to_npm
package/ForgotPassword.js DELETED
@@ -1,102 +0,0 @@
1
- "use strict";
2
- import { id } from './UtilityHtml.js'
3
- import { postFormData } from "./Http.js"
4
- import { emailVal } from "./Utility.js"
5
- import { showError } from "./ShowResponse.js";
6
-
7
- // Environment detection
8
- const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
9
-
10
- // Log warning for browser-only features
11
- const warnNodeEnvironment = (feature) => {
12
- if (!isBrowser) {
13
- console.warn(`Warning: ${feature} is only available in browser environments. This code may not work as expected in Node.js.`);
14
- return false;
15
- }
16
- return true;
17
- };
18
-
19
- // Initialize UI - only in browser environment
20
- if (isBrowser) {
21
- const loaderElement = id("setLoader");
22
- if (loaderElement) {
23
- loaderElement.style.display = "none";
24
- } else {
25
- console.warn("Element 'setLoader' not found in the DOM");
26
- }
27
- } else {
28
- console.warn("Running in Node.js environment. DOM manipulation is not available.");
29
- }
30
-
31
- /**
32
- * @description Handles the forgot password submission process.
33
- * @param {string} formId - The ID of the form to submit.
34
- * @param {string} url - The URL to make the POST request to.
35
- * @param {string} redirectUrl - The URL to redirect the user to after the submission is complete.
36
- * @returns {function} - A function that handles the submission process.
37
- */
38
- export const forgotPasswordSubmission = (formId, url, redirectUrl) => {
39
- // Check if we're in a browser environment before creating the helper
40
- if (!warnNodeEnvironment('forgotPasswordSubmission')) {
41
- // Return a no-op function in Node.js environment
42
- return (e) => {
43
- if (e && typeof e.preventDefault === 'function') {
44
- e.preventDefault();
45
- }
46
- console.warn("Forgot password functionality is only available in browser environments");
47
- };
48
- }
49
-
50
- const emailInput = id('email_id');
51
- if (!emailInput) {
52
- console.warn("Email input element not found");
53
- return (e) => {
54
- if (e && typeof e.preventDefault === 'function') {
55
- e.preventDefault();
56
- }
57
- console.error("Email input element not found");
58
- };
59
- }
60
-
61
- /**
62
- * @description Handles the submission of the forgot password form.
63
- * @param {Event} e - The event object passed by the event listener.
64
- * @returns {void}
65
- * @throws {Error} - If there is an error with the submission
66
- */
67
- const forgotPasswordSubmissionHelper = (e) => {
68
- try {
69
- if (e && typeof e.preventDefault === 'function') {
70
- e.preventDefault();
71
- }
72
-
73
- // Ensure emailInput still exists in the DOM
74
- if (!emailInput) {
75
- throw new Error("Email input element not found");
76
- }
77
-
78
- const email = emailInput.value.trim();
79
- if (!emailVal(email)) {
80
- const loaderElement = id("setLoader");
81
- if (loaderElement) {
82
- loaderElement.style.display = "block";
83
- }
84
-
85
- // Only use localStorage in browser environment
86
- if (typeof localStorage !== 'undefined') {
87
- localStorage.setItem('redirect', redirectUrl);
88
- }
89
-
90
- postFormData(url, formId, redirectUrl);
91
- }
92
- } catch (error) {
93
- if (typeof showError === 'function') {
94
- showError(error);
95
- } else {
96
- console.error("Error in forgot password submission:", error);
97
- }
98
- }
99
- };
100
-
101
- return forgotPasswordSubmissionHelper;
102
- };