From 3461d3159eab2f98439c42645174a0801c06021b Mon Sep 17 00:00:00 2001 From: npalOmega Date: Sun, 11 Jan 2026 17:35:33 -0600 Subject: [PATCH] adding first commit --- package-lock.json | 991 +++++++++++++++++++- package.json | 8 + src/App.js | 38 +- src/Site.Layout.js | 45 + src/apiUtil.js | 108 +++ src/components/ContactTable.js | 59 ++ src/components/Contacts.js | 44 + src/components/Sidebar.js | 157 ++++ src/components/Topbar.js | 20 + src/constants.js | 30 + src/data/contacts.js | 9 + src/index.css | 143 ++- src/index.html | 1 + src/index.js | 21 +- src/pages/ContactsPage.js | 74 ++ src/pages/contacts/edit/api.js | 4 + src/pages/contacts/form/ContactFormPage.css | 180 ++++ src/pages/contacts/form/ContactFormPage.js | 293 ++++++ src/pages/contacts/list/Delete.js | 22 + src/pages/contacts/list/Form.js | 0 src/pages/contacts/list/List.js | 63 ++ src/pages/contacts/list/Table.js | 37 + src/pages/contacts/list/api.js | 27 + src/pages/contacts/list/styles.css | 6 + src/pages/contacts/list/useItems.js | 31 + src/pages/contacts/new/api.js | 4 + src/setupProxy.js | 12 + src/site.css | 7 + src/tokenUtility.js | 32 + 29 files changed, 2399 insertions(+), 67 deletions(-) create mode 100644 src/Site.Layout.js create mode 100644 src/apiUtil.js create mode 100644 src/components/ContactTable.js create mode 100644 src/components/Contacts.js create mode 100644 src/components/Sidebar.js create mode 100644 src/components/Topbar.js create mode 100644 src/constants.js create mode 100644 src/data/contacts.js create mode 100644 src/index.html create mode 100644 src/pages/ContactsPage.js create mode 100644 src/pages/contacts/edit/api.js create mode 100644 src/pages/contacts/form/ContactFormPage.css create mode 100644 src/pages/contacts/form/ContactFormPage.js create mode 100644 src/pages/contacts/list/Delete.js create mode 100644 src/pages/contacts/list/Form.js create mode 100644 src/pages/contacts/list/List.js create mode 100644 src/pages/contacts/list/Table.js create mode 100644 src/pages/contacts/list/api.js create mode 100644 src/pages/contacts/list/styles.css create mode 100644 src/pages/contacts/list/useItems.js create mode 100644 src/pages/contacts/new/api.js create mode 100644 src/setupProxy.js create mode 100644 src/site.css create mode 100644 src/tokenUtility.js diff --git a/package-lock.json b/package-lock.json index 617f548b..bfc315ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,21 @@ "name": "seahaven-new-app", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.1", + "@mui/material": "^7.3.1", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.6.4", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "http-proxy-middleware": "^3.0.5", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", + "react-router-dom": "^7.9.4", "react-scripts": "5.0.1", + "react-toastify": "^11.0.5", "web-vitals": "^2.1.4" } }, @@ -2226,6 +2234,152 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -2758,6 +2912,241 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.1.tgz", + "integrity": "sha512-+mIK1Z0BhOaQ0vCgOkT1mSrIpEHLo338h4/duuL4TBLXPvUMit732mnwJY3W40Avy30HdeSfwUAAGRkKmwRaEQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.1.tgz", + "integrity": "sha512-upzCtG6awpL6noEZlJ5Z01khZ9VnLNLaj7tb6iPbN6G97eYfUTs8e9OyPKy3rEms3VQWmVBfri7jzeaRxdFIzA==", + "dependencies": { + "@babel/runtime": "^7.28.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.1.tgz", + "integrity": "sha512-Xf6Shbo03YmcBedZMwSpEFOwpYDtU7tC+rhAHTrA9FHk0FpsDqiQ9jUa1j/9s3HLs7KWb5mDcGnlwdh9Q9KAag==", + "dependencies": { + "@babel/runtime": "^7.28.2", + "@mui/core-downloads-tracker": "^7.3.1", + "@mui/system": "^7.3.1", + "@mui/types": "^7.4.5", + "@mui/utils": "^7.3.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==" + }, + "node_modules/@mui/private-theming": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.1.tgz", + "integrity": "sha512-WU3YLkKXii/x8ZEKnrLKsPwplCVE11yZxUvlaaZSIzCcI3x2OdFC8eMlNy74hVeUsYQvzzX1Es/k4ARPlFvpPQ==", + "dependencies": { + "@babel/runtime": "^7.28.2", + "@mui/utils": "^7.3.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.1.tgz", + "integrity": "sha512-Nqo6OHjvJpXJ1+9TekTE//+8RybgPQUKwns2Lh0sq+8rJOUSUKS3KALv4InSOdHhIM9Mdi8/L7LTF1/Ky6D6TQ==", + "dependencies": { + "@babel/runtime": "^7.28.2", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.1.tgz", + "integrity": "sha512-mIidecvcNVpNJMdPDmCeoSL5zshKBbYPcphjuh6ZMjhybhqhZ4mX6k9zmIWh6XOXcqRQMg5KrcjnO0QstrNj3w==", + "dependencies": { + "@babel/runtime": "^7.28.2", + "@mui/private-theming": "^7.3.1", + "@mui/styled-engine": "^7.3.1", + "@mui/types": "^7.4.5", + "@mui/utils": "^7.3.1", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.5.tgz", + "integrity": "sha512-ZPwlAOE3e8C0piCKbaabwrqZbW4QvWz0uapVPWya7fYj6PeDkl5sSJmomT7wjOcZGPB48G/a6Ubidqreptxz4g==", + "dependencies": { + "@babel/runtime": "^7.28.2" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.1.tgz", + "integrity": "sha512-/31y4wZqVWa0jzMnzo6JPjxwP6xXy4P3+iLbosFg/mJQowL1KIou0LC+lquWW60FKVbKz5ZUWBg2H3jausa0pw==", + "dependencies": { + "@babel/runtime": "^7.28.2", + "@mui/types": "^7.4.5", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==" + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -2874,6 +3263,15 @@ } } }, + "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==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3524,6 +3922,11 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==" + }, "node_modules/@types/q": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", @@ -3539,6 +3942,23 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "node_modules/@types/react": { + "version": "19.1.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", + "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", + "peer": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "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==", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -5220,6 +5640,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5888,6 +6316,11 @@ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -6183,6 +6616,15 @@ "utila": "~0.4" } }, + "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==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -7523,6 +7965,11 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -8161,6 +8608,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/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==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -8364,26 +8824,19 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", "dependencies": { - "@types/http-proxy": "^1.17.8", + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/https-proxy-agent": { @@ -8851,6 +9304,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -12849,6 +13310,14 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz", "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==" }, + "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==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -12862,6 +13331,50 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz", + "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.4.tgz", + "integrity": "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==", + "dependencies": { + "react-router": "7.9.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -12934,6 +13447,33 @@ } } }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "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==", + "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/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -13699,6 +14239,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -14388,6 +14933,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -15512,6 +16062,29 @@ } } }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, "node_modules/webpack-dev-server/node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -17584,6 +18157,132 @@ "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", "requires": {} }, + "@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + }, + "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "requires": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "requires": { + "@emotion/memoize": "^0.9.0" + } + }, + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "requires": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + } + }, + "@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -17986,6 +18685,111 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, + "@mui/core-downloads-tracker": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.1.tgz", + "integrity": "sha512-+mIK1Z0BhOaQ0vCgOkT1mSrIpEHLo338h4/duuL4TBLXPvUMit732mnwJY3W40Avy30HdeSfwUAAGRkKmwRaEQ==" + }, + "@mui/icons-material": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.1.tgz", + "integrity": "sha512-upzCtG6awpL6noEZlJ5Z01khZ9VnLNLaj7tb6iPbN6G97eYfUTs8e9OyPKy3rEms3VQWmVBfri7jzeaRxdFIzA==", + "requires": { + "@babel/runtime": "^7.28.2" + } + }, + "@mui/material": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.1.tgz", + "integrity": "sha512-Xf6Shbo03YmcBedZMwSpEFOwpYDtU7tC+rhAHTrA9FHk0FpsDqiQ9jUa1j/9s3HLs7KWb5mDcGnlwdh9Q9KAag==", + "requires": { + "@babel/runtime": "^7.28.2", + "@mui/core-downloads-tracker": "^7.3.1", + "@mui/system": "^7.3.1", + "@mui/types": "^7.4.5", + "@mui/utils": "^7.3.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.1", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==" + } + } + }, + "@mui/private-theming": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.1.tgz", + "integrity": "sha512-WU3YLkKXii/x8ZEKnrLKsPwplCVE11yZxUvlaaZSIzCcI3x2OdFC8eMlNy74hVeUsYQvzzX1Es/k4ARPlFvpPQ==", + "requires": { + "@babel/runtime": "^7.28.2", + "@mui/utils": "^7.3.1", + "prop-types": "^15.8.1" + } + }, + "@mui/styled-engine": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.1.tgz", + "integrity": "sha512-Nqo6OHjvJpXJ1+9TekTE//+8RybgPQUKwns2Lh0sq+8rJOUSUKS3KALv4InSOdHhIM9Mdi8/L7LTF1/Ky6D6TQ==", + "requires": { + "@babel/runtime": "^7.28.2", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + } + }, + "@mui/system": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.1.tgz", + "integrity": "sha512-mIidecvcNVpNJMdPDmCeoSL5zshKBbYPcphjuh6ZMjhybhqhZ4mX6k9zmIWh6XOXcqRQMg5KrcjnO0QstrNj3w==", + "requires": { + "@babel/runtime": "^7.28.2", + "@mui/private-theming": "^7.3.1", + "@mui/styled-engine": "^7.3.1", + "@mui/types": "^7.4.5", + "@mui/utils": "^7.3.1", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + } + }, + "@mui/types": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.5.tgz", + "integrity": "sha512-ZPwlAOE3e8C0piCKbaabwrqZbW4QvWz0uapVPWya7fYj6PeDkl5sSJmomT7wjOcZGPB48G/a6Ubidqreptxz4g==", + "requires": { + "@babel/runtime": "^7.28.2" + } + }, + "@mui/utils": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.1.tgz", + "integrity": "sha512-/31y4wZqVWa0jzMnzo6JPjxwP6xXy4P3+iLbosFg/mJQowL1KIou0LC+lquWW60FKVbKz5ZUWBg2H3jausa0pw==", + "requires": { + "@babel/runtime": "^7.28.2", + "@mui/types": "^7.4.5", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.1" + }, + "dependencies": { + "react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==" + } + } + }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -18053,6 +18857,11 @@ "source-map": "^0.7.3" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -18546,6 +19355,11 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, + "@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==" + }, "@types/q": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", @@ -18561,6 +19375,21 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "@types/react": { + "version": "19.1.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", + "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", + "peer": true, + "requires": { + "csstype": "^3.0.2" + } + }, + "@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==", + "requires": {} + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -19776,6 +20605,11 @@ "wrap-ansi": "^7.0.0" } }, + "clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -20240,6 +21074,11 @@ } } }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -20452,6 +21291,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -21451,6 +22299,11 @@ "pkg-dir": "^4.1.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -21870,6 +22723,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "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==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -22018,15 +22886,16 @@ } }, "http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", "requires": { - "@types/http-proxy": "^1.17.8", + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" } }, "https-proxy-agent": { @@ -22324,6 +23193,11 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -25051,6 +25925,12 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz", "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==" }, + "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==", + "requires": {} + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -25061,6 +25941,30 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz", + "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==", + "requires": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "dependencies": { + "cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==" + } + } + }, + "react-router-dom": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.4.tgz", + "integrity": "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==", + "requires": { + "react-router": "7.9.4" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -25116,6 +26020,25 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "requires": { + "clsx": "^2.1.1" + } + }, + "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==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -25656,6 +26579,11 @@ "send": "0.19.0" } }, + "set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" + }, "set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -26164,6 +27092,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -27000,6 +27933,18 @@ "ws": "^8.13.0" }, "dependencies": { + "http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, "ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", diff --git a/package.json b/package.json index a2567625..db909acb 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,21 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.1", + "@mui/material": "^7.3.1", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.6.4", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "http-proxy-middleware": "^3.0.5", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", + "react-router-dom": "^7.9.4", "react-scripts": "5.0.1", + "react-toastify": "^11.0.5", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/src/App.js b/src/App.js index 37845757..a6d479d5 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,21 @@ -import logo from './logo.svg'; -import './App.css'; +import React from "react"; +import Sidebar from "./components/Sidebar"; +import Topbar from "./components/Topbar"; +import Contacts from "./components/Contacts"; +import ContactsList from './pages/contacts/list/List' +import ContactFormPage from './pages/contacts/form/ContactFormPage' -function App() { + +export default function App() { return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
+
+ +
+ +
+ +
+
); -} - -export default App; +} \ No newline at end of file diff --git a/src/Site.Layout.js b/src/Site.Layout.js new file mode 100644 index 00000000..3b0293d1 --- /dev/null +++ b/src/Site.Layout.js @@ -0,0 +1,45 @@ +import React from "react"; +import { Container } from "@mui/material"; +import { ThemeProvider, createTheme } from "@mui/material/styles"; +import "./site.css"; +import TopNav from "./components/TopNav"; +import LeftNav from "./components/LeftNav"; +import Main from "./components/Main"; + +const THEME = createTheme({ + typography: { + fontFamily: `"Dosis", "Nunito", sans-serif`, + fontSize: 14, + fontWeightLight: 300, + fontWeightRegular: 400, + fontWeightMedium: 500, + }, + palette: { + header: { + main: "#4D81B7", + contrastText: "header background", + }, + }, + flexBox: { + display: "flex", + }, +}); + +const Layout = (props) => { + const [open, setOpen] = React.useState(false); + + const handleDrawerOpen = () => { + setOpen(true); + }; + + const handleDrawerClose = () => { + setOpen(false); + }; + return ( + + + +
{props.children}
+
+)}; +export default Layout; diff --git a/src/apiUtil.js b/src/apiUtil.js new file mode 100644 index 00000000..6a6a1c24 --- /dev/null +++ b/src/apiUtil.js @@ -0,0 +1,108 @@ +import { API_ERROR_MESSAGE } from "./constants"; +import { getAccessToken, setToken } from "./tokenUtility"; + +const STANDARD_HEADERS = { + Accept: "application/json", + "Content-Type": "application/json", + pragma: "no-cache", + "cache-control": "no-cache", + Authorization: getAccessToken() +}; + +const STANDARD_HEADERS_FORM = { + accept: "application/json", + pragma: "no-cache", + "cache-control": "no-cache", + Authorization: getAccessToken(), +}; + +export const get = (url) => + fetch(url, { + headers: { ...STANDARD_HEADERS, Authorization: getAccessToken() }, + method: "GET", + }) + .then(handleHttpResponse) + .catch((ex) => { + throw new Error(ex.message || API_ERROR_MESSAGE); + }); + +export const post = (url, payload) => + fetch(url, { + headers: STANDARD_HEADERS, + method: "POST", + body: JSON.stringify(payload), + }) + .then(handleHttpResponse) + .catch((ex) => { + throw new Error(ex.message || API_ERROR_MESSAGE); + }); + + export const postFormBody = (url, payload) => { + const formData = new FormData(); + + for (const [key, value] of Object.entries(payload)) { + formData.append(key, value); + } + + return fetch(url, { + method: "POST", + headers: { + ...STANDARD_HEADERS_FORM, + }, + body: formData, + }) + .then(handleHttpResponse) + .catch((ex) => { + throw new Error(ex.message || API_ERROR_MESSAGE); + }); + }; + +export const put = (url, payload) => + fetch(url, { + headers: STANDARD_HEADERS, + method: "PUT", + body: JSON.stringify(payload), + }) + .then(handleHttpResponse) + .catch((ex) => { + throw new Error(ex.message || API_ERROR_MESSAGE); + }); + +export const putFormBody = (url, payload) => { + const formData = new FormData(); + + for (const [key, value] of Object.entries(payload)) { + formData.append(key, value); + } + + fetch(url, { + method: "PUT", + headers: { + ...STANDARD_HEADERS_FORM, + }, + body: formData, + }) + .then(handleHttpResponse) + .catch((ex) => { + throw new Error(ex.message || API_ERROR_MESSAGE); + }); + } +export const deleteItem = (url, payload) => + fetch(url, { + headers: STANDARD_HEADERS, + method: "Delete", + body: JSON.stringify(payload), + }) + .then(handleHttpResponse) + .catch((ex) => { + throw new Error(ex.message || API_ERROR_MESSAGE); + }); + +const handleHttpResponse = (response) => { + if (response.ok) + return response.json(); + else if (response.status === 401) { + throw new Error("You are not authorized to access this page."); + } + throw new Error(API_ERROR_MESSAGE); +}; diff --git a/src/components/ContactTable.js b/src/components/ContactTable.js new file mode 100644 index 00000000..4306cc8c --- /dev/null +++ b/src/components/ContactTable.js @@ -0,0 +1,59 @@ +import React from "react"; + +export default function ContactTable({rows}) { + return ( +
+ + + + + + + + + + + + {rows.map(r => ( + + + + + + + + ))} + +
NamePhone NumberEmailActions
+
+
{r.name.split(" ").map(n=>n[0]).slice(0,2).join("")}
+
+
{r.name}
+
Officetrax Demo Account
+
+
+
{r.phone}{r.email} + + +
+ +
+
+ Records per page: + +
+
+ Jump to Page: + + 1-6 of {rows.length} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/Contacts.js b/src/components/Contacts.js new file mode 100644 index 00000000..d950e148 --- /dev/null +++ b/src/components/Contacts.js @@ -0,0 +1,44 @@ +import React from "react"; +import ContactTable from "./ContactTable"; + +const sampleContacts = [ + {id:1, name:"Johan Doe", phone:"(575) 877-7777", email:"varucetyq@mailinator.com"}, + {id:2, name:"Adam Moussa", phone:"(631) 664-3950", email:"adam@seahavenind.com"}, + {id:3, name:"test", phone:"(123) 343-444554", email:"test@gmail.com"}, + {id:4, name:"Jane K Smith", phone:"(456) 789-8328", email:"jane@gmail.com"}, + {id:5, name:"Zohan Raley", phone:"(123) 567-7688", email:"zohan@gmail.com"}, + {id:6, name:"npaltest", phone:"9999999999", email:"amanmeutd46@gmail.com"}, +]; + +export default function Contacts(){ + return ( +
+
Contacts
+ +
+
+
+ +
+ + + +
+
+ +
+ + + + +
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js new file mode 100644 index 00000000..ba98f9e3 --- /dev/null +++ b/src/components/Sidebar.js @@ -0,0 +1,157 @@ +import React from "react"; +import { + FaTachometerAlt, + FaCalendarAlt, + FaFileAlt, + FaUserTie, + FaMoneyCheckAlt, + FaAddressBook, + FaChartBar, + FaTools, + FaBell, + FaClipboardList, + FaCubes, + FaMapMarkerAlt, +} from "react-icons/fa"; + +const navItems = [ + { label: "Dashboard", icon: }, + { label: "Calendar", icon: }, + { label: "Documents", icon: }, + { label: "Employees", icon: }, + { label: "Accounts", icon: }, + { label: "Contacts", icon: }, + { label: "Reports", icon: }, + { label: "Work Orders", icon: }, + { label: "Follow Ups", icon: }, + { label: "PM Schedules", icon: }, + { label: "Assets", icon: }, + { label: "Locations", icon: }, +]; + +export default function Sidebar() { + const [selected, setSelected] = React.useState("Contacts"); + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/Topbar.js b/src/components/Topbar.js new file mode 100644 index 00000000..f019d49a --- /dev/null +++ b/src/components/Topbar.js @@ -0,0 +1,20 @@ +import React from "react"; + +export default function Topbar(){ + return ( +
+
+ +
+ +
+
+ +
+
πŸ””1
+
Adam Moussa
+
AM
+
+
+ ); +} \ No newline at end of file diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 00000000..693a88d6 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,30 @@ +export const ADD = "add"; +export const EDIT = "edit"; + +export const API_ERROR_MESSAGE = + "Sorry, something went wrong. Please try again later."; +export const API_SUCCESS_MESSAGE = + "You changes has been successfully saved."; +export const API_DELETE_SUCCESS_MESSAGE = + "Item is deleted"; + +//export const API_URL = "http://console.seahavenind.com/api"; +export const API_URL = "https://localhost:7195/api"; + + +export const form_text_builder = (mode) => { + let PRIMARY_TEXT = ""; + let SECONDARY_TEXT = ""; + let BUTTON_TEXT = ""; + + if (mode == ADD) { + PRIMARY_TEXT = "Add an Item"; + SECONDARY_TEXT = "Add your new item below"; + BUTTON_TEXT = "Add Item"; + } else { + PRIMARY_TEXT = "Edit an Item"; + SECONDARY_TEXT = "Edit your item below"; + BUTTON_TEXT = "Save Item"; + } + return { PRIMARY_TEXT, SECONDARY_TEXT, BUTTON_TEXT }; +}; diff --git a/src/data/contacts.js b/src/data/contacts.js new file mode 100644 index 00000000..b70d2d73 --- /dev/null +++ b/src/data/contacts.js @@ -0,0 +1,9 @@ +const sampleContacts = [ + { id: 1, name: 'Johan Doe', phone: '(575) 877-7777', email: 'varucetyq@mailinator.com' }, + { id: 2, name: 'Adam Moussa', phone: '(631) 664-3950', email: 'adam@seahavenind.com' }, + { id: 3, name: 'test', phone: '(123) 343-444554', email: 'test@gmail.com' }, + { id: 4, name: 'Jane K Smith', phone: '(456) 789-8328', email: 'jane@gmail.com' }, + { id: 5, name: 'Zohan Raley', phone: '(123) 567-7688', email: 'zohan@gmail.com' }, + { id: 6, name: 'npaltest', phone: '9999999999', email: 'amanmeutd46@gmail.com' }, +]; +export default sampleContacts; \ No newline at end of file diff --git a/src/index.css b/src/index.css index ec2585e8..b437586a 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,136 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; +:root{ + --sidebar-w: 220px; + --topbar-h: 64px; + --blue:#0b4b6a; + --muted:#f2f4f6; } -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; +*{box-sizing:border-box} +html,body,#root{height:100%} +body{ + margin:0; + font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background:#f4f6f7; + color:#333; } + +/* Layout */ +.app{display:flex;min-height:100vh;align-items:stretch} +.sidebar{ + width:var(--sidebar-w); + background:#f1f3f4; + border-right:1px solid #e0e3e4; + padding:18px 12px; + position:fixed; + left:0;top:0;bottom:0; + overflow:auto; +} +.mainArea{ + margin-left:var(--sidebar-w); + flex:1; + min-height:100vh; + display:flex; + flex-direction:column; +} +.topbar{ + height:var(--topbar-h); + background:var(--blue); + color:white; + display:flex; + align-items:center; + justify-content:space-between; + padding:0 50px; + position:sticky; + top:0; + z-index:10; +} +.topbar .hamburger{background:transparent;border:none;color:white;font-size:20px;margin-right:12px} +.topbar .searchBox{background:#083a52; padding:6px 10px;border-radius:6px;min-width:420px;} +.topbar input{width:100%;border:none;padding:6px 8px;border-radius:4px;background:white;color:#333} +.topRight{display:flex;align-items:center;gap:12px} +.topRight .avatar{background:#1fa8de;color:white;width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:600} +.topRight .notif{position:relative} +.topRight .dot{position:absolute;right:-6px;top:-6px;background:#ffb400;color:#222;padding:2px 6px;border-radius:10px;font-size:11px} +.topLeft {display: flex;} +/* Sidebar content */ +.logoRow{display:flex;align-items:center;gap:8px;padding-bottom:16px} +.logoMark{width:48px;height:48px;background:linear-gradient(90deg,#60c6ff,#1fa8de);color:white;border-radius:6px;display:flex;align-items:center;justify-content:center;font-weight:800} +.logoText{font-size:14px;line-height:1} +.logoText span{font-size:11px;color:#556} +.navTitle{font-size:12px;color:#7b8588;margin:10px 6px} +.navList{list-style:none;padding:0;margin:0} +.navItem{display:flex;align-items:center;gap:10px;padding:10px;border-radius:6px;color:#394647;cursor:pointer} +.navItem .glyph{display:inline-block;width:22px;text-align:center;color:#8b9496} +.navItem:hover{background:#e9edf0} +.navItem.active{background:#e1edf4;border-left:3px solid #0b80b5} + +/* Content */ +.contentWrapper{padding:20px 40px;} + +/* Contacts page */ +.contactsPage .pageTitle{font-size:18px;font-weight:600;margin-bottom:10px} +.card.controlsCard{ + background:white; + border-radius:4px; + padding:18px; + box-shadow:0 1px 0 rgba(0,0,0,0.06); + border:1px solid #e6e9ea; +} + +/* controls */ +.controlsTop{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px} +.leftControls{display:flex;align-items:center;gap:12px} +.iconBtn{border:1px solid #d6d9da;background:#fff;padding:8px;border-radius:4px} +.filter{display:flex;gap:8px;align-items:center} +.filter select{padding:8px;border-radius:4px;border:1px solid #d8d8d8;background:#fff} +.filter input{padding:8px;border-radius:4px;border:1px solid #d8d8d8;min-width:360px} +.searchBtn{background:#0b95d4;color:#fff;border:none;padding:8px 12px;border-radius:4px} +.rightControls{display:flex;gap:8px;align-items:center} +.iconSmall{background:transparent;border:1px solid #e0e0e0;padding:6px;border-radius:6px;cursor:pointer} + +/* table */ +.tableWrap{margin-top:6px} +.contactTable{width:100%;border-collapse:collapse} +.contactTable thead th{ + text-align:left;padding:12px;background:#9fb2bf;color:white;font-weight:600;border-bottom:1px solid #d5d8d9; +} +.contactTable thead .colCheck{width:48px} +.contactTable tbody td{border-bottom:1px solid #eee;padding:14px 12px;vertical-align:middle} +.contactTable tbody tr:nth-child(odd){background:#fff} +.contactTable tbody tr:nth-child(even){background:#fbfbfb} +.rowName{display:flex;align-items:center;gap:12px} +.avatarThumb{width:40px;height:40px;border-radius:6px;background:#f0b380;border:1px solid #9b6b45;display:flex;align-items:center;justify-content:center;color:#4b2b18;font-weight:700} +.nameText .nameLink{color:#0b6fa1;font-weight:600} +.nameText .sub{font-size:12px;color:#8f9a9f} +.mono{font-family:monospace;color:#444} +.link{color:#0a8ab6} +.actionsCol{width:120px;text-align:right} + +/* footer of table */ +.tableFooter{display:flex;justify-content:space-between;align-items:center;margin-top:12px;padding:8px 6px;color:#666;font-size:13px} +.tableFooter select{margin-left:8px;padding:6px} + +/* rightPanel */ +.rightPanel{ + width:320px; + position:fixed; + right:0; + top:0; + bottom:0; + background:#222b34; + color:white; + border-left:1px solid rgba(255,255,255,0.04); + padding:18px; + overflow:auto; +} +.panelHeader{background:#623be3;padding:12px;border-radius:6px;text-align:center;margin-bottom:14px;font-weight:700;color:white} +.panelBody{padding:6px} +.panelInput{width:100%;padding:10px;border-radius:4px;border:none;background:#333;color:#ddd;margin-bottom:12px} +.panelRow{margin:10px 0} +.allianceList{margin-top:14px} +.allianceRow{display:flex;align-items:center;gap:10px;padding:8px;background:#2a3138;border-radius:6px;margin-bottom:10px;justify-content:space-between} +.pill{padding:6px 10px;border-radius:6px;color:white;font-weight:700} +.pill.blue{background:#6fb3e6} +.pill.purple{background:#7f63e3} +.pill.green{background:#2b9b59} +.numbers{color:#d5dbe0} \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 00000000..98499e9e --- /dev/null +++ b/src/index.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/index.js b/src/index.js index d563c0fb..1c6aa214 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,12 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router"; +import App from "./App"; +import "./index.css"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + - + ); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/pages/ContactsPage.js b/src/pages/ContactsPage.js new file mode 100644 index 00000000..26292383 --- /dev/null +++ b/src/pages/ContactsPage.js @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { + Paper, Typography, Box, Table, TableHead, TableRow, TableCell, + TableBody, TableContainer, IconButton, Checkbox, TablePagination, TextField, Button +} from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; + +import sampleContacts from '../data/contacts'; + +export default function ContactsPage() { + const [rows] = useState(sampleContacts); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [query, setQuery] = useState(''); + + const filtered = rows.filter(r => r.name.toLowerCase().includes(query.toLowerCase()) || r.email.toLowerCase().includes(query.toLowerCase())); + + return ( + + + Contacts + + + + + setQuery(e.target.value)} sx={{ flex: 1 }} /> + + + + + + + + + Name + Phone Number + Email + Actions + + + + + {filtered.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map(row => ( + + + {row.name} + {row.phone} + {row.email} + + + + + + ))} + {filtered.length === 0 && No contacts} + +
+
+ + setPage(p)} + rowsPerPage={rowsPerPage} + onRowsPerPageChange={(e) => { setRowsPerPage(parseInt(e.target.value, 10)); setPage(0); }} + rowsPerPageOptions={[5,10,25,50]} + sx={{ mt: 1 }} + /> +
+ ); +} \ No newline at end of file diff --git a/src/pages/contacts/edit/api.js b/src/pages/contacts/edit/api.js new file mode 100644 index 00000000..e71d8822 --- /dev/null +++ b/src/pages/contacts/edit/api.js @@ -0,0 +1,4 @@ +import {post } from "../../../apiUtil"; +import { API_URL } from '../../../constants'; + +export const editContact = (item) => post(`${API_URL}/Contact/EditContacts`, item) \ No newline at end of file diff --git a/src/pages/contacts/form/ContactFormPage.css b/src/pages/contacts/form/ContactFormPage.css new file mode 100644 index 00000000..790e2fc1 --- /dev/null +++ b/src/pages/contacts/form/ContactFormPage.css @@ -0,0 +1,180 @@ +/* Wrapper & card */ +.contactFormWrapper { + display: flex; + justify-content: center; + padding: 25px 20px; + background: #f5f6fa; + min-height: 100vh; + font-family: "Roboto", "Open Sans", Arial, sans-serif; +} + +.formCard { + background: #fff; + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0,0,0,0.08); + width: 100%; + max-width: 1100px; + padding: 20px 22px 26px; + border: 1px solid #e6e9ee; +} + +/* Header */ +.formHeader { + padding-bottom: 10px; + border-bottom: 1px solid #e9ecef; + margin-bottom: 16px; +} +.formHeader h2 { + margin: 0; + color: #1f3b4d; + font-size: 20px; + font-weight: 600; +} + +/* Section titles */ +.sectionTitle { + margin: 0 0 12px; + color: #2b2f3b; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.6px; + font-weight: 700; +} + +/* Account widget (assignment) */ +.account-widget { + display: flex; + gap: 14px; + align-items: center; + padding: 12px; + border-radius: 4px; + background: #fbfcfd; + border: 1px dashed #d6e6ef; + color: #0a4b6b; +} +.account-icon { + width: 48px; + height: 48px; + background: #e9f4fb; + color: #0077a8; + border-radius: 4px; + display:flex; + align-items:center; + justify-content:center; + font-size:20px; +} +.account-info .account-title { + font-weight:700; +} +.account-info .account-action { + color: #0b63a7; + font-weight:700; + margin-top:4px; +} +.account-sub { + color:#667085; + font-size:12px; + margin-top:4px; +} + +/* Form layout */ +.contactFormBody { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-gap: 12px 18px; + align-items: start; + margin-bottom: 6px; +} + +/* Form groups */ +.formGroup { + display: flex; + flex-direction: column; + margin-bottom: 8px; +} +.formGroup.fullWidth { + grid-column: 1 / -1; +} + +/* Labels and controls */ +.formLabel { + font-size: 13px; + font-weight: 600; + color: #253244; + margin-bottom: 6px; +} +.required { color: #d93025; margin-left: 4px; } + +.formControl { + display: block; + width: 100%; + height: 36px; + padding: 6px 10px; + font-size: 13px; + color: #1f2937; + background: #fff; + border: 1px solid #d6dbe4; + border-radius: 4px; + box-sizing: border-box; +} +.formControl:focus { + outline: none; + border-color: #0b78b0; + box-shadow: 0 0 0 4px rgba(11,120,176,0.08); +} + +/* Hints / muted */ +.muted { + color: #6b7280; + font-size: 13px; + margin-bottom: 8px; +} +.formHint { + font-size: 12px; + color: #6b7280; + margin-top: 6px; +} + +/* Form actions */ +.formActions { + display: flex; + justify-content: space-between; + align-items: center; + border-top: 1px solid #e9eef3; + padding-top: 14px; + margin-top: 14px; +} +.formNote { + color: #555; + font-size: 13px; +} +.btnPrimary { + background-color: #198754; + border: 1px solid #198754; + color: #fff; + padding: 10px 18px; + font-size: 14px; + border-radius: 4px; + cursor: pointer; +} +.btnPrimary:disabled { + background-color: #c9c9c9; + border-color: #c9c9c9; + cursor: not-allowed; +} + +/* Responsive adjustments */ +@media (max-width: 992px) { + .contactFormBody { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 640px) { + .contactFormBody { + grid-template-columns: 1fr; + } + .formCard { + padding: 16px; + } +} \ No newline at end of file diff --git a/src/pages/contacts/form/ContactFormPage.js b/src/pages/contacts/form/ContactFormPage.js new file mode 100644 index 00000000..d2971fd1 --- /dev/null +++ b/src/pages/contacts/form/ContactFormPage.js @@ -0,0 +1,293 @@ +import React, { useState } from "react"; +import { useNavigate, useParams } from "react-router"; +import { toast } from "react-toastify"; +import "./ContactFormPage.css"; +import { addContact } from "../new/api"; +import { editContact } from "../edit/api"; + +const ContactFormPage = () => { + const { id } = useParams(); + const navigate = useNavigate(); + const isEdit = !!id; + const [loading, setLoading] = useState(false); + const [contact, setContact] = useState({}); + + const handleChange = (e) => { + const { name, value, type, checked } = e.target; + setContact((prev) => ({ + ...prev, + [name]: type === "checkbox" ? checked : value, + })); + }; + +const handleSubmit = async (e) => { + e.preventDefault(); + + if (!contact.FirstName || !contact.LastName) { + toast.error("First and last name are required."); + return; + } + + try { + setLoading(true); + + // API call based on mode + const payload = { + ...contact, + Details: { + Gender: contact.Gender, + DateOfBirth: contact.DateOfBirth, + PrivacySetting: contact.PrivacySetting, + About: contact.About, + Tags: contact.Tags, + }, + }; + + if (isEdit) { + await editContact(payload); + toast.success("Contact updated successfully!"); + } else { + await addContact(payload); + toast.success("Contact created successfully!"); + } + + navigate("/contacts"); + } catch (err) { + console.error(err); + toast.error("Something went wrong while saving the contact."); + } finally { + setLoading(false); + } +}; + + + return ( +
+
+
+

{isEdit ? "Edit Contact" : "Create a Contact"}

+
+ +
+ {/* Assignment */} +
+
Assignment
+
+
πŸ”
+
+
No Account selected
+
Click here to search Accounts
+
Use this to identify the company the Contact works for, or is otherwise associated with
+
+
+
+ +
+ + {/* General Details */} +
+
General Details
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
Press Enter after each tag to complete it.
+
+
+
+ +
+ + {/* Address */} +
+
Address
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ + {/* Social Media */} +
+
Social Media
+

Enter the full URL of the Social Media profile you wish to link.

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ + {/* Submit */} +
+
All fields marked with * are required.
+ +
+
+
+
+ ); +}; + +export default ContactFormPage; \ No newline at end of file diff --git a/src/pages/contacts/list/Delete.js b/src/pages/contacts/list/Delete.js new file mode 100644 index 00000000..394ae1a9 --- /dev/null +++ b/src/pages/contacts/list/Delete.js @@ -0,0 +1,22 @@ +import React from "react"; +import { deleteContact } from "./api"; + +export default function DeleteModal({ open, item, onClose, onSuccess }) { + if(!open) return null; + const handleDelete=()=>{ deleteContact(item.id).then(()=>onSuccess()); }; + return ( +
+
+
+
Delete Item?
+
+
Are you sure you want to delete this contact?
+
+ + +
+
+
+
+ ); +} diff --git a/src/pages/contacts/list/Form.js b/src/pages/contacts/list/Form.js new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/contacts/list/List.js b/src/pages/contacts/list/List.js new file mode 100644 index 00000000..c6a589f6 --- /dev/null +++ b/src/pages/contacts/list/List.js @@ -0,0 +1,63 @@ +import React, { useState } from "react"; +import useItems from "./useItems"; +import Table from "./Table"; +import "./styles.css"; + +export default function ContactsList() { + const { rows, search, setSearch, page, setPage, pageSize, setPageSize, + totalCount, loading, err, reload } = useItems(); + + const [formState, setFormState] = useState({ open: false, item: null }); + const [delState, setDelState] = useState({ open: false, item: null }); + + const totalPages = Math.max(1, Math.ceil(totalCount / pageSize)); + const onSubmitSuccess = () => { setFormState({ open: false, item: null }); reload(); }; + const onDeleteSuccess = () => { setDelState({ open: false, item: null }); reload(); }; + + return ( +
+
Contacts
+
+
+
+
+ + setSearch(e.target.value)} placeholder="Enter search criteria here..." /> + +
+
+
+ +
+
+ setFormState({ open: true, item })} + onDelete={(item)=> setDelState({ open: true, item })} page={page} setPage={setPage} + pageSize={pageSize} setPageSize={setPageSize} totalCount={totalCount} totalPages={totalPages} /> + + {/* {formState.open && ( + setFormState({ open: false, item: null })} + onSuccess={onSubmitSuccess} + /> +)} */} + +{/* {delState.open && ( + setDelState({ open: false, item: null })} + onSuccess={onDeleteSuccess} + /> +)} */} + + ); +} diff --git a/src/pages/contacts/list/Table.js b/src/pages/contacts/list/Table.js new file mode 100644 index 00000000..bce57b8d --- /dev/null +++ b/src/pages/contacts/list/Table.js @@ -0,0 +1,37 @@ +import React from "react"; + +export default function Table({ loading, error, rows, onEdit, onDelete, + page, setPage, pageSize, setPageSize, totalCount, totalPages }) { + return ( +
+ {error &&
{error}
} +
+ + + + + + + + {loading ? : + rows.length === 0 ? : + rows.map(r => ( + + + + + + + + + + + ))} + +
NameAccountTitleEmailPhoneOwnerActions
Loading…
No records found.
{r.name}{r.account}{r.title}{r.email}{r.phoneNumber}{r.owner} + + +
+
+ ); +} diff --git a/src/pages/contacts/list/api.js b/src/pages/contacts/list/api.js new file mode 100644 index 00000000..69526405 --- /dev/null +++ b/src/pages/contacts/list/api.js @@ -0,0 +1,27 @@ +import { get, post } from "../../../apiUtil"; +import { API_URL } from '../../../constants'; + + +export const fetchContactsPaged = async ({ search = "", page = 1, pageSize = 12 }) => { + const res = await get(`${API_URL}/Contact/GetContactList?search=${encodeURIComponent(search)}&page=${page}&pageSize=${pageSize}`); + const rows = (res?.data || res?.Data || []).map(x => ({ + id: x.Id, + name: x.Name, + account: x.Account ?? "", + title: x.Title ?? "", + email: x.email ?? x.Email ?? "", + phoneNumber: x.phone ?? x.PhoneNumber ?? "", + owner: x.Owner ?? "", + })); + return { + rows, + page: res?.PageNumber ?? 1, + pageSize: res?.PageSize ?? pageSize, + totalCount: res?.TotalCount ?? rows.length, + totalPages: res?.TotalPages ?? 1, + }; +}; + + +export const deleteContact = (id) => + get(`/api/Contact/DeleteContacts?id=${id}`); diff --git a/src/pages/contacts/list/styles.css b/src/pages/contacts/list/styles.css new file mode 100644 index 00000000..801b23a9 --- /dev/null +++ b/src/pages/contacts/list/styles.css @@ -0,0 +1,6 @@ +/* CSS for modals, table, and controls – minimal to work with existing global styles */ +.contactTable { width:100%; border-collapse:collapse; } +.contactTable th, .contactTable td { border:1px solid #ddd; padding:8px; } +.modalRoot{position:fixed;inset:0;z-index:1000;} +.modalScrim{position:absolute;inset:0;background:rgba(0,0,0,.3);} +.modalCard{position:relative;margin:10% auto;padding:20px;background:#fff;width:400px;} diff --git a/src/pages/contacts/list/useItems.js b/src/pages/contacts/list/useItems.js new file mode 100644 index 00000000..ed8d1bf4 --- /dev/null +++ b/src/pages/contacts/list/useItems.js @@ -0,0 +1,31 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { fetchContactsPaged } from "./api"; + +export default function useItems() { + const [rows, setRows] = useState([]); + const [search, setSearch] = useState(""); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(12); + const [totalCount, setTotalCount] = useState(0); + const [loading, setLoading] = useState(true); + const [err, setErr] = useState(""); + + const reload = useCallback(() => { + setLoading(true); + setErr(""); + fetchContactsPaged({ search, page, pageSize }) + .then(({ rows, totalCount }) => { + setRows(rows); + setTotalCount(totalCount); + }) + .catch((e) => setErr(e.message || "Failed to load")) + .finally(() => setLoading(false)); + }, [search, page, pageSize]); + + useEffect(() => { reload(); }, [reload]); + + return useMemo(() => ({ + rows, search, setSearch, page, setPage, pageSize, setPageSize, + totalCount, loading, err, reload + }), [rows, search, page, pageSize, totalCount, loading, err, reload]); +} diff --git a/src/pages/contacts/new/api.js b/src/pages/contacts/new/api.js new file mode 100644 index 00000000..dd9639f8 --- /dev/null +++ b/src/pages/contacts/new/api.js @@ -0,0 +1,4 @@ +import {post } from "../../../apiUtil"; +import { API_URL } from '../../../constants'; + +export const addContact = (item) => post(`${API_URL}/Contact/AddContacts`, item); \ No newline at end of file diff --git a/src/setupProxy.js b/src/setupProxy.js new file mode 100644 index 00000000..12d35950 --- /dev/null +++ b/src/setupProxy.js @@ -0,0 +1,12 @@ +const { createProxyMiddleware } = require('http-proxy-middleware'); + +module.exports = function(app) { + app.use( + '/api', + createProxyMiddleware({ + //target: 'http://console.seahavenind.com', + target: 'https://localhost:7195', + changeOrigin: true, + }) + ); +}; \ No newline at end of file diff --git a/src/site.css b/src/site.css new file mode 100644 index 00000000..73717903 --- /dev/null +++ b/src/site.css @@ -0,0 +1,7 @@ +@import url("https://fonts.googleapis.com/css2?family=Dosis:wght@200;300;400;500;600&family=Nunito:ital,wght@0,200;0,300;0,400;0,600;1,200;1,300;1,400;1,600&display=swap"); + +* { + margin: 0; + padding: 0; + z-index: 0; +} \ No newline at end of file diff --git a/src/tokenUtility.js b/src/tokenUtility.js new file mode 100644 index 00000000..7d57c93f --- /dev/null +++ b/src/tokenUtility.js @@ -0,0 +1,32 @@ +const tokenKey = "token"; + +export const setUser = (user) => { + if (user && user.token) { + window.localStorage.setItem(tokenKey, JSON.stringify(user)); + } + else { + window.localStorage.removeItem(tokenKey); + } +} + +export const getAccessToken = () => { + let result = window.localStorage.getItem(tokenKey); + if (!result) { + return ""; + } + const user = JSON.parse(result); + return `Bearer ${user.token}`; +} + +export const getUser = () => { + let result = window.localStorage.getItem(tokenKey); + if (!result) { + return {}; + } + + return JSON.parse(result); +} + +export const removeToken = () => { + window.localStorage.removeItem(tokenKey); +} \ No newline at end of file