diff --git a/Surge365.MassEmailReact.Server/Controllers/AuthenticationController.cs b/Surge365.MassEmailReact.Server/Controllers/AuthenticationController.cs new file mode 100644 index 0000000..4640e1c --- /dev/null +++ b/Surge365.MassEmailReact.Server/Controllers/AuthenticationController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Surge365.MassEmailReact.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class AuthencticationController : ControllerBase + { + [HttpPost] + public bool Authenticate(string username, string password) + { + return false; + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..396aff7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "MassEmailReact", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/surge365.massemailreact.client/eslint.config.js b/surge365.massemailreact.client/eslint.config.js index 092408a..34c8040 100644 --- a/surge365.massemailreact.client/eslint.config.js +++ b/surge365.massemailreact.client/eslint.config.js @@ -6,23 +6,26 @@ import tseslint from 'typescript-eslint' export default tseslint.config( { ignores: ['dist'] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "@typescript-eslint/no-explicit-any": "warn", // Changes error to warning + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + "no-unused-vars": "warn", // Changes error to warning for unused variables + "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], // Warns instead of error + }, + } ) diff --git a/surge365.massemailreact.client/index.html b/surge365.massemailreact.client/index.html index e4b78ea..f93a2b1 100644 --- a/surge365.massemailreact.client/index.html +++ b/surge365.massemailreact.client/index.html @@ -4,10 +4,10 @@ - Vite + React + TS + Surge365 Mass Email -
- +
+ diff --git a/surge365.massemailreact.client/package-lock.json b/surge365.massemailreact.client/package-lock.json index 1eb4ef6..83be51e 100644 --- a/surge365.massemailreact.client/package-lock.json +++ b/surge365.massemailreact.client/package-lock.json @@ -8,8 +8,15 @@ "name": "surge365.massemailreact.client", "version": "0.0.0", "dependencies": { + "admin-lte": "4.0.0-beta3", + "bootstrap": "^5.3.3", + "font-awesome": "^4.7.0", + "ionicons": "^7.4.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-bootstrap": "^2.10.9", + "react-dom": "^19.0.0", + "react-icons": "^5.3.0", + "react-router-dom": "^7.0.1" }, "devDependencies": { "@eslint/js": "^9.19.0", @@ -264,6 +271,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", + "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.26.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", @@ -875,32 +894,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.6.tgz", + "integrity": "sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.11.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1058,6 +1064,85 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", @@ -1324,6 +1409,28 @@ "win32" ] }, + "node_modules/@stencil/core": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.26.0.tgz", + "integrity": "sha512-+0Inu+dJ9/LgWSskcZwx7v17v4GILcwIYxNgD+OuK0U+D5z61WsxWw7yHkYG5OqGPBijsJMVssYRx/Tn+e7F9A==", + "license": "MIT", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1369,6 +1476,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1388,16 +1501,20 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.0.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1413,18 +1530,33 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz", - "integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz", + "integrity": "sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/type-utils": "8.24.0", - "@typescript-eslint/utils": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/type-utils": "8.24.1", + "@typescript-eslint/utils": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1444,16 +1576,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz", - "integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.1.tgz", + "integrity": "sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "debug": "^4.3.4" }, "engines": { @@ -1469,14 +1601,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz", - "integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz", + "integrity": "sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0" + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1487,14 +1619,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz", - "integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz", + "integrity": "sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/utils": "8.24.0", + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/utils": "8.24.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1511,9 +1643,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz", - "integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.1.tgz", + "integrity": "sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==", "dev": true, "license": "MIT", "engines": { @@ -1525,14 +1657,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz", - "integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz", + "integrity": "sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1591,16 +1723,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz", - "integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.1.tgz", + "integrity": "sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0" + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1615,13 +1747,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz", - "integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz", + "integrity": "sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/types": "8.24.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1675,6 +1807,12 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/admin-lte": { + "version": "4.0.0-beta3", + "resolved": "https://registry.npmjs.org/admin-lte/-/admin-lte-4.0.0-beta3.tgz", + "integrity": "sha512-q2VoAOu1DtZ7z41M2gQ05VMNYkFCAMxFU+j/HUMwCOlr/e3VhO+qww2SGJw4OxBw5nZQ7YV78+wK2RiB7ConzQ==", + "license": "MIT" + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1722,6 +1860,25 @@ "dev": true, "license": "MIT" }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1827,6 +1984,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1861,6 +2024,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1880,7 +2052,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -1908,10 +2079,29 @@ "dev": true, "license": "MIT" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.101", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.101.tgz", - "integrity": "sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==", + "version": "1.5.102", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz", + "integrity": "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==", "dev": true, "license": "ISC" }, @@ -2275,12 +2465,21 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, + "node_modules/font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", + "license": "(OFL-1.1 AND MIT)", + "engines": { + "node": ">=0.10.3" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2386,6 +2585,24 @@ "node": ">=0.8.19" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ionicons": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz", + "integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==", + "license": "MIT", + "dependencies": { + "@stencil/core": "^4.0.3" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2430,7 +2647,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -2540,6 +2756,18 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2627,6 +2855,15 @@ "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2731,9 +2968,9 @@ } }, "node_modules/postcss": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", - "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -2769,6 +3006,30 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "license": "MIT", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2809,6 +3070,37 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.9.tgz", + "integrity": "sha512-TJUCuHcxdgYpOqeWmRApM/Dy0+hVsxNRFvq2aRFQuxhNi/+ivOxC5OdWIeHS3agxvzJ4Ev4nDw2ZdBl9ymd/JQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", @@ -2821,6 +3113,27 @@ "react": "^19.0.0" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -2831,6 +3144,68 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.2.0.tgz", + "integrity": "sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.2.0.tgz", + "integrity": "sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q==", + "license": "MIT", + "dependencies": { + "react-router": "7.2.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2931,6 +3306,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3016,6 +3397,18 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3044,15 +3437,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.0.tgz", - "integrity": "sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.1.tgz", + "integrity": "sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.24.0", - "@typescript-eslint/parser": "8.24.0", - "@typescript-eslint/utils": "8.24.0" + "@typescript-eslint/eslint-plugin": "8.24.1", + "@typescript-eslint/parser": "8.24.1", + "@typescript-eslint/utils": "8.24.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3066,6 +3459,21 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -3115,14 +3523,14 @@ } }, "node_modules/vite": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", - "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz", + "integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.24.2", - "postcss": "^8.5.1", + "postcss": "^8.5.2", "rollup": "^4.30.1" }, "bin": { @@ -3186,6 +3594,15 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/surge365.massemailreact.client/package.json b/surge365.massemailreact.client/package.json index eac6009..59ff22d 100644 --- a/surge365.massemailreact.client/package.json +++ b/surge365.massemailreact.client/package.json @@ -1,5 +1,6 @@ { "name": "surge365.massemailreact.client", + "homepage": ".", "private": true, "version": "0.0.0", "type": "module", @@ -10,11 +11,19 @@ "preview": "vite preview" }, "dependencies": { + "admin-lte": "4.0.0-beta3", + "bootstrap": "^5.3.3", + "font-awesome": "^4.7.0", + "ionicons": "^7.4.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-bootstrap": "^2.10.9", + "react-icons": "^5.3.0", + "react-router-dom": "^7.0.1" }, "devDependencies": { "@eslint/js": "^9.19.0", + "@types/node": "^22", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", @@ -24,7 +33,7 @@ "globals": "^15.14.0", "typescript": "~5.7.2", "typescript-eslint": "^8.22.0", - "vite": "^6.1.0", - "@types/node": "^22" - } -} \ No newline at end of file + "vite": "^6.1.0" + }, + "proxy": "http://localhost:5293" +} diff --git a/surge365.massemailreact.client/public/content/js/jquery.ss.dbmanager-1.0.js b/surge365.massemailreact.client/public/content/js/jquery.ss.dbmanager-1.0.js new file mode 100644 index 0000000..e49472b --- /dev/null +++ b/surge365.massemailreact.client/public/content/js/jquery.ss.dbmanager-1.0.js @@ -0,0 +1,108 @@ +const INTERNAL_SECOND_MS = 60 * 1000; +const INTERNAL_MINUTE_MS = 60 * INTERNAL_SECOND_MS; +const INTERNAL_HOUR_MS = 60 * INTERNAL_MINUTE_MS; + +var isIndexedDBSupported = 'indexedDB' in window; + + +$.dbManager = { + openDatabases: {} +} + +$.dbManager.alert = function (msg) { + alert(msg); +} + +$.dbManager.openDatabase = function (name, version, onUpgradeNeeded) { + let dbManager = this; + return new Promise(function (resolve, reject) { + let db = dbManager.openDatabases[name]; + if (db !== undefined && db !== null) { + resolve(db); + return; + } + let request = indexedDB.open(name, version); + request.onupgradeneeded = function (event) { + //Upgrade DB? Drop DB and reload? For now, drop db + console.log("onupgradeneeded"); + + if (onUpgradeNeeded != null) { + onUpgradeNeeded(event); + } + else { + dbManager.deleteDatabase(name).then( + function onSuccess(db) { + resolve(db); + }, + function onError(error) { + reject(error); + }); + } + //reject("Upgrade Needed"); + }; + + request.onerror = function () { + console.error("Error: ", request.error); + reject(request.error); + }; + + request.onblocked = function () { + console.error(); + reject("Database blocked"); + }; + + request.onsuccess = function () { + let db = request.result; + + db.onversionchange = function (event) { + let isDeleted = event.newVersion === null; + db.close(); + if(!isDeleted) + alert("Database is outdated, please reload the page: ", event.currentTarget.name) + }; + + console.log("Database opened: ", name); + if (dbManager.openDatabases == null) + dbManager.openDatabases = {}; + + dbManager.openDatabases[name] = db; + db.onclose = function () { //TODO: DOesn't appear to be called currently + console.log('Database connection closed: ', name); + dbManager.openDatabases[name] = null; + } + resolve(db); + }; + }); +} + +$.dbManager.closeDatabase = function (name) { + let currentDB = this; + + return new Promise(function (resolve, reject) { + let db = currentDB.openDatabases[name]; + if (db != null) { + db.close(); + } + currentDB.openDatabases[name] == null; + resolve(); + }); +} + +$.dbManager.deleteDatabase = function (name) { + let currentDB = this; + return new Promise(function (resolve, reject) { + let request = indexedDB.deleteDatabase(name); + + request.onerror = function () { + console.error("Error", request.error); + reject(request.error); + }; + + request.onsuccess = function () { + console.log("database deleted: ", name); + currentDB.openDatabases[name] = null; + resolve(); + }; + }); +} + diff --git a/surge365.massemailreact.client/public/content/js/jquery.usahaulers.global.js b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.global.js new file mode 100644 index 0000000..a8e7685 --- /dev/null +++ b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.global.js @@ -0,0 +1,610 @@ + + +//global variables +var table; +var allUsers = []; +var user = null; + +//login stuff +function logout() { + $.webMethod({ + 'methodPage': 'UserMethods', + 'methodName': 'LogOut', + 'parameters': {}, + success: function (json) { + + document.location.href = '/login.aspx'; + + + } + }); +} + +function checkLoggedInStatus() { + $.webMethod({ + 'methodPage': 'UserMethods', + 'methodName': 'CheckAuthToken', + 'parameters': {}, + success: function (json) { + if ($.getBoolean(json.success)) { + $.localStorage("session_currentUser", json.data); + + + //set user info + user = $.localStorage("session_currentUser"); + + //set franchise if it isn't set yet. + //franchiseDeferred = $.Deferred(); + + //call back to page that called this method. + // $.when(franchiseDeferred).done(function () { + /* + if ($.sessionStorage("Auth-Impersonate-Guid") != null) { + //session is being impersonated, override name and signout + $("#spanProfileName").html("Signed in as " + user.FirstName + ' ' + user.LastName); + $("#spanProfileNameTitle").html("Signed in as " + user.FirstName + ' ' + user.LastName); + } + else { + $("#spanProfileName").html(user.FirstName + ' ' + user.LastName); + $("#spanProfileNameTitle").html(user.FirstName + ' ' + user.LastName); + } */ + + // $("#spanMenuName").html(user.FirstName + ' ' + user.LastName); + + // var userid = $.localStorage("session_currentUser").UserID; + // var imgid = userid + "&t=" + new Date().getTime(); + /* + if (user.HasProfileImage) { + $("#imgRightProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid); + $("#imgRightProfile").on('error', function () { + $("#imgRightProfile").attr("src", "/img/generic_avatar.jpg"); + }); + + $("#imgLeftProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid); + $("#imgLeftProfile").on('error', function () { + $("#imgLeftProfile").attr("src", "/img/generic_avatar.jpg"); + }); + + $("#imgMainProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid); + $("#imgMainProfile").on('error', function () { + $("#imgMainProfile").attr("src", "/img/generic_avatar.jpg"); + }); + } + else { + $("#imgRightProfile").attr("src", "/img/generic_avatar.jpg"); + $("#imgLeftProfile").attr("src", "/img/generic_avatar.jpg"); + $("#imgMainProfile").attr("src", "/img/generic_avatar.jpg"); + } */ + + loadPage(); + + } + else { + $.sessionStorage("redirect_url", document.location.href); + document.location.href = '/login'; + } + } + }); +} + +function setCurrentFranchise(d) { + $.usaHaulersDB.getFranchises().then((data) => { + $.sessionStorage("currentFranchise", data); + if (d != null) { + d.resolve(); + } + }); + + /* + search = {}; + search.FranchiseCode = $.sessionStorage("franchiseCode"); + + $.webMethod({ + 'methodPage': 'UserMethods/', + 'methodName': 'GetReport_Franchise', + 'parameters': { "search": search }, + success: function (json) { + + if ($.getBoolean(json.success)) { + $.sessionStorage("currentFranchise", json.data[0]); + if (d != null) { + d.resolve(); + } + } + } + }); */ + +} + +function createSetting(obj, settingTypeCode, value) { + + //see if setting exists + var bFound = false; + for (var x = 0; x < obj.Settings.length; x++) { + if (obj.Settings[x].SettingTypeCode == settingTypeCode) { + bFound = true; + break; + } + } + if (bFound) { + //existing setting + obj.Settings[x].Value = value; + } + else { + + + setting = {} + setting.SettingTypeCode = settingTypeCode; + setting.Value = value; + + if (obj.Settings == null) { + obj.Settings = []; + } + obj.Settings.push(setting); + } +} + +function createCaseSetting(obj, settingTypeCode, value) { + + //see if setting exists + var bFound = false; + for (var x = 0; x < obj.settings.length; x++) { + if (obj.settings[x].case_setting_type_code == settingTypeCode) { + bFound = true; + break; + } + } + if (bFound) { + //existing setting + obj.settings[x].value = value; + } + else { + + + setting = {} + setting.case_setting_type_code = settingTypeCode; + setting.value = value; + + if (obj.settings == null) { + obj.settings = []; + } + obj.settings.push(setting); + } +} + +function getReportSetting(settings, setting_field, setting_code) { + var value = ""; + if (settings != null) { + for (var x = 0; x < settings.length; x++) { + setting = settings[x]; + if (setting[setting_field] == setting_code) { + value = setting["value"]; + break; + } + } + } + return value; +} + +function getSetting(settings, settingTypeCode) { + var value = ""; + for (var x = 0; x < settings.length; x++) { + setting = settings[x]; + if (setting.SettingTypeCode == settingTypeCode) { + value = setting.Value; + break; + } + } + return value; +} + + + + +//web calls +function callMethod(methodname, callback, sessionName, deferred, search) { + + var params = {}; + + if (search == null) { + search = {}; + } + + + $.webMethod({ + 'methodPage': 'UserMethods/', + 'methodName': methodname, + 'parameters': { "search": search }, + success: function (json) { + + $.sessionStorage(sessionName, json.data); + + if ($.getBoolean(json.success)) { + if (deferred != null) { + deferred.resolve(true); + } + if (callback != null) { + callback(); + } + } + else { + notify("Error", "There was an error performing this action. Please try again.") + } + } + }); +} +function callSearchTransactions(methodname, callback, sessionName, deferred, search) { + //REPEAT OF CALL METHOD,, JSON.DATA DOESN'T WORK HERE.. + //TODO MAKE DYNAMIC LATER. + + var params = {}; + + if (search == null) { + search = {}; + } + + + $.webMethod({ + 'methodPage': 'UserMethods/', + 'methodName': methodname, + 'parameters': { "search": search }, + success: function (json) { + + $.sessionStorage(sessionName, json); + + if ($.getBoolean(json.success)) { + if (deferred != null) { + deferred.resolve(true); + } + if (callback != null) { + callback(); + } + } + else { + notify("Error", "There was an error performing this action. Please try again.") + } + } + }); +} +//overlay +function showwait(item) { + $("#waitOverlay").show(); + if (item) { + item.show(); + + } + else { + $("#loading").show(); + + } + + +} +function hidewait(item) { + $("#waitOverlay").hide(); + if (item) { + item.hide(); + } + else { + $("#loading").hide(); + + } +} + +//helper function +function getParameterByName(name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +} +function getCurrency(val) { + return '$' + parseFloat(val, 10).toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,").toString() +} +function getDate(val) { + var newDate = new Date(Date.parse(val)).toLocaleDateString() + return newDate; +} +function changeDateFormat(inputDate) { // expects Y-m-d + var splitDate = inputDate.split('-'); + if (splitDate.count == 0) { + return null; + } + + var year = splitDate[0]; + var month = splitDate[1]; + var day = splitDate[2]; + + return month + '/' + day + '/' + year; +} + +function getTime(val) { + var newDate = new Date(Date.parse(val)).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + return newDate; +} +function getDateAndTime(val) { + var newDate = new Date(Date.parse(val)).toLocaleDateString() + ' ' + new Date(Date.parse(val)).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }) + return newDate; +} +function getDateAndTimeAndSeconds(val) { + var newDate = new Date(Date.parse(val)).toLocaleDateString() + ' ' + new Date(Date.parse(val)).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true, second: '2-digit' }) + return newDate; +} +function notify(title, message) { + $.gritter.add({ + title: title, + text: message, + sticky: false, + time: '2000', + + }); +} +function getObjectByKey(key, value, objectArray) { + for (var x = 0; x < objectArray.length; x++) { + if (objectArray[x][key] == value) { + return objectArray[x]; + break; + } + } +} + +function formatSelect2Data(id, name, data) { + var newdata = []; + //var blank = { }; + // blank.id = ""; + //blank.text = ""; + //data.push(blank); + for (var x = 0; x < data.length; x++) { + var obj = {}; + obj.id = data[x][id]; + obj.text = data[x][name] + newdata.push(obj); + + } + return newdata; +} + +function formatSelect2DataForReturnedBatched(id, name, data) { + var newdata = []; + //var blank = { }; + // blank.id = ""; + //blank.text = ""; + //data.push(blank); + for (var x = 0; x < data.length; x++) { + if (data[x].Type != "ShiftCharges") { + var obj = {}; + obj.id = data[x][id]; + obj.text = data[x][name] + newdata.push(obj); + } + + } + return newdata; +} + +function formatSelect2DataForUser(id, data) { + var newdata = []; + //var blank = { }; + // blank.id = ""; + //blank.text = ""; + //data.push(blank); + for (var x = 0; x < data.length; x++) { + var obj = {}; + obj.id = data[x][id]; + obj.text = data[x]["FirstName"] + ' ' + data[x]["LastName"] + ' - ' + data[x]["UserID"]; + newdata.push(obj); + + } + return newdata; +} + +//batches +function fillDropDown(dropdown, method, session, deferred, fieldname, fieldvalue, placeholder) { + if ($.sessionStorage(session) == null) { + callMethod(method, fillDropDown, session, deferred, null); + } + else { + var data = formatSelect2Data(fieldname, fieldvalue, $.sessionStorage(session)); + $(control).select2({ + placeholder: placeholder, + allowClear: true, + selectOnClose: false, + //closeOnSelect: false, + data: data, + width: "200px" + }); + batchDeferred.resolve(true); + } +} + + +var currentDropdown; +//location +function fillLocationsDropDown(deferred) { + + + + var search = {}; + if (!$.userHasRole("Administrators")) { + search.UserID = $.localStorage("session_currentUser").UserID; + } + else { + search = null; + } + + $.webMethod({ + 'methodPage': 'UserMethods/', + 'methodName': 'SearchLocations', + 'parameters': { "search": search }, + success: function (json) { + + $.sessionStorage("session_locations", json.data); + + if ($.getBoolean(json.success)) { + var data = formatSelect2Data("ID", "Name", $.sessionStorage("session_locations")); + + if ($.userHasRole("Administrators")) { + $("#selLocation").select2({ + placeholder: "Select a location...", + allowClear: true, + selectOnClose: false, + //closeOnSelect: false, + data: data + }); + $("#selLocation").enable(true); + } + else { + $("#selLocation").select2({ + + data: data + }); + $("#selLocation").select2('val', data[0].id); + $("#selLocation").enable(false); + } + + if (deferred != null) { + deferred.resolve(true); + } + } + else { + notify("Error", "There was an error performing this action. Please try again.") + } + } + }); + + +} + +var currentSpinner = null +function spin2(spin) { + //spinner = $("#spanSpinner"); + currentSpinner.show(); + + if (spin) { + rotation = function () { + currentSpinner.rotate({ + angle: 0, + animateTo: 360, + callback: rotation + }); + } + rotation(); + } + else { + currentSpinner.stopRotate(); + currentSpinner.hide(); + } +} + +function spin(spin, spinner) { + //spinner = $("#spanSpinner"); + spinner.show(); + + if (spin) { + rotation = function () { + spinner.rotate({ + angle: 0, + animateTo: 360, + callback: rotation + }); + } + rotation(); + } + else { + spinner.stopRotate(); + spinner.hide(); + } +} + +//employee +function fillEmployeesDropDown(deferred, dropdown) { + + if (dropdown == null) { + dropdown = $("#selEmployee"); + } + $.webMethod({ + 'methodPage': 'UserMethods/', + 'methodName': 'GetUsers', + 'parameters': { "activeOnly": false }, + success: function (json) { + + $.sessionStorage("session_users", json.data); + + if ($.getBoolean(json.success)) { + var data = formatSelect2DataForUser("UserID", $.sessionStorage("session_users")); + dropdown.select2({ + placeholder: "Select an employee...", + allowClear: true, + selectOnClose: false, + //closeOnSelect: false, + data: data + }); + + if (deferred != null) { + deferred.resolve(true); + } + } + else { + notify("Error", "There was an error performing this action. Please try again.") + } + } + }); + + +} + + +//new imove global functions + + +function stripCharacters(str) { + return str.replace(/[-' ]/g, '').toUpperCase(); +} + +function searchUsers(d) { + search = {}; + + $.webMethod({ + 'methodPage': 'UserMethods/', + 'methodName': 'SearchUsers', + 'parameters': { "search": search }, + success: function (json) { + + + if ($.getBoolean(json.success)) { + + allUsers = json.data; + d.resolve(); + } + } + }); +} + +function growlWarn(msg) { + $.bootstrapGrowl("" + msg + "", { + type: 'warning', + align: 'center', + width: 'auto', + allow_dismiss: true, + offset: { from: 'top', amount: 60 } + }); +} +function growlSuccess(msg) { + $.bootstrapGrowl("" + msg + "", { + type: 'success', + align: 'center', + width: 'auto', + allow_dismiss: true, + offset: { from: 'top', amount: 60 } + }); +} + +function getUser(users, id) { + for (var x = 0; x < users.length; x++) { + if (users[x].UserID == id) { + return users[x]; + break; + } + } +} + diff --git a/surge365.massemailreact.client/public/content/js/jquery.usahaulers.logging-1.0.js b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.logging-1.0.js new file mode 100644 index 0000000..34b1cfb --- /dev/null +++ b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.logging-1.0.js @@ -0,0 +1,931 @@ +const DEFAULT_LOG_LEVELS_TO_LOG = ["debug","warn","info","error"]; +const DEFAULT_LOG_AJAX_REQUESTS = true; + +const DEFAULT_LOG_AJAX_REQUESTS_TO_CONSOLE = true; +const DEFAULT_LOG_AJAX_REQUESTS_TO_CONSOLE_ERROR_ONLY = true; +const DEFAULT_LOG_AJAX_REQUESTS_TO_CONSOLE_MAX_RESPONSE_CHARS = 200 + +///CURRENTLY THESE INDEXEDDB CONSTS ARE NOT USED, LEAVING HERE IN CASE I FIGURE OUT HOW TO WIRE THEM UP. +const DEFAULT_LOG_AJAX_REQUESTS_TO_INDEXEDDB = true; +const DEFAULT_LOG_AJAX_REQUESTS_TO_INDEXEDDB_ERROR_ONLY = true; +const DEFAULT_LOG_AJAX_REQUESTS_TO_INDEXEDDB_MAX_RESPONSE_CHARS = -1 //< 0 = log all, 0 = log none, > 0 log that # of chars +const DEFAULT_LOG_URL = 'https://usahaulerslog.solutionsmith.net/WebServices/LoggingMethods.aspx/SaveLogs'; + +const INTERNAL_LOG_URLS = { + DEFAULT: "https://usahaulerslogdev.solutionsmith.net/WebServices/LoggingMethods.aspx/SaveLogs", + DEV: "https://usahaulerslogdev.solutionsmith.net/WebServices/LoggingMethods.aspx/SaveLogs", + PROD: "https://usahaulerslog.solutionsmith.net/WebServices/LoggingMethods.aspx/SaveLogs" +} +const DEFAULT_LOG_CONFIG_OBJECT = { + id: 0, + defaultLogLevelsToLog: DEFAULT_LOG_LEVELS_TO_LOG, + logAjaxRequests: DEFAULT_LOG_AJAX_REQUESTS, + logAjaxRequestsToConsole: DEFAULT_LOG_AJAX_REQUESTS_TO_CONSOLE, + logAjaxRequestsToConsoleErrorOnly: DEFAULT_LOG_AJAX_REQUESTS_TO_CONSOLE_ERROR_ONLY, + logAjaxRequestsToConsoleMaxResponseChars: DEFAULT_LOG_AJAX_REQUESTS_TO_CONSOLE_MAX_RESPONSE_CHARS, + logAjaxRequestsToIndexedDB: DEFAULT_LOG_AJAX_REQUESTS_TO_INDEXEDDB, + logAjaxRequestsToIndexedDBErrorOnly: DEFAULT_LOG_AJAX_REQUESTS_TO_INDEXEDDB_ERROR_ONLY, + logAjaxRequestsToIndexedDBMaxResponseChars: DEFAULT_LOG_AJAX_REQUESTS_TO_INDEXEDDB_MAX_RESPONSE_CHARS +} + +const OLD_CONSOLE_LOG = console.log; +const OLD_CONSOLE_DEBUG = console.debug; +const OLD_CONSOLE_WARN = console.warn; +const OLD_CONSOLE_INFO = console.info; +const OLD_CONSOLE_ERROR = console.error; + +const USAHAULERSLOGGING_DATABASE_NAME = "usahaulerslogging"; +const USAHAULERSLOGGING_DATABASE_VERSION = 1; + +const INTERNAL_USAHAULERSLOGGING_USEHAULERSLOGGINGDBCONFIG_OBJECT_STORE = "usahaulersloggingdb.config"; +const INTERNAL_USAHAULERSLOGGING_LOG_REFRESH_PERIOD = INTERNAL_MINUTE_MS * 5; + +const INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE = "logs"; +const INTERNAL_USAHAULERSLOGGING_LOG_INDEX_DATE = "date"; +const INTERNAL_USAHAULERSLOGGING_LOG_INDEX_LEVEL = "level"; +const INTERNAL_USAHAULERSLOGGING_LOG_INDEX_UPLOADED = "uploaded"; +const INTERNAL_USAHAULERSLOGGING_MAXUPLOADQTY = 100 + +$.usaHaulersLoggingDB = { + config: DEFAULT_LOG_CONFIG_OBJECT, + shouldLogLevel: (level) => { + var config = $.usaHaulersLoggingDB.config; + if (config == null) + config = DEFAULT_LOG_CONFIG_OBJECT; + return config.defaultLogLevelsToLog.includes(level.trim().toLowerCase()); + }, + shouldLogAjaxRequests: function () { + var config = $.usaHaulersLoggingDB.config; + if (config == null) + config = DEFAULT_LOG_CONFIG_OBJECT; + return config.logAjaxRequests && (config.logAjaxRequestsToConsole || config.logAjaxRequestsToIndexedDB); + }, + shouldLogAjaxRequestsToConsole: function () { + var config = $.usaHaulersLoggingDB.config; + if (config == null) + config = DEFAULT_LOG_CONFIG_OBJECT; + return config.logAjaxRequests && config.logAjaxRequestsToConsole; + }, + shouldLogAjaxRequestsToConsoleErrorOnly: function () { + var config = $.usaHaulersLoggingDB.config; + if (config == null) + config = DEFAULT_LOG_CONFIG_OBJECT; + return config.logAjaxRequests && config.logAjaxRequestsToConsole && config.logAjaxRequestsToConsoleErrorOnly; + }, + logAjaxRequestsToConsoleMaxResponseChars: function () { + var config = $.usaHaulersLoggingDB.config; + if (config == null) + config = DEFAULT_LOG_CONFIG_OBJECT; + return (config.logAjaxRequests && config.logAjaxRequestsToConsole ? config.logAjaxRequestsToConsoleMaxResponseChars : 0); + }, + shouldLogAjaxRequestsToIndexedDB: function () { + var config = $.usaHaulersLoggingDB.config; + if (config == null) + config = DEFAULT_LOG_CONFIG_OBJECT; + return config.logAjaxRequests && config.logAjaxRequestsToIndexedDB && $.usaHaulersLoggingDB.dbInitialized; + }, + shouldLogAjaxRequestsToIndexedDBErrorOnly: function () { + var config = $.usaHaulersLoggingDB.config; + if (config == null) + config = DEFAULT_LOG_CONFIG_OBJECT; + return config.logAjaxRequests && config.logAjaxRequestsToIndexedDB && config.logAjaxRequestsToIndexedDBErrorOnly; + }, + logAjaxRequestsToIndexedDBMaxResponseChars: function () { + var config = $.usaHaulersLoggingDB.config; + if (config == null) + config = DEFAULT_LOG_CONFIG_OBJECT; + return (config.logAjaxRequests && config.logAjaxRequestsToIndexedDB ? config.logAjaxRequestsToIndexedDBMaxResponseChars : 0); + }, + prepareLog: function (argumentArray) { + var LOG_PREFIX = new Date().toISOString(); + var args = Array.from(argumentArray); + args.unshift(LOG_PREFIX + ": "); + return args; + }, + getLogMsgFromArguments: function (argumentArray) { + if (argumentArray === undefined || argumentArray === null) + return "Unknown log request: null/undefined passed to getLogMsgFromArguments"; + + var args = Array.from(argumentArray); + + var msg = ""; + + args.forEach((arg) => { + var msg1 = ""; + if (arg !== undefined && arg !== null) { + if (typeof arg === 'object') + msg1 = JSON.stringify(arg); + else + msg1 = arg.toString(); + + if (msg.length > 0) + msg += ", "; + + msg += msg1; + } + }); + return msg; + }, + getBoolean: function (variable) { + if ($.getBoolean) + return $.getBoolean(variable); + + var vtype; + var toReturn; + + if (variable != null) { + switch (typeof (variable)) { + case 'boolean': + vtype = "boolean"; + return variable; + break; + + case 'number': + vtype = "number"; + if (variable == 0) + toReturn = false; + else toReturn = true; + break; + + case 'string': + vtype = "string"; + if (variable.toLowerCase() == "true" || variable.toLowerCase() == "yes") + toReturn = true; + else if (variable.toLowerCase() == "false" || variable.toLowerCase() == "no") + toReturn = false; + else if (variable.length > 0) + toReturn = true; + else if (variable.length == 0) + toReturn = false; + break; + + } + + return toReturn; + } + }, + removeObjectMethodsForSerialization: function (obj) { + try { + return JSON.parse(JSON.stringify(obj)); + } + catch (error) { + return obj; + } + } +} + +console.log = console.debug = function () { + var args = $.usaHaulersLoggingDB.prepareLog(arguments); + OLD_CONSOLE_DEBUG.apply(console, args); + if ($.usaHaulersLoggingDB && $.usaHaulersLoggingDB.addLog && $.usaHaulersLoggingDB.dbInitialized && $.usaHaulersLoggingDB.shouldLogLevel("debug")) { + var msg = $.usaHaulersLoggingDB.getLogMsgFromArguments(arguments); + var dbArgs = Array.from(arguments); + $.usaHaulersLoggingDB.addLog("DEBUG", msg, dbArgs); + } +} +console.warn = function () { + var args = $.usaHaulersLoggingDB.prepareLog(arguments); + OLD_CONSOLE_WARN.apply(console, args); + if ($.usaHaulersLoggingDB && $.usaHaulersLoggingDB.addLog && $.usaHaulersLoggingDB.dbInitialized && $.usaHaulersLoggingDB.shouldLogLevel("warn")) { + var msg = $.usaHaulersLoggingDB.getLogMsgFromArguments(arguments); + var dbArgs = Array.from(arguments); + $.usaHaulersLoggingDB.addLog("WARN", msg, dbArgs); + } +} +console.info = function () { + var args = $.usaHaulersLoggingDB.prepareLog(arguments); + OLD_CONSOLE_INFO.apply(console, args); + if ($.usaHaulersLoggingDB && $.usaHaulersLoggingDB.addLog && $.usaHaulersLoggingDB.dbInitialized && $.usaHaulersLoggingDB.shouldLogLevel("info")) { + var msg = $.usaHaulersLoggingDB.getLogMsgFromArguments(arguments); + var dbArgs = Array.from(arguments); + $.usaHaulersLoggingDB.addLog("INFO", msg, dbArgs); + } +} +console.error = function () { + var args = $.usaHaulersLoggingDB.prepareLog(arguments); + OLD_CONSOLE_ERROR.apply(console, args); + if ($.usaHaulersLoggingDB && $.usaHaulersLoggingDB.addLog && $.usaHaulersLoggingDB.dbInitialized && $.usaHaulersLoggingDB.shouldLogLevel("error")) { + var msg = $.usaHaulersLoggingDB.getLogMsgFromArguments(arguments); + var dbArgs = Array.from(arguments); + $.usaHaulersLoggingDB.addLog("ERROR", msg, dbArgs); + } +} + +if ($ && $.ajaxSetup) { + $(document).ready(function () { + $(document).ajaxSuccess(function (event, xhr, settings) { + if ($.usaHaulersLoggingDB.shouldLogAjaxRequests()) { + var responseText = ""; + var isSuccess = true; + if (xhr.responseJson != null && xhr.responseJson.d != null && xhr.responseJson.d.success != null && !xhr.responseJson.d.success) + isSuccess = false; + + if (!$.usaHaulersLoggingDB.shouldLogAjaxRequestsToConsoleErrorOnly() || !isSuccess) { + if (xhr.responseText && xhr.responseText != null) { + if ($.usaHaulersLoggingDB.logAjaxRequestsToConsoleMaxResponseChars() <= 0 || xhr.responseText.length <= $.usaHaulersLoggingDB.logAjaxRequestsToConsoleMaxResponseChars()) + responseText = "response = '" + xhr.responseText + "';"; + else + responseText = "responseSnippet='" + xhr.responseText.substring(0, $.usaHaulersLoggingDB.logAjaxRequestsToConsoleMaxResponseChars()) + "';"; + } + } + else { + return; + } + + if ($.usaHaulersLoggingDB.shouldLogAjaxRequestsToConsole()) { + if (!isSuccess) + console.debug("Ajax Success, returned success:false", { + url: settings.url, + httpMethod: settings.type, + responseText: responseText + }); + else { + console.debug("Ajax Success", { + url: settings.url, + httpMethod: settings.type, + responseText: responseText + }); + } + } + } + if (xhr.responseJson != null && xhr.responseJson.d != null && xhr.responseJson.d.success != null && xhr.responseJson.d.success) { + var lg_session_guid = request.getResponseHeader('lg_session_guid'); + var lg_access_token = $.getBoolean(request.getResponseHeader('lg_access_token')); + var lg_application_code = $.getBoolean(request.getResponseHeader('lg_application_code')); + var lg_application_user_id = $.getBoolean(request.getResponseHeader('lg_application_user_id')); + if ($.cookie) { + $.cookie.raw = true; + $.cookie('lg_session_guid', lg_session_guid, { path: '/' }); + $.cookie('lg_access_token', lg_access_token, { path: '/' }); + $.cookie('lg_application_code', lg_application_code, { path: '/' }); + $.cookie('lg_application_user_id', lg_application_user_id, { path: '/' }); + } + } + }); + $(document).ajaxError(function (event, xhr, settings) { + if ($.usaHaulersLoggingDB.shouldLogAjaxRequests()) { + var responseText = ""; + + if (xhr.responseText && xhr.responseText != null) { + if ($.usaHaulersLoggingDB.logAjaxRequestsToConsoleMaxResponseChars() <= 0 || xhr.responseText.length <= $.usaHaulersLoggingDB.logAjaxRequestsToConsoleMaxResponseChars()) + responseText = "response = '" + xhr.responseText + "';"; + else + responseText = "responseSnippet='" + xhr.responseText.substring(0, $.usaHaulersLoggingDB.logAjaxRequestsToConsoleMaxResponseChars()) + "';"; + } + + if ($.usaHaulersLoggingDB.shouldLogAjaxRequestsToConsole()) { + console.error("Ajax Error", { + url: settings.url, + httpMethod: settings.type, + responseText: responseText, + xhr: $.usaHaulersLoggingDB.removeObjectMethodsForSerialization(xhr), + settings: $.usaHaulersLoggingDB.removeObjectMethodsForSerialization(settings) + }); + } + } + }); + }); +} + +const INTERNAL_USAHAULERSLOGGING_ONUPGRADEDNEEDED = function (event) { + const db = event.target.result; + const transaction = event.currentTarget.transaction; + let oldVersion = event.oldVersion; + let newVersion = event.newVersion; + + if (oldVersion < 1 && newVersion >= 1) { + + //setup object stores + db.createObjectStore(INTERNAL_USAHAULERSLOGGING_USEHAULERSLOGGINGDBCONFIG_OBJECT_STORE, { keyPath: 'id' }); + + if (db.objectStoreNames.contains(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE)) { + db.deleteObjectStore(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE); + } + db.createObjectStore(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE, { keyPath: 'id' }); + + + //setup indexes + let store = transaction.objectStore(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE); + + if (store.indexNames.contains(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_DATE)) { + store.deleteIndex(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_DATE); + } + store.createIndex(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_DATE, "date", { unique: false }); + + if (store.indexNames.contains(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_LEVEL)) { + store.deleteIndex(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_LEVEL); + } + store.createIndex(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_LEVEL, "level", { unique: false }); + + if (store.indexNames.contains(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_UPLOADED)) { + store.deleteIndex(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_UPLOADED); + } + store.createIndex(INTERNAL_USAHAULERSLOGGING_LOG_INDEX_UPLOADED, "upload_status", { unique: false }); + + } + + if (oldVersion < 2 && newVersion >= 2) { + // + } + +} + +$.extend($.usaHaulersLoggingDB, { + dbInitialized: false, + database: null, + initializeDB: function () { + let currentDB = this; + return new Promise(function (resolve, reject) { + if (currentDB.dbInitialized) { + resolve(); + return; + } + + currentDB.refreshConfigObject().then(function () { + currentDB.dbInitialized = true; + resolve(); + }); + }); + }, + createConfigObject: function () { + let currentDB = this; + var config = (!currentDB.config || currentDB.config == null ? DEFAULT_LOG_CONFIG_OBJECT : currentDB.config); + return config; + }, + refreshConfigObject: function () { + let currentDB = this; + return new Promise(async function (resolve, reject) { + try { + let config = await currentDB.getObjects(INTERNAL_USAHAULERSLOGGING_USEHAULERSLOGGINGDBCONFIG_OBJECT_STORE); + let configDefault = currentDB.createConfigObject(); + + if (!config || config.length == 0) { + currentDB.config = configDefault; + await currentDB.persistConfigObject(); + } + else { + currentDB.config = config[0]; + let updatedConfig = false; + for (const prop in configDefault) { + if (prop !== "id" || currentDB.config[prop] === undefined) { + updatedConfig = true; + currentDB.config[prop] = configDefault[prop]; + } + } + if (updatedConfig) + await currentDB.persistConfigObject(); + } + resolve(); + } + catch (error) { + reject(error); + } + }); + }, + persistConfigObject: function () { + let currentDB = this; + return currentDB.runTransactionReadWrite(INTERNAL_USAHAULERSLOGGING_USEHAULERSLOGGINGDBCONFIG_OBJECT_STORE, async function (transaction) { + const configStore = transaction.objectStore(INTERNAL_USAHAULERSLOGGING_USEHAULERSLOGGINGDBCONFIG_OBJECT_STORE); + + configStore.put(currentDB.config); + + return transaction.complete; + }); + } +}); + +//#region basic indexeddb methods +$.extend($.usaHaulersLoggingDB, { + create: function (deleteIfExists = true) { + return new Promise(async function (resolve, reject) { + if (deleteIfExists === true) { + try { + await $.dbManager.deleteDatabase(USAHAULERSLOGGING_DATABASE_NAME); + } + catch (error) { + reject(error); + return; + } + } + try { + await $.dbManager.openDatabase(USAHAULERSLOGGING_DATABASE_NAME, USAHAULERSLOGGING_DATABASE_VERSION, INTERNAL_USAHAULERSLOGGING_ONUPGRADEDNEEDED); + } + catch (error) { + reject(error); + return; + } + resolve(); + }); + }, + delete: function () { + let currentDB = this; + return $.dbManager.deleteDatabase(USAHAULERSLOGGING_DATABASE_NAME); + }, + open: function () { + let currentDB = this; + return $.dbManager.openDatabase(USAHAULERSLOGGING_DATABASE_NAME, USAHAULERSLOGGING_DATABASE_VERSION, INTERNAL_USAHAULERSLOGGING_ONUPGRADEDNEEDED).then( + function onSuccessInternal(db) { + currentDB.database = db; + if (!currentDB.dbInitialized) + return currentDB.initializeDB(); + else + return currentDB.refreshConfigObject(); + }, + function onErrorInternal(error) { + } + ); + }, + close: function () { + let currentDB = this; + + return $.dbManager.closeDatabase(USAHAULERSLOGGING_DATABASE_NAME).then( + function onSuccessInternal(db) { + currentDB.database = null; + }, + function onErrorInternal(error) { + } + ); + }, + runTransactionReadOnly: function (objectNames, transactionCode) { + let currentDB = this; + return new Promise(function (resolve, reject) { + if (transactionCode != null) { + let didOpenDB = false; + var onSuccessInternal = function () { + let transaction = currentDB.database.transaction(objectNames, "readonly"); + + transactionCode(transaction); + + //if (didOpenDB) + // currentDB.close(); + + transaction.oncomplete = function (event) { + resolve(); + }; + + transaction.onerror = function (event) { + reject(error.error); + }; + //Transactions auto-commit after scope ends, cannot manually commit + }; + if ($.usaHaulersLoggingDB.database == null) { + didOpenDB = true; + currentDB.open().then( + onSuccessInternal, + function onErrorInternal(error) { + reject(error); + }); + } + else { + onSuccessInternal(); + } + } + else { + reject("No transactionCode passed"); + } + }); + }, + runTransactionReadWrite: function (objectNames, transactionCode) { + let currentDB = this; + return new Promise(function (resolve, reject) { + if (transactionCode != null) { + let didOpenDB = false; + var onSuccessInternal = function () { + let transaction = currentDB.database.transaction(objectNames, "readwrite"); + + transactionCode(transaction); + + //if (didOpenDB) + // currentDB.close(); + + transaction.oncomplete = function (event) { + resolve(); + }; + + transaction.onerror = function (event) { + reject(error.error); + }; + //Transactions auto-commit after scope ends, cannot manually commit + }; + if ($.usaHaulersLoggingDB.database == null) { + didOpenDB = true; + currentDB.open().then( + onSuccessInternal, + function onErrorInternal(error) { + reject(error); + }); + } + else { + onSuccessInternal(); + } + } + else { + reject("No transactionCode passed"); + } + }); + }, + getObjects: function (objectName, query, indexes) { + let currentDB = this; + return new Promise(function (resolve, reject) { + currentDB.runTransactionReadOnly(objectName, function (transaction) { + const objectStore = transaction.objectStore(objectName); + let request = {}; + if (indexes && ((typeof indexes === "string" || indexes instanceof String) == false || indexes.length > 0)) + if (query + && (typeof query === "IDBKeyRange" || query instanceof IDBKeyRange || typeof query === "string" || query instanceof String)) + request = objectStore.index(indexes).getAll(query); + else + request = objectStore.index(indexes).getAll(); + else + if (query + && (typeof query === "IDBKeyRange" || query instanceof IDBKeyRange || typeof query === "string" || query instanceof String)) + request = objectStore.getAll(query); + else + request = objectStore.getAll() + request.onsuccess = function () { + if (request.result !== undefined) { + objects = request.result; + resolve(objects); + } + else { + console.log("no objects found: ", objectName); + reject("no objects found: ", objectName); + } + }; + request.onerror = function (error) { + console.log("Error getting objects(", objectName, ") from db: ", error); + reject(error); + }; + }); + }); + }, + getObjectsCustom: function (objectName, query, indexes, predicateBlock) { + let currentDB = this; + return new Promise(function (resolve, reject) { + currentDB.runTransactionReadOnly(objectName, function (transaction) { + const objectStore = transaction.objectStore(objectName); + let request = {}; + if (indexes && ((typeof indexes === "string" || indexes instanceof String) == false || indexes.length > 0)) + request = (query && ((typeof query === "string" || query instanceof String) == false || query.length > 0) ? objectStore.index(indexes).getAll(query) : objectStore.index(indexes).getAll()); + else + request = (query && ((typeof query === "string" || query instanceof String) == false || query.length > 0) ? objectStore.getAll(query) : objectStore.getAll()); + + request.onsuccess = function () { + if (request.result !== undefined) { + let objectsUnfiltered = request.result; + let objects = []; + for (ix in objectsUnfiltered) { + if (!predicateBlock || predicateBlock(objectsUnfiltered[ix])) { + objects.push(objectsUnfiltered[ix]); + } + } + resolve(objects); + } + else { + console.log("no objects found: ", objectName); + reject("no objects found: ", objectName); + } + }; + request.onerror = function (error) { + console.log("Error getting objects(", objectName, ") from db: ", error); + reject(error); + }; + }); + }); + }, + deleteAllObjects: function () { + let currentDB = this; + var error = null; + var objectNames = INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE; + return this.runTransactionReadWrite(objectNames, async function (transaction) { + const logStore = transaction.objectStore(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE); + try { + await logStore.clear(); + } + catch (error2) { + error = error2; + } + return transaction.complete; + }); + if (error) { + throw error; + } + } +}); +//#endregion + +//ADD NON-GENERIC METHODS +$.extend($.usaHaulersLoggingDB, { + newGuid: function () { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0, + v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }, + createLogObjectInternal: function (id, level, msg, args, date) { + return { + id: (id == null ? this.newGuid() : id), + version: '1.0', + url: window.location.href, + application_code: $.cookie('lg_application_code'), + application_user_id: $.cookie('lg_application_user_id'), + environment_code: $.cookie('lg_environment_code'), + level: level, + msg: msg, + arguments: args, + date: (date == null ? new Date() : date), + time: (date == null ? new Date() : date).toISOString(), + uploaded: false, + upload_status: 'Not Uploaded', + upload_date: null + } + }, + createLogObject: function (level, msg, args) { + return this.createLogObjectInternal(null, level, msg, args, null); + }, + addLog: async function (level, msg, args) { + let currentDB = this; + + args = currentDB.removeObjectMethodsForSerialization(args); + try { + await currentDB.refreshConfigObject(); + var error = null; + var log = currentDB.createLogObject(level, msg, args); + return this.runTransactionReadWrite(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE, async function (transaction) { + const logStore = transaction.objectStore(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE); + logStore.put(log); + return transaction.complete; + }).then( + function () { + }, + function (errorInternal) { + error = errorInternal; + } + ); + + if (error) { + throw error; + } + } + catch (error) { + console.log(error); + throw error; + } + }, + deleteLogs: async function (logs) { + let currentDB = this; + + try { + await currentDB.refreshConfigObject(); + var error = null; + return this.runTransactionReadWrite(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE, async function (transaction) { + const logStore = transaction.objectStore(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE); + + logs.forEach((log) => { + logStore.delete(log.id); + }); + return transaction.complete; + }).then( + function () { + }, + function (errorInternal) { + error = errorInternal; + } + ); + + if (error) { + throw error; + } + } + catch (error) { + console.log(error); + throw error; + } + }, + getLogsByLevel: async function (level, startDate, endDate) { + let currentDB = this; + let levelText = ""; + if (level != undefined && level != null) + levelText = level.toUpperCase(); + + let query = levelText; + + let indexes = INTERNAL_USAHAULERSLOGGING_LOG_INDEX_LEVEL; + let logs = await currentDB.getObjects(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE, query, indexes); + let logs2 = []; + logs.forEach((log) => { + if ((startDate == null || log.date >= startDate) && (endDate == null || log.date <= endDate)) + logs2.push(log); + }); + + logs2.sort(function (l1, l2) { + return l1.date - l2.date; + }); + + return logs2; + }, + getLogsAll: async function (startDate, endDate) { + let currentDB = this; + let query = ""; + let logs = await currentDB.getObjects(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE, query); + let logs2 = []; + logs.forEach((log) => { + if ((startDate == null || log.date >= startDate) && (endDate == null || log.date <= endDate)) + logs2.push(log); + }); + + logs2.sort(function (l1, l2) { + return l1.date - l2.date; + }); + + return logs2; + }, + getLogsByUploaded: async function (uploaded, startDate, endDate) { + let currentDB = this; + let query = uploaded ? "Uploaded" : "Not Uploaded"; + + let indexes = INTERNAL_USAHAULERSLOGGING_LOG_INDEX_UPLOADED; + let logs = await currentDB.getObjects(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE, query, indexes); + let logs2 = []; + logs.forEach((log) => { + if ((startDate == null || log.date >= startDate) && (endDate == null || log.date <= endDate)) + logs2.push(log); + }); + + logs2.sort(function (l1, l2) { + return l1.date - l2.date; + }); + + return logs2; + }, + getLogsByUploadStatus: async function (uploadStatus, startDate, endDate) { + let currentDB = this; + let query = uploadStatus + + let indexes = INTERNAL_USAHAULERSLOGGING_LOG_INDEX_UPLOADED; + let logs = await currentDB.getObjects(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE, query, indexes); + let logs2 = []; + logs.forEach((log) => { + if ((startDate == null || log.date >= startDate) && (endDate == null || log.date <= endDate)) + logs2.push(log); + }); + + logs2.sort(function (l1, l2) { + return l1.date - l2.date; + }); + + return logs2; + }, + markLogsUploaded: async function (logs) { + let currentDB = this; + + try { + await currentDB.refreshConfigObject(); + var error = null; + + logs.forEach((log) => { + log.uploaded = true; + log.upload_status = "Uploaded"; + log.upload_date = new Date(); + }); + return this.runTransactionReadWrite(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE, async function (transaction) { + const logStore = transaction.objectStore(INTERNAL_USAHAULERSLOGGING_LOG_OBJECT_STORE); + logs.forEach((log) => { + logStore.put(log); + }); + return transaction.complete; + }).then( + function () { + }, + function (errorInternal) { + error = errorInternal; + } + ); + + if (error) { + throw error; + } + } + catch (error) { + console.error(error); + //throw error; + } + + }, + uploadLogs: async function () { + var currentDB = this; + try { + await currentDB.refreshConfigObject(); + var logs = null; + var maxUploadQty = INTERNAL_USAHAULERSLOGGING_MAXUPLOADQTY; + if (maxUploadQty <= 0) + maxUploadQty = 999999; + + do { + logs = await currentDB.getLogsByUploaded(false, maxUploadQty); + if (logs != null && logs.length > 0) { + var success = false; + + //try { + success = await currentDB.uploadLogChunk(logs); + //} + //catch { + // throw; + //} + if (!success) { + console.debug("currentDB.uploadLogChunk returned false"); + return false; + } + //await currentDB.markLogsUploaded(logs); + await currentDB.deleteLogs(logs); + if (logs.length < maxUploadQty) + break; + } + } while (logs != null && logs.length > 0); + return true; + } + catch (error) { + if (!(error instanceof Error)) + console.error(JSON.stringify(error)); + else + console.error(error); + return false; + //throw error; + } + }, + getLogURL: function () { + var environmentCode = $.cookie('lg_environment_code'); + if (environmentCode === null || environmentCode.trim().length == 0) + environmentCode = "DEFAULT"; + + environmentCode = environmentCode.toUpperCase(); + var logUrl = INTERNAL_LOG_URLS[environmentCode]; + if (logUrl === null || logUrl.trim().length == 0) + logUrl = INTERNAL_LOG_URLS['DEFAULT']; + return logUrl; + }, + uploadLogChunk: async function (logs) { + var currentDB = this; + + var success = false; + await $.ajax({ + type: "POST", + url: currentDB.getLogURL(), + data: JSON.stringify({ "jsonData": JSON.stringify(logs) }), + contentType: 'application/json; charset=utf-8', + dataType: 'json', + async: true, + cache: false, + timeout: 30000, + disable_auth_token:true, + headers: { + lg_session_id: $.cookie('lg_session_guid'), + lg_access_token: $.cookie('lg_access_token') + }, + success: function (value, textStatus, request) { + var json = value.hasOwnProperty("d") ? $.parseJSON(value.d) : $.parseJSON(value); + if (currentDB.getBoolean(json.success)) { + success = true; + return true; + } + success = false; + return false; + }, + error: function (xhr, error, errorThrown) { + //if ((error instanceof jqXHR) + // console.error(JSON.stringify(error)); + //else if (!(error instanceof Error)) + // console.error(JSON.stringify(error)); + //else + // console.error(error); + success = false; + return false; + } + }); + return success; + } +}); + +//TODO: Decide what to do here. This PROBABLY should be a method call that the UI calls on page load, otherwise some calls could happen before this is done/ready. Can't put await on this method, jquery .ready queue will not wait correctly. +$(document).ready(function () { + $.usaHaulersLoggingDB.open().then(() => { + console.log("$.usaHaulersLoggingDB.open() success"); + return $.usaHaulersLoggingDB.initializeDB(); + }) + .then(() => { + console.log("$.usaHaulersLoggingDB.initializeDB() success"); + return $.usaHaulersLoggingDB.uploadLogs(); + }, + (error) => { + console.error(error); + }); +}); + +$(document).ajaxSend(function (event, xhr, settings) { + if (settings.disable_auth_token) { + xhr.setRequestHeader('Auth-Token', null); + xhr.setRequestHeader('Auth-Impersonate-Guid', null); + xhr.setRequestHeader('Auth-Current-Franchise', null); + } +}); \ No newline at end of file diff --git a/surge365.massemailreact.client/public/content/js/jquery.usahaulers.utilities-1.0.js b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.utilities-1.0.js new file mode 100644 index 0000000..7ad8c3d --- /dev/null +++ b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.utilities-1.0.js @@ -0,0 +1,342 @@ +//iMove utility functions + + +//ie is the version of IE running. +var ie = (function () { + + var undef, + v = 3, + div = document.createElement('div'), + all = div.getElementsByTagName('i'); + + while ( + div.innerHTML = '', + all[0] + ); + + return v > 4 ? v : undef; + +} ()); + +$(document).ajaxSend(function (event, xhr, settings) { + var authToken = $.cookie('Auth-Token'); + if (authToken != null && authToken != undefined && authToken.trim().length > 0) { + xhr.setRequestHeader('Auth-Token', authToken); + } + + var impersonateGuid = $.getParameterByName("impersonateid"); + if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) { + $.sessionStorage('Auth-Impersonate-Guid', impersonateGuid); + } + + impersonateGuid = $.sessionStorage('Auth-Impersonate-Guid'); + if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) { + xhr.setRequestHeader('Auth-Impersonate-Guid', impersonateGuid); + } + + var franchiseCode = $.sessionStorage('franchiseCode'); + if (franchiseCode != null && franchiseCode != undefined && franchiseCode.trim().length > 0) { + xhr.setRequestHeader('Auth-Current-Franchise', franchiseCode); + } +}); + +$.webMethod = function (options) { + var settings = $.extend({ + 'protocol': location.protocol, //http or https + 'methodPage': '', //This should be something like UserMethods + 'methodName': '', //Something like Createuser + 'contentType': 'application/json; charset=utf-8', + 'dataType': 'json', + 'async': true, + 'cache': false, + timeout: 300000, + 'parameters': {}, + success: function (response) { }, + error: function (xhr, error, error_thrown) { } + }, options); + + if (settings.protocol.indexOf(':') < 0) { + settings.protocol = settings.protocol + ':'; + } + + var result; + var baseUrl = window.API_BASE_URL; + //var baseUrl = $("base").attr("href"); + if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0) + baseUrl = ""; + if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/") + baseUrl = baseUrl + "/"; + var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName); + $.ajax({ + type: "POST", + url: url, + data: JSON.stringify(settings.parameters), + contentType: settings.contentType, + dataType: settings.dataType, + async: settings.async, + cache: settings.cache, + timeout: settings.timeout, + beforeSend: null, + success: function (value, textStatus, request) { + if (value.hasOwnProperty("d")) + result = $.parseJSON(value.d); + else if (typeof value === 'object') + result = value; + else + result = $.parseJSON(value); + var authToken = request.getResponseHeader('Auth-Token'); + var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in')); + $.cookie.raw = true; + + if ($.getRememberMe() === true) { + $.cookie('Auth-Token', authToken, { expires: 14, path: '/' }); + $.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' }); + } + else { + $.cookie('Auth-Token', authToken, { expires: 365, path: '/' }); + $.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' }); + } + if (settings.success !== undefined) + settings.success(result); + }, + error: function (xhr, error, errorThrown) { + if (settings.error !== undefined) + settings.error(xhr, error, errorThrown); + } + }); +} + +$.webMethodAsync = async function (options) { + var settings = $.extend({ + 'protocol': location.protocol, //http or https + 'methodPage': '', //This should be something like UserMethods. + 'methodName': '', //Something like Createuser + 'contentType': 'application/json; charset=utf-8', + 'dataType': 'json', + 'async': true, + 'cache': false, + timeout: 300000, + 'parameters': {}, + success: function (response) { }, + error: function (xhr, error, error_thrown) { } + }, options); + + if (settings.protocol.indexOf(':') < 0) { + settings.protocol = settings.protocol + ':'; + } + + var result; + var baseUrl = window.API_BASE_URL; + //var baseUrl = $("base").attr("href"); + if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0) + baseUrl = ""; + if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/") + baseUrl = baseUrl + "/"; + var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName); + return $.ajax({ + type: "POST", + url: url, + data: JSON.stringify(settings.parameters), + contentType: settings.contentType, + dataType: settings.dataType, + async: settings.async, + cache: settings.cache, + timeout: settings.timeout, + beforeSend: null, + success: function (value, textStatus, request) { + if (value.hasOwnProperty("d")) + result = $.parseJSON(value.d); + else if (typeof value === 'object') + result = value; + else + result = $.parseJSON(value); + var authToken = request.getResponseHeader('Auth-Token'); + var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in')); + $.cookie.raw = true; + + if ($.getRememberMe() === true) { + $.cookie('Auth-Token', authToken, { expires: 14, path: '/' }); + $.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' }); + } + else { + $.cookie('Auth-Token', authToken, { expires: 365, path: '/' }); + $.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' }); + } + if (settings.success !== undefined) + settings.success(result); + }, + error: function (xhr, error, errorThrown) { + if (settings.error !== undefined) + settings.error(xhr, error, errorThrown); + } + }); +} + +$.getBoolean = function (variable) { + var vtype; + var toReturn; + + if (variable != null) { + switch (typeof (variable)) { + case 'boolean': + vtype = "boolean"; + return variable; + break; + + case 'number': + vtype = "number"; + if (variable == 0) + toReturn = false; + else toReturn = true; + break; + + case 'string': + vtype = "string"; + if (variable.toLowerCase() == "true" || variable.toLowerCase() == "yes") + toReturn = true; + else if (variable.toLowerCase() == "false" || variable.toLowerCase() == "no") + toReturn = false; + else if (variable.length > 0) + toReturn = true; + else if (variable.length == 0) + toReturn = false; + break; + + } + + return toReturn; + } +}; + +$.isLoggedIn = function () { + return $.getBoolean($.cookie('usahl_logged_in')); +} + +//Send the auth-token in all ajax requests +$.ajaxSetup({ + beforeSend: function (xhr, settings) { + xhr.setRequestHeader('Auth-Token', $.cookie('Auth-Token')); + } +}); + +$.sessionStorage = function (key, value) { + if (value === undefined) { + var val = window.sessionStorage.getItem(key); + if ((/^usahl_json/).test(val)) { + val = val.substring(11, val.length); + val = $.parseJSON(val); + } + return val; + } + else { + var val = value; + if (typeof value === 'object') { + val = "usahl_json:" + JSON.stringify(value); + } + + window.sessionStorage.setItem(key, val); + } +}; + +$.sessionStorageClear = function () { + window.sessionStorage.clear(); +}; + +$.sessionStorageRemove = function (key) { + window.sessionStorage.removeItem(key); +}; + +$.localStorage = function (key, value) { + if (value === undefined) { + var val = window.localStorage.getItem(key); + if ((/^usahl_json/).test(val)) { + val = val.substring(11, val.length); + val = $.parseJSON(val); + } + return val; + } + else { + var val = value; + if (typeof value === 'object') { + val = "usahl_json:" + JSON.stringify(value); + } + + window.localStorage.setItem(key, val); + } +}; + +$.localStorageClear = function () { + window.localStorage.clear(); +}; + +$.localStorageRemove = function (key) { + window.localStorage.removeItem(key); +}; + +$.setRememberMe = function (rememberMe) { + $.sessionStorage("usahl_remember_me", rememberMe); +} +$.getRememberMe = function () { + return true; + /* + var rememberMe = $.sessionStorage("usahl_remember_me"); + if (rememberMe === undefined || rememberMe == null) { + return false; + } + + return $.getBoolean(rememberMe); + */ +} + +$.userHasPermission = function (permissionCode) { + var allowed = false; + var user = $.localStorage("session_currentUser"); + $.each(user.Permissions, function () { + if ($.compareStrings(this.Code, permissionCode)) { + allowed = true; + } + }); + return allowed; +} + +$.userHasRole = function (role) { + var hasRole = false; + var user = $.localStorage("session_currentUser"); + $.each(user.Roles, function () { + if ($.compareStrings(this.FriendlyName, role)) { + hasRole = true; + } + }); + return hasRole; +} + +$.checkForRole = function (user, role) { + var hasRole = false; + $.each(user.Roles, function () { + if ($.compareStrings(this.FriendlyName, role)) { + hasRole = true; + } + }); + return hasRole; +} + +$.checkForRoleCode = function (user, role) { + var hasRole = false; + $.each(user.roles, function () { + if ($.compareStrings(this.role_code, role)) { + hasRole = true; + } + }); + return hasRole; +} + +$.compareStrings = function (string1, string2) { + return string1.toLowerCase() === string2.toLowerCase(); +} + +$.getParameterByName = function (name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +} \ No newline at end of file diff --git a/surge365.massemailreact.client/public/content/js/jquery.usahaulers.webauthn.js b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.webauthn.js new file mode 100644 index 0000000..8e0e4cc --- /dev/null +++ b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.webauthn.js @@ -0,0 +1,404 @@ + +$.webauthn = { + methodCodeRegister: "Register", + methodCodeLogin: "Login", + isSupported: function () {// Availability of `window.PublicKeyCredential` means webauthn is usable. + if (window.PublicKeyCredential && PublicKeyCredential.isConditionalMediationAvailable) { + return true; + } + return false; + }, + abortController: new AbortController(), + getPublicKey: async function (method, type, username) { + let jsonData = {}; + let error = null; + await $.webMethodAsync({ + 'methodPage': 'Webauthn/GetPublicKey', + 'parameters': { "method": method, "type": type, "username": username }, + success: function (json) { + if (json.success === true || json.success === "true") { + jsonData = json.data; + } + else { + error = "success=false"; + } + }, + error: function (errorInternal) { + error = errorInternal; + } + }); + + if (error) + throw error;//// + + return jsonData; + }, + getPublicKeyForLogin: async function (username) { + return $.webauthn.getPublicKey($.webauthn.methodCodeLogin, "Any", username); + }, + authenticate: async function (publicKey) { + if (!this.isSupported()) + throw new Error("webauthn is not supported"); + + var publicKeyMain = JSON.parse(JSON.stringify(publicKey)); + + if (publicKey.publicKey !== undefined) { + publicKey = publicKey.publicKey; + } + var manager = this; + + if (publicKey.challenge !== undefined) + publicKey.challenge = $.webauthn.coerceToArrayBuffer(publicKey.challenge); + + if (publicKey.user !== undefined && publicKey.user.id !== undefined) + publicKey.user.id = $.webauthn.coerceToArrayBuffer(publicKey.user.id); + + if (publicKey.excludeCredentials !== undefined && publicKey.excludeCredentials !== null) { + for (var exKey of publicKey.excludeCredentials) { + if (exKey.id !== undefined && exKey.id !== null) + exKey.id = $.webauthn.coerceToArrayBuffer(exKey.id); + } + } + if (publicKey.allowCredentials !== undefined && publicKey.allowCredentials !== null) { + for (var allowCred of publicKey.allowCredentials) { + if (allowCred.id !== undefined && allowCred.id !== null) + allowCred.id = $.webauthn.coerceToArrayBuffer(allowCred.id); + } + } + + var clientResponse; + try { + assertedCredential = await navigator.credentials.get({ + publicKey: publicKey, + signal: $.webauthn.abortController.signal + }); + } + catch (error) { + console.error(error); + showAlert("error", error); + return null; + } + + let authData = new Uint8Array(assertedCredential.response.authenticatorData); + let clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON); + let rawId = new Uint8Array(assertedCredential.rawId); + let sig = new Uint8Array(assertedCredential.response.signature); + const data = { + id: assertedCredential.id, + rawId: $.webauthn.coerceToBase64Url(rawId), + type: assertedCredential.type, + extensions: assertedCredential.getClientExtensionResults(), + response: { + authenticatorData: $.webauthn.coerceToBase64Url(authData), + clientDataJSON: $.webauthn.coerceToBase64Url(clientDataJSON), + signature: $.webauthn.coerceToBase64Url(sig) + } + }; + + return await $.webauthn.verifyKey(data); + }, + saveKey: async function (type, publicKey, newKey) { + let error = null; + var outputJson = null; + await $.webMethodAsync({ + 'methodPage': 'Webauthn/SaveKey', + 'parameters': { "type": type, "publicKey": publicKey, "newKey": newKey }, + success: function (json) { + if (json.success === true || json.success === "true") { + outputJson = json; + } + else { + error = "success=false"; + } + }, + error: function (errorInternal) { + error = errorInternal; + } + }); + + if (error) + throw error; + + return outputJson; + }, + verifyKey: async function (clientResponse) { + let error = null; + var outputJson = null; + await $.webMethodAsync({ + 'methodPage': 'Webauthn/VerifyKey', + 'parameters': { "clientResponse": clientResponse }, + success: function (json) { + if (json.success === true || json.success === "true") { + outputJson = json; + } + else { + error = "success=false"; + } + }, + error: function (errorInternal) { + error = errorInternal; + } + }); + + if (error) + throw error; + + return outputJson; + }, + passkeys: { + typeCode: "passkey", + abortController: new AbortController(), + isSupported: function () { + if ($.webauthn.isSupported()) { + return true; + //// Check if conditional mediation is available. + //const isCMA = await PublicKeyCredential.isConditionalMediationAvailable(); + //if (isCMA) { + // return true; + //} + } + return false; + }, + getPublicKeyForRegister: async function (username) { + return $.webauthn.getPublicKey($.webauthn.methodCodeRegister, this.typeCode, username); + }, + getPublicKeyForLogin: async function (username) { + return $.webauthn.getPublicKey($.webauthn.methodCodeLogin, this.typeCode, username); + }, + authenticate: async function (publicKey) { + if (!this.isSupported()) + throw new Error("webauthn/passkeys is not supported"); + + return $.webauthn.authenticate(publicKey); + }, + create: async function (publicKey) { + if (!this.isSupported()) + throw new Error("webauthn/passkeys is not supported"); + + var publicKeyMain = JSON.parse(JSON.stringify(publicKey)); + + if (publicKey.publicKey !== undefined) { + publicKey = publicKey.publicKey; + } + var manager = this; + + if (publicKey.challenge !== undefined) + publicKey.challenge = $.webauthn.coerceToArrayBuffer(publicKey.challenge); + + if (publicKey.user !== undefined && publicKey.user.id !== undefined) + publicKey.user.id = $.webauthn.coerceToArrayBuffer(publicKey.user.id); + + if (publicKey.excludeCredentials !== undefined && publicKey.excludeCredentials !== null) { + for (var exKey of publicKey.excludeCredentials) { + if (exKey.id !== undefined && exKey.id !== null) + exKey.id = $.webauthn.coerceToArrayBuffer(exKey.id); + } + } + + try { + var credential = await navigator.credentials.create({ + publicKey: publicKey, + signal: $.webauthn.abortController.signal + }); + } + catch (error) { + console.error(error); + showAlert("error", error); + return null; + } + + var credential2 = { + authenticatorAttachment: credential.authenticatorAttachment, + id: credential.id, + rawId: $.webauthn.coerceToBase64Url(credential.rawId), + type: credential.type, + extensions: credential.getClientExtensionResults(), + response: { + attestationObject: $.webauthn.coerceToBase64Url(credential.response.attestationObject), + clientDataJSON: $.webauthn.coerceToBase64Url(credential.response.clientDataJSON), + transports: credential.response.getTransports() + }, + type: credential.type + } + + var publicKey2 = JSON.parse(JSON.stringify(publicKey)); + publicKey2.user.id = publicKeyMain.publicKey.user.id; + publicKey2.challenge = publicKeyMain.publicKey.challenge; + var keySaved = await $.webauthn.saveKey(this.typeCode, publicKey2, credential2); + + if ($.getBoolean(keySaved.success)) + showAlert("success", "Key created"); + else + showAlert("error", "Key creation failed"); + } + }, + securityKeys: { + typeCode: "securitykey", + abortController: new AbortController(), + isSupported: function () { + if ($.webauthn.isSupported()) { + return true; + //// Check if conditional mediation is available. + //const isCMA = await PublicKeyCredential.isConditionalMediationAvailable(); + //if (isCMA) { + // return true; + //} + } + return false; + }, + getPublicKeyForRegister: async function (username) { + return $.webauthn.getPublicKey($.webauthn.methodCodeRegister, this.typeCode, username); + }, + getPublicKeyForLogin: async function (username) { + return $.webauthn.getPublicKey($.webauthn.methodCodeLogin, this.typeCode, username); + }, + saveKey: async function (publicKey, newKey) { + return $.webauthn.saveKey(this.typeCode, publicKey, newKey); + }, + authenticate: async function (publicKey) { + if (!this.isSupported()) + throw new Error("webauthn/securityKeys is not supported"); + + return $.webauthn.authenticate(publicKey); + }, + create: async function (publicKey) { + if (!this.isSupported()) + throw new Error("webauthn/securityKeys is not supported"); + + var publicKeyMain = JSON.parse(JSON.stringify(publicKey)); + + if (publicKey.publicKey !== undefined) { + publicKey = publicKey.publicKey; + } + var manager = this; + + if (publicKey.challenge !== undefined) + publicKey.challenge = $.webauthn.coerceToArrayBuffer(publicKey.challenge); + + if (publicKey.user !== undefined && publicKey.user.id !== undefined) + publicKey.user.id = $.webauthn.coerceToArrayBuffer(publicKey.user.id); + + if (publicKey.excludeCredentials !== undefined && publicKey.excludeCredentials !== null) { + for (var exKey of publicKey.excludeCredentials) { + if (exKey.id !== undefined && exKey.id !== null) + exKey.id = $.webauthn.coerceToArrayBuffer(exKey.id); + } + } + + try { + var credential = await navigator.credentials.create({ + publicKey: publicKey, + signal: $.webauthn.abortController.signal + }); + } + catch (error) { + console.error(error); + showAlert("error", error); + return null; + } + + var credential2 = { + authenticatorAttachment: credential.authenticatorAttachment, + id: credential.id, + rawId: $.webauthn.coerceToBase64Url(credential.rawId), + type: credential.type, + extensions: credential.getClientExtensionResults(), + response: { + attestationObject: $.webauthn.coerceToBase64Url(credential.response.attestationObject), + clientDataJSON: $.webauthn.coerceToBase64Url(credential.response.clientDataJSON), + transports: credential.response.getTransports() + }, + type: credential.type + } + + var publicKey2 = JSON.parse(JSON.stringify(publicKey)); + publicKey2.user.id = publicKeyMain.publicKey.user.id; + publicKey2.challenge = publicKeyMain.publicKey.challenge; + var keySaved = await $.webauthn.saveKey(this.typeCode, publicKey2, credential2); + + if ($.getBoolean(keySaved.success)) + showAlert("success", "Key created"); + else + showAlert("error", "Key creation failed"); + } + }, + stringToArrayBuffer: function (string) { + var string2 = (typeof string !== "string" ? string.toString() : string); + return Uint8Array.from(string2, c => c.charCodeAt(0)); + }, + arrayBufferToString: function (buffer) { + return String.fromCharCode.apply(null, new Uint8Array(buffer)); + }, + arrayBufferToBase64String: function (buffer) { + return window.btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); + }, + coerceToArrayBuffer: function (thing, name) { + if (thing === undefined || thing === null) + return null; + + if (typeof thing === "string") { + // base64url to base64 + thing = thing.replace(/-/g, "+").replace(/_/g, "/"); + + // base64 to Uint8Array + var str = window.atob(thing); + var bytes = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + bytes[i] = str.charCodeAt(i); + } + thing = bytes; + } + + // Array to Uint8Array + if (Array.isArray(thing)) { + thing = new Uint8Array(thing); + } + + // Uint8Array to ArrayBuffer + if (thing instanceof Uint8Array) { + thing = thing.buffer; + } + + // error if none of the above worked + if (!(thing instanceof ArrayBuffer)) { + throw new TypeError("could not coerce '" + name + "' to ArrayBuffer"); + } + + return thing; + }, + + coerceToBase64Url: function (thing) { + if (thing === undefined || thing === null) + return null; + + // Array or ArrayBuffer to Uint8Array + if (Array.isArray(thing)) { + thing = Uint8Array.from(thing); + } + + if (thing instanceof ArrayBuffer) { + thing = new Uint8Array(thing); + } + + // Uint8Array to base64 + if (thing instanceof Uint8Array) { + var str = ""; + var len = thing.byteLength; + + for (var i = 0; i < len; i++) { + str += String.fromCharCode(thing[i]); + } + thing = window.btoa(str); + } + + if (typeof thing !== "string") { + throw new Error("could not coerce to string"); + } + + // base64 to base64url + // NOTE: "=" at the end of challenge is optional, strip it off here + //thing = thing.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, ""); + + return thing; + } +} \ No newline at end of file diff --git a/surge365.massemailreact.client/public/content/js/jquery.usahaulers.webmethods-1.0.js b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.webmethods-1.0.js new file mode 100644 index 0000000..323cc49 --- /dev/null +++ b/surge365.massemailreact.client/public/content/js/jquery.usahaulers.webmethods-1.0.js @@ -0,0 +1,149 @@ + + +async function CheckAuthToken() { + + var data = null; + + + return new Promise((resolve, reject) => { + try { + callMethod("CheckAuthToken", {}).then((data) => { + //return the first element in the array for this call. + resolve(data[0]); + }).catch((data) => { + reject(data); + }); + } catch (error) { + reject(error); + } + }); +} + +async function GetVehicles() { + var data = null; + + + return new Promise((resolve, reject) => { + try { + callMethod("GetVehicles", {}).then((data) => { + //return the first element in the array for this call. + resolve(data); + }).catch((data) => { + reject(data); + }); + } catch (error) { + reject(error); + } + }); +} + +async function SaveVehicle(vehicleId, vehicleData) +{ + var data = null; + return new Promise((resolve, reject) => { + try { + callMethod("SaveVehicle", {"vehicleId":vehicleId, "jsonData": JSON.stringify(vehicle) }).then((data) => { + //return the first element in the array for this call. + resolve(data); + }).catch((data) => { + reject(data); + }); + } catch (error) { + reject(error); + } + }); +} + +async function GetVehicleImages(vehicleID) { + var data = null; + + + return new Promise((resolve, reject) => { + try { + callMethod("GetVehicleImages", { "vehicleID": vehicleID }).then((data) => { + //return the first element in the array for this call. + resolve(data); + }).catch((data) => { + reject(data); + }); + } catch (error) { + reject(error); + } + }); +} + +async function SaveCurrentUser() { + + user = currentUser; + return new Promise((resolve, reject) => { + try { + callMethod("SaveUser", { "userJson": JSON.stringify(user) }).then((data) => { + resolve(data); + }).catch((data) => { + reject(data); + }); + } catch (error) { + reject(error); + } + }); +} + +async function ServerHealthCheck() { + return new Promise((resolve, reject) => { + $.webMethod({ + 'methodPage': 'UserMethods/', + 'methodName': "ServerHealthCheck", + 'parameters': {}, + 'timeout': 3000, + success: function (json) { + + if ($.getBoolean(json.success)) { + + data = json; + resolve(data); + } + else { + data = json; + reject(data); + } + }, + error: function (data) { + reject(data); + } + }); + }); +} + + + +async function callMethod(methodName, parameters) { + return new Promise((resolve, reject) => { + $.webMethod({ + 'methodPage': 'UserMethods', + 'methodName': methodName, + 'parameters': parameters, + 'timeout': 60000, + success: function (json) { + + if (json.data != null) { + data = json.data; + } + else { + data = json; + } + + if ($.getBoolean(json.success)) { + resolve(data); + } + else { + reject(data); + } + }, + error: function (data) { + + } + }); + }); +} + + diff --git a/surge365.massemailreact.client/src/App.css b/surge365.massemailreact.client/src/App.css deleted file mode 100644 index 32d0d22..0000000 --- a/surge365.massemailreact.client/src/App.css +++ /dev/null @@ -1,11 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -th, td { - padding-left: 1rem; - padding-right: 1rem; -} \ No newline at end of file diff --git a/surge365.massemailreact.client/src/App.tsx b/surge365.massemailreact.client/src/App.tsx deleted file mode 100644 index 3b53b26..0000000 --- a/surge365.massemailreact.client/src/App.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useEffect, useState } from 'react'; -import './App.css'; - -interface Forecast { - date: string; - temperatureC: number; - temperatureF: number; - summary: string; -} - -function App() { - const [forecasts, setForecasts] = useState(); - - useEffect(() => { - populateWeatherData(); - }, []); - - const contents = forecasts === undefined - ?

Loading... Please refresh once the ASP.NET backend has started. See https://aka.ms/jspsintegrationreact for more details.

- : - - - - - - - - - - {forecasts.map(forecast => - - - - - - - )} - -
DateTemp. (C)Temp. (F)Summary
{forecast.date}{forecast.temperatureC}{forecast.temperatureF}{forecast.summary}
; - - return ( -
-

Weather forecast

-

This component demonstrates fetching data from the server.

- {contents} -
- ); - - async function populateWeatherData() { - const response = await fetch('weatherforecast'); - if (response.ok) { - const data = await response.json(); - setForecasts(data); - } - } -} - -export default App; \ No newline at end of file diff --git a/surge365.massemailreact.client/src/components/layouts/Layout.tsx b/surge365.massemailreact.client/src/components/layouts/Layout.tsx new file mode 100644 index 0000000..386e4b1 --- /dev/null +++ b/surge365.massemailreact.client/src/components/layouts/Layout.tsx @@ -0,0 +1,120 @@ +import { ReactNode } from 'react'; +//import React from 'react'; +import PropTypes from 'prop-types'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'admin-lte/dist/css/adminlte.min.css'; +import 'font-awesome/css/font-awesome.min.css'; +/*import 'ionicons/dist/css/ionicons.min.css';*/ + +import 'bootstrap/dist/js/bootstrap.bundle.min.js'; // Bootstrap JS +import 'admin-lte/dist/js/adminlte.min.js'; + +const Layout = function Layout({ children }: { children: ReactNode }) { + return ( +
+ + + + + + {/*AdminLTE?*/} + + {/**/} + {/**/} + + + + {/**/} + {/**/} + {/**/} + {/**/} +
+ + + Logo + + + Logo + USAHaulers + + + +
+ + + + {children} + +
+
+ Version 1.0.0 +
+ Copyright © 2024 Surge365. All rights reserved. +
+
+ ); +} + +Layout.propTypes = { + children: PropTypes.any +}; + +export default Layout; diff --git a/surge365.massemailreact.client/src/components/layouts/LayoutLogin.tsx b/surge365.massemailreact.client/src/components/layouts/LayoutLogin.tsx new file mode 100644 index 0000000..34f2f6a --- /dev/null +++ b/surge365.massemailreact.client/src/components/layouts/LayoutLogin.tsx @@ -0,0 +1,29 @@ +import { ReactNode } from 'react'; +//import { useEffect } from 'react'; + +import PropTypes from 'prop-types'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'admin-lte/dist/css/adminlte.min.css'; +import 'font-awesome/css/font-awesome.min.css'; +/*import 'ionicons/dist/css/ionicons.min.css';*/ + +import '@/css/adminlte-custom.css'; +import '@/css/surge365.css'; + + +import 'bootstrap/dist/js/bootstrap.bundle.min.js'; // Bootstrap JS +import 'admin-lte/dist/js/adminlte.min.js'; +import 'admin-lte/dist/js/adminlte.min.js'; + + + +const LayoutLogin = function LayoutLogin({ children }: { children: ReactNode }) { + return children; +} + +LayoutLogin.propTypes = { + children: PropTypes.any +}; + +export default LayoutLogin; diff --git a/surge365.massemailreact.client/src/components/layouts/LayoutLogin_Backup.tsx b/surge365.massemailreact.client/src/components/layouts/LayoutLogin_Backup.tsx new file mode 100644 index 0000000..7fe9c3e --- /dev/null +++ b/surge365.massemailreact.client/src/components/layouts/LayoutLogin_Backup.tsx @@ -0,0 +1,37 @@ +import { ReactNode } from 'react'; +//import { useEffect } from 'react'; +import { Helmet, HelmetProvider } from 'react-helmet-async'; + +import PropTypes from 'prop-types'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'admin-lte/dist/css/adminlte.min.css'; +import 'font-awesome/css/font-awesome.min.css'; +/*import 'ionicons/dist/css/ionicons.min.css';*/ + +import '@/css/adminlte-custom.css'; +import '@/css/surge365.css'; + + +import 'bootstrap/dist/js/bootstrap.bundle.min.js'; // Bootstrap JS +import 'admin-lte/dist/js/adminlte.min.js'; +import 'admin-lte/dist/js/adminlte.min.js'; + + + +const LayoutLogin = function LayoutLogin({ children }: { children: ReactNode }) { + + return ( + + + + {children} + + ); +} + +LayoutLogin.propTypes = { + children: PropTypes.any +}; + +export default LayoutLogin; diff --git a/surge365.massemailreact.client/src/components/layouts/Layout_backup.tsx b/surge365.massemailreact.client/src/components/layouts/Layout_backup.tsx new file mode 100644 index 0000000..13865ad --- /dev/null +++ b/surge365.massemailreact.client/src/components/layouts/Layout_backup.tsx @@ -0,0 +1,125 @@ +import { ReactNode } from 'react'; +//import React from 'react'; +import { Helmet, HelmetProvider } from 'react-helmet-async'; +import PropTypes from 'prop-types'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'admin-lte/dist/css/adminlte.min.css'; +import 'font-awesome/css/font-awesome.min.css'; +/*import 'ionicons/dist/css/ionicons.min.css';*/ + +import 'bootstrap/dist/js/bootstrap.bundle.min.js'; // Bootstrap JS +import 'admin-lte/dist/js/adminlte.min.js'; + +const Layout = function Layout({ children }: { children: ReactNode }) { + return ( + +
+ + + + + + + {/*AdminLTE?*/} + + {/**/} + {/**/} + + + + {/**/} + {/**/} + {/**/} + {/**/} + +
+ + + Logo + + + Logo + USAHaulers + + + +
+ + + + {children} + +
+
+ Version 1.0.0 +
+ Copyright © 2024 Surge365. All rights reserved. +
+
+
+ ); +} + +Layout.propTypes = { + children: PropTypes.any +}; + +export default Layout; diff --git a/surge365.massemailreact.client/src/components/modals/ForgotPasswordModal.tsx b/surge365.massemailreact.client/src/components/modals/ForgotPasswordModal.tsx new file mode 100644 index 0000000..7dd1c33 --- /dev/null +++ b/surge365.massemailreact.client/src/components/modals/ForgotPasswordModal.tsx @@ -0,0 +1,137 @@ +import { useState } from 'react'; +import { Modal, Button, Form } from 'react-bootstrap'; +import { FaExclamationCircle } from 'react-icons/fa'; // For optional font icon +import PropTypes from 'prop-types'; + +import utils from '@/ts/utils.ts' + +const ForgotPasswordModal = ({ show, onClose }) => { + const [email, setEmail] = useState(''); + const [formErrors, setFormErrors] = useState({}); + const [emailNotFound, setEmailNotFound] = useState(false); + const [recoveryStarted, setRecoveryStarted] = useState(false); + + const validate = () => { + setFormErrors({}); + + const errors = {}; + if (!email.trim()) { + errors.email = 'Email is required'; + } else if (!/\S+@\S+\.\S+/.test(email)) { + errors.email = 'Invalid email address'; + } + + if (Object.keys(errors).length > 0) { + setFormErrors(errors); + return false; + } else { + return true; + } + } + + const handleStartPasswordRecovery = async (e) => { + e.preventDefault(); + setEmailNotFound(false); + + if (validate()) { + console.log('Processing forgot password for', email); + await utils.webMethod({ + 'methodPage': 'UserMethods', + 'methodName': 'GeneratePasswordRecovery', + 'parameters': { "emailAddress": email }, + success: function (json) { + if (utils.getBoolean(json.success)) { + setRecoveryStarted(true); + } + else { + setEmailNotFound(true); + } + } + }); + } + }; + + /* + $("#btnResetPassword").click(function (e) { + $("#frmResetPassword").validator('validate'); + e.preventDefault(); + var isValid = !$('#frmResetPassword .has-error').length + + if (isValid) { + $.webMethod({ + 'methodPage': 'UserMethods', + 'methodName': 'ResetPasswordFromToken', + 'parameters': { "token": getParameterByName("guid"), "password": $("#txtNewPassword").val() }, + success: function (json) { + + if ($.getBoolean(json.success)) { + + //redirect user to Dashboard + location.href = "/vehicles"; + + } + else { + //error + + } + } + }); + } + + }) + */ + return ( + + + Forgot your password? + + + {emailNotFound && ( + An email has been sent to the address you provided. Please follow the instructions in the email in order to reset your password. + )} + {!recoveryStarted && ( +
+ + Enter your email address below and we'll send you instructions on how to reset your password... + Email Addresss + setEmail(e.target.value)} + required + autoFocus + size="lg" + /> + {/* Validation Icon */} + {formErrors.email && ( + + )} + {/* Validation Message */} + {formErrors.email} + + +
+ )} +
+ + + +
+ ); +}; + +ForgotPasswordModal.propTypes = { + show: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, +}; + +export default ForgotPasswordModal; diff --git a/surge365.massemailreact.client/src/components/pages/App.tsx b/surge365.massemailreact.client/src/components/pages/App.tsx new file mode 100644 index 0000000..b3948c0 --- /dev/null +++ b/surge365.massemailreact.client/src/components/pages/App.tsx @@ -0,0 +1,35 @@ +// App.tsx or main routing component +//import React from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; +import Layout from '@/components/layouts/Layout'; +import LayoutLogin from '@/components/layouts/LayoutLogin'; +import Vehicles from '@/components/pages/Vehicles'; +import Login from '@/components/pages/Login'; + +const App = () => { + return ( + + + } /> + + + + } + /> + + + + } + /> + + + ); +}; + +export default App; diff --git a/surge365.massemailreact.client/src/components/pages/Login.tsx b/surge365.massemailreact.client/src/components/pages/Login.tsx new file mode 100644 index 0000000..526f4b5 --- /dev/null +++ b/surge365.massemailreact.client/src/components/pages/Login.tsx @@ -0,0 +1,177 @@ +import { useState } from 'react'; +import { Button, Form, Spinner } from 'react-bootstrap'; +//import { Helmet, HelmetProvider } from 'react-helmet-async'; + +import utils from '@/ts/utils.ts'; +import ForgotPasswordModal from '@/components/modals/ForgotPasswordModal'; + +type SpinnerState = Record; +type FormErrors = Record; + +function Login() { + const [isLoading, setIsLoading] = useState(false); + const [spinners, setSpinnersState] = useState({}); + const [formErrors, setFormErrors] = useState({}); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [showForgotPasswordModal, setShowForgotPasswordModal] = useState(false); + const [user, setUser] = useState(null); + const [loginError, setLoginError] = useState(false); + + //const setSpinners = (newValues: Partial) => { + // setSpinnersState((prevSpinners) => ({ + // ...prevSpinners, + // ...newValues, + // })); + //}; + const setSpinners = (newValues: Partial) => { + setSpinnersState((prevSpinners) => { + const updatedSpinners: SpinnerState = { ...prevSpinners }; + for (const key in newValues) { + if (newValues[key] !== undefined) { + updatedSpinners[key] = newValues[key] as boolean; + } + } + return updatedSpinners; + }); + }; + + const handleCloseForgotPasswordModal = () => { + setShowForgotPasswordModal(false); + }; + + const handleEmailBlur = () => { + }; + + const validateLoginForm = () => { + setFormErrors({}); + + const errors: FormErrors = {}; + if (!email.trim()) { + errors.email = 'Email is required'; + } else if (!/\S+@\S+\.\S+/.test(email)) { + errors.email = 'Invalid email address'; + } + if (!password.trim()) { + errors.password = 'Password is required'; + } + + if (Object.keys(errors).length > 0) { + setFormErrors(errors); + } + }; + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + spinners.Login = true; + setSpinners(spinners); + + validateLoginForm(); + + if (Object.keys(formErrors).length > 0) return; + + setLoginError(false); + let loggedInUser: any = null; + + await utils.webMethod({ + methodPage: 'UserMethods', + methodName: 'AuthenticateUser', + parameters: { emailAddress: email, password: password }, + success: (json: any) => { + if (utils.getBoolean(json.success)) { + loggedInUser = json.data; + setUser(loggedInUser); + } else { + setLoginError(true); + setIsLoading(false); + spinners.Login = false; + } + } + }); + + if (loginError) { + return; + } + + if (loggedInUser == null) { + setLoginError(true); + setIsLoading(false); + spinners.Login = false; + setSpinners(spinners); + } else { + await finishUserLogin(loggedInUser); + } + }; + + const finishUserLogin = async (user: any) => { + setIsLoading(false); + spinners.Login = false; + spinners.LoginWithPasskey = false; + setSpinners(spinners); + + utils.localStorage("session_currentUser", user); + + const redirectUrl = utils.sessionStorage("redirect_url"); + if (redirectUrl) { + utils.sessionStorage("redirect_url", null); + document.location.href = redirectUrl; + } else { + document.location.href = '/vehicles'; + } + }; + + return ( +
+
+

surge365 - React

+
+
+

Please sign in

+
+ {loginError && ( + Login error + )} + + Email address + setEmail(e.target.value)} + onBlur={handleEmailBlur} + required + autoFocus + size="sm" + /> + {spinners.Username && } + + + + Password + setPassword(e.target.value)} + required + size="sm" + /> + + + + +
+
+ + +
+ ); +} + +export default Login; diff --git a/surge365.massemailreact.client/src/components/pages/Vehicles.tsx b/surge365.massemailreact.client/src/components/pages/Vehicles.tsx new file mode 100644 index 0000000..fa1e683 --- /dev/null +++ b/surge365.massemailreact.client/src/components/pages/Vehicles.tsx @@ -0,0 +1,370 @@ +//import { useState } from 'react'; + +function Vehicles() { + return (
+ + + + + +
+ + {/* Content Header (Page header) */} +
+ +

Vehicles + + + +

+ +
+
+ +
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ + + +
+ + +
+
+ +
+ + + +
+
+
+
+ + + + + +
+ +
+
+ +
+
+ +
+
+ + {/* Info boxes */} +
+
+ {/* Box Comment */} +
+ + {/* /.box-header */} +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
IDVINNamePriceStock #YearMakeModelExt. ColorInt. ColorSeatingTransmissionTitleFeatured
+ Image
ImagesViewEdit
+
+ {/* /.box-body */} +
+ {/* /.box */} +
+ {/* /.col */} +
+ {/* /.row */} +
+
+ +
+
+ +
+
+ +
+ +

Edit Patient

+
+
+
+
+
+ + +
+ + + +
+
+
+
+
+ + +
+ + + +
+
+
+
+
+ + +
+ + + +
+
+
+
+
+ + +
+ + +
+
+
+
+
+ + +
+ + +
+
+
+
+
+ + +
+ + +
+
+
+
+
+ + +
+ + +
+
+
+
+
+ + +
+ + + +
+
+
+
+
+ + +
+ + + +
+
+
+
+
+ + +
+ + + +
+
+
+
+
+ + +
+ + + +
+
+
+
+
+ +
+ + +
+
+
+
+
+ +
+ Drag and drop images here or click to select + +
+ +
+
+
+ + + +
+ + + + +
+
+ +
+ + + + +
+
+
+
+ + {/* /.modal-content */} +
+ {/* /.modal-dialog */} +
+ + + +
+
+
+
+
+ +

Upload new document?

+
+
+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+ {/* /.modal-content */} +
+ {/* /.modal-dialog */} +
+ ); + //return content; +} + +export default Vehicles; diff --git a/surge365.massemailreact.client/src/components/pages/main.tsx b/surge365.massemailreact.client/src/components/pages/main.tsx new file mode 100644 index 0000000..f1e1809 --- /dev/null +++ b/surge365.massemailreact.client/src/components/pages/main.tsx @@ -0,0 +1,11 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import '@/css/main.css' +import App from '@/components/pages/App.tsx' +import '@/config/constants'; + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/surge365.massemailreact.client/src/config/constants.js b/surge365.massemailreact.client/src/config/constants.js new file mode 100644 index 0000000..5574ef7 --- /dev/null +++ b/surge365.massemailreact.client/src/config/constants.js @@ -0,0 +1,12 @@ +// src/config/constants.js + +// Set up constants from environment variables +export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; + +// Add more constants as needed, all prefixed by 'VITE_' for compatibility + + +// Add global values to window object for use in static JS +if (typeof window !== 'undefined') { + window.API_BASE_URL = API_BASE_URL; +} diff --git a/surge365.massemailreact.client/src/css/Vehicles.css b/surge365.massemailreact.client/src/css/Vehicles.css new file mode 100644 index 0000000..59ad7a2 --- /dev/null +++ b/surge365.massemailreact.client/src/css/Vehicles.css @@ -0,0 +1,78 @@ +.has-feedback .form-control { + padding-right: 30.5px; +} + +.notesStyle { + text-overflow: ellipsis; + overflow: hidden; + max-width: 200px; + min-width: 200px; + white-space: nowrap; +} + +.table > thead > tr > th, .table > tbody > tr > th, .table > tfoot > tr > th, .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td { + padding: 3px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} + +.dataTables_wrapper { + padding: 20px; +} + + .dataTables_wrapper .sorting_icon { + width: 0px; + } + +.no-sort { + padding-right: 3px !important; +} + +.drop-zone { + width: 300px; + height: 200px; + border: 2px dashed #ccc; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + margin: 20px auto; + cursor: pointer; +} + + .drop-zone.dragover { + background-color: #f0f0f0; + } + +.preview { + display: flex; + flex-wrap: wrap; + margin-top: 20px; +} + + .preview div { + position: relative; + margin: 10px; + } + + .preview img { + max-width: 100px; + display: block; + } + + .preview button { + position: absolute; + top: 0; + right: 0; + background: red; + color: white; + border: none; + border-radius: 50%; + cursor: pointer; + } + +.form-group { + margin-bottom: 5px; +} \ No newline at end of file diff --git a/surge365.massemailreact.client/src/css/adminlte-custom.css b/surge365.massemailreact.client/src/css/adminlte-custom.css new file mode 100644 index 0000000..e407cfc --- /dev/null +++ b/surge365.massemailreact.client/src/css/adminlte-custom.css @@ -0,0 +1,107 @@ +/* Custom CSS to restore old AdminLTE color classes in AdminLTE 4.x */ + +/* Orange Background */ +.bg-orange { + background-color: #ff851b !important; + color: #ffffff; +} + +/* Blue Background */ +.bg-blue { + background-color: #0073b7 !important; + color: #ffffff; +} + +/* Green Background */ +.bg-green { + background-color: #00a65a !important; + color: #ffffff; +} + +/* Red Background */ +.bg-red { + background-color: #dd4b39 !important; + color: #ffffff; +} + +/* Yellow Background */ +.bg-yellow { + background-color: #f39c12 !important; + color: #ffffff; +} + +/* Purple Background */ +.bg-purple { + background-color: #605ca8 !important; + color: #ffffff; +} + +/* Light Blue Background */ +.bg-light-blue { + background-color: #3c8dbc !important; + color: #ffffff; +} + +/* Navy Background */ +.bg-navy { + background-color: #001f3f !important; + color: #ffffff; +} + +/* Teal Background */ +.bg-teal { + background-color: #39cccc !important; + color: #ffffff; +} + +/* Maroon Background */ +.bg-maroon { + background-color: #d81b60 !important; + color: #ffffff; +} + +/* Black Background */ +.bg-black { + background-color: #111 !important; + color: #ffffff; +} + +/* Olive Background */ +.bg-olive { + background-color: #3d9970 !important; + color: #ffffff; +} + +/* Lime Background */ +.bg-lime { + background-color: #01ff70 !important; + color: #ffffff; +} + +/* Fuchsia Background */ +.bg-fuchsia { + background-color: #f012be !important; + color: #ffffff; +} + +/* Aqua Background */ +.bg-aqua { + background-color: #00c0ef !important; + color: #ffffff; +} + +/* Gray Background */ +.bg-gray { + background-color: #d2d6de !important; + color: #ffffff; +} + +/* Flat Button */ +.btn-flat { + box-shadow: none; + border-radius: 0; + border: none; + padding: 10px 15px; + font-size: 14px; + transition: background-color 0.3s ease; +} diff --git a/surge365.massemailreact.client/src/css/main.css b/surge365.massemailreact.client/src/css/main.css new file mode 100644 index 0000000..e69de29 diff --git a/surge365.massemailreact.client/src/css/surge365.css b/surge365.massemailreact.client/src/css/surge365.css new file mode 100644 index 0000000..1c7569e --- /dev/null +++ b/surge365.massemailreact.client/src/css/surge365.css @@ -0,0 +1,38 @@ +body { +} + + +.form-signin { + max-width: 330px; + padding: 15px; + margin: 0 auto; +} +.form-signin .form-signin-heading, +.form-signin .checkbox { + margin-bottom: 10px; +} +.form-signin .checkbox { + font-weight: normal; +} +.form-signin .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; + font-size: 16px; +} +.form-signin .form-control:focus { + z-index: 2; +} +.form-signin input[type="email"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} diff --git a/surge365.massemailreact.client/src/index.css b/surge365.massemailreact.client/src/index.css deleted file mode 100644 index 6119ad9..0000000 --- a/surge365.massemailreact.client/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/surge365.massemailreact.client/src/main.tsx b/surge365.massemailreact.client/src/main.tsx deleted file mode 100644 index bef5202..0000000 --- a/surge365.massemailreact.client/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' - -createRoot(document.getElementById('root')!).render( - - - , -) diff --git a/surge365.massemailreact.client/src/ts/utils.ts b/surge365.massemailreact.client/src/ts/utils.ts new file mode 100644 index 0000000..f739ca2 --- /dev/null +++ b/surge365.massemailreact.client/src/ts/utils.ts @@ -0,0 +1,156 @@ +const utils = { + getCookie: (name: string): string | null => { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop()?.split(';').shift() ?? null; + return null; + }, + getParameterByName: (name: string): string | null => { + const regex = new RegExp(`[\?&]${name}=([^&#]*)`); + const results = regex.exec(window.location.search); + return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null; + }, + addAuthHeaders: (headers: Record = {}): Record => { + const authToken = utils.getCookie('Auth-Token'); + if (authToken) { + headers['Auth-Token'] = authToken; + } + + const impersonateGuid = utils.getParameterByName("impersonateid") || sessionStorage.getItem('Auth-Impersonate-Guid'); + if (impersonateGuid) { + sessionStorage.setItem('Auth-Impersonate-Guid', impersonateGuid); + headers['Auth-Impersonate-Guid'] = impersonateGuid; + } + + const franchiseCode = sessionStorage.getItem('franchiseCode'); + if (franchiseCode) { + headers['Auth-Current-Franchise'] = franchiseCode; + } + + return headers; + }, + webMethod: async ({ + httpMethod = 'POST', + baseMethodPath = 'api/', + methodPage = '', + methodName = '', + parameters = {}, + contentType = 'application/json;', + timeout = 300000, + success = () => { }, + error = () => { }, + }: { + httpMethod?: string; + baseMethodPath?: string; + methodPage?: string; + methodName?: string; + parameters?: Record; + contentType?: string; + timeout?: number; + success?: (data: any) => void; + error?: (err: any) => void; + }): Promise => { + try { + const baseUrl = window.API_BASE_URL || ''; + const url = `${baseUrl.replace(/\/$/, '')}/${baseMethodPath.replace(/\/$/, '')}/${methodPage}${methodName ? '/' + methodName : ''}`; + + const headers = utils.addAuthHeaders({ + 'Content-Type': contentType, + }); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + + const response = await fetch(url, { + method: httpMethod, + headers, + body: JSON.stringify(parameters), + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const authToken = response.headers.get('Auth-Token'); + const loggedIn = response.headers.get('usahl_logged_in') === 'true'; + + const expires = loggedIn ? 365 : 14; + document.cookie = `Auth-Token=${authToken};path=/;max-age=${expires * 24 * 60 * 60}`; + document.cookie = `usahl_logged_in=${loggedIn};path=/;max-age=${expires * 24 * 60 * 60}`; + + const data = await response.json(); + success(data); + } catch (err) { + if ((err as Error).name === 'AbortError') { + console.error('Request timed out'); + } + error(err); + } + }, + getBoolean: (variable: any): boolean => { + if (variable != null) { + switch (typeof variable) { + case 'boolean': + return variable; + case 'number': + return variable !== 0; + case 'string': + return /^(true|yes)$/i.test(variable) || variable.length > 0; + } + } + return false; + }, + isLoggedIn: (): boolean => { + return utils.getBoolean(utils.getCookie('usahl_logged_in')); + }, + sessionStorage: (key: string, value?: any): any => { + if (value === undefined) { + let val = window.sessionStorage.getItem(key); + if (val && val.startsWith('usahl_json:')) { + val = val.substring(11); + return JSON.parse(val); + } + return val; + } else { + const val = typeof value === 'object' ? `usahl_json:${JSON.stringify(value)}` : value; + window.sessionStorage.setItem(key, val); + } + }, + sessionStorageClear: (): void => { + window.sessionStorage.clear(); + }, + sessionStorageRemove: (key: string): void => { + window.sessionStorage.removeItem(key); + }, + localStorage: (key: string, value?: any): any => { + if (value === undefined) { + let val = window.localStorage.getItem(key); + if (val && val.startsWith('usahl_json:')) { + val = val.substring(11); + return JSON.parse(val); + } + return val; + } else { + const val = typeof value === 'object' ? `usahl_json:${JSON.stringify(value)}` : value; + window.localStorage.setItem(key, val); + } + }, + localStorageClear: (): void => { + window.localStorage.clear(); + }, + localStorageRemove: (key: string): void => { + window.localStorage.removeItem(key); + } +}; + +declare global { + interface Window { + utils: object + } +} +window.utils = utils; + +export default utils; diff --git a/surge365.massemailreact.client/surge365.massemailreact.client.esproj b/surge365.massemailreact.client/surge365.massemailreact.client.esproj index ed6708f..727f12a 100644 --- a/surge365.massemailreact.client/surge365.massemailreact.client.esproj +++ b/surge365.massemailreact.client/surge365.massemailreact.client.esproj @@ -8,4 +8,11 @@ $(MSBuildProjectDirectory)\dist + + + + + + + \ No newline at end of file diff --git a/surge365.massemailreact.client/tsconfig.json b/surge365.massemailreact.client/tsconfig.json index 1ffef60..633ff2b 100644 --- a/surge365.massemailreact.client/tsconfig.json +++ b/surge365.massemailreact.client/tsconfig.json @@ -3,5 +3,11 @@ "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } - ] + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": [ "./src/*" ] + } + } }