From 86b1eaf6f7cc6558273f691f9b54c9ad6af36873 Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Sat, 16 Aug 2025 12:57:07 -0500 Subject: [PATCH] big update --- ditch-the-agent/.env | 1 + ditch-the-agent/package-lock.json | 842 +++++++++++------- ditch-the-agent/package.json | 3 + ditch-the-agent/src/axiosApi.js | 167 ++-- .../src/components/CategoryGridTemplate.tsx | 29 + .../src/components/DasboardTemplate.tsx | 67 ++ .../src/components/FloatingChatButton.tsx | 310 ++++--- .../src/components/ItemListDetailTemplate.tsx | 78 ++ .../components/base/FormattedListingText.tsx | 21 + .../src/components/base/GeocodeComponent.tsx | 58 ++ .../src/components/base/LoadingSkeleton.tsx | 25 + .../src/components/base/MapComponent.tsx | 77 ++ .../components/base/MapSearchComponent.tsx | 165 ++++ .../dashboard/Home/Bids/AddBidDialog.tsx | 118 +++ .../sections/dashboard/Home/Bids/BidCard.tsx | 89 ++ .../dashboard/Home/Bids/VendorBidCard.tsx | 112 +++ .../dashboard/Home/Bids/VendorBids.tsx | 42 + .../Home/Dashboard/AttorneyDashboard.tsx | 175 ++++ .../Home/Dashboard/DashboardErrorPage.tsx | 41 + .../Home/Dashboard/DashboardLoading.tsx | 26 + .../Home/Dashboard/NotificationInfoCards.tsx | 55 ++ .../Home/Dashboard/PropertyOwnerDashboard.tsx | 302 +++++++ .../Dashboard/RealEstateAgentDashboard.tsx | 169 ++++ .../Home/Dashboard/VendorDashboard.tsx | 292 ++++++ .../dashboard/Home/Education/CategoryGrid.tsx | 24 + .../Home/Education/EducationInfo.tsx | 11 +- .../Home/Education/EducationTable.tsx | 75 ++ .../Home/Education/EducationVideoPlayer.tsx | 28 + .../Home/Education/VideoCategoryCard.tsx | 45 + .../Home/Education/VideoListItem.tsx | 45 + .../dashboard/Home/Education/VideoPlayer.tsx | 290 ++++++ .../Home/Education/VideoPlayerPage.tsx | 89 ++ .../Home/Offer/CreateOfferDialog.tsx | 152 ++++ .../Home/Profile/AddPropertyDialog.tsx | 500 +++++++++++ .../Home/Profile/AttorneyProfile.tsx | 118 +++ .../Home/Profile/AttorneyProfileCard.tsx | 503 +++++++++++ .../dashboard/Home/Profile/DrawingManager.tsx | 58 ++ .../Home/Profile/EstimatedMonthlyCostCard.tsx | 61 ++ .../Home/Profile/OfferSubmissionCard.tsx | 67 ++ .../dashboard/Home/Profile/OpenHouseCard.tsx | 58 ++ .../dashboard/Home/Profile/ProfileCard.tsx | 188 ++++ .../dashboard/Home/Profile/PropertyCard..tsx | 112 +++ .../Home/Profile/PropertyOwnerProfile.tsx | 230 +++++ .../dashboard/Home/Profile/ServiceCard.tsx | 256 ++++++ .../dashboard/Home/Profile/VendorProfile.tsx | 183 ++++ .../Home/Profile/VendorProfileCard.tsx | 416 +++++++++ .../Home/Property/PropertyDetailCard.tsx | 474 ++++++++++ .../Home/Property/PropertyDetailsCard.tsx | 80 -- .../dashboard/Home/Property/PropertyInfo.tsx | 56 +- .../Home/Property/PropertyListItem.tsx | 69 ++ .../Home/Property/PropertySearchFilters.tsx | 107 +++ .../Home/Property/PropertyStatusCard.tsx | 131 +++ .../Home/Property/SaleTaxHistoryCard.tsx | 85 ++ .../dashboard/Home/Property/SchoolCard.tsx | 84 ++ .../dashboard/Home/Property/WalkScoreCard.tsx | 74 ++ .../Home/Upgrade/ProfessionalUpgrade.tsx | 70 ++ .../Home/Upgrade/PropertyOwnerUpgrade.tsx | 82 ++ .../Home/Vendor/VendorCategoryCard.tsx | 47 + .../dashboard/Home/Vendor/VendorDetail.tsx | 162 ++++ .../dashboard/Home/Vendor/VendorListItem.tsx | 42 + .../dashboard/Home/Vendor/VendorMap.tsx | 57 ++ .../src/contexts/AccountContext.tsx | 51 ++ .../src/contexts/ConversationContext.tsx | 80 ++ .../src/contexts/MessageContext.tsx | 138 +++ .../src/contexts/WebSocketContext.tsx | 208 +++++ .../src/data/attorney-nav-items.ts | 42 + ditch-the-agent/src/data/basic-nav-items.ts | 113 +++ .../src/data/mock_autocomplete_results.ts | 406 +++++++++ .../src/data/mock_property_search.ts | 682 ++++++++++++++ ditch-the-agent/src/data/nav-items.ts | 224 ++--- ditch-the-agent/src/data/vendor-nav-items.ts | 42 + .../src/layouts/main-layout/Footer.tsx | 52 +- .../layouts/main-layout/Sidebar/Sidebar.tsx | 260 +++--- .../src/layouts/main-layout/Topbar/Topbar.tsx | 22 - .../src/layouts/main-layout/index.tsx | 184 ++-- ditch-the-agent/src/main.tsx | 57 +- ditch-the-agent/src/pages/Bids/Bids.tsx | 75 ++ .../src/pages/Education/Education.tsx | 176 +++- .../src/pages/Messages/Messages.tsx | 362 ++++++++ ditch-the-agent/src/pages/Offers/Offers.tsx | 340 +++++++ ditch-the-agent/src/pages/Profile/Profile.tsx | 42 + .../src/pages/Property/Property.tsx | 450 ++++++++-- .../src/pages/Property/PropertyDetailPage.tsx | 200 +++++ .../src/pages/Property/PropertySearchPage.tsx | 170 ++++ .../src/pages/Tools/AmoritizationTable.tsx | 238 +++++ .../src/pages/Tools/HomeAffordability.tsx | 392 ++++++++ .../src/pages/Tools/MortgageCalculator.tsx | 292 ++++++ .../src/pages/Tools/NetTermsSheet.tsx | 292 ++++++ .../src/pages/Upgrade/UpgradePage.tsx | 35 + ditch-the-agent/src/pages/Vendors/Vendors.tsx | 160 +++- .../src/pages/authentication/Login.tsx | 203 +++-- .../src/pages/authentication/SignUp.tsx | 584 ++++++------ ditch-the-agent/src/pages/home/Dashboard.tsx | 40 + ditch-the-agent/src/pages/home/Sales.tsx | 65 -- .../src/pages/home/TermsOfService.tsx | 279 ++++-- ditch-the-agent/src/routes/paths.ts | 79 +- ditch-the-agent/src/routes/router.tsx | 600 ++++++++----- ditch-the-agent/src/types.ts | 714 +++++++++++++++ ditch-the-agent/src/utils.tsx | 26 + ditch-the-agent/vite.config.ts | 38 +- 100 files changed, 14935 insertions(+), 1871 deletions(-) create mode 100644 ditch-the-agent/.env create mode 100644 ditch-the-agent/src/components/CategoryGridTemplate.tsx create mode 100644 ditch-the-agent/src/components/DasboardTemplate.tsx create mode 100644 ditch-the-agent/src/components/ItemListDetailTemplate.tsx create mode 100644 ditch-the-agent/src/components/base/FormattedListingText.tsx create mode 100644 ditch-the-agent/src/components/base/GeocodeComponent.tsx create mode 100644 ditch-the-agent/src/components/base/LoadingSkeleton.tsx create mode 100644 ditch-the-agent/src/components/base/MapComponent.tsx create mode 100644 ditch-the-agent/src/components/base/MapSearchComponent.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Bids/AddBidDialog.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Bids/BidCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Bids/VendorBidCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Bids/VendorBids.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/AttorneyDashboard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/DashboardErrorPage.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/DashboardLoading.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/NotificationInfoCards.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/PropertyOwnerDashboard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/RealEstateAgentDashboard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/VendorDashboard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Education/CategoryGrid.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationTable.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationVideoPlayer.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoCategoryCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoListItem.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoPlayer.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoPlayerPage.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Offer/CreateOfferDialog.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/AddPropertyDialog.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/AttorneyProfile.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/AttorneyProfileCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/DrawingManager.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/EstimatedMonthlyCostCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/OfferSubmissionCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/OpenHouseCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/ProfileCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/PropertyCard..tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/PropertyOwnerProfile.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/ServiceCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/VendorProfile.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Profile/VendorProfileCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Property/PropertyDetailCard.tsx delete mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Property/PropertyDetailsCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Property/PropertyListItem.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Property/PropertySearchFilters.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Property/PropertyStatusCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Property/SaleTaxHistoryCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Property/SchoolCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Property/WalkScoreCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Upgrade/ProfessionalUpgrade.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Upgrade/PropertyOwnerUpgrade.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Vendor/VendorCategoryCard.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Vendor/VendorDetail.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Vendor/VendorListItem.tsx create mode 100644 ditch-the-agent/src/components/sections/dashboard/Home/Vendor/VendorMap.tsx create mode 100644 ditch-the-agent/src/contexts/AccountContext.tsx create mode 100644 ditch-the-agent/src/contexts/ConversationContext.tsx create mode 100644 ditch-the-agent/src/contexts/MessageContext.tsx create mode 100644 ditch-the-agent/src/contexts/WebSocketContext.tsx create mode 100644 ditch-the-agent/src/data/attorney-nav-items.ts create mode 100644 ditch-the-agent/src/data/basic-nav-items.ts create mode 100644 ditch-the-agent/src/data/mock_autocomplete_results.ts create mode 100644 ditch-the-agent/src/data/mock_property_search.ts create mode 100644 ditch-the-agent/src/data/vendor-nav-items.ts create mode 100644 ditch-the-agent/src/pages/Bids/Bids.tsx create mode 100644 ditch-the-agent/src/pages/Messages/Messages.tsx create mode 100644 ditch-the-agent/src/pages/Offers/Offers.tsx create mode 100644 ditch-the-agent/src/pages/Profile/Profile.tsx create mode 100644 ditch-the-agent/src/pages/Property/PropertyDetailPage.tsx create mode 100644 ditch-the-agent/src/pages/Property/PropertySearchPage.tsx create mode 100644 ditch-the-agent/src/pages/Tools/AmoritizationTable.tsx create mode 100644 ditch-the-agent/src/pages/Tools/HomeAffordability.tsx create mode 100644 ditch-the-agent/src/pages/Tools/MortgageCalculator.tsx create mode 100644 ditch-the-agent/src/pages/Tools/NetTermsSheet.tsx create mode 100644 ditch-the-agent/src/pages/Upgrade/UpgradePage.tsx create mode 100644 ditch-the-agent/src/pages/home/Dashboard.tsx delete mode 100644 ditch-the-agent/src/pages/home/Sales.tsx create mode 100644 ditch-the-agent/src/types.ts create mode 100644 ditch-the-agent/src/utils.tsx diff --git a/ditch-the-agent/.env b/ditch-the-agent/.env new file mode 100644 index 0000000..7ccb34e --- /dev/null +++ b/ditch-the-agent/.env @@ -0,0 +1 @@ +REACT_APP_Maps_API_KEY="AIzaSyDJTApP1OoMbo7b1CPltPu8IObxe8UQt7w" diff --git a/ditch-the-agent/package-lock.json b/ditch-the-agent/package-lock.json index 3f25860..f9e96de 100644 --- a/ditch-the-agent/package-lock.json +++ b/ditch-the-agent/package-lock.json @@ -13,6 +13,8 @@ "@mui/material": "^5.15.14", "@mui/x-data-grid": "^7.2.0", "@mui/x-data-grid-generator": "^7.2.0", + "@react-google-maps/api": "^2.20.7", + "@vis.gl/react-google-maps": "^1.5.4", "axios": "^1.10.0", "dayjs": "^1.11.10", "echarts": "^5.5.0", @@ -35,6 +37,7 @@ "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.57.0", + "eslint-plugin-prettier": "^5.5.3", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "typescript": "^5.2.2", @@ -65,12 +68,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dependencies": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -268,17 +272,17 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "engines": { "node": ">=6.9.0" } @@ -293,38 +297,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", - "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", - "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, + "dependencies": { + "@babel/types": "^7.28.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -363,25 +355,22 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", - "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -409,13 +398,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -555,9 +543,9 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -571,9 +559,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -587,9 +575,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -603,9 +591,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -619,9 +607,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -635,9 +623,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -651,9 +639,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -667,9 +655,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -683,9 +671,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -699,9 +687,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -715,9 +703,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -731,9 +719,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -747,9 +735,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -763,9 +751,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -779,9 +767,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -795,9 +783,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -811,9 +799,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -827,9 +815,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -843,9 +831,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -859,9 +847,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -875,9 +863,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -891,9 +879,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -907,9 +895,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -970,9 +958,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -1086,6 +1074,20 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, + "node_modules/@googlemaps/js-api-loader": { + "version": "1.16.8", + "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz", + "integrity": "sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==" + }, + "node_modules/@googlemaps/markerclusterer": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz", + "integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "supercluster": "^8.0.1" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -1101,9 +1103,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -1610,6 +1612,18 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1619,6 +1633,33 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-google-maps/api": { + "version": "2.20.7", + "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.20.7.tgz", + "integrity": "sha512-ys7uri3V6gjhYZUI43srHzSKDC6/jiKTwHNlwXFTvjeaJE3M3OaYBt9FZKvJs8qnOhL6i6nD1BKJoi1KrnkCkg==", + "dependencies": { + "@googlemaps/js-api-loader": "1.16.8", + "@googlemaps/markerclusterer": "2.5.3", + "@react-google-maps/infobox": "2.20.0", + "@react-google-maps/marker-clusterer": "2.20.0", + "@types/google.maps": "3.58.1", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19", + "react-dom": "^16.8 || ^17 || ^18 || ^19" + } + }, + "node_modules/@react-google-maps/infobox": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.20.0.tgz", + "integrity": "sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ==" + }, + "node_modules/@react-google-maps/marker-clusterer": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.20.0.tgz", + "integrity": "sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw==" + }, "node_modules/@remix-run/router": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", @@ -1628,9 +1669,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", - "integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", "cpu": [ "arm" ], @@ -1641,9 +1682,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz", - "integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", "cpu": [ "arm64" ], @@ -1654,9 +1695,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz", - "integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", "cpu": [ "arm64" ], @@ -1667,9 +1708,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz", - "integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", "cpu": [ "x64" ], @@ -1679,10 +1720,49 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz", - "integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", "cpu": [ "arm" ], @@ -1693,9 +1773,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz", - "integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", "cpu": [ "arm64" ], @@ -1706,9 +1786,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz", - "integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", "cpu": [ "arm64" ], @@ -1718,12 +1798,25 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz", - "integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==", + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", "cpu": [ - "ppc64le" + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" ], "dev": true, "optional": true, @@ -1732,9 +1825,22 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz", - "integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", "cpu": [ "riscv64" ], @@ -1745,9 +1851,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz", - "integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", "cpu": [ "s390x" ], @@ -1758,9 +1864,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz", - "integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", "cpu": [ "x64" ], @@ -1771,9 +1877,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz", - "integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", "cpu": [ "x64" ], @@ -1784,9 +1890,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz", - "integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", "cpu": [ "arm64" ], @@ -1797,9 +1903,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz", - "integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", "cpu": [ "ia32" ], @@ -1810,9 +1916,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz", - "integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", "cpu": [ "x64" ], @@ -1864,9 +1970,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "node_modules/@types/format-util": { @@ -1874,6 +1980,11 @@ "resolved": "https://registry.npmjs.org/@types/format-util/-/format-util-1.0.4.tgz", "integrity": "sha512-xrCYOdHh5zA3LUrn6CvspYwlzSWxPso11Lx32WnAG6KvLCRecKZ/Rh21PLXUkzUFsQmrGcx/traJAFjR6dVS5Q==" }, + "node_modules/@types/google.maps": { + "version": "3.58.1", + "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz", + "integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", @@ -2151,6 +2262,19 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vis.gl/react-google-maps": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vis.gl/react-google-maps/-/react-google-maps-1.5.4.tgz", + "integrity": "sha512-pD3e2wDtOfd439mamkacRgrM6I2B/lue61QCR0pGQT8MVaG9pz9/LajHbsjZW2lms8Ao8mf2PQJeiGC2FxI0Fw==", + "dependencies": { + "@types/google.maps": "^3.54.10", + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "react": ">=16.8.0 || ^19.0 || ^19.0.0-rc", + "react-dom": ">=16.8.0 || ^19.0 || ^19.0.0-rc" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", @@ -2216,17 +2340,6 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/archiver": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", @@ -2400,20 +2513,20 @@ "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2554,27 +2667,6 @@ "node": "*" } }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/chance": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/chance/-/chance-1.1.11.tgz", @@ -2593,19 +2685,6 @@ "node": ">=6" } }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2685,9 +2764,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -2914,9 +2993,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -2926,29 +3005,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -3026,6 +3105,36 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", + "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", @@ -3091,9 +3200,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -3277,6 +3386,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -3339,9 +3454,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -3411,9 +3526,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3582,9 +3697,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3658,14 +3773,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3786,6 +3893,14 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3970,6 +4085,11 @@ "node": ">=18" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4209,12 +4329,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -4281,9 +4401,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -4454,9 +4574,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -4471,9 +4591,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -4490,9 +4610,9 @@ } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4507,6 +4627,34 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -4680,11 +4828,6 @@ "node": ">=10" } }, - "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==" - }, "node_modules/reselect": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", @@ -4740,12 +4883,12 @@ } }, "node_modules/rollup": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", - "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -4755,21 +4898,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.14.0", - "@rollup/rollup-android-arm64": "4.14.0", - "@rollup/rollup-darwin-arm64": "4.14.0", - "@rollup/rollup-darwin-x64": "4.14.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.14.0", - "@rollup/rollup-linux-arm64-gnu": "4.14.0", - "@rollup/rollup-linux-arm64-musl": "4.14.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.14.0", - "@rollup/rollup-linux-riscv64-gnu": "4.14.0", - "@rollup/rollup-linux-s390x-gnu": "4.14.0", - "@rollup/rollup-linux-x64-gnu": "4.14.0", - "@rollup/rollup-linux-x64-musl": "4.14.0", - "@rollup/rollup-win32-arm64-msvc": "4.14.0", - "@rollup/rollup-win32-ia32-msvc": "4.14.0", - "@rollup/rollup-win32-x64-msvc": "4.14.0", + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" } }, @@ -4938,9 +5086,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -4983,15 +5131,12 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "kdbush": "^4.0.2" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -5005,6 +5150,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -5039,14 +5199,6 @@ "node": ">=14.14" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5246,14 +5398,14 @@ } }, "node_modules/vite": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", - "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -5272,6 +5424,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -5289,6 +5442,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/ditch-the-agent/package.json b/ditch-the-agent/package.json index 09463b8..0102468 100644 --- a/ditch-the-agent/package.json +++ b/ditch-the-agent/package.json @@ -17,6 +17,8 @@ "@mui/material": "^5.15.14", "@mui/x-data-grid": "^7.2.0", "@mui/x-data-grid-generator": "^7.2.0", + "@react-google-maps/api": "^2.20.7", + "@vis.gl/react-google-maps": "^1.5.4", "axios": "^1.10.0", "dayjs": "^1.11.10", "echarts": "^5.5.0", @@ -39,6 +41,7 @@ "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.57.0", + "eslint-plugin-prettier": "^5.5.3", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "typescript": "^5.2.2", diff --git a/ditch-the-agent/src/axiosApi.js b/ditch-the-agent/src/axiosApi.js index 8f02863..71a6191 100644 --- a/ditch-the-agent/src/axiosApi.js +++ b/ditch-the-agent/src/axiosApi.js @@ -1,96 +1,105 @@ -import axios from "axios" -import Cookies from 'js-cookie' - +import axios from 'axios'; +import Cookies from 'js-cookie'; const baseURL = 'http://127.0.0.1:8010/api/'; //const baseURL = 'https://backend.ditchtheagent.com/api/'; +export const axiosRealEstateApi = axios.create({ + baseURL: 'https://api.realestateapi.com/v2/', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': 'AIMLOPERATIONSLLC-a7ce-7525-b38f-cf8b4d52ca70', + 'X-User-Id': 'UniqueUserIdentifier', + }, +}); + +export const axiosWalkScoreApiInstance = axios.create({ + baseURL: 'https://api.walkscore.com/', + timeout: 5000, +}); export const axiosInstance = axios.create({ - baseURL: baseURL, - timeout: 5000, - headers: { - "Authorization": 'JWT ' + localStorage.getItem('access_token'), - 'Content-Type': 'application/json', - 'Accept': 'application/json', - } + baseURL: baseURL, + timeout: 5000, + headers: { + Authorization: 'JWT ' + localStorage.getItem('access_token'), + 'Content-Type': 'application/json', + Accept: 'application/json', + }, }); export const cleanAxiosInstance = axios.create({ - baseURL: baseURL, - timeout: 5000, - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - } + baseURL: baseURL, + timeout: 5000, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, }); export const axiosInstanceCSRF = axios.create({ - baseURL: baseURL, - timeout: 5000, - headers: { - 'X-CSRFToken': Cookies.get('csrftoken'), // Include CSRF token in headers - }, - withCredentials: true, - } -); + baseURL: baseURL, + timeout: 5000, + headers: { + 'X-CSRFToken': Cookies.get('csrftoken'), // Include CSRF token in headers + }, + withCredentials: true, +}); -axiosInstance.interceptors.request.use(config => { - config.timeout = 100000; - return config; -}) +axiosInstance.interceptors.request.use((config) => { + config.timeout = 100000; + return config; +}); axiosInstance.interceptors.response.use( - response => response, - error => { - const originalRequest = error.config; + (response) => response, + (error) => { + const originalRequest = error.config; - // Prevent infinite loop - if (error.response.status === 401 && originalRequest.url === baseURL+'/token/refresh/') { - window.location.href = '/signin/'; - //console.log('remove the local storage here') - return Promise.reject(error); - } - - if(error.response.data.code === "token_not_valid" && - error.response.status == 401 && - error.response.statusText == 'Unauthorized') - { - const refresh_token = localStorage.getItem('refresh_token'); - - if (refresh_token){ - const tokenParts = JSON.parse(atob(refresh_token.split('.')[1])); - - const now = Math.ceil(Date.now() / 1000); - //console.log(tokenParts.exp) - - if(tokenParts.exp > now){ - return axiosInstance.post('/token/refresh/', {refresh: refresh_token}).then((response) => { - localStorage.setItem('access_token', response.data.access); - localStorage.setItem('refresh_token', response.data.refresh); - - axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access; - originalRequest.headers['Authorization'] = 'JWT ' + response.data.access; - - return axiosInstance(originalRequest); - }).catch(err => { - console.log(err) - }); - - }else{ - console.log('Refresh token is expired'); - window.location.href = '/signin/'; - - } - }else { - console.log('Refresh token not available'); - window.location.href = '/signin/'; - - } - - - - } - return Promise.reject(error); + // Prevent infinite loop + if (error.response.status === 401 && originalRequest.url === baseURL + '/token/refresh/') { + window.location.href = '/authentication/login/'; + //console.log('remove the local storage here') + return Promise.reject(error); } -); \ No newline at end of file + + if ( + error.response.data.code === 'token_not_valid' && + error.response.status == 401 && + error.response.statusText == 'Unauthorized' + ) { + const refresh_token = localStorage.getItem('refresh_token'); + + if (refresh_token) { + const tokenParts = JSON.parse(atob(refresh_token.split('.')[1])); + + const now = Math.ceil(Date.now() / 1000); + //console.log(tokenParts.exp) + + if (tokenParts.exp > now) { + return axiosInstance + .post('/token/refresh/', { refresh: refresh_token }) + .then((response) => { + localStorage.setItem('access_token', response.data.access); + localStorage.setItem('refresh_token', response.data.refresh); + + axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access; + originalRequest.headers['Authorization'] = 'JWT ' + response.data.access; + + return axiosInstance(originalRequest); + }) + .catch((err) => { + console.log(err); + }); + } else { + console.log('Refresh token is expired'); + window.location.href = '/authentication/login/'; + } + } else { + console.log('Refresh token not available'); + window.location.href = '/authentication/login/'; + } + } + return Promise.reject(error); + }, +); diff --git a/ditch-the-agent/src/components/CategoryGridTemplate.tsx b/ditch-the-agent/src/components/CategoryGridTemplate.tsx new file mode 100644 index 0000000..b141453 --- /dev/null +++ b/ditch-the-agent/src/components/CategoryGridTemplate.tsx @@ -0,0 +1,29 @@ +// src/templates/CategoryGridTemplate.tsx +import React, { ReactNode } from 'react'; +import { Grid } from '@mui/material'; +import { GenericCategory } from 'types'; + + +interface CategoryGridTemplateProps { + categories: TCategory[]; + onSelectCategory: (categoryId: string) => void; + renderCategoryCard: (category: TCategory, onSelect: (categoryId: string) => void) => ReactNode; +} + +function CategoryGridTemplate({ + categories, + onSelectCategory, + renderCategoryCard, +}: CategoryGridTemplateProps) { + return ( + + {categories.map((category) => ( + + {renderCategoryCard(category, onSelectCategory)} + + ))} + + ); +} + +export default CategoryGridTemplate; \ No newline at end of file diff --git a/ditch-the-agent/src/components/DasboardTemplate.tsx b/ditch-the-agent/src/components/DasboardTemplate.tsx new file mode 100644 index 0000000..f5ae823 --- /dev/null +++ b/ditch-the-agent/src/components/DasboardTemplate.tsx @@ -0,0 +1,67 @@ +// src/templates/DashboardTemplate.tsx +import React, { useState, ReactNode } from 'react'; +import { Container, Typography, Box, Button } from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { GenericCategory, GenericItem } from 'types'; + +interface DashboardTemplateProps { + pageTitle: string; + data: { + categories: TCategory[]; + items: TItem[]; + }; + renderCategoryGrid: ( + categories: TCategory[], + onSelectCategory: (categoryId: string) => void, + ) => ReactNode; + renderItemListDetail: ( + selectedCategory: TCategory, + itemsInSelectedCategory: TItem[], + onBack: () => void, + ) => ReactNode; +} + +function DashboardTemplate({ + pageTitle, + data, + renderCategoryGrid, + renderItemListDetail, +}: DashboardTemplateProps) { + const [selectedCategoryId, setSelectedCategoryId] = useState(null); + + const handleSelectCategory = (categoryId: string) => { + setSelectedCategoryId(categoryId); + }; + + const handleBackToCategories = () => { + setSelectedCategoryId(null); + }; + + const selectedCategory = selectedCategoryId + ? data.categories.find((cat) => cat.id === selectedCategoryId) + : null; + const itemsInSelectedCategory = selectedCategoryId + ? data.items.filter((item: any) => item.categoryId === selectedCategoryId) // Assuming items have a categoryId field + : []; + + return ( + + + {pageTitle} + + + {selectedCategoryId && selectedCategory ? ( + + + {renderItemListDetail(selectedCategory, itemsInSelectedCategory, handleBackToCategories)} + + ) : ( + renderCategoryGrid(data.categories, handleSelectCategory) + )} + + ); +} + +export default DashboardTemplate; diff --git a/ditch-the-agent/src/components/FloatingChatButton.tsx b/ditch-the-agent/src/components/FloatingChatButton.tsx index cedf22e..fda079e 100644 --- a/ditch-the-agent/src/components/FloatingChatButton.tsx +++ b/ditch-the-agent/src/components/FloatingChatButton.tsx @@ -1,7 +1,28 @@ -import { ReactElement, useState, useEffect, useRef, ChangeEvent, KeyboardEvent } from 'react'; +import { + ReactElement, + useState, + useEffect, + useRef, + ChangeEvent, + KeyboardEvent, + useContext, +} from 'react'; import { MessageSquareText, Minus, X, Send } from 'lucide-react'; // Using lucide-react for icons -import { Box, Button, AppBar, Typography, useTheme, Fab, TextField, Paper, Toolbar, IconButton } from '@mui/material'; - +import { + Box, + Button, + AppBar, + Typography, + useTheme, + Fab, + TextField, + Paper, + Toolbar, + IconButton, +} from '@mui/material'; +import { ChatMessage, WebSocketContext } from 'contexts/WebSocketContext'; +import { AccountContext } from 'contexts/AccountContext'; +import FormattedListingText from './base/FormattedListingText'; interface FloatingActionButtonProps { onClick: () => void; @@ -9,32 +30,26 @@ interface FloatingActionButtonProps { const FloatingActionButton = ({ onClick }: FloatingActionButtonProps) => { return ( - - - - + + + ); }; -interface ChatMessage { - text: string; - sender: 'user' | 'ai'; -} - interface ChatPaneProps { showChat: boolean; isMinimized: boolean; @@ -42,7 +57,6 @@ interface ChatPaneProps { closeChat: () => void; } - // Chat Pane Component const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPaneProps) => { const [messages, setMessages] = useState([]); @@ -52,8 +66,34 @@ const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPane const [isLoading, setIsLoading] = useState(false); // Ref for the messages container to scroll to the bottom const messagesEndRef = useRef(null); + const messageRef = useRef(''); + const [currentMessage, setCurrentMessage] = useState(''); const theme = useTheme(); + const { account, accountLoading } = useContext(AccountContext); + const { subscribe, unsubscribe, socket, sendMessages } = useContext(WebSocketContext); + + useEffect(() => { + if (accountLoading) return; + const channelName = `ACCOUNT_ID_${account?.email}`; + subscribe(channelName, (message: string) => { + if (message === 'BEGINING_OF_THE_WORLD') { + } else if (message === 'END_OF_THE_WORLD') { + const deepCopiedMessage = structuredClone(messageRef.current); + setMessages((prevMessages) => [...prevMessages, { text: deepCopiedMessage, sender: 'ai' }]); + messageRef.current = ''; + setCurrentMessage(messageRef.current); + setIsLoading(false); + } else { + messageRef.current += message; + setCurrentMessage(messageRef.current); + } + }); + + return () => { + unsubscribe(channelName); + }; + }, [account, subscribe, unsubscribe]); // Scroll to the bottom of the chat window whenever messages change useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); @@ -62,56 +102,73 @@ const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPane // Function to send a message to the AI const handleSendMessage = async () => { if (inputMessage.trim() === '') return; - - const newUserMessage: ChatMessage = { text: inputMessage, sender: 'user' }; - setMessages((prevMessages: ChatMessage[]) => [...prevMessages, newUserMessage]); - setInputMessage(''); // Clear input field - - setIsLoading(true); // Show loading indicator - - try { - // Construct chat history for the API call - let chatHistory = messages.map(msg => ({ - role: msg.sender === 'user' ? 'user' : 'model', - parts: [{ text: msg.text }] - })); - chatHistory.push({ role: "user", parts: [{ text: newUserMessage.text }] }); - - const payload = { contents: chatHistory }; - const apiKey = ""; // Leave this as-is; Canvas will provide the API key at runtime - const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; - - const response = await fetch(apiUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); - - const result = await response.json(); - - if (result.candidates && result.candidates.length > 0 && - result.candidates[0].content && result.candidates[0].content.parts && - result.candidates[0].content.parts.length > 0) { - const aiResponseText = result.candidates[0].content.parts[0].text; - setMessages((prevMessages) => [...prevMessages, { text: aiResponseText, sender: 'ai' }]); - } else { - // Handle cases where the response structure is unexpected or content is missing - console.error("Unexpected API response structure:", result); - setMessages((prevMessages) => [...prevMessages, { text: "Error: Could not get a response from AI.", sender: 'ai' }]); - } - } catch (error) { - console.error('Error fetching AI response:', error); - setMessages((prevMessages) => [...prevMessages, { text: "Error: Failed to connect to AI.", sender: 'ai' }]); - } finally { - setIsLoading(false); // Hide loading indicator + if (account !== undefined) { + const newMessage: ChatMessage = { + text: inputMessage, + sender: 'user', + }; + sendMessages([...messages, newMessage]); + setIsLoading(true); + setMessages((prevMessages) => [...prevMessages, newMessage]); } + + // const newUserMessage: ChatMessage = { text: inputMessage, sender: 'user' }; + // setMessages((prevMessages: ChatMessage[]) => [...prevMessages, newUserMessage]); + // setInputMessage(''); // Clear input field + + // setIsLoading(true); // Show loading indicator + + // try { + // // Construct chat history for the API call + // let chatHistory = messages.map((msg) => ({ + // role: msg.sender === 'user' ? 'user' : 'model', + // parts: [{ text: msg.text }], + // })); + // chatHistory.push({ role: 'user', parts: [{ text: newUserMessage.text }] }); + + // const payload = { contents: chatHistory }; + // const apiKey = ''; // Leave this as-is; Canvas will provide the API key at runtime + // const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; + + // const response = await fetch(apiUrl, { + // method: 'POST', + // headers: { 'Content-Type': 'application/json' }, + // body: JSON.stringify(payload), + // }); + + // const result = await response.json(); + + // if ( + // result.candidates && + // result.candidates.length > 0 && + // result.candidates[0].content && + // result.candidates[0].content.parts && + // result.candidates[0].content.parts.length > 0 + // ) { + // const aiResponseText = result.candidates[0].content.parts[0].text; + // setMessages((prevMessages) => [...prevMessages, { text: aiResponseText, sender: 'ai' }]); + // } else { + // // Handle cases where the response structure is unexpected or content is missing + // console.error('Unexpected API response structure:', result); + // setMessages((prevMessages) => [ + // ...prevMessages, + // { text: 'Error: Could not get a response from AI.', sender: 'ai' }, + // ]); + // } + // } catch (error) { + // console.error('Error fetching AI response:', error); + // setMessages((prevMessages) => [ + // ...prevMessages, + // { text: 'Error: Failed to connect to AI.', sender: 'ai' }, + // ]); + // } finally { + // setIsLoading(false); // Hide loading indicator + // } }; if (!showChat) return null; // Don't render anything if chat is not shown return ( - - {/* Chat Header */} - - - - {/* minHeight to match h-16 for minimized */} + + + {' '} + {/* minHeight to match h-16 for minimized */} AI Assistant @@ -148,15 +210,11 @@ const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPane - + @@ -200,7 +258,8 @@ const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPane borderBottomLeftRadius: msg.sender === 'user' ? '12px' : 0, }} > - {msg.text} + {/*{msg.text}*/} + ))} @@ -218,9 +277,19 @@ const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPane }} > - . - . - . + + . + + + . + + + . + @@ -249,7 +318,9 @@ const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPane size="small" value={inputMessage} onChange={(e: ChangeEvent) => setInputMessage(e.target.value)} - onKeyPress={(e: KeyboardEvent) => e.key === 'Enter' && handleSendMessage()} + onKeyPress={(e: KeyboardEvent) => + e.key === 'Enter' && handleSendMessage() + } placeholder="Type your message..." disabled={isLoading} sx={{ @@ -321,51 +392,46 @@ const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPane `} - ); }; -const FloatingChatButton = (): ReactElement => -{ - const [showChat, setShowChat] = useState(false); - // State to control if the chat pane is minimized - const [isMinimized, setIsMinimized] = useState(false); +const FloatingChatButton = (): ReactElement => { + const [showChat, setShowChat] = useState(false); + // State to control if the chat pane is minimized + const [isMinimized, setIsMinimized] = useState(false); - // Function to toggle the chat pane visibility - const toggleChat = () => { - setShowChat(!showChat); - // When opening, ensure it's not minimized - if (!showChat) { - setIsMinimized(false); - } - }; + // Function to toggle the chat pane visibility + const toggleChat = () => { + setShowChat(!showChat); + // When opening, ensure it's not minimized + if (!showChat) { + setIsMinimized(false); + } + }; - // Function to toggle minimize/maximize the chat pane - const toggleMinimize = () => { - setIsMinimized(!isMinimized); - }; + // Function to toggle minimize/maximize the chat pane + const toggleMinimize = () => { + setIsMinimized(!isMinimized); + }; - // Function to close the chat pane - const closeChat = () => { - setShowChat(false); - setIsMinimized(false); // Reset minimize state when closing - }; - return( -
+ // Function to close the chat pane + const closeChat = () => { + setShowChat(false); + setIsMinimized(false); // Reset minimize state when closing + }; + return ( +
{/* Floating Action Button */} {!showChat && } - + -
+
+ ); +}; - - ) - -} - -export default FloatingChatButton; \ No newline at end of file +export default FloatingChatButton; diff --git a/ditch-the-agent/src/components/ItemListDetailTemplate.tsx b/ditch-the-agent/src/components/ItemListDetailTemplate.tsx new file mode 100644 index 0000000..8a51497 --- /dev/null +++ b/ditch-the-agent/src/components/ItemListDetailTemplate.tsx @@ -0,0 +1,78 @@ +// src/templates/ItemListDetailTemplate.tsx +import React, { useState, useEffect, ReactNode } from 'react'; +import { Box, Grid, List, ListItem, ListItemText, Typography, Paper, Button, Stack, IconButton } from '@mui/material'; +import { GenericCategory, GenericItem } from 'types'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; + +interface ItemListDetailTemplateProps { + category: TCategory; + items: TItem[]; + onBack: () => void; + renderListItem: (item: TItem, isSelected: boolean, onSelect: (itemId: string) => void) => ReactNode; + renderItemDetail: (item: TItem) => ReactNode; +} + +function ItemListDetailTemplate({ + category, + items, + onBack, + renderListItem, + renderItemDetail, +}: ItemListDetailTemplateProps) { + const [selectedItemId, setSelectedItemId] = useState(null); + + // Default to the first item in the list + let temp = null + useEffect(() => { + + if (items.length > 0) { + temp = items[0].id + setSelectedItemId(items[0].id); + } else { + + setSelectedItemId(null); + } + }, [items]); + + const selectedItem = selectedItemId ? items.find((item) => item.id === selectedItemId) : null; + + console.log(selectedItemId, selectedItem) + + const handleItemSelect = (itemId: string) => { + setSelectedItemId(itemId); + }; + + return ( + + + + + + + + + {category.name} List + + + {items.map((item) => ( + + {renderListItem(item, selectedItem?.id === item.id, handleItemSelect)} + + ))} + + + + + {selectedItem ? ( + renderItemDetail(selectedItem) + ) : ( + + Select an item to view details + + )} + + + ); +} + +export default ItemListDetailTemplate; \ No newline at end of file diff --git a/ditch-the-agent/src/components/base/FormattedListingText.tsx b/ditch-the-agent/src/components/base/FormattedListingText.tsx new file mode 100644 index 0000000..e383887 --- /dev/null +++ b/ditch-the-agent/src/components/base/FormattedListingText.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +interface FormattedListingTextProps { + text: string; +} + +const FormattedListingText: React.FC = ({ text }) => { + const parts = text.split(/\*\*(.*?)\*\*/g); + + return ( +
+ {parts.map((part, index) => + index % 2 === 1 ? + {part} : + part + )} +
+ ); +}; + +export default FormattedListingText; \ No newline at end of file diff --git a/ditch-the-agent/src/components/base/GeocodeComponent.tsx b/ditch-the-agent/src/components/base/GeocodeComponent.tsx new file mode 100644 index 0000000..7868cac --- /dev/null +++ b/ditch-the-agent/src/components/base/GeocodeComponent.tsx @@ -0,0 +1,58 @@ +import React, { useState, useEffect } from 'react'; +import { useMapsLibrary } from '@vis.gl/react-google-maps'; + +export const GeocodeComponent = () => { + // Use state to store the geocoding results + const [latLng, setLatLng] = useState<{ lat: number; lng: number } | null>(null); + const [address, setAddress] = useState(''); + + // Use the hook to load the geocoding library + const geocodingLibrary = useMapsLibrary('geocoding'); + + // Create an instance of the Geocoder once the library is loaded + useEffect(() => { + if (!geocodingLibrary || !address) { + return; + } + + const geocoder = new geocodingLibrary.Geocoder(); + + // Perform the geocode request + geocoder.geocode( + { + address: address, + }, + (results, status) => { + if (status === 'OK' && results) { + // If a result is found, extract the lat/lng + const location = results[0].geometry.location; + setLatLng({ lat: location.lat(), lng: location.lng() }); + } else { + setLatLng(null); + console.error('Geocode was not successful for the following reason: ' + status); + } + }, + ); + }, [geocodingLibrary, address]); + + const handleInputChange = (e: React.ChangeEvent) => { + setAddress(e.target.value); + }; + + return ( +
+ + {latLng && ( +

+ Latitude: {latLng.lat}, Longitude: {latLng.lng} +

+ )} + {!latLng && address &&

No results found.

} +
+ ); +}; \ No newline at end of file diff --git a/ditch-the-agent/src/components/base/LoadingSkeleton.tsx b/ditch-the-agent/src/components/base/LoadingSkeleton.tsx new file mode 100644 index 0000000..79c1e0a --- /dev/null +++ b/ditch-the-agent/src/components/base/LoadingSkeleton.tsx @@ -0,0 +1,25 @@ +import { Paper, Container, Grid, Box, Typography, Skeleton } from '@mui/material'; + +import { ReactElement} from 'react'; + +const LoadingSkeleton = (): ReactElement => { + return ( + + + + + + + + + + + + + + + ) + +} + +export default LoadingSkeleton; \ No newline at end of file diff --git a/ditch-the-agent/src/components/base/MapComponent.tsx b/ditch-the-agent/src/components/base/MapComponent.tsx new file mode 100644 index 0000000..9225663 --- /dev/null +++ b/ditch-the-agent/src/components/base/MapComponent.tsx @@ -0,0 +1,77 @@ +import React, { useMemo } from 'react'; +//import { GoogleMap, Marker, useLoadScript } from '@react-google-maps/api'; +import { APIProvider, Map, AdvancedMarker, Pin } from '@vis.gl/react-google-maps'; + +import { Box, CircularProgress, Typography } from '@mui/material'; +import LocationOnIcon from '@mui/icons-material/LocationOn'; + +interface MapComponentProps { + lat: number; + lng: number; + zoom?: number; + address?: string; // Optional for display +} + +const libraries: ('places' | 'drawing' | 'geometry' | 'localContext' | 'visualization')[] = ['places']; // 'places' is a common library to load + +const MapComponent: React.FC = ({ lat, lng, zoom = 15, address }) => { + const latitude = Number(lat); + const longitude = Number(lng); + const defaultProps = { + center: { latitude, longitude }, + zoom, + }; + + + // Replace 'YOUR_Maps_API_KEY' with your actual API key + + // const { isLoaded, loadError } = useLoadScript({ + // id: 'dta_demo', + // googleMapsApiKey: 'AIzaSyDJTApP1OoMbo7b1CPltPu8IObxe8UQt7w',//process.env.REACT_APP_Maps_API_KEY!, // Replace with your actual API key environment variable + // libraries: libraries, + // }); + + const center = useMemo(() => ({ + lat: latitude, + lng: longitude, + }), [lat, lng]); + + // if (loadError) { + // return ( + // + // Error loading maps + // + // ); + // } + + // if (!isLoaded) { + // return ( + // + // + // + // ); + // } + return ( + + {lat && lng && center? ( + + + + + + ) : ( + + Map not available. Please ensure valid latitude and longitude. + + )} + + ); +}; + +export default MapComponent; \ No newline at end of file diff --git a/ditch-the-agent/src/components/base/MapSearchComponent.tsx b/ditch-the-agent/src/components/base/MapSearchComponent.tsx new file mode 100644 index 0000000..ac81c54 --- /dev/null +++ b/ditch-the-agent/src/components/base/MapSearchComponent.tsx @@ -0,0 +1,165 @@ +import React, { useState } from 'react'; +import { + APIProvider, + Map, + AdvancedMarker, + Pin, + InfoWindow, + useMap, + useMapsLibrary, + MapCameraChangedEvent, +} from '@vis.gl/react-google-maps'; +import { Box, Typography, useTheme, Button } from '@mui/material'; + +import { useNavigate } from 'react-router-dom'; +import { PropertiesAPI } from 'types'; +import DrawingManager from '../sections/dashboard/Home/Profile/DrawingManager'; +import { cloneSourceShallow } from 'echarts/types/src/data/Source.js'; + +// Custom Marker component +interface MapMarkerProps { + property: PropertiesAPI; + onMarkerClick: (propertyId: number) => void; + onMarkerHover: (property: PropertiesAPI) => void; + onMarkerUnhover: () => void; + isListItemSelected: boolean; + centerMapToMarker: (position: google.maps.LatLngLiteral) => void; +} + +const MapMarker: React.FC = ({ + property, + onMarkerClick, + onMarkerHover, + onMarkerUnhover, + isListItemSelected, + centerMapToMarker, +}) => { + const theme = useTheme(); + const [infowindowOpen, setInfowindowOpen] = useState(false); + const position = { lat: Number(property.latitude)!, lng: Number(property.longitude)! }; + + const handleMarkerClick = (e: any) => { + //e.stopPropagation(); + setInfowindowOpen(true); + centerMapToMarker(position); + }; + + return ( + { + onMarkerHover(property); + }} + onMouseOut={() => { + onMarkerUnhover(); + }} + // You can use a custom pin or a regular one + // We'll use the Pin component for a simple custom look + // The isListItemSelected state will be handled by the parent + > + + {infowindowOpen && ( + setInfowindowOpen(false)}> + + + {property.address} + + + {property.city}, {property.state} + + + + + )} + + ); +}; + +// Main Map Component +interface MapProps { + center: google.maps.LatLngLiteral; + zoom: number; + properties: PropertiesAPI[]; + selectedPropertyId: number | null; + onBoundsChanged: (bounds: any) => void; + onBoxDrawn: (bounds: any) => void; + onMarkerClick: (propertyId: number) => void; + onMarkerHover: (property: PropertiesAPI) => void; + onMarkerUnhover: () => void; +} + +const MapSerachComponent: React.FC = ({ + center, + zoom, + properties, + selectedPropertyId, + onBoundsChanged, + onBoxDrawn, + onMarkerClick, + onMarkerHover, + onMarkerUnhover, +}) => { + const navigate = useNavigate(); + const [map, setMap] = useState(null); + + const onMapChange = (event: MapCameraChangedEvent) => { + const bounds = event.bounds; + onBoundsChanged({ + ne: bounds.northEast, + sw: bounds.southWest, + }); + }; + + const handleMarkerClick = (propertyId: number) => { + console.log('clicked a marker'); + navigate(`/property/${propertyId}`); + onMarkerClick(propertyId); + }; + + const centerMapToMarker = (position: google.maps.LatLngLiteral) => { + map?.setCenter(position); + map?.setZoom(15); + }; + console.log(properties); + + return ( + + + + {properties.map( + (property) => + property.latitude && + property.longitude && ( + + ), + )} + {/* */} + + + + ); +}; + +export default MapSerachComponent; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Bids/AddBidDialog.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Bids/AddBidDialog.tsx new file mode 100644 index 0000000..e8a64be --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Bids/AddBidDialog.tsx @@ -0,0 +1,118 @@ +import React, { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + TextField, + Button, + FormControl, + InputLabel, + Select, + MenuItem, + Stack, +} from '@mui/material'; +import { axiosInstance } from '../../../../../axiosApi'; + +import axios, { AxiosResponse } from 'axios'; +import { BidAPI, PropertiesAPI } from 'types'; + +interface AddBidDialogProps { + open: boolean; + onClose: () => void; + properties: PropertiesAPI[]; + onBidAdded: () => void; +} + +export const AddBidDialog: React.FC = ({ + open, + onClose, + properties, + onBidAdded, +}) => { + const [property, setProperty] = useState(''); + const [description, setDescription] = useState(''); + const [bidType, setBidType] = useState(''); + const [location, setLocation] = useState(''); + const [images, setImages] = useState([]); + + const handleSubmit = async () => { + const formData = new FormData(); + formData.append('property', property); + formData.append('description', description); + formData.append('bid_type', bidType); + formData.append('location', location); + images.forEach((image) => formData.append('images', image)); + + try { + const { data }: AxiosResponse = await axiosInstance.post('/bids/', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + onBidAdded(); + onClose(); + } catch (error) { + console.error('Failed to create bid', error); + } + }; + + return ( + + Create New Bid + + + + Property + + + setDescription(e.target.value)} + /> + + Bid Type + + + + Location + + + + + + + + ); +}; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Bids/BidCard.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Bids/BidCard.tsx new file mode 100644 index 0000000..295d50e --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Bids/BidCard.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { Card, CardContent, Typography, Button, Box, Grid } from '@mui/material'; + +import axios from 'axios'; +import { axiosInstance } from '../../../../../axiosApi'; +import { BidAPI } from 'types'; + +interface BidCardProps { + bid: BidAPI; + onDelete: (bidId: number) => void; + isOwner: boolean; +} + +export const BidCard: React.FC = ({ bid, onDelete, isOwner }) => { + const handleSelectResponse = async (responseId: number) => { + try { + await axiosInstance.post(`/bids/${bid.id}/select_response/`, { response_id: responseId }); + // You might want to refresh the parent component's state here + window.location.reload(); + } catch (error) { + console.error('Failed to select response', error); + } + }; + + return ( + + + Bid for {bid.bid_type} + Location: {bid.location} + + {bid.description} + + {bid.images.length > 0 && ( + + {bid.images.map((image) => ( + Bid + ))} + + )} + {isOwner && ( + + )} + {/* Responses Section */} + {bid.responses.length > 0 && ( + + Responses: + {bid.responses.map((response) => ( + + + Vendor: {response.vendor.business_name} + + + Price: ${response.price} + + + Description: {response.description} + + + Status: {response.status} + + {isOwner && response.status !== 'selected' && ( + + )} + {isOwner && response.status === 'selected' && ( + + )} + + ))} + + )} + + + ); +}; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Bids/VendorBidCard.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Bids/VendorBidCard.tsx new file mode 100644 index 0000000..09d7ff6 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Bids/VendorBidCard.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import { + Card, + CardContent, + Typography, + Button, + Dialog, + DialogTitle, + DialogContent, + TextField, + Box, +} from '@mui/material'; +import { axiosInstance } from '../../../../../axiosApi'; + +import axios from 'axios'; +import { BidAPI } from 'types'; + +interface VendorBidCardProps { + bid: BidAPI; + onResponseSubmitted: () => void; +} + +export const VendorBidCard: React.FC = ({ bid, onResponseSubmitted }) => { + const [openResponseDialog, setOpenResponseDialog] = useState(false); + const [responseDescription, setResponseDescription] = useState(''); + const [responsePrice, setResponsePrice] = useState(''); + + const myResponse = bid.responses.find((res) => res.vendor.user.id === 'current_user_id'); // Replace with actual user ID logic + + const handleSubmitResponse = async () => { + try { + await axiosInstance.post('/bid-responses/', { + bid: bid.id, + description: responseDescription, + price: responsePrice, + }); + onResponseSubmitted(); + setOpenResponseDialog(false); + } catch (error) { + console.error('Failed to submit response', error); + } + }; + + return ( + + + Bid for {bid.bid_type} + Location: {bid.location} + + {bid.description} + + {bid.images.length > 0 && ( + + {bid.images.map((image) => ( + Bid + ))} + + )} + + {myResponse ? ( + + Your Response: + + Price: ${myResponse.price} + + + Description: {myResponse.description} + + + Status: {myResponse.status} + + + ) : ( + + )} + + + setOpenResponseDialog(false)}> + Submit Response + + setResponsePrice(e.target.value)} + fullWidth + margin="normal" + /> + setResponseDescription(e.target.value)} + fullWidth + margin="normal" + /> + + + + + ); +}; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Bids/VendorBids.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Bids/VendorBids.tsx new file mode 100644 index 0000000..f6cb8ce --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Bids/VendorBids.tsx @@ -0,0 +1,42 @@ +import React, { useState, useEffect } from 'react'; +import { Container, Typography, Grid, Card, CardContent } from '@mui/material'; + +import axios, { AxiosResponse } from 'axios'; +import { axiosInstance } from '../../../../../axiosApi'; +import { BidAPI } from 'types'; +import { VendorBidCard } from './VendorBidCard'; + +const VendorBidsPage: React.FC = () => { + const [bids, setBids] = useState([]); + + useEffect(() => { + fetchBids(); + }, []); + + const fetchBids = async () => { + try { + // Endpoint to get all bids a vendor can see + const { data: bidData }: AxiosResponse = await axiosInstance.get('/bids/'); + setBids(bidData); + } catch (error) { + console.error('Failed to fetch bids', error); + } + }; + + return ( + + + Available Bids + + + {bids.map((bid) => ( + + + + ))} + + + ); +}; + +export default VendorBidsPage; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/AttorneyDashboard.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/AttorneyDashboard.tsx new file mode 100644 index 0000000..5f7f03f --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/AttorneyDashboard.tsx @@ -0,0 +1,175 @@ +// src/pages/AttorneyDashboardPage.tsx + +import React, { ReactElement, useEffect, useState } from 'react'; +import { + Container, + Typography, + Box, + Grid, + Card, + CardContent, + Divider, + List, + ListItem, + ListItemText, + ListItemIcon, + Button, + Alert, +} from '@mui/material'; +import FolderIcon from '@mui/icons-material/Folder'; +import EventIcon from '@mui/icons-material/Event'; +import DescriptionIcon from '@mui/icons-material/Description'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import WarningIcon from '@mui/icons-material/Warning'; +import { DashboardProps } from 'pages/home/Dashboard'; +import { useNavigate } from 'react-router-dom'; +import { AttorneyAPI } from 'types'; +import { axiosInstance } from '../../../../../axiosApi'; +import DashboardLoading from './DashboardLoading'; +import DashboardErrorPage from './DashboardErrorPage'; +import { AxiosResponse } from 'axios'; + +// Mock Data for the Attorney Dashboard +interface AttorneyCase { + id: number; + title: string; + status: 'active' | 'closed' | 'urgent'; + deadline: string; +} + +const mockAttorneyCases: AttorneyCase[] = [ + { id: 1, title: 'Closing for 123 Main St', status: 'urgent', deadline: 'August 15, 2025' }, + { id: 2, title: 'Contract Review - 456 Oak Ave', status: 'active', deadline: 'August 20, 2025' }, + { id: 3, title: 'Title Search - 789 Pine Ln', status: 'active', deadline: 'September 1, 2025' }, +]; + +const AttorneyDashboard = ({ account }: DashboardProps): ReactElement => { + const [attorney, setAttorney] = useState(null); + const [loadingData, setLoadingData] = useState(true); + const navigate = useNavigate(); + useEffect(() => { + const fetchAttorney = async () => { + try { + const { data }: AxiosResponse = await axiosInstance.get('/attorney/'); + if (data.length > 0) { + setAttorney(data[0]); + } + } catch (error) { + console.log(error); + } finally { + setLoadingData(false); + } + }; + fetchAttorney(); + }, []); + if (loadingData) { + return ; + } + if (attorney === null) { + return ; + } + return ( + + + Attorney Dashboard + + {!account.profile_created && ( + + Please set up your profile + + )} + + {/* Active Cases Card */} + + + + + + + Active Cases + + + + {mockAttorneyCases.map((c) => ( + + + + {c.status === 'urgent' ? ( + + ) : ( + + )} + + + + + + ))} + + + + + + {/* Upcoming Deadlines Card */} + + + + + + + Upcoming Deadlines + + + + {mockAttorneyCases + .filter((c) => c.status === 'urgent') + .map((c) => ( + + + + + + + ))} + + + + + + {/* Documents to Review Card */} + + + + + + + Documents Requiring Action + + + + + + + + + + + + + + + + ); +}; + +export default AttorneyDashboard; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/DashboardErrorPage.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/DashboardErrorPage.tsx new file mode 100644 index 0000000..e92d42b --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/DashboardErrorPage.tsx @@ -0,0 +1,41 @@ +import React, { ReactElement } from 'react'; +import { Container, Box, Typography, Button } from '@mui/material'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import RefreshIcon from '@mui/icons-material/Refresh'; + +interface DashboardErrorPageProps { + errorMessage?: string; + onRetry?: () => void; +} + +const DashboardErrorPage = ({ + errorMessage = "We couldn't load your dashboard data.", + onRetry, +}: DashboardErrorPageProps): ReactElement => { + return ( + + + + + Oops! Something went wrong. + + + {errorMessage} Please try again. + + {onRetry && ( + + )} + + + ); +}; + +export default DashboardErrorPage; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/DashboardLoading.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/DashboardLoading.tsx new file mode 100644 index 0000000..8462d96 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/DashboardLoading.tsx @@ -0,0 +1,26 @@ +import React, { ReactElement } from 'react'; +import { Container, Box, CircularProgress, Typography } from '@mui/material'; + +const DashboardLoading = (): ReactElement => { + return ( + + + + + Loading your dashboard... + + + Please wait while we fetch the latest data. + + + + ); +}; + +export default DashboardLoading; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/NotificationInfoCards.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/NotificationInfoCards.tsx new file mode 100644 index 0000000..a940830 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/NotificationInfoCards.tsx @@ -0,0 +1,55 @@ +import { ReactElement } from 'react'; +import { Card, CardContent, CardMedia, Stack, Typography } from '@mui/material'; +import { PropertiesAPI } from 'types'; +import Image from 'components/base/Image'; +import { currencyFormat } from 'helpers/format-functions'; +import MarkUnreadChatAltIcon from '@mui/icons-material/MarkUnreadChatAlt'; + +export const NotificationInfoCard = () => { + return( + + ({ + boxShadow: theme.shadows[4], + width: 1, + height: 'auto', + })} + > + + + + + + + Unread Notifications + + + + Messages + + + Offers + + + + + + + + ) +} + +export default NotificationInfoCard; \ No newline at end of file diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/PropertyOwnerDashboard.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/PropertyOwnerDashboard.tsx new file mode 100644 index 0000000..617cbb2 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/PropertyOwnerDashboard.tsx @@ -0,0 +1,302 @@ +import { AxiosResponse } from 'axios'; +import { ReactElement, useContext, useEffect, useState } from 'react'; +import { PropertiesAPI, UserAPI } from 'types'; +import { axiosInstance } from '../../../../../axiosApi'; +import Grid from '@mui/material/Unstable_Grid2'; +import { + Alert, + Button, + Card, + CardActionArea, + CardContent, + Divider, + Stack, + Typography, +} from '@mui/material'; +import { drawerWidth } from 'layouts/main-layout'; +import { ProperyInfoCards } from '../Property/PropertyInfo'; +import { useNavigate } from 'react-router-dom'; + +import HouseIcon from '@mui/icons-material/House'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import FavoriteIcon from '@mui/icons-material/Favorite'; +import RequestQuoteIcon from '@mui/icons-material/RequestQuote'; +import { EducationInfoCards } from '../Education/EducationInfo'; +import { DataGrid, GridRenderCellParams } from '@mui/x-data-grid'; +import { GridColDef } from '@mui/x-data-grid'; +import PropertyDetailCard from '../Property/PropertyDetailCard'; +import { DashboardProps } from 'pages/home/Dashboard'; + +interface Row { + id: number; +} + +const PropertyOwnerDashboard = ({ account }: DashboardProps): ReactElement => { + const [properties, setProperties] = useState([]); + const [numBids, setNumBids] = useState(0); + const [numOffers, setNumOffers] = useState(0); + + const navigate = useNavigate(); + useEffect(() => { + const fetchProperties = async () => { + try { + const { data }: AxiosResponse = await axiosInstance.get('/properties/'); + if (data.length > 0) { + setProperties(data); + } + } catch (error) { + console.log(error); + } + }; + const fetchOffers = async () => { + try { + const { data }: AxiosResponse = await axiosInstance.get('/offers/'); + if (data.length > 0) { + setNumOffers(data.length); + } + } catch (error) { + console.log(error); + } + }; + + const fetchBids = async () => { + try { + const { data }: AxiosResponse = await axiosInstance.get('/bids/'); + if (data.length > 0) { + setNumBids(data.length); + } + } catch (error) { + console.log(error); + } + }; + fetchProperties(); + fetchOffers(); + fetchBids(); + }, []); + const handleSaveProperty = (updatedProperty: PropertiesAPI) => { + console.log('handle save. IMPLEMENT ME'); + }; + + const handleDeleteProperty = (propertyId: number) => { + console.log('handle delete. IMPLEMENT ME'); + }; + + const documentColumns: GridColDef[] = [ + { + field: 'id', + headerName: 'ID', + }, + { + field: 'title', + headerName: 'Title', + flex: 1, + }, + { + field: 'action', + headerName: 'Action', + flex: 0, + renderCell: (params: GridRenderCellParams) => { + return ( + + ); + }, + }, + ]; + + const DocumentRows = [ + { + id: 1, + title: 'Offer', + }, + { + id: 2, + title: 'Disclousre Form', + }, + ]; + + const numViews = properties.reduce((accum, currProperty) => { + return accum + currProperty.views; + }, 0); + const numSaves = properties.reduce((accum, currProperty) => { + return accum + currProperty.saves; + }, 0); + + return ( + + {/* quick states */} + {!account.profile_created && ( + + + Please set up your profile + + + )} + + navigate('/profile')}> + + + + {properties.length} + Active Listings + + + + + + + + + + {numViews} + Total Views + + + + + + + + + + {numSaves} + Total Saves + + + + + + navigate('/offers')}> + + + + {numOffers} + Total Offers + + + + + + navigate('/bids')}> + + + + {numBids} + Total Bids + + + + + + + + + {/* Properties */} + {properties.map((item) => ( + + + {/* */} + + ))} + + + + + + + Documents Requiring Attention + something + + + + 70} rows={DocumentRows} columns={documentColumns} /> + + + + + + + + Video Progress + + Complete our FSBO training to maximize your sale potential + + + + + + + + + + + + Upgrade your account + + Unlock premium features to get more features and sell faster + + + + + + + + + + {/* + + + + + + + + + + + + + + + + + + + + + + + + */} + + ); +}; + +export default PropertyOwnerDashboard; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/RealEstateAgentDashboard.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/RealEstateAgentDashboard.tsx new file mode 100644 index 0000000..4909615 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/RealEstateAgentDashboard.tsx @@ -0,0 +1,169 @@ +// src/pages/RealEstateAgentDashboardPage.tsx + +import React, { ReactElement } from 'react'; +import { + Container, + Typography, + Box, + Grid, + Card, + CardContent, + Divider, + List, + ListItem, + ListItemText, + ListItemIcon, + Button, +} from '@mui/material'; + +import HomeIcon from '@mui/icons-material/Home'; +import GavelIcon from '@mui/icons-material/Gavel'; +import EventAvailableIcon from '@mui/icons-material/EventAvailable'; +import PersonAddIcon from '@mui/icons-material/PersonAdd'; +import { DashboardProps } from 'pages/home/Dashboard'; + +// Mock Data for the Real Estate Agent Dashboard +interface AgentListing { + id: number; + address: string; + status: 'active' | 'pending'; + offers: number; + views: number; +} + +interface AgentOffer { + id: number; + property_address: string; + offer_amount: number; + offer_date: string; +} + +const mockAgentListings: AgentListing[] = [ + { id: 1, address: '123 Main St, Anytown', status: 'active', offers: 3, views: 250 }, + { id: 2, address: '456 Oak Ave, Anytown', status: 'pending', offers: 1, views: 180 }, + { id: 3, address: '789 Pine Ln, Othertown', status: 'active', offers: 0, views: 50 }, +]; + +const mockAgentOffers: AgentOffer[] = [ + { + id: 1, + property_address: '123 Main St, Anytown', + offer_amount: 510000, + offer_date: 'August 5, 2025', + }, +]; + +const RealEstateAgentDashboard = ({ account }: DashboardProps): ReactElement => { + return ( + + + Agent Dashboard + + + {/* Listings Summary Card */} + + + + + + + My Listings + + + + {mockAgentListings.length} + + Total Active Listings + + + Total Offers: {mockAgentListings.reduce((sum, l) => sum + l.offers, 0)} + + + + + + {/* New Offers Card */} + + + + + + + New Offers + + + {mockAgentOffers.length > 0 ? ( + + {mockAgentOffers.map((offer) => ( + + + + ))} + + ) : ( + No new offers at this time. + )} + + + + + + {/* Upcoming Showings Card */} + + + + + + + Upcoming Appointments + + + + + + + + + + + + + + + + + + + + + + {/* Example of other cards */} + + + + + Listing Performance + + {/* A chart or a list of top-performing listings could go here */} + + 123 Main St is your top-performing listing with 250 views. + + + + + + + ); +}; + +export default RealEstateAgentDashboard; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/VendorDashboard.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/VendorDashboard.tsx new file mode 100644 index 0000000..ba8f7df --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Dashboard/VendorDashboard.tsx @@ -0,0 +1,292 @@ +// src/pages/VendorDashboardPage.tsx + +import React, { ReactElement, useEffect, useState } from 'react'; +import { + Container, + Typography, + Box, + Grid, + Card, + CardContent, + Divider, + Chip, + Alert, +} from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import GavelIcon from '@mui/icons-material/Gavel'; +import ChatBubbleIcon from '@mui/icons-material/ChatBubble'; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import PaidIcon from '@mui/icons-material/Paid'; +import { DashboardProps } from 'pages/home/Dashboard'; +import { BidAPI, ConverationAPI, VendorAPI, VendorItem } from 'types'; +import { AxiosResponse } from 'axios'; +import { axiosInstance } from '../../../../../axiosApi'; +import DashboardLoading from './DashboardLoading'; +import DashboardErrorPage from './DashboardErrorPage'; +import VendorDetail from '../Vendor/VendorDetail'; + +// Mock Data for the Vendor Dashboard +interface VendorDashboardData { + views: { + total: number; + last_30_days: number; + }; + bids: { + total: number; + responded_to: number; + new_bids: number; + selected_for: number; + }; + conversations: { + unread: number; + }; +} + +const mockVendorData: VendorDashboardData = { + views: { + total: 5432, + last_30_days: 215, + }, + bids: { + total: 45, + responded_to: 38, + new_bids: 7, + selected_for: 12, + }, + conversations: { + unread: 3, + }, +}; + +const VendorDashboard = ({ account }: DashboardProps): ReactElement => { + const [vendor, setVendor] = useState(null); + const [loadingData, setLoadingData] = useState(true); + // bid data + const [numBids, setNumBids] = useState(0); + const [numBidResponses, setNumBidResponses] = useState(0); + const [numBidsSelected, setNumBidsSelected] = useState(0); + const [newBids, setNewBids] = useState(0); + const [numConverstaions, setNumConversations] = useState(0); + + const navigate = useNavigate(); + useEffect(() => { + const fetchVendor = async () => { + try { + const { data }: AxiosResponse = await axiosInstance.get('/vendors/'); + if (data.length > 0) { + setVendor(data[0]); + } + } catch (error) { + console.log(error); + } finally { + setLoadingData(false); + } + }; + const fetchBids = async () => { + try { + const { data }: AxiosResponse = await axiosInstance.get('/bids/'); + if (data.length > 0) { + setNumBids(data.length); + let numBidResponsesFound: number = 0; + let numBidsSelectedFound: number = 0; + let newBidsFound: number = 0; + data.map((item) => { + let foundNewBid: boolean = true; + item.responses.map((response) => { + if (response.vendor.user.id === account.id) { + numBidResponsesFound = numBidResponsesFound + 1; + foundNewBid = false; + } + if (response.vendor.user.id === account.id && response.status === 'selected') { + numBidsSelectedFound = numBidsSelectedFound + 1; + } + }); + newBidsFound = foundNewBid ? newBidsFound + 1 : newBidsFound; + }); + + setNumBidResponses(numBidResponsesFound); + setNumBidsSelected(numBidsSelectedFound); + setNewBids(newBidsFound); + } + } catch (error) { + console.log(error); + } + }; + const fetchConversations = async () => { + try { + const { data }: AxiosResponse = + await axiosInstance.get('/conversations/'); + if (data.length > 0) { + setNumConversations(data.length); + } + } catch (error) { + console.log(error); + } finally { + setLoadingData(false); + } + }; + fetchVendor(); + fetchBids(); + fetchConversations(); + }, []); + if (loadingData) { + return ; + } + if (vendor === null) { + return ; + } + + const vendorItem: VendorItem = { + contactPerson: vendor.user.first_name + ' ' + vendor.user.last_name, + name: vendor.business_name, + description: vendor.description, + phone: vendor.phone_number, + email: vendor.user.email, + address: vendor.address, + vendorImageUrl: '', + rating: 5, + servicesOffered: vendor.services, + serviceAreas: vendor.service_areas, + categoryId: vendor.business_type, + latitude: Number(vendor.latitude), + longitude: Number(vendor.longitude), + views: vendor.views, + }; + return ( + + + Vendor Dashboard + + {!account.profile_created && ( + + Please set up your profile + + )} + + {/* Views Card */} + + + + + + + Profile Views + + + + {vendor.views.toLocaleString()} + + + Total Views + + + + {mockVendorData.views.last_30_days} in the last 30 days + + + + + + {/* Bids Card */} + + + + + + + Bids & Opportunities + + + + + + {numBids} + + + Total Bids + + + + + + + + New Bids + + + + + + + + {numBidResponses} + + + Responded To + + + + + {numBidsSelected} + + + Selected For + + + + + + + + {/* Conversations Card */} + + + + + + + Conversations + + + + + {numConverstaions} + + Unread Messages + + + + Stay on top of your messages to win more bids! + + + + + + {/* Example of other cards that could be useful */} + + {/* + + + + + + Recent Payments + + + You have no new payments. + + + */} + + + + + + + + + ); +}; + +export default VendorDashboard; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Education/CategoryGrid.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Education/CategoryGrid.tsx new file mode 100644 index 0000000..1696b02 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Education/CategoryGrid.tsx @@ -0,0 +1,24 @@ +// src/components/CategoryGrid.tsx +import React from 'react'; +import { Grid } from '@mui/material'; +import CategoryCard from './VideoCategory'; +import { VideoCategory } from 'pages/Education/Education'; + +interface CategoryGridProps { + categories: VideoCategory[]; + onSelectCategory: (categoryName: string) => void; +} + +const CategoryGrid: React.FC = ({ categories, onSelectCategory }) => { + return ( + + {categories.map((category) => ( + + + + ))} + + ); +}; + +export default CategoryGrid; \ No newline at end of file diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationInfo.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationInfo.tsx index 5419a56..6051db4 100644 --- a/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationInfo.tsx +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationInfo.tsx @@ -107,16 +107,7 @@ const EducationInfo = ({ title }: EducationInfoProps): ReactElement => { category: "Pricing Strategy", progress: 100, status: "COMPLETED", - }, - - - - - - - - - + }, { id: 6, title: "The Ultimate Home Staging Checklist for FSBO Sellers", diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationTable.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationTable.tsx new file mode 100644 index 0000000..7d2a3dd --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationTable.tsx @@ -0,0 +1,75 @@ +import { Box, Card, CardContent, Divider, LinearProgress, Stack, Typography } from '@mui/material'; + +type EducationVideoColumnCardProps = { + title: string; + category: string; + progressValue: number; + +} + + + +type EducationVideoCategoryColumnCardProps = { + category: string; + videos: Array; +} + +const EducationVideoCategoryColumnCard = ({category, videos}:EducationVideoCategoryColumnCardProps ) => { + return( + + + {category} + + {videos.map(({title, category, progressValue}) => )} + + ) +} + +const EducationVideoColumnCard = ({title, category, progressValue}: EducationVideoColumnCardProps ) => { + return ( + + + + {title} + + + + + ) + // return ( + // + // + // {title} + // + // + // {category} + // + // + + // + // ) +} + +type EducationTableProps = { + videosByCategories: { + category: string; + videos: { + id: number; + title: string; + progressValue: number; + status: string; + }[]; + }[] +} + + +export default EducationVideoCategoryColumnCard; diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationVideoPlayer.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationVideoPlayer.tsx new file mode 100644 index 0000000..145cfa8 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Education/EducationVideoPlayer.tsx @@ -0,0 +1,28 @@ +import { Box, Typography } from '@mui/material'; +import { ReactElement } from 'react'; + +type EducationVideoPlayerProps = { + videoUrl: string | null; + videoTitle: string | null; +} + +const EducationVideoPlayer = ({videoUrl, videoTitle}: EducationVideoPlayerProps) => { + if (videoUrl){ + return ( +

play video here

+ ) + + }else{ + return ( + + + Please select a vido from the list + + + ) + + } + +} + +export default EducationVideoPlayer; \ No newline at end of file diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoCategoryCard.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoCategoryCard.tsx new file mode 100644 index 0000000..48962be --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoCategoryCard.tsx @@ -0,0 +1,45 @@ +// src/components/VideoApp/VideoCategoryCard.tsx +import React from 'react'; +import { Card, CardContent, CardMedia, Typography, Button, LinearProgress, Box } from '@mui/material'; +import { VideoCategory } from 'types'; + + + +interface VideoCategoryCardProps { + category: VideoCategory; + onSelectCategory: (categoryId: string) => void; // Now uses categoryId +} + +const VideoCategoryCard: React.FC = ({ category, onSelectCategory }) => { + console.log(category) + return ( + + + + + {category.name} + + + {category.description} + + + {`${category.completedVideos}/${category.totalVideos} videos completed`} + + {`${category.categoryProgress.toFixed(0)}%`} + + + + + + + ); +}; + +export default VideoCategoryCard; \ No newline at end of file diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoListItem.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoListItem.tsx new file mode 100644 index 0000000..4408090 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoListItem.tsx @@ -0,0 +1,45 @@ +// src/components/VideoApp/VideoListItem.tsx +import React from 'react'; +import { ListItem, ListItemText, Typography, LinearProgress, Box } from '@mui/material'; +import { VideoItem } from 'types'; + + + +interface VideoListItemProps { + video: VideoItem; + isSelected: boolean; + onSelect: (videoId: string) => void; +} + +const VideoListItem: React.FC = ({ video, isSelected, onSelect }) => { + + const percentage: number = Math.round(video.progress/video.duration*100) + return ( + { + console.log('selecting new video') + onSelect(video.id) + }} + sx={{ borderBottom: '1px solid #eee' }} + > + + + {video.description} + + + + {`${percentage.toFixed(0)}% ${video.status === 'completed' ? '(Completed)' : ''}`} + + + } + /> + + ); +}; + +export default VideoListItem; \ No newline at end of file diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoPlayer.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoPlayer.tsx new file mode 100644 index 0000000..baa9d52 --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoPlayer.tsx @@ -0,0 +1,290 @@ +// src/components/VideoApp/VideoPlayer.tsx +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { Box, Typography, Paper, Tooltip, IconButton } from '@mui/material'; +import { AccountContext } from 'contexts/AccountContext'; +import {axiosInstance} from '../../../../../axiosApi'; +import { VideoItem, VideoProgressAPI } from 'types'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import PauseIcon from '@mui/icons-material/Pause'; +import ReplayIcon from '@mui/icons-material/Replay'; +import FullscreenIcon from '@mui/icons-material/Fullscreen'; +import ExitFullscreenIcon from '@mui/icons-material/FullscreenExit'; + +interface VideoProgress { + id?: number; // Optional as it might not exist for new progress entries + user?: number; // Assuming user ID is a number + video?: number; // Video ID + current_time?: number; // Current playback time in seconds + completed?: boolean; + last_watched?: string; // Optional, set by backend + progress: number; +} + + +interface VideoPlayerProps { + video: VideoItem; +} + +const VideoPlayer: React.FC = ({ video }) => { + const {account, accountLoading} = useContext(AccountContext); + + if (!video || accountLoading) { + return ( + + No video selected + + ); + } + // + const videoRef = useRef(null); + const [currentTime, setCurrentTime] = useState(0); + const [isPlaying, setIsPlaying] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(''); + const [isFullScreen, setIsFullScreen] = useState(false); + const debounceTimeoutRef = useRef(null); + + // Function to save progress to the backend + const saveProgress = useCallback(async (time: number, completed: boolean = false) => { + if (!videoRef.current) return; + + const progressData: VideoProgress = { + + progress: Math.round(time), + + }; + + try { + // First, try to fetch existing progress + const response = await axiosInstance.get(`/videos/progress/?user=${account?.id}&video=${video.id}`); + if (response.data.length > 0) { + // If progress exists, update it + const existingProgress = response.data[0]; + await axiosInstance.patch(`/videos/progress/${existingProgress.id}/`, progressData); + } else { + // If no progress, create a new one + await axiosInstance.post('/videos/progress/', progressData); + } + if (completed) { + setSnackbarMessage('Video progress saved: Completed!'); + } else { + setSnackbarMessage(`Video progress saved: ${Math.floor(time)}s`); + } + setSnackbarOpen(true); + } catch (err) { + console.error('Failed to save video progress:', err); + setError('Failed to save video progress.'); + } + }, [video.id, account?.id]); + + // Fetch initial progress when video changes or component mounts + useEffect(() => { + const fetchProgress = async () => { + setIsLoading(true); + setError(null); + try { + const {data,} = await axiosInstance.get(`/videos/progress/${video.id}/?user=${account?.id}`); + + if (data) { + + const progress: VideoProgress = { + current_time: data.progress, + progress: data.progress, + } + setCurrentTime(progress?.current_time || 0); + + if (videoRef.current) { + videoRef.current.currentTime = progress.current_time || 0; + } + setSnackbarMessage(`Resuming from ${Math.floor(progress?.current_time || 0)}s`); + setSnackbarOpen(true); + } else { + setCurrentTime(0); + if (videoRef.current) { + videoRef.current.currentTime = 0; + } + } + } catch (err) { + console.error('Failed to fetch video progress:', err); + setError('Failed to load video progress.'); + } finally { + setIsLoading(false); + } + }; + + fetchProgress(); + + // Cleanup on unmount or video change + return () => { + if (debounceTimeoutRef.current) { + clearTimeout(debounceTimeoutRef.current); + } + // Save final progress when component unmounts or video changes + if (videoRef.current && !videoRef.current.ended) { + saveProgress(videoRef.current.currentTime); + } + }; + }, [video.id, account?.id, saveProgress]); + + + // Video event handlers + const handleTimeUpdate = () => { + if (videoRef.current) { + const newTime = videoRef.current.currentTime; + setCurrentTime(newTime); + + // Debounce progress saving to avoid too many API calls + if (debounceTimeoutRef.current) { + clearTimeout(debounceTimeoutRef.current); + } + debounceTimeoutRef.current = setTimeout(() => { + saveProgress(newTime); + }, 5000); // Save every 5 seconds of playback + } + }; + + const handlePlay = () => { + setIsPlaying(true); + if (videoRef.current) { + videoRef.current.play().catch(e => console.error("Error playing video:", e)); + } + }; + + const handlePause = () => { + setIsPlaying(false); + if (videoRef.current) { + videoRef.current.pause(); + saveProgress(videoRef.current.currentTime); // Save immediately on pause + } + }; + + const handleEnded = () => { + setIsPlaying(false); + saveProgress(video.duration, true); // Mark as completed + }; + + const handleLoadedData = () => { + setIsLoading(false); + // Attempt to play if it was playing before, or if it's the first load + if (videoRef.current && currentTime > 0) { + videoRef.current.currentTime = currentTime; + videoRef.current.play().catch(e => console.error("Error resuming video:", e)); + setIsPlaying(true); + } + }; + + const handleVideoError = (event: React.SyntheticEvent) => { + console.error('Video error:', event); + setError('Error loading video. The file might be missing or corrupted.'); + setIsLoading(false); + }; + + const handleReplay = () => { + if (videoRef.current) { + videoRef.current.currentTime = 0; + setCurrentTime(0); + saveProgress(0, false); // Reset progress + videoRef.current.play(); + setIsPlaying(true); + } + }; + + const toggleFullScreen = () => { + if (!videoRef.current) return; + + if (!document.fullscreenElement) { + videoRef.current.requestFullscreen().catch(err => { + alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`); + }); + } else { + document.exitFullscreen(); + } + }; + + useEffect(() => { + const handleFullScreenChange = () => { + setIsFullScreen(!!document.fullscreenElement); + }; + + document.addEventListener('fullscreenchange', handleFullScreenChange); + return () => { + document.removeEventListener('fullscreenchange', handleFullScreenChange); + }; + }, []); + + const formatTime = (seconds: number) => { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.floor(seconds % 60); + return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`; + }; + + const handleSnackbarClose = (event?: React.SyntheticEvent | Event, reason?: string) => { + if (reason === 'clickaway') { + return; + } + setSnackbarOpen(false); + }; + + + return ( + + {video.name} + {video.description} + + + + + + + + {isPlaying ? : } + + + + + + + + + {formatTime(currentTime)} / {formatTime(video.duration)} + + + + + {isFullScreen ? : } + + + + + Current Progress: {Math.round(video.progress/video.duration*100)}% - Status: {video.status} + + + ); +}; + +export default VideoPlayer; \ No newline at end of file diff --git a/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoPlayerPage.tsx b/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoPlayerPage.tsx new file mode 100644 index 0000000..575143f --- /dev/null +++ b/ditch-the-agent/src/components/sections/dashboard/Home/Education/VideoPlayerPage.tsx @@ -0,0 +1,89 @@ +// src/components/VideoPlayerPage.tsx +import React, { useState, useEffect } from 'react'; +import { Box, Grid, List, ListItem, ListItemText, Typography, Button, Paper, LinearProgress } from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import VideoPlayer from './VideoPlayer'; +import { Video } from 'pages/Education/Education'; + + +interface VideoPlayerPageProps { + categoryName: string; + videos: Video[]; + onBack: () => void; + // You might pass a function to update video status/progress here + // onVideoProgressUpdate: (videoId: string, progress: number, status: 'completed' | 'in-progress') => void; +} + +const VideoPlayerPage: React.FC = ({ categoryName, videos, onBack }) => { + const [selectedVideo, setSelectedVideo] = useState