From daf92b3c944345deb1245f3e10fea00b3dba75ff Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Mon, 11 Mar 2024 13:25:38 -0700 Subject: [PATCH] pushing current edits for publishing --- .hintrc | 15 + package.json | 2 + src/App.js | 65 +- src/Main.jsx | 5 +- src/assets/animations/ZenEnso.jsx | 59 + src/assets/css/index.css | 272 +++- src/assets/themes/base/colors.jsx | 7 + .../themes/base/customColorPalettes.jsx | 94 +- src/assets/themes/base/typography.jsx | 3 +- src/assets/themes/colors/Azure.jsx | 70 + .../themes/components/buttons/contained.jsx | 19 +- src/assets/themes/themeSettings.jsx | 184 --- .../buttons/actionButtons/ActionButton.jsx | 100 ++ .../buttons/actionButtons/AddButton.jsx | 51 - .../actionButtons/GenericActionButtons.jsx | 344 +++-- .../buttons/actionButtons/RemoveButton.jsx | 58 - .../renderFullWidthAddButton.jsx | 303 ----- src/components/buttons/other/AuthSwitch.jsx | 134 +- src/components/buttons/other/CronTrigger.jsx | 46 +- src/components/buttons/other/CustomButton.jsx | 78 +- .../buttons/other/OrderSubmitButton.js | 46 +- src/components/buttons/other/SearchButton.js | 58 +- src/components/cards/CardTextSection.jsx | 119 ++ src/components/cards/GenericCard.jsx | 80 +- src/components/dialogs/CollectionDialog.jsx | 3 +- src/components/dialogs/DeckEditPanel.js | 92 +- src/components/dialogs/LoginDialog.jsx | 445 ++----- .../dialogs/SelectionErrorDialog.jsx | 136 ++ .../dialogs/cardDialog/GenericCardDialog.jsx | 252 ++-- src/components/forms/AddCollectionForm.jsx | 22 +- .../forms/CollectionStatisticsSelector.jsx | 57 - src/components/forms/CustomSelector.js | 168 +-- src/components/forms/LoginForm.jsx | 198 +-- src/components/forms/ProfileForm.jsx | 2 +- src/components/forms/SearchForm.jsx | 45 +- src/components/forms/SignupForm.jsx | 176 +-- src/components/forms/TimeRangeSelector.jsx | 82 -- src/components/forms/UpdateCollectionForm.jsx | 22 +- src/components/forms/UpdateDeckForm.jsx | 12 +- .../{ => forms}/reusable/FormField.jsx | 4 +- .../{ => forms}/reusable/FormTextField.jsx | 0 src/components/forms/reusable/Select.jsx | 97 ++ .../search/SearchComponent.jsx | 2 +- .../{other => forms}/search/SearchResults.jsx | 0 .../search/SearchSettings.jsx | 0 .../CollectionStatisticsSelector.jsx | 37 + .../forms/selectors/ThemeSelector.jsx | 53 + .../forms/selectors/TimeRangeSelector.jsx | 192 +++ .../forms/selectors/useTimeRange.jsx | 54 + .../other/InputComponents/CardNameInput.js | 22 - .../other/InputComponents/UpdateStatusBox.jsx | 100 -- .../InputComponents/UpdateStatusBox2.jsx | 91 -- .../other/dataDisplay/CardCountDisplay.jsx | 42 - .../other/dataDisplay/CartSummary.js | 29 - .../other/dataDisplay/CartTotal.jsx | 10 - .../other/dataDisplay/ProgressCircle.jsx | 23 - src/components/other/dataDisplay/StatBox.jsx | 43 - src/components/other/dataDisplay/StatCard.jsx | 17 - .../other/dataDisplay/chart/LinearChart.js | 18 - src/components/reusable/icons/GlassyIcon.jsx | 92 ++ .../MAIN_CONTEXT/AuthContext/authContext.js | 243 ++-- .../MAIN_CONTEXT/CartContext/CartContext.js | 37 +- .../ChartContext/ChartContext.jsx | 101 +- .../MAIN_CONTEXT/ChartContext/helpers.jsx | 251 +--- .../CollectionContext/CollectionContext.jsx | 126 +- .../CollectionContext/helpers.jsx | 791 +----------- .../CollectionContext/nivoTestData.json | 951 ++++++++++++++ .../useCollectionManager.jsx | 1127 +++++++++------- .../CollectionContext/useCollectionStats.jsx | 84 ++ .../useSelectedCollection.jsx | 508 ++++++++ .../MAIN_CONTEXT/DeckContext/DeckContext.js | 68 +- .../MAIN_CONTEXT/UserContext/UserContext.js | 107 +- .../AppContext/AppContextProvider.jsx | 28 +- .../CombinedContext/CombinedProvider.jsx | 1134 ++++++++--------- .../MISC_CONTEXT/CombinedContext/helpers.jsx | 176 +-- .../CronJobContext/CronJobContext.jsx | 54 +- .../StatisticsContext/StatisticsContext.jsx | 45 +- .../FormContext/FormContext.jsx | 421 ++++-- .../UTILITIES_CONTEXT/FormContext/schemas.jsx | 77 +- .../PageContext/PageContext.jsx | 166 ++- .../UTILITIES_CONTEXT/VisibilityContext.jsx | 38 + src/context/constants.jsx | 364 +++++- src/context/hooks/useCardActions.jsx | 69 +- src/context/hooks/useCardStore.jsx | 22 +- src/context/hooks/useCollectionVisibility.jsx | 62 +- src/context/hooks/useCounter.jsx | 6 +- src/context/hooks/useDialogState.jsx | 31 + src/context/hooks/useFetchWrapper.jsx | 136 +- src/context/hooks/useGet.jsx | 61 + src/context/hooks/useIsFirstRender.jsx | 21 + src/context/hooks/useLoading.jsx | 135 +- src/context/hooks/useLocalStorage.jsx | 53 +- src/context/hooks/useLogger.jsx | 7 +- src/context/hooks/useMasterCardList.jsx | 43 + src/context/hooks/useScreenWidth.jsx | 6 +- src/context/hooks/useSelectionDialog.jsx | 10 +- src/context/index.js | 15 +- src/context/user.jsx | 42 + src/layout/Containers/PageLayout.jsx | 10 +- .../REUSABLE_COMPONENTS/DashboardBox.jsx | 11 + .../HOC/DynamicSnackbar.jsx | 190 +-- .../HOC/withDynamicSnackbar.jsx | 76 ++ src/layout/REUSABLE_COMPONENTS/Icons.jsx | 10 + .../REUSABLE_COMPONENTS/ProgressCircle.jsx | 26 + .../REUSABLE_COMPONENTS/SkeletonVariants.jsx | 65 + src/layout/REUSABLE_COMPONENTS/StatBox.jsx | 48 + .../REUSABLE_COMPONENTS/unique/Badge.jsx | 114 ++ .../unique/ChartWrapper.jsx | 20 + .../unique/FixedHeightCardWrapper.jsx | 18 + .../unique/IconStatWrapper.jsx | 89 ++ .../unique/IconWrapper.jsx | 15 + .../unique/InfoStackWrapper.jsx | 72 ++ .../REUSABLE_COMPONENTS/unique/Overlay.jsx | 32 + .../unique/SimpleButton.jsx | 73 ++ .../REUSABLE_COMPONENTS/unique/SimpleCard.jsx | 152 +++ .../unique/SimplePieChart.jsx | 89 ++ .../unique/SimpleSectionHeader.jsx | 51 + .../unique/uniqueTheme.jsx | 73 ++ .../ReusableStyledComponents.jsx | 264 ++++ .../SpecificStyledComponents.jsx | 52 + src/layout/cart/CartSummary.js | 29 + .../collectionGrids/ChartGridLayout.jsx | 95 +- .../collectionGrids/CollectionListStats.jsx | 23 +- .../CollectionPortfolioChartContainer.jsx | 120 -- .../collectionGrids/StatisticsCardsGrid.jsx | 27 +- .../cards-chart/ChartConfigs.jsx | 376 ++++++ .../cards-chart}/ChartErrorBoundary.jsx | 0 .../CollectionPortfolioChartContainer.jsx | 165 +++ .../cards-chart/GenerateNivoTestData.jsx | 77 ++ .../cards-chart/LinearChart.js | 34 + .../cards-chart/NivoContainer.jsx | 9 + .../cards-chart}/UpdaterAndStatisticsRow.jsx | 23 +- .../cards-datatable/DataTableHeadCell.jsx | 2 +- .../collectionGrids/cards-datatable/index.jsx | 320 +++++ .../cards-datatable/useSkeletonLoader.jsx | 209 +++ .../collections-list/CollectionListItem.jsx | 259 ++-- .../SelectCollectionHeader.jsx | 114 ++ .../collections-list/SelectCollectionList.jsx | 260 ++-- .../collections-list/StatBoard.jsx | 83 ++ .../collections-list/statItems/Header.jsx | 28 + .../collections-list/statItems/PieChart.jsx | 232 ++++ .../statItems/PricedCardList.jsx | 111 ++ .../statItems/TotalPriceStatBox.jsx | 37 + .../statItems/ValuDistributionCircle.jsx | 32 + .../statItems/ValueDistPieChart.jsx | 27 + .../collection/collectionGrids/index.jsx | 240 ---- .../data/collectionPortfolioData.jsx | 32 +- src/layout/collection/data/mockData.jsx | 1131 ++++++++++++++++ src/layout/collection/data/statList.jsx | 29 + src/layout/collection/data/topCards.jsx | 95 ++ src/layout/collection/index.jsx | 581 +++++++-- .../CollectionPortfolioHeader.jsx | 185 ++- .../sub-components}/ComplexStatisticsCard.jsx | 4 +- .../sub-components}/PieChartStats.jsx | 0 .../sub-components/SelectCollectionHeader.jsx | 69 - .../StatisticsDisplaySection.jsx | 2 +- .../sub-components}/TopCardsDisplay.jsx | 4 +- .../sub-components/TopCardsDisplayRow.jsx | 2 +- .../sub-components}/TopFiveExpensiveCards.jsx | 0 .../TotalValueOfCollectionsDisplay.jsx | 0 src/layout/deck/DeckDisplay.js | 2 +- src/layout/deck/index.jsx | 2 +- src/layout/store/StoreSearch.jsx | 2 +- src/pages/CartPage.js | 13 +- src/pages/CollectionPage.js | 39 +- src/pages/DeckBuilderPage.js | 6 +- src/pages/HomePage.js | 52 +- .../ReusableStyledComponents.jsx | 90 -- .../SpecificStyledComponents.jsx | 11 - src/pages/pageStyles/StyledComponents.jsx | 49 +- src/pages/pageStyles/useLoadingAndModal.jsx | 3 +- src/pages/sections/FeatureCardsSection.jsx | 4 +- src/pages/sections/HeroSection.jsx | 387 +++--- src/pages/sections/MainContentSection.jsx | 24 +- src/tests/CardChart.jsx | 26 +- src/zcleanup/AddButton.jsx | 71 ++ src/zcleanup/InputComponents/CardNameInput.js | 22 + .../InputComponents/UpdateStatusBox.jsx | 100 ++ .../InputComponents/UpdateStatusBox2.jsx | 91 ++ src/zcleanup/RemoveButton.jsx | 75 ++ src/zcleanup/dataDisplay/CardCountDisplay.jsx | 42 + src/zcleanup/dataDisplay/CartTotal.jsx | 10 + src/zcleanup/dataDisplay/StatBox.jsx | 43 + src/zcleanup/dataDisplay/StatCard.jsx | 17 + src/zcleanup/renderFullWidthAddButton.jsx | 185 +++ 185 files changed, 13757 insertions(+), 6691 deletions(-) create mode 100644 .hintrc create mode 100644 src/assets/animations/ZenEnso.jsx create mode 100644 src/assets/themes/colors/Azure.jsx create mode 100644 src/components/buttons/actionButtons/ActionButton.jsx delete mode 100644 src/components/buttons/actionButtons/AddButton.jsx delete mode 100644 src/components/buttons/actionButtons/RemoveButton.jsx delete mode 100644 src/components/buttons/actionButtons/renderFullWidthAddButton.jsx create mode 100644 src/components/cards/CardTextSection.jsx create mode 100644 src/components/dialogs/SelectionErrorDialog.jsx delete mode 100644 src/components/forms/CollectionStatisticsSelector.jsx delete mode 100644 src/components/forms/TimeRangeSelector.jsx rename src/components/{ => forms}/reusable/FormField.jsx (88%) rename src/components/{ => forms}/reusable/FormTextField.jsx (100%) create mode 100644 src/components/forms/reusable/Select.jsx rename src/components/{other => forms}/search/SearchComponent.jsx (98%) rename src/components/{other => forms}/search/SearchResults.jsx (100%) rename src/components/{other => forms}/search/SearchSettings.jsx (100%) create mode 100644 src/components/forms/selectors/CollectionStatisticsSelector.jsx create mode 100644 src/components/forms/selectors/ThemeSelector.jsx create mode 100644 src/components/forms/selectors/TimeRangeSelector.jsx create mode 100644 src/components/forms/selectors/useTimeRange.jsx delete mode 100644 src/components/other/InputComponents/CardNameInput.js delete mode 100644 src/components/other/InputComponents/UpdateStatusBox.jsx delete mode 100644 src/components/other/InputComponents/UpdateStatusBox2.jsx delete mode 100644 src/components/other/dataDisplay/CardCountDisplay.jsx delete mode 100644 src/components/other/dataDisplay/CartSummary.js delete mode 100644 src/components/other/dataDisplay/CartTotal.jsx delete mode 100644 src/components/other/dataDisplay/ProgressCircle.jsx delete mode 100644 src/components/other/dataDisplay/StatBox.jsx delete mode 100644 src/components/other/dataDisplay/StatCard.jsx delete mode 100644 src/components/other/dataDisplay/chart/LinearChart.js create mode 100644 src/components/reusable/icons/GlassyIcon.jsx create mode 100644 src/context/MAIN_CONTEXT/CollectionContext/nivoTestData.json create mode 100644 src/context/MAIN_CONTEXT/CollectionContext/useCollectionStats.jsx create mode 100644 src/context/MAIN_CONTEXT/CollectionContext/useSelectedCollection.jsx create mode 100644 src/context/UTILITIES_CONTEXT/VisibilityContext.jsx create mode 100644 src/context/hooks/useDialogState.jsx create mode 100644 src/context/hooks/useGet.jsx create mode 100644 src/context/hooks/useIsFirstRender.jsx create mode 100644 src/context/hooks/useMasterCardList.jsx create mode 100644 src/context/user.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/DashboardBox.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/HOC/withDynamicSnackbar.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/Icons.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/ProgressCircle.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/SkeletonVariants.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/StatBox.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/Badge.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/ChartWrapper.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/FixedHeightCardWrapper.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/IconStatWrapper.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/IconWrapper.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/InfoStackWrapper.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/Overlay.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/SimpleButton.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/SimpleCard.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/SimplePieChart.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/SimpleSectionHeader.jsx create mode 100644 src/layout/REUSABLE_COMPONENTS/unique/uniqueTheme.jsx create mode 100644 src/layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents.jsx create mode 100644 src/layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx create mode 100644 src/layout/cart/CartSummary.js delete mode 100644 src/layout/collection/collectionGrids/CollectionPortfolioChartContainer.jsx create mode 100644 src/layout/collection/collectionGrids/cards-chart/ChartConfigs.jsx rename src/{components/reusable => layout/collection/collectionGrids/cards-chart}/ChartErrorBoundary.jsx (100%) create mode 100644 src/layout/collection/collectionGrids/cards-chart/CollectionPortfolioChartContainer.jsx create mode 100644 src/layout/collection/collectionGrids/cards-chart/GenerateNivoTestData.jsx create mode 100644 src/layout/collection/collectionGrids/cards-chart/LinearChart.js create mode 100644 src/layout/collection/collectionGrids/cards-chart/NivoContainer.jsx rename src/layout/collection/{sub-components => collectionGrids/cards-chart}/UpdaterAndStatisticsRow.jsx (56%) create mode 100644 src/layout/collection/collectionGrids/cards-datatable/index.jsx create mode 100644 src/layout/collection/collectionGrids/cards-datatable/useSkeletonLoader.jsx create mode 100644 src/layout/collection/collectionGrids/collections-list/SelectCollectionHeader.jsx create mode 100644 src/layout/collection/collectionGrids/collections-list/StatBoard.jsx create mode 100644 src/layout/collection/collectionGrids/collections-list/statItems/Header.jsx create mode 100644 src/layout/collection/collectionGrids/collections-list/statItems/PieChart.jsx create mode 100644 src/layout/collection/collectionGrids/collections-list/statItems/PricedCardList.jsx create mode 100644 src/layout/collection/collectionGrids/collections-list/statItems/TotalPriceStatBox.jsx create mode 100644 src/layout/collection/collectionGrids/collections-list/statItems/ValuDistributionCircle.jsx create mode 100644 src/layout/collection/collectionGrids/collections-list/statItems/ValueDistPieChart.jsx delete mode 100644 src/layout/collection/collectionGrids/index.jsx create mode 100644 src/layout/collection/data/mockData.jsx create mode 100644 src/layout/collection/data/statList.jsx create mode 100644 src/layout/collection/data/topCards.jsx rename src/{components/other/dataDisplay => layout/collection/sub-components}/ComplexStatisticsCard.jsx (96%) rename src/{components/other/dataDisplay => layout/collection/sub-components}/PieChartStats.jsx (100%) delete mode 100644 src/layout/collection/sub-components/SelectCollectionHeader.jsx rename src/{components/other/dataDisplay => layout/collection/sub-components}/StatisticsDisplaySection.jsx (98%) rename src/{components/other/dataDisplay => layout/collection/sub-components}/TopCardsDisplay.jsx (97%) rename src/{components/other/dataDisplay => layout/collection/sub-components}/TopFiveExpensiveCards.jsx (100%) rename src/{components/other/dataDisplay => layout/collection/sub-components}/TotalValueOfCollectionsDisplay.jsx (100%) delete mode 100644 src/pages/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents.jsx delete mode 100644 src/pages/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx create mode 100644 src/zcleanup/AddButton.jsx create mode 100644 src/zcleanup/InputComponents/CardNameInput.js create mode 100644 src/zcleanup/InputComponents/UpdateStatusBox.jsx create mode 100644 src/zcleanup/InputComponents/UpdateStatusBox2.jsx create mode 100644 src/zcleanup/RemoveButton.jsx create mode 100644 src/zcleanup/dataDisplay/CardCountDisplay.jsx create mode 100644 src/zcleanup/dataDisplay/CartTotal.jsx create mode 100644 src/zcleanup/dataDisplay/StatBox.jsx create mode 100644 src/zcleanup/dataDisplay/StatCard.jsx create mode 100644 src/zcleanup/renderFullWidthAddButton.jsx diff --git a/.hintrc b/.hintrc new file mode 100644 index 0000000..6746605 --- /dev/null +++ b/.hintrc @@ -0,0 +1,15 @@ +{ + "extends": [ + "development" + ], + "hints": { + "compat-api/css": [ + "default", + { + "ignore": [ + "backdrop-filter" + ] + } + ] + } +} \ No newline at end of file diff --git a/package.json b/package.json index 25cfc95..1f140a9 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@mui/x-data-grid": "^6.16.2", "@mui/x-date-pickers": "^6.10.1", "@nivo/line": "^0.83.0", + "@nivo/pie": "^0.84.0", "@stripe/react-stripe-js": "^2.1.1", "@stripe/stripe-js": "^1.54.2", "auth0-js": "^9.22.1", @@ -31,6 +32,7 @@ "material-ui-image": "^3.3.2", "moment": "^2.29.4", "notistack": "^3.0.1", + "polished": "^4.3.1", "react": "^17.0.0 || ^18.0.0", "react-cookie": "^4.1.1", "react-device-detect": "^2.2.3", diff --git a/src/App.js b/src/App.js index 9f9f6d9..20a69a0 100644 --- a/src/App.js +++ b/src/App.js @@ -6,13 +6,10 @@ import { FormProvider, ModalProvider, PopoverProvider, - SocketProvider, UserProvider, useMode, CollectionProvider, - CombinedProvider, CardProvider, - CronJobProvider, DeckProvider, CartProvider, CardImagesProvider, @@ -24,13 +21,14 @@ import { usePageContext, ErrorBoundary, ConfiguratorProvider, + VisibilityProvider, } from './context'; import { ThemeProvider } from 'styled-components'; import { SnackbarProvider, useSnackbar } from 'notistack'; - import { useNavigate } from 'react-router-dom'; import { CssBaseline, GlobalStyles } from '@mui/material'; import { ParallaxProvider } from 'react-scroll-parallax'; +import { useLoading } from './context/hooks/useLoading'; // ==============================|| APP ||============================== // @@ -39,12 +37,13 @@ const App = () => { const navigate = useNavigate(); const { resetLogoutTimer, logout, authUser, userId, isLoggedIn } = useAuthContext(); - const { loadingStatus, returnDisplay, setLoading, setError } = - usePageContext(); - useEffect(() => { - if (!isLoggedIn && !loadingStatus.isPageLoading) navigate('/login'); - }, [isLoggedIn, navigate, loadingStatus.isPageLoading]); - if (loadingStatus?.isPageLoading || loadingStatus?.error) { + const { returnDisplay } = usePageContext(); + const { isLoading, isPageLoading, error } = useLoading(); + + // useEffect(() => { + // if (!isLoggedIn && !isPageLoading) navigate('/login'); + // }, [isLoggedIn, navigate, isPageLoading]); + if (isPageLoading || error) { return returnDisplay(); } return ( @@ -54,41 +53,37 @@ const App = () => { - - + + - - - - - - - - - - -
- - - - - - - - - - + + + + + + + + +
+ + + + + + + + - - + + diff --git a/src/Main.jsx b/src/Main.jsx index 069d24d..f58dade 100644 --- a/src/Main.jsx +++ b/src/Main.jsx @@ -9,6 +9,7 @@ import Navigation from './layout/navigation/Navigation.jsx'; import LoadingIndicator from './components/reusable/indicators/LoadingIndicator.js'; import Configurator from './layout/REUSABLE_COMPONENTS/Configurator/index.jsx'; import { useCardStoreHook } from './context/hooks/useCardStore.jsx'; +import { AppContainer } from './pages/pageStyles/StyledComponents.jsx'; // Define all routes in a single array including the component name for laziness const ROUTE_CONFIG = [ @@ -39,7 +40,7 @@ const Main = () => { const { isLoggedIn } = useAuthContext(); const { isConfiguratorOpen, toggleConfigurator } = useConfiguratorContext(); return ( - + <> {!isLoggedIn ? ( ) : ( @@ -71,7 +72,7 @@ const Main = () => { )} - + ); }; diff --git a/src/assets/animations/ZenEnso.jsx b/src/assets/animations/ZenEnso.jsx new file mode 100644 index 0000000..e659de6 --- /dev/null +++ b/src/assets/animations/ZenEnso.jsx @@ -0,0 +1,59 @@ +import React, { useRef, useEffect } from 'react'; +import * as THREE from 'three'; + +const ZenEnso = () => { + const mountRef = useRef(null); + + useEffect(() => { + const width = mountRef.current.clientWidth; + const height = mountRef.current.clientHeight; + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0xffffff); + const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); + camera.position.z = 5; + + const renderer = new THREE.WebGLRenderer(); + renderer.setSize(width, height); + mountRef.current.appendChild(renderer.domElement); + + const points = []; + const detail = 100; + const radius = 2; + const incompleteFactor = 0.85; // Making the circle incomplete + const variance = 0.2; // Increased randomness + + for (let i = 0; i <= detail * incompleteFactor; i++) { + const ratio = i / detail; + const angle = 2 * Math.PI * ratio; + const randomVariance = 1 + Math.random() * variance - variance / 2; + const x = radius * Math.cos(angle) * randomVariance; + const y = radius * Math.sin(angle) * randomVariance; + points.push(new THREE.Vector3(x, y, 0)); + } + + const geometry = new THREE.BufferGeometry().setFromPoints(points); + const material = new THREE.LineBasicMaterial({ + color: 0x000000, + linewidth: 2, + }); // Slight width + const enso = new THREE.Line(geometry, material); + + scene.add(enso); + + const animate = () => { + requestAnimationFrame(animate); + enso.rotation.z += 0.01; // Rotation + renderer.render(scene, camera); + }; + + animate(); + + return () => { + mountRef.current.removeChild(renderer.domElement); + }; + }, []); + + return
; +}; + +export default ZenEnso; diff --git a/src/assets/css/index.css b/src/assets/css/index.css index 5568980..5ff2073 100644 --- a/src/assets/css/index.css +++ b/src/assets/css/index.css @@ -11,6 +11,11 @@ --primary: #6a59ff; --white: #ffffff; --bg: #f5f5f5; + --button-bg-color: #1976d2; /* Original color, but let's introduce a blue scheme */ + --button-hover-bg-color: #4a6da7; /* Subtle blue for hover state */ + --button-hover-border-color: #34597f; /* A darker blue for border on hover */ + --button-border-color: #6a59ff; /* Initial border color */ + --button-focus-outline-color: #62a4ff; /* Bright blue for focus outline for accessibility */ } html { @@ -18,7 +23,7 @@ html { scroll-behavior: smooth; } -@media (min-width: 1440px) { +/* @media (min-width: 1440px) { html { zoom: 1.5; } @@ -34,10 +39,10 @@ html { html { zoom: 2.5; } -} +} */ ::-webkit-scrollbar { - width: 1.3rem; + width: 0.1rem; } ::-webkit-scrollbar-thumb { @@ -55,40 +60,184 @@ html { } body { - font-size: 1.6rem; + font-size: 2.6rem; background: var(--bg); } +form { + border-radius: 15px; + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + background-color: rgba(255, 255, 255, 0.13); + justify-content: center; + align-items: center; + padding: 20px; +} + +input { + width: 100%; + padding: 10px 15px; + border-radius: 5px; + border: 1px solid #ccc; +} + +button { + padding: 15px 0; + font-size: 18px; + font-weight: 600; + border-radius: 5px; + border: none; + background: #ffffff; + color: #080710; + cursor: pointer; + transition: background 0.3s ease; +} + +.loading-button { + background-color: var(--button-bg-color); + border-color: var(--button-border-color); + border-width: 2px; + border-style: solid; /* Ensure border style is defined */ + display: flex; /* Assuming you want to use flex properties */ + flex-grow: 1; + align-items: center; /* Vertically center content */ + justify-content: center; /* Horizontally center content */ + margin-left: auto; + margin-right: auto; + margin-bottom: 16px; /* Assuming 'my: 2' meant a vertical margin, adjust as needed */ + margin-top: 16px; + position: relative; /* Assuming you want it positioned in a specific context */ + bottom: 0; /* Keep at the bottom, adjust as necessary */ + cursor: pointer; /* Indicates an interactive button */ + transition: + background-color 0.3s, + border-color 0.3s, + color 0.3s; /* Smooth transition for hover and focus states */ +} + +.loading-button:hover { + font-weight: bold; + background-color: var(--button-hover-bg-color); + border-color: var(--button-hover-border-color); +} + +.loading-button:focus { + outline: 2px solid var(--button-focus-outline-color); /* Accessible focus outline */ + outline-offset: 2px; /* Ensures outline does not overlap element's border */ +} + +.dialog-login { + border-radius: 15px; + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + background-color: rgba(255, 255, 255, 0.13); + justify-content: center; + align-items: center; + padding: 20px; +} +/* --------------------------------- */ +/* PAGES - home sections */ +/* --------------------------------- */ +.hero-section { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: '100%'; + /* padding: 60px; */ +} + +.hero-section-container { + background: rgba(189, 181, 181, 0.1); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 25px; + /* padding: 30px 0px; */ + width: '100vw'; + /* width: min(1200px, 100%); */ +} + +/* .hero-section h1 { + font-size: 3.5rem; + font-weight: 600; + /* color: var(--primary); */ +/* text-align: center; + margin: 20px 0px 40px; +} */ + +/* --------------------------------- */ +/* Swiper */ +/* --------------------------------- */ + .container { - max-width: 124rem; + /* max-width: 124rem; */ + /* max-width: 100vw; */ padding: 4rem 1rem; margin: 0 auto; } -.heading { +.hero-section-title { padding: 1rem 0; font-size: 3.5rem; text-align: center; + font-weight: 600; } -.swiper_container { - height: 52rem; - padding: 2rem 0; - position: relative; +.hero-section-subtitle { + padding: 1rem 0; + font-size: 2rem; + text-align: center; + font-weight: 400; } -.swiper-slide { - width: 37rem; - height: 42rem; - position: relative; +.hero-section-card-title { + padding: 1rem 0; + font-size: 2rem; + text-align: center; + font-weight: 600; } -/* .swiper-slide { + +.hero-section-card-subtitle { + padding: 1rem 0; + font-size: 1.5rem; + text-align: center; + font-weight: 400; +} + +.swiper { + width: 100%; + height: 100%; + margin-bottom: 1rem; +} + +.swiper_container { + width: 100vw; /* Adjust based on your design needs */ + overflow: hidden; /* Prevent overflow */ +} +.swiper-scrollbar { + --swipeer-scrollbar-bottom: 0; + --swipeer-scrollbar-drag-bg-color: #dda3b6; + --swipeer-scrollbar-drag-bg-opacity: 0.2; + --swipeer-scrollbar-drag-bg-radius: 0; + --swipeer-scrollbar-size: 5px; +} +.swiper-slide { + width: calc( + (100vw - 120px) / 7 + ); /* Adjust based on actual space between and number of slides */ + height: auto; /* Adjust height as needed */ display: flex; justify-content: center; align-items: center; - /* Adjust the height as needed */ - /* height: auto; /* Change from a fixed height to auto */ -/* } */ +} +/* .swiper-slide { + width: auto; /* Adjust based on your content */ +/* height: auto; Adjust based on your content */ + +/* Add more styling as needed */ +/* } */ @media (max-width: 500px) { .swiper_container { @@ -104,24 +253,91 @@ body { } } +@media (max-width: 768px) { + .swiper_container { + width: 100%; /* Adjust for smaller screens */ + } + .swiper-slide { + width: 30rem; + height: 36rem; + /* height: auto; */ + /* Adjust the height as needed */ + } + .swiper-slide img { + width: 30rem; + height: 36rem; + } +} + +@media (max-width: 1024px) { + .swiper_container { + width: 100%; /* Adjust for smaller screens */ + } + .swiper-slide { + width: 40rem; + height: 48rem; + /* height: auto; */ + /* Adjust the height as needed */ + } + .swiper-slide img { + width: 30rem; + height: 36rem; + } +} + +@media (max-width: 1280px) { + .swiper_container { + width: 100%; /* Adjust for smaller screens */ + } + .swiper-slide { + width: 30rem; + height: 36rem; + /* height: auto; */ + /* Adjust the height as needed */ + } + .swiper-slide img { + width: 30rem; + height: 36rem; + } +} + +@media (max-width: 1440px) { + .swiper_container { + width: 100%; /* Adjust for smaller screens */ + } + .swiper-slide { + width: 30rem; + height: 36rem; + /* height: auto; */ + /* Adjust the height as needed */ + } + .swiper-slide img { + width: 30rem; + height: 36rem; + } +} + .swiper-slide img { width: 37rem; height: 42rem; border-radius: 2rem; object-fit: cover; + /* maxWidth: '100%', + maxHeight: '100%', + objectFit: 'contain', */ } -/* .swiper-slide img { - width: auto; - height: 100%; - max-height: 400px; - object-fit: contain; - border-radius: 2rem; -} */ + .swiper-slide-shadow-left, .swiper-slide-shadow-right { display: none; } +.swiper-slide-active { + transform: scale(2); /* Make the active slide larger */ + transition: transform 0.3s; /* Smooth transition */ + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* Add a shadow */ +} + .slider-controler { position: relative; bottom: 2rem; @@ -184,8 +400,8 @@ body { .swiper-pagination { /* position: relative; */ - position: absolute; - text-align: center; + position: absolute; + text-align: center; width: 15rem !important; bottom: 1rem; } @@ -197,5 +413,3 @@ body { .swiper-pagination .swiper-pagination-bullet-active { background: var(--primary); } - - diff --git a/src/assets/themes/base/colors.jsx b/src/assets/themes/base/colors.jsx index 5eb0054..3bd6619 100644 --- a/src/assets/themes/base/colors.jsx +++ b/src/assets/themes/base/colors.jsx @@ -4,6 +4,9 @@ import { backgroundB, backgroundE, backgroundF, + backgroundG, + backgroundGSecondary, + chartTheme, rarity, info, warning, @@ -14,6 +17,7 @@ import { action, } from './customColorPalettes'; const colors = { + chartTheme: chartTheme, // PRIMARY COLORS backgroundA, backgroundD: { @@ -41,6 +45,9 @@ const colors = { // TERTIARY COLORS backgroundE, backgroundF, + backgroundG, + backgroundGSecondary, + // COLORS FOR CARD RARITY OVERLAY rarity, // CARD RARITY OVERLAYS diff --git a/src/assets/themes/base/customColorPalettes.jsx b/src/assets/themes/base/customColorPalettes.jsx index ea3b2a0..931a134 100644 --- a/src/assets/themes/base/customColorPalettes.jsx +++ b/src/assets/themes/base/customColorPalettes.jsx @@ -23,7 +23,7 @@ const success = { hoverContrastText: '#111', }; const info = { - main: '#0d5d96', + main: '#3781F1', // main: '#2196f3', dark: '#1976d2', @@ -113,6 +113,95 @@ const backgroundF = { lightest: hexToRgba(backgroundE.lightest, 0.3), }; +const backgroundG = { + darkest: '#073b4cff', // --midnight-green-- + darker: '#0c637fff', // --cerulean-- + dark: '#118ab2ff', // --blue-ncs-- + default: '#0cb0a9ff', // --light-sea-green-- + light: '#06d6a0ff', // --emerald-- + lighter: '#91dacbff', // --tiffany-blue-- + lightest: '#c8ede5ff', // --mint-green-- + contrastText: '#fff', +}; +const backgroundGSecondary = { + darkest: '#7f2e2eff', // --persian-plum-- + darker: '#a63c3cff', // --pomegranate-- + dark: '#cc4a4aff', // --flamingo-- + default: '#e55e5eff', // --sunset-orange-- + light: '#f4755fff', // --bittersweet-- + lighter: '#f89a7dff', // --rajah-- + lightest: '#facbb0ff', // --navajo-white-- + contrastText: '#fff', +}; + +const grey = { + darkest: '#141414', + darkert: '#292929', + dark: '#3d3d3d', + default: '#525252', + light: '#666666', + lighter: '#858585', + lightest: '#a3a3a3', + evenLighter: '#c2c2c2', + contrastText: '#e0e0e0', +}; + +const primary = { + darkest: '#040509', + darker: '#040509', + dark: '#040509', + default: '#f2f0f0', + light: '#141b2d', + lighter: '#1F2A40', + lightest: '#727681', + evenLighter: '#a1a4ab', + contrastText: '#e0e0e0', +}; + +const greenAccent = { + darkest: '#0f2922', // Assuming this is the darkest + darker: '#1e5245', // Next darker shade + dark: '#2e7c67', // Next dark shade + default: '#3da58a', // Default considered here as the mid-point + light: '#4cceac', // Light shade + lighter: '#70d8bd', // Lighter shade + lightest: '#94e2cd', // Lightest shade + evenLighter: '#b7ebde', // Even lighter than the lightest + contrastText: '#dbf5ee', // Most contrasting or lightest, could be adjusted +}; + +const redAccent = { + darkest: '#2c100f', + darker: '#58201e', + dark: '#832f2c', + default: '#af3f3b', + light: '#db4f4a', + lighter: '#e2726e', + lightest: '#e99592', + evenLighter: '#f1b9b7', + contrastText: '#f8dcdb', +}; + +const blueAccent = { + darkest: '#151632', + darker: '#2a2d64', + dark: '#3e4396', + default: '#535ac8', + light: '#6870fa', + lighter: '#868dfb', + lightest: '#a4a9fc', + evenLighter: '#c3c6fd', + contrastText: '#e1e2fe', +}; + +const chartTheme = { + primary, + grey, + greenAccent, + redAccent, + blueAccent, +}; + const rarity = { common: '#C0C0C0', // Silver uncommon: '#B8860B', // DarkGoldenRod @@ -131,12 +220,15 @@ const rarity = { }; export { + chartTheme, backgroundA, backgroundB, backgroundC, backgroundD, backgroundE, backgroundF, + backgroundG, + backgroundGSecondary, rarity, error, warning, diff --git a/src/assets/themes/base/typography.jsx b/src/assets/themes/base/typography.jsx index 059ab98..846a45e 100644 --- a/src/assets/themes/base/typography.jsx +++ b/src/assets/themes/base/typography.jsx @@ -22,7 +22,8 @@ const { dark } = colors; // fontSize3XL: pxToRem(30), // }; const baseProperties = { - fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', + // fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', + fontFamily: 'Poppins, sans-serif', fontWeightLighter: 100, fontWeightLight: 300, fontWeightRegular: 400, diff --git a/src/assets/themes/colors/Azure.jsx b/src/assets/themes/colors/Azure.jsx new file mode 100644 index 0000000..bfb5af3 --- /dev/null +++ b/src/assets/themes/colors/Azure.jsx @@ -0,0 +1,70 @@ +// /* CSS HEX */ +// --yale-blue: #173667ff; +// --polynesian-blue: #214c91ff; +// --sapphire: #2557a5ff; +// --true-blue: #2d6ac7ff; +// --azure: #3781f1ff; +// --silver-lake-blue: #668cc8ff; +// --powder-blue: #b8ceefff; + +const azure = { + darkest: '#173667ff', // --yale-blue-- + darker: '#214c91ff', // --polynesian-blue-- + dark: '#2557a5ff', // --sapphire-- + default: '#2d6ac7ff', // --true-blue-- + // test1: '#3276dcff', // --crayola-- + light: '#3781f1ff', // --azure-- + // test2: '#4f87ddff', // --chafchaouen-blue-- + lighter: '#668cc8ff', // --silver-lake-blue-- + // test3: '#8faddcff', // --vista-blue-- + lightest: '#b8ceefff', // --powder-blue-- + contrastText: '#fff', + lightContrastText: '#d6e3f5', // --lavendar-- + secondaryContrastText: '#173667', +}; + +const emerald = { + darkest: '#073b4cff', // --midnight-green-- + darker: '#0c637fff', // --cerulean-- + dark: '#118ab2ff', // --blue-ncs-- + default: '#0cb0a9ff', // --light-sea-green-- + light: '#06d6a0ff', // --emerald-- + lighter: '#91dacbff', // --tiffany-blue-- + lightest: '#c8ede5ff', // --mint-green-- + contrastText: '#fff', +}; + +const coral = { + darkest: '#7f2e2eff', // --persian-plum-- + darker: '#a63c3cff', // --pomegranate-- + dark: '#cc4a4aff', // --flamingo-- + default: '#e55e5eff', // --sunset-orange-- + light: '#f4755fff', // --bittersweet-- + lighter: '#f89a7dff', // --rajah-- + lightest: '#facbb0ff', // --navajo-white-- + contrastText: '#fff', +}; + +const defalt = { + id: 'lightBlue', + borderRadius: '0.75rem', + colorBackground: '#f4f4f8', + colorNavbar: '#151939', + // colorNavbarLabel: rgba('white', 0.65), + // colorNavbarLink: colorTextForDark, + colorText: '#343239', + colorLabel: '#A4A3A6', + colorBorder: '#f0f0f9', + colorPrimary: '#06d6a0ff', + // colorPrimaryText: colorTextForDark, + colorAccent: '#c8ede5ff', + // colorAccentText: colorTextForDark, + colorCardBackground: '#ffffff', + colorDefaultBackground: '#ececf0', + colorDefaultText: '#73707C', + colorDisabledBackground: '#d5d5e3', + colorDisabledText: '#bebed0', + colorCode: '#a5a5a5', + colorChartShading: '#696969', + boxShadowLogo: 'none', +}; diff --git a/src/assets/themes/components/buttons/contained.jsx b/src/assets/themes/components/buttons/contained.jsx index 83e0df8..44f48e2 100644 --- a/src/assets/themes/components/buttons/contained.jsx +++ b/src/assets/themes/components/buttons/contained.jsx @@ -3,12 +3,21 @@ import colors from '../../base/colors'; import typography from '../../base/typography'; import pxToRem from '../../functions/pxToRem'; -const { white, text, info, secondary, success, green, backgroundE } = colors; +const { + white, + text, + info, + secondary, + success, + green, + backgroundE, + backgroundG, +} = colors; const { size } = typography; const contained = { base: { - backgroundColor: green.main, + backgroundColor: backgroundG.default, minHeight: pxToRem(40), color: text.main, padding: `${pxToRem(10)} ${pxToRem(24)}`, @@ -49,14 +58,14 @@ const contained = { }, primary: { - backgroundColor: green.main, + backgroundColor: backgroundG.default, '&:hover': { - backgroundColor: green.main, + backgroundColor: backgroundG.dark, }, '&:focus:not(:hover)': { - backgroundColor: green.focus, + backgroundColor: backgroundG.light, }, }, diff --git a/src/assets/themes/themeSettings.jsx b/src/assets/themes/themeSettings.jsx index d57a8ab..7d2f144 100644 --- a/src/assets/themes/themeSettings.jsx +++ b/src/assets/themes/themeSettings.jsx @@ -77,190 +77,6 @@ export const themeSettings = (mode) => { '0px 5px 15px rgba(0,0,0,0.1)', // example for theme.shadows[10] ], typography, - // STYLING FOR REUSABLE COMPONENTS - responsiveStyles: { - // BREAKPOINT GENERAL VALUES: UP/DOWN - isXSmall: (breakpoints) => breakpoints.down('xs'), - isSmall: (breakpoints) => breakpoints.down('sm'), - isSmallMedium: (breakpoints) => breakpoints.up('sm'), - isMedium: (breakpoints) => breakpoints.down('md'), - isMediumLarge: (breakpoints) => breakpoints.up('md'), - isLarge: (breakpoints) => breakpoints.up('lg'), - - // BREAKPOINT SPECIFIC VALUES: UP - isSmUp: (breakpoints) => breakpoints.up('sm'), - isMdUp: (breakpoints) => breakpoints.up('md'), - isLgUp: (breakpoints) => breakpoints.up('lg'), - isXlUp: (breakpoints) => breakpoints.up('xl'), - - // BREAKPOINT SPECIFIC VALUES: DOWN - isSmDown: (breakpoints) => breakpoints.down('sm'), - isMdDown: (breakpoints) => breakpoints.down('md'), - isLgDown: (breakpoints) => breakpoints.down('lg'), - isXlDown: (breakpoints) => breakpoints.down('xl'), - - getTypographyVariant: (breakpoints) => { - if (breakpoints.down('xs')) return 'h4'; - if (breakpoints.down('sm')) return 'h3'; - if (breakpoints.down('md')) return 'h2'; - return 'h2'; - }, - - getButtonTypographyVariant: (breakpoints) => { - if (breakpoints.down('xs')) return 'body1'; - if (breakpoints.down('sm')) return 'body2'; - if (breakpoints.down('md')) return 'body3'; - if (breakpoints.up('lg')) return 'body3'; - return 'body1'; - }, - - getButtonTypographyVariant2: (breakpoints) => { - if (breakpoints.down('xs')) return 'h6'; - if (breakpoints.down('sm')) return 'h6'; - if (breakpoints.down('md')) return 'h6'; - if (breakpoints.up('lg')) return 'body1'; - return 'body1'; - }, - - getProductGridContainerStyle: { - // maxWidth: 'lg', - // maxHeight: '100%', - // display: 'flex', - // flexDirection: 'column', - marginTop: 4, // theme.spacing(4) - }, - - getHeaderStyle: (breakpoints) => { - if (breakpoints.down('xs')) { - return { - fontWeight: 'bold', - color: 'text.primary', - marginBottom: 2, // theme.spacing(2) - fontSize: '1.25rem', - }; - } - if (breakpoints.down('sm')) { - return { - fontWeight: 'bold', - color: 'text.primary', - marginBottom: 2, // theme.spacing(2) - fontSize: '1.5rem', - }; - } - if (breakpoints.down('md')) { - return { - fontWeight: 'bold', - color: 'text.primary', - marginBottom: 2, // theme.spacing(2) - fontSize: '1.75rem', - }; - } - return { - fontWeight: 'bold', - color: 'text.primary', - marginBottom: 2, // theme.spacing(2) - fontSize: '1.75rem', - }; - }, - getIconForTitle: (title) => { - switch (title) { - case 'Deck Builder': - return ; - case 'Collection Tracker': - return ; - case 'Store': - return ; - default: - return null; - } - }, - }, - genericButtonStyles: { - contextText: { - color: 'text.primary', // Use color from theme - fontWeight: 'bold', - fontSize: '0.8rem', - '@media (min-width:600px)': { - fontSize: '1rem', - }, - }, - addButton: { - color: 'success.contrastText', // Use color from theme - backgroundColor: 'success.main', // Use color from theme - '&:hover': { - backgroundColor: 'success.darker', // Use color from theme - }, - marginRight: 0.5, // Use spacing from theme - }, - removeButton: { - color: 'error.contrastText', // Use color from theme - backgroundColor: 'error.main', // Use color from theme - '&:hover': { - backgroundColor: 'error.dark', // Use color from theme - }, - marginRight: 0.5, // Use spacing from theme - }, - actionRow: { - display: 'flex', - alignItems: 'center', - width: '100%', - flexWrap: 'nowrap', - }, - featureCardButton: { - color: 'success.contrastText', // Use color from theme - }, - circleButtonContainer: { - display: 'flex', - justifyContent: 'space-around', - alignItems: 'center', - width: '100%', - padding: 1, // Use spacing from theme - gap: 1, // Use spacing from theme - }, - }, - pageStyles: { - // styles.js - homeCardStyles: { - display: 'flex', - flexDirection: 'column', - height: '100%', - }, - homeTypographyStyles: { - fontWeight: 'bold', - marginBottom: '1rem', - fontSize: '2.5rem', - lineHeight: '1.2', - letterSpacing: 'normal', - }, - homePaperStyles: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - mx: 'auto', - padding: '1rem', - background: backgroundC.dark, - maxWidth: specialBreakpoints.isLgUp ? '100%' : '100%', // Adjust width based on screen size - }, - homeCardAnimationBoxStyles: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - maxWidth: '100%', - maxHeight: '100%', - padding: '1rem', - }, - homeCardChartBoxStyles: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - maxWidth: specialBreakpoints.isMdUp ? '50%' : '100%', // Adjust width based on screen size - maxHeight: '100%', - }, - }, // LAYOUTS skeletonLayouts: { tertiaryContent: { diff --git a/src/components/buttons/actionButtons/ActionButton.jsx b/src/components/buttons/actionButtons/ActionButton.jsx new file mode 100644 index 0000000..148b7e1 --- /dev/null +++ b/src/components/buttons/actionButtons/ActionButton.jsx @@ -0,0 +1,100 @@ +import { LoadingButton } from '@mui/lab'; +import { useMode } from '../../../context'; +import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import { useLoading } from '../../../context/hooks/useLoading'; +import AddCircleOutlineOutlined from '@mui/icons-material/AddCircleOutlineOutlined'; +import RemoveCircleOutlineOutlined from '@mui/icons-material/RemoveCircleOutlineOutlined'; + +// Determines the label and variant based on button size and action +const getLabelAndVariant = (buttonSize, labelValue, action) => { + const labelTypeMap = { + extraSmall: null, + small: action, + medium: action, + large: labelValue, + }; + const buttonVariantMap = { + extraSmall: 'body4', + small: 'body3', + medium: 'body2', + large: 'body4', + }; + return { + buttonLabel: labelTypeMap[buttonSize], + buttonVariant: buttonVariantMap[buttonSize], + }; +}; + +// Generic Action Button Component +const ActionButton = ({ + buttonSize, + handleCardAction, + labelValue, + actionType, + card, +}) => { + const { theme } = useMode(); + const { isLoading } = useLoading(); + const { buttonLabel, buttonVariant } = getLabelAndVariant( + buttonSize, + labelValue, + actionType + ); + + // Select icon based on action type + const actionIcon = + actionType === 'add' ? ( + + ) : ( + + ); + const loadingKey = + actionType === 'add' ? 'addCardsToCollection' : 'removeCardsFromCollection'; + + return ( + handleCardAction('add', card) + : () => handleCardAction('remove', card) + } + startIcon={actionIcon} + sx={{ + width: '100%', + flexGrow: 1, + borderRadius: theme.shape.borderRadius, + maxWidth: '100%', + justifyContent: 'center', + alignItems: 'center', + display: 'flex', + backgroundColor: + labelValue === 'add' + ? theme.palette.success.main + : theme.palette.error.main, + '&:hover': { + backgroundColor: + labelValue === 'add' + ? theme.palette.success.dark + : theme.palette.error.dark, + }, + }} + > + + {String(labelValue)} {/* Force conversion to string */} + + + ); +}; + +export default ActionButton; diff --git a/src/components/buttons/actionButtons/AddButton.jsx b/src/components/buttons/actionButtons/AddButton.jsx deleted file mode 100644 index fcb7bcc..0000000 --- a/src/components/buttons/actionButtons/AddButton.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { LoadingButton } from '@mui/lab'; -import MDButton from '../../../layout/REUSABLE_COMPONENTS/MDBUTTON'; // Assuming MDButton is used elsewhere or can be removed if not needed -import AddIcon from '@mui/icons-material/Add'; // Make sure this import is used if you need it elsewhere in your component or remove it if it's unused -import { AddCircleOutlineOutlined } from '@mui/icons-material'; -import { useMode } from '../../../context'; -import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; -import { getContextIcon } from '../../reusable/icons'; - -// Styled add button -const AddButton = ({ - onClick, // Make sure to use this if you need it for onClick events or replace it in the onClick prop below - buttonSize, - isLoading, - handleCardAction, - buttonLabel, - buttonVariant, - labelValue, -}) => { - const { theme } = useMode(); - const currentContextIcon = getContextIcon(labelValue); - - return ( - handleCardAction('add')} - startIcon={} - sx={{ - width: '100%', - flexGrow: 1, - borderRadius: theme.shape.borderRadius, - maxWidth: '100%', - backgroundColor: theme.palette.success.main, - justifyContent: 'center', - alignItems: 'center', - display: 'flex', - }} - > - - {buttonLabel} - - - ); -}; - -export default AddButton; diff --git a/src/components/buttons/actionButtons/GenericActionButtons.jsx b/src/components/buttons/actionButtons/GenericActionButtons.jsx index b2a3630..aad8ca1 100644 --- a/src/components/buttons/actionButtons/GenericActionButtons.jsx +++ b/src/components/buttons/actionButtons/GenericActionButtons.jsx @@ -1,99 +1,279 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { Box } from '@mui/material'; +import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import AddButton from '../../../zcleanup/AddButton'; +import RemoveButton from '../../../zcleanup/RemoveButton'; import { useModalContext } from '../../../context/UTILITIES_CONTEXT/ModalContext/ModalContext'; -import { - Button, - Dialog, - DialogTitle, - DialogContent, - DialogActions, -} from '@mui/material'; -import { useCollectionStore } from '../../../context/MAIN_CONTEXT/CollectionContext/CollectionContext'; +import useSelectedContext from '../../../context/hooks/useSelectedContext'; +import { getContextIcon } from '../../../components/reusable/icons/index'; +// import { +// useCollectionManager, +// useSelectedCollection, +// } from '../../../context/MAIN_CONTEXT/CollectionContext'; import { useDeckStore } from '../../../context/MAIN_CONTEXT/DeckContext/DeckContext'; -import { useSelectionDialog } from '../../../context/hooks/useSelectionDialog'; -import { useAppContext, useMode } from '../../../context'; -import { renderFullWidthAddButton } from './renderFullWidthAddButton'; +import { useCartStore } from '../../../context/MAIN_CONTEXT/CartContext/CartContext'; +import { DEFAULT_COLLECTION } from '../../../context/constants'; +import { useCardActions } from '../../../context/hooks/useCardActions'; +import useCollectionManager from '../../../context/MAIN_CONTEXT/CollectionContext/useCollectionManager'; +import useSelectedCollection from '../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import ActionButton from './ActionButton'; +import { useSnackbar } from 'notistack'; +import GlassyIcon from '../../../components/reusable/icons/GlassyIcon'; +import MDBox from '../../../layout/REUSABLE_COMPONENTS/MDBOX'; + +// Utility function for mapping cardSize to buttonSize +const buttonSizeMap = { + xs: 'extraSmall', + sm: 'small', + md: 'medium', + lg: 'large', +}; + const GenericActionButtons = ({ card, - context = context || context?.pageContext, - onClick, // New onClick prop for handling context selection + context = 'Collection', + onClick, onSuccess, onFailure, page, - cardSize, + cardSize = 'md', }) => { - if (typeof context === 'undefined') { - context = 'Collection'; - } - const [isLoadingApiResponse, setIsLoadingApiResponse] = React.useState(false); - const { closeModal, isModalOpen, setModalOpen } = useModalContext(); - const { theme } = useMode(); - const { selectedCollection, allCollections } = useCollectionStore(); - const { selectedDeck, allDecks } = useDeckStore(); - const { selectDialogOpen, itemsForSelection, setSelectDialogOpen } = - useSelectionDialog( - context, - selectedCollection, - selectedDeck, - allCollections, - allDecks - ); - const [buttonSize, setButtonSize] = React.useState('medium'); - const [isLoading, setIsLoading] = React.useState(false); - const renderSelectionDialog = () => ( - setSelectDialogOpen(false)}> - - Select a {context} - - - - {itemsForSelection?.map((item) => ( - - ))} - - - + const { enqueueSnackbar } = useSnackbar(); // Add this line to use Notistack + const { addOneToCollection, removeOneFromCollection } = + useCollectionManager(); + const { selectedCollection, allCollections, handleSelectCollection } = + useSelectedCollection(); + const { + addOneToDeck, + removeOneFromDeck, + selectedDeck, + allDecks, + setSelectedDeck, + } = useDeckStore(); + const { addOneToCart, removeOneFromCart, cartData } = useCartStore(); + const [buttonSize, setButtonSize] = useState( + buttonSizeMap[cardSize] || 'medium' ); - const labelValue = - typeof context === 'string' ? context : context?.pageContext; + const { closeModal } = useModalContext(); useEffect(() => { - const buttonSizeMap = { - xs: 'extraSmall', - sm: 'small', - md: 'medium', - lg: 'large', // Adjust if there's another size you want for 'l' - }; - const size = buttonSizeMap[cardSize] || 'medium'; // Default to 'medium' if size is not defined - setButtonSize(size); + setButtonSize(buttonSizeMap[cardSize] || 'medium'); }, [cardSize]); + const addActions = useMemo( + () => ({ + Collection: addOneToCollection, + Deck: addOneToDeck, + Cart: addOneToCart, + }), + [addOneToCollection, addOneToDeck, addOneToCart] + ); + const removeActions = useMemo( + () => ({ + Collection: removeOneFromCollection, + Deck: removeOneFromDeck, + Cart: removeOneFromCart, + }), + [removeOneFromCollection, removeOneFromDeck, removeOneFromCart] + ); + const handleAction = useCallback( + async (action, cardData, currentContext) => { + if (!cardData) { + console.error('No card data provided.'); + enqueueSnackbar('Action failed.', { variant: 'error' }); + return; + } + console.log( + `Action: ${action}, Card: ${cardData?.name}, Context: ${currentContext}` + ); + // Dynamic action handling + if (action === 'add' && addActions[currentContext]) { + addActions[currentContext](cardData); + } else if (action === 'remove' && removeActions[currentContext]) { + removeActions[currentContext](cardData); + } + // Hook into success or failure callbacks as necessary + // onSuccess(); + // onFailure(); + }, + [card, context, addActions, removeActions] + ); return ( - - {renderSelectionDialog()} - {renderFullWidthAddButton( - isLoading, - buttonSize, - isModalOpen, - labelValue, - cardSize, - context, - card, - page, - onClick, - closeModal, - setIsLoading, - setIsLoadingApiResponse, - onSuccess, - onFailure - )} - + handleAction('add', card, context)} + /> + ); +}; + +const ActionButtons = ({ + buttonSize, + card, + context, + page, + handleCardAction, +}) => { + // console.log( + // `ActionButtons: buttonSize: ${buttonSize}, card: ${card?.name}, context: ${context}` + // ); + const labelValue = + typeof context === 'string' ? context : context?.pageContext; + const stackDirection = buttonSize === 'extraSmall' ? 'column' : 'row'; + const currentContextIcon = getContextIcon(labelValue); + + return ( + + + + + + + + + + + ); }; export default GenericActionButtons; + +// import React, { useCallback, useEffect } from 'react'; +// import { useModalContext } from '../../../context/UTILITIES_CONTEXT/ModalContext/ModalContext'; +// import { renderFullWidthAddButton } from './renderFullWidthAddButton'; +// import useSelectedContext from '../../../context/hooks/useSelectedContext'; + +// const GenericActionButtons = ({ +// card, +// context = context || context?.pageContext, +// onClick, // New onClick prop for handling context selection +// onSuccess, +// onFailure, +// page, +// cardSize, +// }) => { +// if (typeof context === 'undefined') { +// context = 'Collection'; +// } +// const { closeModal, isModalOpen, setModalOpen } = useModalContext(); +// const { selectedCollection, allCollections } = useSelectedContext(); +// const [buttonSize, setButtonSize] = React.useState('medium'); + +// const labelValue = +// typeof context === 'string' ? context : context?.pageContext; +// useEffect(() => { +// const buttonSizeMap = { +// xs: 'extraSmall', +// sm: 'small', +// md: 'medium', +// lg: 'large', // Adjust if there's another size you want for 'l' +// }; +// const size = buttonSizeMap[cardSize] || 'medium'; // Default to 'medium' if size is not defined +// setButtonSize(size); +// }, [cardSize]); + +// return ( +// +// {renderFullWidthAddButton( +// buttonSize, +// isModalOpen, +// labelValue, +// cardSize, +// context, +// card, +// page, +// onClick, +// closeModal, +// onSuccess, +// onFailure +// )} +// +// ); +// }; + +// export default GenericActionButtons; + +// // const renderSelectionDialog = () => ( +// // setSelectDialogOpen(false)}> +// // +// // +// // Select a {context} +// // +// // +// // +// // +// // {itemsForSelection?.map((item) => ( +// // +// // ))} +// // +// // +// // +// // ); diff --git a/src/components/buttons/actionButtons/RemoveButton.jsx b/src/components/buttons/actionButtons/RemoveButton.jsx deleted file mode 100644 index b956fd9..0000000 --- a/src/components/buttons/actionButtons/RemoveButton.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import { LoadingButton } from '@mui/lab'; -import MDButton from '../../../layout/REUSABLE_COMPONENTS/MDBUTTON'; -import RemoveIcon from '@mui/icons-material/Remove'; -import { useMode } from '../../../context'; -import { RemoveCircleOutlineOutlined } from '@mui/icons-material'; -import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; -import { getContextIcon } from '../../reusable/icons'; -// Styled remove button -const RemoveButton = ({ - onClick, - buttonSize, - isLoading, - handleCardAction, - buttonLabel, - buttonVariant, - labelValue, -}) => { - const { theme } = useMode(); - const currentContextIcon = getContextIcon(labelValue); - - return ( - handleCardAction('remove')} - // getButtonLabel={getButtonLabel} - startIcon={} - sx={{ - width: '100%', // Button grows to fill the container - flexGrow: 1, // Grow to fill the parent container - // minWidth: buttonSize === 'small' ? '25px' : '100px', // Ensure buttons have a minimum width - backgroundColor: theme.palette.error.main, - borderRadius: theme.shape.borderRadius, // Use theme values for consistent styling - // width: '100%', // Button grows to fill the container - // minWidth: '100px', // Ensure buttons have a minimum width - maxWidth: '100%', // Ensure buttons have a maximum width - justifyContent: 'center', - alignItems: 'center', - display: 'flex', - // p: 1, - '&:hover': { - backgroundColor: theme.palette.error.dark, - }, - }} - > - - {buttonLabel} - - - ); -}; - -export default RemoveButton; diff --git a/src/components/buttons/actionButtons/renderFullWidthAddButton.jsx b/src/components/buttons/actionButtons/renderFullWidthAddButton.jsx deleted file mode 100644 index 4f3efa8..0000000 --- a/src/components/buttons/actionButtons/renderFullWidthAddButton.jsx +++ /dev/null @@ -1,303 +0,0 @@ -import React, { useCallback, useEffect } from 'react'; -import { - Box, - Stack, - Button, - Grid, - Paper, - Card, - Typography, -} from '@mui/material'; -import { - AddCircleOutlineOutlined, - RemoveCircleOutlineOutlined, -} from '@mui/icons-material'; -import LoadingButton from '@mui/lab/LoadingButton'; // Assuming you're using MUI Lab for LoadingButton -import { useMode } from '../../../context'; -import MDBox from '../../../layout/REUSABLE_COMPONENTS/MDBOX'; -import iconsWithButton, { - getContextIcon, -} from '../../../components/reusable/icons/index'; -import AddButton from './AddButton'; -import RemoveButton from './RemoveButton'; -import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; -import { useCardActions } from '../../../context/hooks/useCardActions'; -import { useCollectionStore } from '../../../context/MAIN_CONTEXT/CollectionContext/CollectionContext'; -import { useDeckStore } from '../../../context/MAIN_CONTEXT/DeckContext/DeckContext'; -import { useCartStore } from '../../../context/MAIN_CONTEXT/CartContext/CartContext'; -import { useSelectionDialog } from '../../../context/hooks/useSelectionDialog'; - -export const renderFullWidthAddButton = ( - isLoading, - buttonSize, - isModalOpen, - labelValue, - cardSize, - context, - card, - page, - onClick, - closeModal, - setIsLoading, - setIsLoadingApiResponse, - onSuccess, - onFailure -) => { - const { theme } = useMode(); - const [buttonLabel, setButtonLabel] = React.useState('Add'); - const [buttonVariant, setButtonVariant] = React.useState('button'); - const labelTypeMap = { - extraSmall: null, - small: 'Add', - medium: 'Add', - large: `Add to ${labelValue}`, - }; - const buttonVariantMap = { - extraSmall: 'body4', - small: 'body3', - medium: 'body2', - large: 'body4', - }; - useEffect(() => { - const label = labelTypeMap[buttonSize]; - // console.log('SETTING BUTTON LABEL', label); - setButtonLabel(label); - const variant = buttonVariantMap[buttonSize]; - // console.log('SETTING BUTTON VARIANT', variant); - setButtonVariant(variant); - }, [buttonSize]); - const currentContextIcon = getContextIcon(labelValue); - const stackDirection = buttonSize === 'extraSmall' ? 'column' : 'row'; - const { addButton, removeButton, actionRow, circleButtonContainer } = - theme.genericButtonStyles; - const { - addOneToCollection, - removeOneFromCollection, - selectedCollection, - allCollections, - setSelectedCollection, - } = useCollectionStore(); - const { - addOneToDeck, - removeOneFromDeck, - selectedDeck, - allDecks, - setSelectedDeck, - } = useDeckStore(); - const { addOneToCart, removeOneFromCart, cartData } = useCartStore(); - useEffect(() => { - if (context === 'Deck' && !selectedDeck && allDecks?.length > 0) { - console.warn('No deck selected. Defaulting to first deck.'); - setSelectedDeck(allDecks[0]); - } - if ( - context === 'Collection' && - !selectedCollection && - allCollections.length > 0 - ) { - console.warn('No collection selected. Defaulting to first collection.'); - setSelectedCollection(allCollections[0]); - } - }, [ - context, - selectedDeck, - selectedCollection, - allDecks, - allCollections, - setSelectedDeck, - setSelectedCollection, - ]); - const { performAction, count } = useCardActions( - context, - card, - selectedCollection, - selectedDeck, - addOneToCollection, - removeOneFromCollection, - addOneToDeck, - removeOneFromDeck, - addOneToCart, - removeOneFromCart, - onSuccess, - onFailure, - page - ); - const handleCardAction = useCallback( - async (actionType) => { - console.log('SET LOADING FOR ', actionType); - - setIsLoading(true); - setIsLoadingApiResponse(true); - onClick?.(); - performAction(actionType); - closeModal?.(); - try { - // Simulate API call with a timeout - await new Promise((resolve) => setTimeout(resolve, 500)); - - if (actionType === 'add') { - console.log(`Adding ${card.name} to ${labelValue}`); - - onSuccess?.(); - } else if (actionType === 'remove') { - console.log(`Removing ${card.name} from ${labelValue}`); - performAction(actionType); - - onSuccess?.(); - } - } catch (error) { - console.error('Action failed:', error); - onFailure?.(error); - } finally { - setIsLoading(false); - setIsLoadingApiResponse(false); - closeModal(); - } - }, - [card, labelValue, onSuccess, onFailure, closeModal] - ); - - return ( - - {/* */} - {/* */} - {/* */} - {/* - - - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - - {' '} - {currentContextIcon} - - {/* */} - {/* */} - {/* */} - {/* */} - - - - - {/* */} - - {/* */} - {/* */} - {/* */} - {/* */} - {/* - - - */} - {/* - */} - - ); -}; diff --git a/src/components/buttons/other/AuthSwitch.jsx b/src/components/buttons/other/AuthSwitch.jsx index 5d9dc1f..6f76bf7 100644 --- a/src/components/buttons/other/AuthSwitch.jsx +++ b/src/components/buttons/other/AuthSwitch.jsx @@ -1,33 +1,125 @@ import React from 'react'; -import { Switch, FormControlLabel, FormControl } from '@mui/material'; -import { useMode } from '../../../context'; // Adjust with actual path -const AuthSwitch = ({ signupMode, toggleAuthMode, formLabel }) => { +import { renderToStaticMarkup } from 'react-dom/server'; + +import { Switch, FormControlLabel, FormControl, alpha } from '@mui/material'; +import { useFormContext, useMode } from '../../../context'; // Adjust with actual path +import { AuthModeSwitch } from '../../../layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents'; +import LoginIcon from '@mui/icons-material/Login'; +import PersonAddIcon from '@mui/icons-material/PersonAdd'; +import styled from 'styled-components'; +import { useCookies } from 'react-cookie'; +const convertSvg = (svg) => { + const markup = renderToStaticMarkup(svg); + const encoded = encodeURIComponent(markup); + const dataUri = `url('data:image/svg+xml;utf8,${encoded}')`; + return dataUri; +}; +const AuthSwitch = () => { + // const AuthSwitch = ({ signupMode, formLabel }) => { const { theme } = useMode(); // Ensures theme is applied correctly + // const colors = theme; + const colors = theme.palette.chartTheme; + const cookies = useCookies('colorMode'); + const mode = cookies.colorMode; + + const primary = colors.primary.default; + const blue = colors.blueAccent.default; + const green = colors.greenAccent.light; + const lihhtgreen = colors.greenAccent.default; + const greenliht = colors.greenAccent.light; + const lightgrey = colors.grey.light; + const darkgrey = colors.grey.dark; + const darkestgrey = colors.grey.darkest; + + const { currentSchemaKey, toggleForm } = useFormContext(); // Use toggleForm for toggling modes + const signupMode = currentSchemaKey === 'signupForm'; + const handleToggle = () => { + toggleForm(currentSchemaKey === 'loginForm' ? 'signupForm' : 'loginForm'); + }; + const switchBaseStyles = { + '& .MuiSwitch-switchBase': { + padding: 1, // Center the switch base padding for proper alignment + '&.Mui-checked': { + transform: 'translateX(16px)', // Ensure this aligns with your switch size + color: theme.palette.common.white, // Thumb color when checked + '& + .MuiSwitch-track': { + backgroundColor: theme.palette.primary.main, // Track color when checked + }, + }, + }, + }; + const thumbStyles = { + '& .MuiSwitch-thumb': { + width: 22, // Adjust thumb size for better visual alignment + height: 22, // Adjust thumb size for better visual alignment + backgroundColor: mode === 'dark' ? green : lihhtgreen, + // backgroundColor: theme.palette.common.white, // Default thumb color + + '&:before': { + content: '" "', + display: 'block', + backgroundImage: signupMode + ? convertSvg() + : convertSvg(), + width: '100%', + height: '100%', + backgroundSize: '50%', + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', + }, + // '&:after': { + // right: 12, // Adjust the right icon position + // backgroundImage: convertSvg(), + // }, + }, + }; + const trackStyles = { + '& .MuiSwitch-track': { + backgroundColor: theme.palette.chartTheme.grey.light, // Default track color + opacity: 1, + width: 50, // Adjust thumb size for better visual alignment + height: 14, // Adjust thumb size for better visual alignment + // '&:before, &:after': { + // content: '""', + // position: 'absolute', + // top: '50%', + // transform: 'translateY(-50%)', + // }, + // '&:before': { + // left: 12, // Adjust the left icon position + // backgroundImage: convertSvg(), + // }, + // '&:after': { + // right: 12, // Adjust the right icon position + // backgroundImage: convertSvg(), + // }, + }, + }; + const switchStyles = { + ...switchBaseStyles, + ...thumbStyles, + ...trackStyles, + // backgroundImage: convertSvg(), + // right: 2, + }; return ( - + } - // label={formLabel} - style={{ - margin: theme.spacing(1), // Provide some spacing - justifyContent: 'space-between', // Align items nicely - }} + label={signupMode ? 'Sign Up' : 'Log In'} + style={{ margin: 'auto', justifyContent: 'center' }} // Center label and switch /> ); diff --git a/src/components/buttons/other/CronTrigger.jsx b/src/components/buttons/other/CronTrigger.jsx index 2e746f4..9710b37 100644 --- a/src/components/buttons/other/CronTrigger.jsx +++ b/src/components/buttons/other/CronTrigger.jsx @@ -1,27 +1,27 @@ -import React from 'react'; -import { Box, Button } from '@mui/material'; -import { useCombinedContext } from '../../../context/CombinedProvider'; -import { useAuthContext } from '../../../context'; +// import React from 'react'; +// import { Box, Button } from '@mui/material'; +// import { useCombinedContext } from '../../../context/CombinedProvider'; +// import { useAuthContext } from '../../../context'; -const CronTrigger = () => { - const { stopCronJob, handleSendAllCardsInCollections, listOfMonitoredCards } = - useCombinedContext(); - const { userId } = useAuthContext(); - const handleTriggerCron = () => { - console.log('TRIGGERING CRON JOB TO UPDATE: ' + listOfMonitoredCards); - handleSendAllCardsInCollections(userId, listOfMonitoredCards); - }; +// const CronTrigger = () => { +// const { stopCronJob, handleSendAllCardsInCollections, listOfMonitoredCards } = +// useCombinedContext(); +// const { userId } = useAuthContext(); +// const handleTriggerCron = () => { +// console.log('TRIGGERING CRON JOB TO UPDATE: ' + listOfMonitoredCards); +// handleSendAllCardsInCollections(userId, listOfMonitoredCards); +// }; - const handleStopCron = () => { - stopCronJob(); - }; +// const handleStopCron = () => { +// stopCronJob(); +// }; - return ( - - - - - ); -}; +// return ( +// +// +// +// +// ); +// }; -export default CronTrigger; +// export default CronTrigger; diff --git a/src/components/buttons/other/CustomButton.jsx b/src/components/buttons/other/CustomButton.jsx index 27aeed4..272b23a 100644 --- a/src/components/buttons/other/CustomButton.jsx +++ b/src/components/buttons/other/CustomButton.jsx @@ -1,43 +1,43 @@ -import React from 'react'; -import { Button } from '@mui/material'; -import { useMode } from '../../../context'; // Adjust the import path based on your project structure -import MDButton from '../../../layout/REUSABLE_COMPONENTS/MDBUTTON'; +// import React from 'react'; +// import { Button } from '@mui/material'; +// import { useMode } from '../../../context'; // Adjust the import path based on your project structure +// import MDButton from '../../../layout/REUSABLE_COMPONENTS/MDBUTTON'; -const CustomButton = ({ - text, - onClick, - variant = 'contained', - size = 'large', - sx = {}, -}) => { - const { theme } = useMode(); +// const CustomButton = ({ +// text, +// onClick, +// variant = 'contained', +// size = 'large', +// sx = {}, +// }) => { +// const { theme } = useMode(); - const defaultStyles = { - background: theme.palette.backgroundE.darker, - borderColor: theme.palette.backgroundB.darkest, - borderWidth: 2, - mx: 1, - width: '70%', - '&:hover': { - fontWeight: 'bold', - background: theme.palette.backgroundF.dark, - borderColor: theme.palette.backgroundB.darkest, - border: `1px solid ${theme.palette.backgroundB.darkest}`, - }, - ...sx, // Allow custom styles to override defaults - }; +// const defaultStyles = { +// background: theme.palette.backgroundE.darker, +// borderColor: theme.palette.backgroundB.darkest, +// borderWidth: 2, +// mx: 1, +// width: '70%', +// '&:hover': { +// fontWeight: 'bold', +// background: theme.palette.backgroundF.dark, +// borderColor: theme.palette.backgroundB.darkest, +// border: `1px solid ${theme.palette.backgroundB.darkest}`, +// }, +// ...sx, // Allow custom styles to override defaults +// }; - return ( - - {text} - - ); -}; +// return ( +// +// {text} +// +// ); +// }; -export default CustomButton; +// export default CustomButton; diff --git a/src/components/buttons/other/OrderSubmitButton.js b/src/components/buttons/other/OrderSubmitButton.js index 43bf387..e990dfd 100644 --- a/src/components/buttons/other/OrderSubmitButton.js +++ b/src/components/buttons/other/OrderSubmitButton.js @@ -1,25 +1,25 @@ -import React from 'react'; -import { Button } from '@mui/material'; -import { useMode } from '../../../context'; +// import React from 'react'; +// import { Button } from '@mui/material'; +// import { useMode } from '../../../context'; -const OrderSubmitButton = ({ onClick }) => { - const { theme } = useMode(); - return ( - - ); -}; +// const OrderSubmitButton = ({ onClick }) => { +// const { theme } = useMode(); +// return ( +// +// ); +// }; -export default OrderSubmitButton; +// export default OrderSubmitButton; diff --git a/src/components/buttons/other/SearchButton.js b/src/components/buttons/other/SearchButton.js index a06ead0..fcd05ae 100644 --- a/src/components/buttons/other/SearchButton.js +++ b/src/components/buttons/other/SearchButton.js @@ -1,32 +1,32 @@ -import React from 'react'; -import { Button, Grid } from '@mui/material'; -import { useMode } from '../../../context'; +// import React from 'react'; +// import { Button, Grid } from '@mui/material'; +// import { useMode } from '../../../context'; -const SearchButton = ({ searchParams, handleSubmit }) => { - // const { handleRequest } = useCardStore(); - const { theme } = useMode(); +// const SearchButton = ({ searchParams, handleSubmit }) => { +// // const { handleRequest } = useCardStore(); +// const { theme } = useMode(); - return ( - - - - ); -}; +// return ( +// +// +// +// ); +// }; -export default SearchButton; +// export default SearchButton; diff --git a/src/components/cards/CardTextSection.jsx b/src/components/cards/CardTextSection.jsx new file mode 100644 index 0000000..2000a90 --- /dev/null +++ b/src/components/cards/CardTextSection.jsx @@ -0,0 +1,119 @@ +import React from 'react'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import { useMode } from '../../../context'; +import FlexBetween from '../../layout/REUSABLE_COMPONENTS/FlexBetween'; +import MDBox from '../../layout/REUSABLE_COMPONENTS/MDBOX'; + +// Updated SimpleSectionHeader component with additional parameters +const CardTextSection = ({ + name, + price, + cartQuantity, + collectionQuantity, + deckQuantity, + cardSet, + setCode, + cardRarity, +}) => { + const { theme } = useMode(); + return ( + + {/* CARD TITLE SECTION: name */} + + {name} + + {/* CARD TITLE SUBSECTION: set name, rarity, set code */} + + {`Set: ${cardSet}`} + + {cardRarity} + + + {setCode} + + + {/* CARD TITLE SUBSECTION B: context quantities, price, price change */} + + + + {`Cart: ${cartQuantity}`} + + + {`Collection: ${collectionQuantity}`} + + + {`Deck: ${deckQuantity}`} + + + + + {`Price: ${price}`} + + {`Price Change: ${price}`} + + + + + + ); +}; + +export default CardTextSection; diff --git a/src/components/cards/GenericCard.jsx b/src/components/cards/GenericCard.jsx index f75af85..6f9be82 100644 --- a/src/components/cards/GenericCard.jsx +++ b/src/components/cards/GenericCard.jsx @@ -26,6 +26,9 @@ import { getQuantity } from '../componentHelpers'; import { useMode } from '../../context'; import { useAppContext } from '../../context'; import MDTypography from '../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import { enqueueSnackbar } from 'notistack'; +import useSelectedContext from '../../context/hooks/useSelectedContext'; +import useSelectedCollection from '../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; const GenericCard = React.forwardRef((props, ref) => { const { card, context, page } = props; const { theme } = useMode(); @@ -42,33 +45,31 @@ const GenericCard = React.forwardRef((props, ref) => { else if (width >= 219) setCardSize('lg'); } }; - - // Measure the card on the first render measureCard(); - // Add resize event listener to re-measure when window size changes window.addEventListener('resize', measureCard); - - // Cleanup function to remove the event listener return () => { window.removeEventListener('resize', measureCard); }; }, []); const { cartData } = useCartStore(); - const { selectedCollection, allCollections } = useCollectionStore(); + const { selectedCollection, allCollections } = useSelectedCollection(); const { selectedDeck, allDecks } = useDeckStore(); + const { setContext, setIsContextSelected } = useSelectedContext(); + const { isCardInContext } = useAppContext(); + // const { isCardInContext } = useAppContext(); - const isCardInContext = useCallback( - (selectedCollection, selectedDeck, cartData, context, card) => { - const cardsList = { - Collection: selectedCollection?.cards, - Deck: selectedDeck?.cards, - Cart: cartData?.cart, - }; - return !!cardsList[context]?.find((c) => c?.id === card?.id); - }, - [context, selectedCollection, selectedDeck, cartData] - ); + // const isCardInContext = useCallback( + // (selectedCollection, selectedDeck, cartData, context, card) => { + // const cardsList = { + // Collection: selectedCollection?.cards, + // Deck: selectedDeck?.cards, + // Cart: cartData?.cart, + // }; + // return !!cardsList[context]?.find((c) => c?.id === card?.id); + // }, + // [context, selectedCollection, selectedDeck, cartData] + // ); const { openModalWithCard, setModalOpen, setClickedCard, isModalOpen } = useModalContext(); const { setHoveredCard, setIsPopoverOpen, hoveredCard } = @@ -86,16 +87,17 @@ const GenericCard = React.forwardRef((props, ref) => { }, [setHoveredCard, setIsPopoverOpen, card] ); + const handleContextSelect = useCallback( + (newContext) => { + setContext(newContext); + setIsContextSelected(true); + }, + [setContext, setIsContextSelected] + ); useEffect(() => { setIsPopoverOpen(hoveredCard === card); }, [hoveredCard, card, setIsPopoverOpen]); - const isInContext = isCardInContext( - selectedCollection, - selectedDeck, - cartData, - context, - card - ); + const isInContext = isCardInContext(card); const name = card?.name; const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; const price = `Price: ${ @@ -167,7 +169,6 @@ const GenericCard = React.forwardRef((props, ref) => { /> {cardContent} - {/* */} { handleContextSelect(context)} + onSuccess={() => + enqueueSnackbar( + { + title: 'Action successful', + message: `Card added to ${card?.name || ''} successfully.`, + }, + 'success', + null + ) + } + onFailure={(error) => + enqueueSnackbar( + { + title: 'Action failed', + message: `Failed to add card to ${card?.name || ''}.`, + }, + 'error', + error + ) + } page={page} cardSize={cardSize} /> @@ -188,11 +210,3 @@ const GenericCard = React.forwardRef((props, ref) => { GenericCard.displayName = 'GenericCard'; export default GenericCard; -// const isCardInContext = useCallback(() => { -// const cardsList = { -// Collection: selectedCollection?.cards, -// Deck: selectedDeck?.cards, -// Cart: cartData?.cart, -// }; -// return !!cardsList[context]?.find((c) => c?.id === card?.id); -// }, [context, card.id, selectedCollection, selectedDeck, cartData]); diff --git a/src/components/dialogs/CollectionDialog.jsx b/src/components/dialogs/CollectionDialog.jsx index af2c216..5147405 100644 --- a/src/components/dialogs/CollectionDialog.jsx +++ b/src/components/dialogs/CollectionDialog.jsx @@ -15,6 +15,7 @@ import AddCollectionForm from '../forms/AddCollectionForm'; import UpdateCollectionForm from '../forms/UpdateCollectionForm'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import MDTypography from '../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; const CollectionDialog = ({ open, onClose, @@ -133,4 +134,4 @@ CollectionDialog.propTypes = { }), }; -export default CollectionDialog; +export default withDynamicSnackbar(CollectionDialog); diff --git a/src/components/dialogs/DeckEditPanel.js b/src/components/dialogs/DeckEditPanel.js index f13a7e5..532d924 100644 --- a/src/components/dialogs/DeckEditPanel.js +++ b/src/components/dialogs/DeckEditPanel.js @@ -14,51 +14,72 @@ import { import { useDeckStore, useFormContext, useMode } from '../../context'; import useSnackBar from '../../context/hooks/useSnackBar'; import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; -import FormField from '../reusable/FormField'; import SaveIcon from '@mui/icons-material/Save'; import DeleteIcon from '@mui/icons-material/Delete'; +import { FormBox } from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; +import FormField from '../forms/reusable/FormField'; const DeckEditPanel = ({ selectedDeck, showSnackbar }) => { const { theme } = useMode(); // const { showSnackbar } = useSnackBar(); // Assuming snackbar hook for user notifications const { + formMethods, + // formStates: { errors, isSubmitting, ...formStates }, register, handleSubmit, setValue, watch, - formState: { errors }, + formState: { errors, isSubmitting }, reset, - isSubmitting, - onSubmit, // Assuming this function is correctly set up in your form context to handle deck submissions + setFormSchema, + onSubmit, + // Removal of onSubmit from destructuring as we will define a local handler } = useFormContext(); // Local state for dynamic fields like tags + const [newTag, setNewTag] = useState(''); const tags = watch('tags', selectedDeck?.tags || []); const color = watch('color'); useEffect(() => { - if (selectedDeck && selectedDeck?.cards?.length >= 1) { + setFormSchema('updateDeckForm'); + }, [setFormSchema]); + + useEffect(() => { + if (selectedDeck) { reset({ - name: selectedDeck?.name, - description: selectedDeck?.description, - tags: selectedDeck?.tags || [], - color: selectedDeck?.color || 'red', + name: selectedDeck.name, + description: selectedDeck.description, + tags: selectedDeck.tags || [], + color: selectedDeck.color || 'red', }); } }, [selectedDeck, reset]); - // Handle adding new tags const handleAddNewTag = (newTag) => { if (newTag && !tags.includes(newTag)) { setValue('tags', [...tags, newTag.trim()]); } }; - - // Handle deleting a tag const handleTagDelete = (tagToDelete) => { setValue( 'tags', tags.filter((tag) => tag !== tagToDelete) ); }; + const handleFormSubmit = async (data) => { + try { + // Assuming `onSubmit` is a function passed via context or props that handles the actual submission + await onSubmit(data); // Adjust based on actual implementation + showSnackbar({ + message: 'Deck updated successfully', + variant: 'success', + }); + } catch (error) { + showSnackbar({ + message: error.message || 'An error occurred while updating the deck.', + variant: 'error', + }); + } + }; // const handleDeleteClick = async () => { // try { // // await deleteDeck(selectedDeck._id); @@ -85,18 +106,10 @@ const DeckEditPanel = ({ selectedDeck, showSnackbar }) => { }} > Deck Editor -
{ - onSubmit(data); - // Show snackbar after form submission - const message = { - title: 'Form Submitted', - description: - 'Deck updated successfully' || 'Deck added successfully', - }; - const variant = 'success'; - showSnackbar(message, variant); - })} + { multiline required /> + - {tags?.map((tag, index) => ( - handleTagDelete(tag)} - /> - ))} - ( + handleTagDelete(tag)} + /> + ))} + handleAddNewTag(e.target.value)} - // Clear the input field after adding a tag + value={newTag} + onChange={(e) => setNewTag(e.target.value)} + onBlur={handleAddNewTag} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); - handleAddNewTag(e.target.value); - e.target.value = ''; + handleAddNewTag(); } }} /> @@ -184,7 +202,7 @@ const DeckEditPanel = ({ selectedDeck, showSnackbar }) => { Delete Deck - +
); }; diff --git a/src/components/dialogs/LoginDialog.jsx b/src/components/dialogs/LoginDialog.jsx index 4da8091..7649763 100644 --- a/src/components/dialogs/LoginDialog.jsx +++ b/src/components/dialogs/LoginDialog.jsx @@ -1,220 +1,3 @@ -// import React, { useEffect, useState } from 'react'; -// import { -// Avatar, -// Box, -// Button, -// Checkbox, -// CircularProgress, -// CssBaseline, -// DialogContent, -// FormControlLabel, -// Grid, -// IconButton, -// Link, -// Paper, -// Typography, -// } from '@mui/material'; -// import Brightness4Icon from '@mui/icons-material/Brightness4'; -// import Brightness7Icon from '@mui/icons-material/Brightness7'; -// import { useFormContext, useMode, usePageContext } from '../../context'; // Adjust import paths as necessary -// import useAuthDialog from '../../context/hooks/useAuthDialog'; // Adjust import paths as necessary -// import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; // Adjust import paths as necessary -// import { -// StyledDialog, -// StyledDialogActions, -// StyledDialogContent, -// StyledDialogTitle, -// } from '../../pages/pageStyles/StyledComponents'; -// import MDBox from '../../layout/REUSABLE_COMPONENTS/MDBOX'; -// import SignupSwitch from '../buttons/other/SignupSwitch'; -// import FormField from '../reusable/FormField'; -// import MDButton from '../../layout/REUSABLE_COMPONENTS/MDBUTTON'; -// import styled from 'styled-components'; -// import LoginForm from '../forms/LoginForm'; -// import SignupForm from '../forms/SignupForm'; -// import MDTypography from '../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; -// import { CopyrightOutlined } from '@mui/icons-material'; -// import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; -// import ImageWithFallback from '../../pages/pageStyles/ImageWithFallback'; - -// const StyledFormArea = styled(Box)(({ theme }) => ({ -// maxWidth: '100%', -// borderRadius: theme.shape.borderRadius, -// padding: theme.spacing(2), -// })); -// function LoginDialog({ showSnackbar }) { -// const { theme, toggleColorMode, mode } = useMode(); -// const { toggleLoginDialog, isLoggedIn, logout } = useAuthDialog(); -// const { setCurrentForm, currentForm } = useFormContext(); -// // const [currentForm, setCurrentForm] = useState('loginForm'); - -// useEffect(() => { -// if (!isLoggedIn) { -// toggleLoginDialog(); -// } -// }, [isLoggedIn, toggleLoginDialog]); - -// const handleLogout = () => { -// logout(); -// toggleLoginDialog(); -// }; -// const toggleFormType = () => -// setCurrentForm((prevForm) => -// prevForm === 'loginForm' ? 'signupForm' : 'loginForm' -// ); - -// useEffect(() => { -// setCurrentForm('loginForm'); -// }, []); - -// return ( -// -// -// - -// -// -// {/* */} -// -// -// -// - -// -// -// {currentForm === 'loginForm' ? 'Login' : 'Sign Up'} -// -// -// {mode === 'dark' ? : } -// -// -// -// -// -// -// {/* */} -// -// {isLoggedIn ? ( -// -// Log Out -// -// ) : ( -// -// -// {currentForm === 'signupForm' ? ( -// -// ) : ( -// -// )} -// -// } -// label="Remember me" -// /> -// -// )} -// -// -// -// -// -// -// {"Don't have an account? Sign Up"} -// -// -// -// -// -// -// -// -// ); -// } - -// export default withDynamicSnackbar(LoginDialog); import React, { useEffect } from 'react'; import { Avatar, @@ -224,6 +7,8 @@ import { CircularProgress, CssBaseline, DialogContent, + DialogTitle, + Divider, FormControl, FormControlLabel, Grid, @@ -232,6 +17,7 @@ import { Paper, Switch, Typography, + useMediaQuery, } from '@mui/material'; import Brightness4Icon from '@mui/icons-material/Brightness4'; import Brightness7Icon from '@mui/icons-material/Brightness7'; @@ -239,16 +25,29 @@ import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; // Adjust import paths as necessary import LoginForm from '../forms/LoginForm'; import SignupForm from '../forms/SignupForm'; -import { StyledDialog } from '../../pages/pageStyles/StyledComponents'; import { useFormContext, useMode } from '../../context'; import useAuthDialog from '../../context/hooks/useAuthDialog'; // Adjust import paths as necessary import MDBox from '../../layout/REUSABLE_COMPONENTS/MDBOX'; import MDTypography from '../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import { + DialogContentsBox, + DialogPaer, + DialogPaper, + FormPaper, + StyledDialog, + StyledDialogContent, +} from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; +import MDAvatar from '../../layout/REUSABLE_COMPONENTS/MDAVATAR'; +import { AuthModeSwitch } from '../../layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents'; +import AuthSwitch from '../buttons/other/AuthSwitch'; +import SimpleButton from '../../layout/REUSABLE_COMPONENTS/unique/SimpleButton'; function LoginDialog({ showSnackbar }) { const { theme, toggleColorMode, mode } = useMode(); const { toggleLoginDialog, isLoggedIn, logout } = useAuthDialog(); - const { currentForm, setCurrentForm } = useFormContext(); + // const { currentForm, setFormSchema } = useFormContext(); + const { formMethods, onSubmit, setFormSchema, currentSchemaKey } = + useFormContext(); // EFFECT: If the user is not logged in, open the login dialog useEffect(() => { @@ -258,146 +57,122 @@ function LoginDialog({ showSnackbar }) { }, [isLoggedIn, toggleLoginDialog]); // EFFECT: Set the current form to 'loginForm' when the component mounts useEffect(() => { - setCurrentForm('loginForm'); - }, [setCurrentForm]); + setFormSchema('loginForm'); + }, [setFormSchema]); // HANDLE: Logout the user and close the login dialog const handleLogout = () => { logout(); toggleLoginDialog(); }; + const formTitle = currentSchemaKey === 'loginForm' ? 'Login' : 'Sign Up'; - const toggleAuthMode = () => - setCurrentForm((prevForm) => - prevForm === 'loginForm' ? 'signupForm' : 'loginForm' - ); - - const signupMode = currentForm === 'signupForm'; - + const signupMode = currentSchemaKey === 'signupForm'; const formLabel = () => { - {currentForm === 'loginForm' ? 'Sign Up' : 'Login'} + {currentSchemaKey === 'loginForm' ? 'Sign Up' : 'Login'} ; }; return ( - - - + + - + + + + + - - - - - - {currentForm === 'loginForm' ? 'Login' : 'Sign Up'} - - - {mode === 'dark' ? : } - - - {currentForm === 'loginForm' ? ( - - ) : ( - - )} - - } - label="Remember me" - /> - {isLoggedIn && ( - - )} - - - - - - {'Copyright © '} - ReedThaHuman LLC {new Date().getFullYear()} - - - - - - + + + + {formTitle} + + + {/* */} + + {/* */} + + + + + + {currentSchemaKey === 'loginForm' ? ( + + ) : ( + + )} + {/* */} + } + label="Remember me" + /> + {isLoggedIn && ( + + )} + + + {'Copyright © '} + ReedThaHuman LLC {new Date().getFullYear()} + + + ); } diff --git a/src/components/dialogs/SelectionErrorDialog.jsx b/src/components/dialogs/SelectionErrorDialog.jsx new file mode 100644 index 0000000..7b1e736 --- /dev/null +++ b/src/components/dialogs/SelectionErrorDialog.jsx @@ -0,0 +1,136 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Button from '@mui/material/Button'; +import Avatar from '@mui/material/Avatar'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import DialogTitle from '@mui/material/DialogTitle'; +import Dialog from '@mui/material/Dialog'; +import PersonIcon from '@mui/icons-material/Person'; +import AddIcon from '@mui/icons-material/Add'; +import Typography from '@mui/material/Typography'; +import { blue } from '@mui/material/colors'; +import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; +import useSelectedCollection from '../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import { DialogContent, Slide } from '@mui/material'; + +const Transition = React.forwardRef(function Transition(props, ref) { + return ; +}); + +function SelectionErrorDialog(props) { + const { onClose, selectedValue, open, showSnackbar } = props; + const { + handleBackToCollections, + showCollections, + allCollections, + handleSelectCollection, + } = useSelectedCollection(); + const handleClose = () => { + onClose(selectedValue); + }; + + // const handleListItemClick = (value) => { + // if (typeof value?._id === 'string') { + // showSnackbar('Not implemented yet', 'successs'); + // } else { + // showSnackbar(`${value} selected as backup account`, 'success'); + // } + // onClose(value); + // }; + const handleListItemClick = React.useCallback( + (collection) => { + if (collection._id) { + showSnackbar('Not implemented yet', 'info'); // Assuming 'successs' was a typo, corrected to 'info' for a non-implemented feature + } else { + showSnackbar(`${collection} selected as backup account`, 'success'); + } + onClose(collection); + }, + [onClose, showSnackbar] + ); + const handleAction = React.useCallback( + (message, severity, error) => { + showSnackbar(message, severity); + if (error) console.error('Action failed:', error); + }, + [showSnackbar] + ); + + return ( + + Set backup account + + + {allCollections?.map((collection) => ( + + handleListItemClick(collection)}> + + + + + + {collection?.name} + } + /> + + + ))} + + handleListItemClick(collection)} + // onClick={() => handleContextSelect(mappedContext)} + onSuccess={() => + handleAction( + { + title: 'Action successful', + message: `Card added to ${selectedValue} successfully.`, + }, + 'success', + null + ) + } + onFailure={(error) => + handleAction( + { + title: 'Action failed', + message: `Failed to add card to ${selectedValue}.`, + }, + 'error', + error + ) + } + > + + + + + + + + + + + + ); +} + +SelectionErrorDialog.propTypes = { + onClose: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, + selectedValue: PropTypes.string.isRequired, +}; + +export default withDynamicSnackbar(SelectionErrorDialog); diff --git a/src/components/dialogs/cardDialog/GenericCardDialog.jsx b/src/components/dialogs/cardDialog/GenericCardDialog.jsx index e04b0fb..a0a0146 100644 --- a/src/components/dialogs/cardDialog/GenericCardDialog.jsx +++ b/src/components/dialogs/cardDialog/GenericCardDialog.jsx @@ -29,6 +29,8 @@ import CloseIcon from '@mui/icons-material/Close'; import CardDetail from '../../cards/CardDetail'; import { useOverlay } from '../../../context/hooks/useOverlay'; import CardDetailsContainer from '../../cards/CardDetailsContainer'; +import { enqueueSnackbar } from 'notistack'; +import { DialogPaper } from '../../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; const GenericCardDialog = (props) => { const { theme } = useMode(); @@ -58,27 +60,26 @@ const GenericCardDialog = (props) => { } = props || {}; const fullScreen = useMediaQuery(theme.breakpoints.down('md')); const { closeModal } = useModalContext(); - const { getHeaderStyle, isSmall } = theme.responsiveStyles; const [imageUrl, setImageUrl] = useState(card?.card_images[0]?.image_url); - const { handleSnackBar, handleCloseSnackbar } = useSnackbar(); + // const { handleSnackBar, handleCloseSnackbar } = useSnackbar(); const [hasLoggedCard, setHasLoggedCard] = useState(false); const { setContext, setIsContextSelected } = useSelectedContext(); const handleAction = useCallback( (message, severity, error) => { - handleSnackBar(message, severity); + enqueueSnackbar(message, severity); if (error) console.error('Action failed:', error); }, - [handleSnackBar] + [enqueueSnackbar] ); useEffect(() => { if (open && card && !hasLoggedCard) { - handleSnackBar('Card details loaded successfully.', 'success'); + enqueueSnackbar('Card details loaded successfully.', 'success'); setHasLoggedCard(true); } return () => { if (!open) setHasLoggedCard(false); }; - }, [open, card, handleSnackBar, hasLoggedCard]); + }, [open, card, enqueueSnackbar, hasLoggedCard]); // Context selection handling const handleContextSelect = useCallback( (newContext) => { @@ -105,134 +106,123 @@ const GenericCardDialog = (props) => { }, }} > - - {title} - {closeIcon && ( - - - - )} - - - - - - - {generateOverlay()} - + + + {title} + {closeIcon && ( + + + + )} + + + + + + + {generateOverlay()} + - {/* these two grid items are for the card details */} - - } - title="Description" - value={card?.desc} - /> - } - title="Price" - value={card?.price} - /> - set.set_rarity)} - onRarityClick={handleRarityClick} - /> - set.set_code)} - onRarityClick={handleRarityClick} - /> - - - - + {/* these two grid items are for the card details */} + + } + title="Description" + value={card?.desc} + /> + } + title="Price" + value={card?.price} + /> + set.set_rarity)} + onRarityClick={handleRarityClick} + /> + set.set_code)} + onRarityClick={handleRarityClick} + /> + + + + {/* */} - {/* these two grid items are for the related user inventory data and action buttons */} - - - - - Inventory - - - - {/* */} - {'Cart'} - {'Deck'} - {'Collection'} - - - - - - {['Deck', 'Collection', 'Cart'].map((mappedContext) => ( - handleContextSelect(mappedContext)} - onSuccess={() => - handleAction( - { - title: 'Action successful', - message: `Card added to ${mappedContext} successfully.`, - }, - 'success' - ) - } - onFailure={(error) => - handleAction( - { - title: 'Action failed', - message: `Failed to add card to ${mappedContext}.`, - }, - 'error', - error - ) - } - /> - ))} - + {/* these two grid items are for the related user inventory data and action buttons */} + {/* */} + + + + Inventory + + + + {/* */} + {'Cart'} + {'Deck'} + {'Collection'} + + + + + + {['Deck', 'Collection', 'Cart'].map((mappedContext) => ( + handleContextSelect(mappedContext)} + onSuccess={() => + handleAction( + { + title: 'Action successful', + message: `Card added to ${mappedContext} successfully.`, + }, + 'success' + ) + } + onFailure={(error) => + handleAction( + { + title: 'Action failed', + message: `Failed to add card to ${mappedContext}.`, + }, + 'error', + error + ) + } + /> + ))} + + - - - - {/* - - {snackbar.message} - - */} + + + ); }; diff --git a/src/components/forms/AddCollectionForm.jsx b/src/components/forms/AddCollectionForm.jsx index 03c387f..b64b9d8 100644 --- a/src/components/forms/AddCollectionForm.jsx +++ b/src/components/forms/AddCollectionForm.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { Box } from '@mui/material'; -import FormField from '../reusable/FormField'; +import FormField from './reusable/FormField'; import { LoadingButton } from '@mui/lab'; import { useFormContext, useMode } from '../../context'; import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; @@ -10,8 +10,12 @@ import { formSchemas, getDefaultValuesFromSchema, } from '../../context/UTILITIES_CONTEXT/FormContext/schemas'; +import { + FormBox, + FormFieldBox, +} from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; -const AddCollectionForm = ({ showSnackbar, setLoading }) => { +const AddCollectionForm = ({ showSnackbar }) => { const formId = 'addCollectionForm'; const { onSubmit } = useFormContext(); const { theme } = useMode(); @@ -52,7 +56,9 @@ const AddCollectionForm = ({ showSnackbar, setLoading }) => { } }; return ( -
{ padding: 2, }} > - + { errors={errors} required /> - - + + { multiline rows={4} /> - + { > Create Collection -
+ ); }; diff --git a/src/components/forms/CollectionStatisticsSelector.jsx b/src/components/forms/CollectionStatisticsSelector.jsx deleted file mode 100644 index 34fbf84..0000000 --- a/src/components/forms/CollectionStatisticsSelector.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState } from 'react'; -import { Box, Grid, Select, MenuItem, Container } from '@mui/material'; -import { useCollectionStore } from '../../context/MAIN_CONTEXT/CollectionContext/CollectionContext'; -import { useStatisticsStore } from '../../context/SECONDARY_CONTEXT/StatisticsContext/StatisticsContext'; -import StatCard from '../other/dataDisplay/StatCard'; -import { StyledChartBox } from '../../pages/pageStyles/StyledComponents'; -import { useMode } from '../../context'; -const CollectionStatisticsSelector = () => { - const { selectedCollection } = useCollectionStore(); - const { statsByCollectionId, stats, selectedStat, setSelectedStat } = - useStatisticsStore(); - - const handleChange = (event) => setSelectedStat(event.target.value); - const { theme } = useMode(); - return ( - - - - - {selectedStat && ( - - - - - - )} - - - ); -}; - -export default CollectionStatisticsSelector; diff --git a/src/components/forms/CustomSelector.js b/src/components/forms/CustomSelector.js index 27d39c8..bc9e771 100644 --- a/src/components/forms/CustomSelector.js +++ b/src/components/forms/CustomSelector.js @@ -1,93 +1,93 @@ -import React from 'react'; -import { useTheme } from '@mui/material/styles'; -import OutlinedInput from '@mui/material/OutlinedInput'; -import MenuItem from '@mui/material/MenuItem'; -import FormControl from '@mui/material/FormControl'; -import Select from '@mui/material/Select'; -import PropTypes from 'prop-types'; +// import React from 'react'; +// import { useTheme } from '@mui/material/styles'; +// import OutlinedInput from '@mui/material/OutlinedInput'; +// import MenuItem from '@mui/material/MenuItem'; +// import FormControl from '@mui/material/FormControl'; +// import Select from '@mui/material/Select'; +// import PropTypes from 'prop-types'; -const ITEM_HEIGHT = 48; -const ITEM_PADDING_TOP = 8; -const MenuProps = { - PaperProps: { - style: { - maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, - width: 250, - }, - }, -}; +// const ITEM_HEIGHT = 48; +// const ITEM_PADDING_TOP = 8; +// const MenuProps = { +// PaperProps: { +// style: { +// maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, +// width: 250, +// }, +// }, +// }; -function getStyles(name, selected, theme) { - return { - fontWeight: - selected.indexOf(name) === -1 - ? theme.typography.fontWeightRegular - : theme.typography.fontWeightMedium, - }; -} +// function getStyles(name, selected, theme) { +// return { +// fontWeight: +// selected.indexOf(name) === -1 +// ? theme.typography.fontWeightRegular +// : theme.typography.fontWeightMedium, +// }; +// } -const CustomMultipleSelect = ({ - items, - selectedItems, - onChange, - placeholder, - label, -}) => { - const theme = useTheme(); +// const CustomMultipleSelect = ({ +// items, +// selectedItems, +// onChange, +// placeholder, +// label, +// }) => { +// const theme = useTheme(); - const handleChange = (event) => { - const { - target: { value }, - } = event; - onChange(typeof value === 'string' ? value.split(',') : value); - }; +// const handleChange = (event) => { +// const { +// target: { value }, +// } = event; +// onChange(typeof value === 'string' ? value.split(',') : value); +// }; - return ( - - } +// renderValue={(selected) => { +// if (selected.length === 0) { +// return {placeholder}; +// } - return selected.join(', '); - }} - MenuProps={MenuProps} - inputProps={{ 'aria-label': label }} - > - - {placeholder} - - {items.map((name) => ( - - {name} - - ))} - - - ); -}; +// return selected.join(', '); +// }} +// MenuProps={MenuProps} +// inputProps={{ 'aria-label': label }} +// > +// +// {placeholder} +// +// {items.map((name) => ( +// +// {name} +// +// ))} +// +//
+// ); +// }; -CustomMultipleSelect.propTypes = { - items: PropTypes.arrayOf(PropTypes.string).isRequired, - selectedItems: PropTypes.arrayOf(PropTypes.string).isRequired, - onChange: PropTypes.func.isRequired, - placeholder: PropTypes.string, - label: PropTypes.string, -}; +// CustomMultipleSelect.propTypes = { +// items: PropTypes.arrayOf(PropTypes.string).isRequired, +// selectedItems: PropTypes.arrayOf(PropTypes.string).isRequired, +// onChange: PropTypes.func.isRequired, +// placeholder: PropTypes.string, +// label: PropTypes.string, +// }; -CustomMultipleSelect.defaultProps = { - placeholder: 'Select items', - label: 'Custom select', -}; +// CustomMultipleSelect.defaultProps = { +// placeholder: 'Select items', +// label: 'Custom select', +// }; -export default CustomMultipleSelect; +// export default CustomMultipleSelect; diff --git a/src/components/forms/LoginForm.jsx b/src/components/forms/LoginForm.jsx index 53bd440..06fcbe1 100644 --- a/src/components/forms/LoginForm.jsx +++ b/src/components/forms/LoginForm.jsx @@ -1,141 +1,151 @@ import React, { useEffect } from 'react'; -import { Box, Checkbox, FormControlLabel, Grid, Link } from '@mui/material'; -import FormField from '../reusable/FormField'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; import { - formSchemas, - getDefaultValuesFromSchema, -} from '../../context/UTILITIES_CONTEXT/FormContext/schemas'; + Box, + Checkbox, + FormControlLabel, + Grid, + InputAdornment, + Link, +} from '@mui/material'; +import FormField from './reusable/FormField'; import { LoadingButton } from '@mui/lab'; import { useFormContext, useMode } from '../../context'; import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; import { CopyrightOutlined } from '@mui/icons-material'; import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; import AuthSwitch from '../buttons/other/AuthSwitch'; -const LoginForm = ({ - showSnackbar, - setLoading, - signupMode, - toggleAuthMode, - formLabel, -}) => { - const { onSubmit } = useFormContext(); +import LoginIcon from '@mui/icons-material/Login'; +import PersonIcon from '@mui/icons-material/Person'; +import LockIcon from '@mui/icons-material/Lock'; +import { + FormBox, + FormFieldBox, +} from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; +import SimpleButton from '../../layout/REUSABLE_COMPONENTS/unique/SimpleButton'; +const baseButtonStyles = { + bgcolor: '#6a59ff', // background-color + borderColor: '#6a59ff', + borderWidth: 2, + borderStyle: 'solid', + display: 'flex', + flexGrow: 1, + alignItems: 'center', + justifyContent: 'center', + marginLeft: 'auto', + marginRight: 'auto', + marginBottom: '16px', + marginTop: '16px', + position: 'relative', + bottom: 0, + cursor: 'pointer', + transition: 'background-color 0.3s, border-color 0.3s, color 0.3s', + ':hover': { + fontWeight: 'bold', + bgcolor: '#4a6da7', + borderColor: '#34597f', + }, + ':focus': { + outline: '2px solid #62a4ff', + outlineOffset: 2, + }, +}; +const LoginForm = ({ showSnackbar, signupMode, toggleAuthMode, formLabel }) => { + const { formMethods, onSubmit, setFormSchema } = useFormContext(); const { theme } = useMode(); const { register, handleSubmit, formState: { errors, isSubmitting }, - } = useForm({ - resolver: zodResolver(formSchemas.loginForm), - defaultValues: getDefaultValuesFromSchema(formSchemas.loginForm), - }); + } = formMethods; + useEffect(() => { + setFormSchema('loginForm'); + }, [setFormSchema]); const fields = [ - { name: 'username', label: 'Username', type: 'text' }, - { name: 'password', label: 'Password', type: 'password' }, + { + name: 'username', + label: 'Username', + type: 'text', + icon: , + }, + { + name: 'password', + label: 'Password', + type: 'password', + icon: , + }, ]; - const onFormSubmit = async (data) => { - try { - // Simulate form submission - await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulated delay - - onSubmit(data, 'loginForm'); - // On success: - showSnackbar( - { - title: 'Success', - description: "You've successfully logged in.", - }, - 'success' - ); // Adjust message as needed - } catch (error) { - // On error: - showSnackbar( - { - title: 'Error', - description: 'Login failed. Please try again.', - }, - 'error' - ); // Adjust message as needed - } + const onFormSubmit = (data) => { + onSubmit(data, 'loginForm') + .then(() => { + showSnackbar( + { title: 'Success', description: "You've successfully logged in." }, + 'success' + ); + }) + .catch((error) => { + showSnackbar( + { title: 'Error', description: 'Login failed. Please try again.' }, + 'error' + ); + }); }; - - useEffect(() => { - setLoading(isSubmitting); - console.log('isSubmitting:', isSubmitting); - }, [isSubmitting, setLoading]); return ( -
{fields.map((field, index) => ( - + {field.icon} + ), + }} /> {errors[field.name] && ( {errors[field.name].message} )} - + ))} + {/* */} } + fullWidth sx={{ - background: theme.palette.backgroundE.darker, - borderColor: theme.palette.backgroundB.darkest, + background: theme.palette.backgroundG.light, + borderColor: theme.palette.backgroundG.light, borderWidth: 2, - flexGrow: 1, - justifySelf: 'bottom', - bottom: 0, - mx: 'auto', - my: 2, - '&:hover': { - fontWeight: 'bold', - background: theme.palette.backgroundF.dark, - borderColor: theme.palette.backgroundB.darkest, - border: `1px solid ${theme.palette.backgroundB.darkest}`, - }, + '&:hover': { background: theme.palette.backgroundG.default }, + '&:focus': { background: theme.palette.backgroundG.default }, }} - fullWidth > Login - - + ); }; diff --git a/src/components/forms/ProfileForm.jsx b/src/components/forms/ProfileForm.jsx index 87522bb..60791ca 100644 --- a/src/components/forms/ProfileForm.jsx +++ b/src/components/forms/ProfileForm.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { TextField, Button } from '@mui/material'; import { useFormContext } from '../../context'; -import FormField from '../reusable/FormField'; +import FormField from './reusable/FormField'; const ProfileForm = ({ userName, name, age, status, onSave }) => { const { forms, handleChange, handleSubmit } = useFormContext(); diff --git a/src/components/forms/SearchForm.jsx b/src/components/forms/SearchForm.jsx index 18fe5c7..2902961 100644 --- a/src/components/forms/SearchForm.jsx +++ b/src/components/forms/SearchForm.jsx @@ -1,9 +1,13 @@ import React, { useCallback, useEffect } from 'react'; import { Button, Grid } from '@mui/material'; import { useFormContext, useMode, usePageContext } from '../../context'; -import FormField from '../reusable/FormField'; -import { StyledFormPaper } from '../../pages/pageStyles/StyledComponents'; +import { + StyledFormBox, + StyledFormPaper, +} from '../../pages/pageStyles/StyledComponents'; +import { FormBox } from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; import { LoadingButton } from '@mui/lab'; +import FormField from './reusable/FormField'; const SearchForm = ({ onFocus, onBlur }) => { const { theme } = useMode(); @@ -18,19 +22,26 @@ const SearchForm = ({ onFocus, onBlur }) => { handleBlur, handleFocus, forms, + handleChange, + setFormSchema, } = useFormContext(); - const handleChange = (e) => { - const { name, value } = e.target; - if (name === 'searchTerm') { - handleSearchTermChange(value); // Call the method when searchTerm changes - } - handleFieldChange(formId, name, value); // Continue to update the form state as before - }; - const { searchTerm } = formStates.searchForm; - const handleKeyPress = (event) => { - if (event.key === 'Enter') { - event.preventDefault(); + useEffect(() => { + setFormSchema(formId); + }, [setFormSchema]); + + // const handleChange = (e) => { + // const { name, value } = e.target; + // if (name === 'searchTerm') { + // handleSearchTermChange(value); // Call the method when searchTerm changes + // } + // handleFieldChange(formId, name, value); // Continue to update the form state as before + // }; + + // const { searchTerm } = formStates?.searchForm; + const handleKeyPress = (e) => { + if (e.key === 'Enter') { + e.preventDefault(); formMethods.handleSubmit((data) => { onSubmit(data); }); @@ -44,7 +55,9 @@ const SearchForm = ({ onFocus, onBlur }) => { background: theme.palette.backgroundB.lightest, }} > -
onSubmit(data, 'searchForm') )} @@ -55,7 +68,7 @@ const SearchForm = ({ onFocus, onBlur }) => { register={formMethods.register} errors={errors} required - value={searchTerm} + value={forms?.searchForm?.searchTerm} onChange={handleChange} onFocus={handleFocus} onBlur={handleBlur} @@ -83,7 +96,7 @@ const SearchForm = ({ onFocus, onBlur }) => { {isSubmitting ? 'Loading...' : 'Search'} {errors?.root &&

{errors?.root?.message}

} -
+ ); }; diff --git a/src/components/forms/SignupForm.jsx b/src/components/forms/SignupForm.jsx index bf97dea..3e6e74f 100644 --- a/src/components/forms/SignupForm.jsx +++ b/src/components/forms/SignupForm.jsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { Box, Button } from '@mui/material'; -import FormField from '../reusable/FormField'; +import FormField from './reusable/FormField'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { @@ -8,29 +8,60 @@ import { getDefaultValuesFromSchema, } from '../../context/UTILITIES_CONTEXT/FormContext/schemas'; // Ensure this path is correct import { LoadingButton } from '@mui/lab'; -import { useFormContext, useMode } from '../../context'; +import { useFormContext, useMode, usePageContext } from '../../context'; import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; import AuthSwitch from '../buttons/other/AuthSwitch'; +import PersonAddIcon from '@mui/icons-material/PersonAdd'; +import { + FormBox, + FormFieldBox, +} from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; +const baseButtonStyles = { + bgcolor: '#6a59ff', // background-color + borderColor: '#6a59ff', + borderWidth: 2, + borderStyle: 'solid', + display: 'flex', + flexGrow: 1, + alignItems: 'center', + justifyContent: 'center', + marginLeft: 'auto', + marginRight: 'auto', + marginBottom: '16px', + marginTop: '16px', + position: 'relative', + bottom: 0, + cursor: 'pointer', + transition: 'background-color 0.3s, border-color 0.3s, color 0.3s', + ':hover': { + fontWeight: 'bold', + bgcolor: '#4a6da7', + borderColor: '#34597f', + }, + ':focus': { + outline: '2px solid #62a4ff', + outlineOffset: 2, + }, +}; const SignupForm = ({ showSnackbar, - setLoading, signupMode, toggleAuthMode, formLabel, }) => { const { theme } = useMode(); + const { formMethods, onSubmit, setFormSchema } = useFormContext(); - const { onSubmit } = useFormContext(); const { register, handleSubmit, formState: { errors, isSubmitting }, - } = useForm({ - resolver: zodResolver(formSchemas.signupForm), - defaultValues: getDefaultValuesFromSchema(formSchemas.signupForm), - }); + } = formMethods; + useEffect(() => { + setFormSchema('signupForm'); + }, [setFormSchema]); const fields = [ { name: 'firstName', label: 'First Name', type: 'text' }, { name: 'lastName', label: 'Last Name', type: 'text' }, @@ -39,51 +70,43 @@ const SignupForm = ({ { name: 'password', label: 'Password', type: 'password' }, ]; - const onFormSubmit = async (data) => { - try { - // Simulate form submission - await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulated delay - - onSubmit(data, 'loginForm'); - // On success: - showSnackbar( - { - title: 'Success', - description: "You've successfully logged in.", - }, - 'success' - ); // Adjust message as needed - } catch (error) { - // On error: - showSnackbar( - { - title: 'Error', - description: 'Login failed. Please try again.', - }, - 'error' - ); // Adjust message as needed - } + // Updated onFormSubmit to directly use onSubmit from context + const onFormSubmit = (data) => { + onSubmit(data, 'signupForm') + .then(() => { + showSnackbar( + { title: 'Success', description: "You've successfully signed up." }, + 'success' + ); + }) + .catch((error) => { + showSnackbar( + { title: 'Error', description: 'Signup failed. Please try again.' }, + 'error' + ); + }); }; - useEffect(() => { - setLoading(isSubmitting); - console.log('isSubmitting:', isSubmitting); - }, [isSubmitting, setLoading]); + // useEffect(() => { + // setIsFormDataLoading(isSubmitting); + // console.log('isSubmitting:', isSubmitting); + // }, [isSubmitting, setIsFormDataLoading]); return ( -
{ - console.log('Login data:', data); - onSubmit(data, 'loginForm'); - })} - style={{ - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - margin: 2, - padding: 2, - }} + {fields.map((field, index) => ( - + - + ))} {errors.form && ( @@ -102,34 +125,43 @@ const SignupForm = ({ type="submit" variant="contained" loading={isSubmitting} - onClick={() => handleSubmit(onFormSubmit)()} - color="primary" + size="large" + style={baseButtonStyles} + startIcon={} + fullWidth sx={{ - background: theme.palette.backgroundE.darker, - borderColor: theme.palette.backgroundB.darkest, + background: theme.palette.backgroundG.light, + borderColor: theme.palette.backgroundG.light, borderWidth: 2, - flexGrow: 1, - justifySelf: 'bottom', - bottom: 0, - mx: 'auto', - my: 2, - '&:hover': { - fontWeight: 'bold', - background: theme.palette.backgroundF.dark, - borderColor: theme.palette.backgroundB.darkest, - border: `1px solid ${theme.palette.backgroundB.darkest}`, - }, + '&:hover': { background: theme.palette.backgroundG.default }, + '&:focus': { background: theme.palette.backgroundG.default }, }} - fullWidth + // type="submit" + // variant="contained" + // loading={isSubmitting} + // onClick={() => handleSubmit(onFormSubmit)()} + // color="primary" + // sx={{ + // background: theme.palette.backgroundE.darker, + // borderColor: theme.palette.backgroundB.darkest, + // borderWidth: 2, + // flexGrow: 1, + // justifySelf: 'bottom', + // bottom: 0, + // mx: 'auto', + // my: 2, + // '&:hover': { + // fontWeight: 'bold', + // background: theme.palette.backgroundF.dark, + // borderColor: theme.palette.backgroundB.darkest, + // border: `1px solid ${theme.palette.backgroundB.darkest}`, + // }, + // }} + // fullWidth > Sign Up - - +
); }; diff --git a/src/components/forms/TimeRangeSelector.jsx b/src/components/forms/TimeRangeSelector.jsx deleted file mode 100644 index 9fbb8e5..0000000 --- a/src/components/forms/TimeRangeSelector.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; -import { MenuItem, Select, FormControl, InputLabel } from '@mui/material'; -import { StyledChartBox } from '../../pages/pageStyles/StyledComponents'; -import { useChartContext, useMode } from '../../context'; -import { useForm, Controller } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { formSchemas } from '../../context/UTILITIES_CONTEXT/FormContext/schemas'; - -const TimeRangeSelector = () => { - const { timeRange, timeRanges, setTimeRange } = useChartContext(); - const { theme } = useMode(); - const defaultValues = { timeRange: timeRange?.value || '' }; - - const { - control, - formState: { errors }, - } = useForm({ - resolver: zodResolver(formSchemas.TimeRangeSchema), - defaultValues: defaultValues, - }); - - const onFormSubmit = (data) => { - // Find the range object based on the value - const foundRange = timeRanges?.find( - (range) => range.value === data.timeRange - ); - if (foundRange) { - setTimeRange(foundRange); - } - }; - return ( - - {/*eslint-disable-next-line @typescript-eslint/no-empty-function */} - ( - - {/* Time Range */} - - - )} - /> - {/* */} - - ); -}; - -export default TimeRangeSelector; diff --git a/src/components/forms/UpdateCollectionForm.jsx b/src/components/forms/UpdateCollectionForm.jsx index a0bcce0..80cfbc8 100644 --- a/src/components/forms/UpdateCollectionForm.jsx +++ b/src/components/forms/UpdateCollectionForm.jsx @@ -1,6 +1,5 @@ import React, { useEffect } from 'react'; import { Box } from '@mui/material'; -import FormField from '../reusable/FormField'; import { LoadingButton } from '@mui/lab'; import { useFormContext, useMode } from '../../context'; import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; @@ -10,8 +9,13 @@ import { formSchemas, getDefaultValuesFromSchema, } from '../../context/UTILITIES_CONTEXT/FormContext/schemas'; +import FormField from './reusable/FormField'; +import { + FormBox, + FormFieldBox, +} from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; -const UpdateCollectionForm = ({ showSnackbar, setLoading, collectionData }) => { +const UpdateCollectionForm = ({ showSnackbar, collectionData }) => { const formId = 'updateCollectionForm'; const { onSubmit } = useFormContext(); const { theme } = useMode(); @@ -65,8 +69,10 @@ const UpdateCollectionForm = ({ showSnackbar, setLoading, collectionData }) => { }; return ( -
{ padding: 2, }} > - + { errors={errors} required /> - - + + { multiline rows={4} /> - + { > Update Collection -
+ ); }; diff --git a/src/components/forms/UpdateDeckForm.jsx b/src/components/forms/UpdateDeckForm.jsx index b277059..4c8d719 100644 --- a/src/components/forms/UpdateDeckForm.jsx +++ b/src/components/forms/UpdateDeckForm.jsx @@ -1,12 +1,14 @@ import React, { useEffect } from 'react'; -import { useFormContext } from '../../context'; +import { useFormContext, useMode } from '../../context'; import FormField from '../reusable/FormField'; import { Button, Paper, Typography, Box } from '@mui/material'; import SaveIcon from '@mui/icons-material/Save'; import DeleteIcon from '@mui/icons-material/Delete'; import { withDynamicSnackbar } from '../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; +import { FormBox } from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; const UpdateDeckForm = ({ selectedDeck, showSnackbar }) => { + const { theme } = useMode(); const { formStates, onSubmit } = useFormContext(); const formId = 'updateDeckForm'; // Assuming this is the formId for updating decks @@ -47,7 +49,11 @@ const UpdateDeckForm = ({ selectedDeck, showSnackbar }) => { return ( Update Deck -
+ { Delete Deck - +
); }; diff --git a/src/components/reusable/FormField.jsx b/src/components/forms/reusable/FormField.jsx similarity index 88% rename from src/components/reusable/FormField.jsx rename to src/components/forms/reusable/FormField.jsx index b88bbe5..776600d 100644 --- a/src/components/reusable/FormField.jsx +++ b/src/components/forms/reusable/FormField.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; // Import PropTypes -import { StyledTextField } from '../../pages/pageStyles/StyledComponents'; -import { useMode } from '../../context'; +import { useMode } from '../../../context'; +import { StyledTextField } from '../../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; const FormField = ({ name, register, errors, ...props }) => { const { theme } = useMode(); diff --git a/src/components/reusable/FormTextField.jsx b/src/components/forms/reusable/FormTextField.jsx similarity index 100% rename from src/components/reusable/FormTextField.jsx rename to src/components/forms/reusable/FormTextField.jsx diff --git a/src/components/forms/reusable/Select.jsx b/src/components/forms/reusable/Select.jsx new file mode 100644 index 0000000..cf0e3c6 --- /dev/null +++ b/src/components/forms/reusable/Select.jsx @@ -0,0 +1,97 @@ +// import React from 'react'; +// import { Select, MenuItem } from '@mui/material'; +// import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +// import { useMode } from '../../../context'; + +// const SelectComponent = ({ value, onChange, options, fullWidthTrue }) => { +// const { theme } = useMode(); +// return ( +// +// ); +// }; + +// export default SelectComponent; +import React from 'react'; +import { Select, MenuItem } from '@mui/material'; +import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import { useMode } from '../../../context'; + +const SelectComponent = React.forwardRef( + ({ value, onChange, options, fullWidth = true }, ref) => { + const { theme } = useMode(); + const safeValue = value !== undefined ? value : ''; + + return ( + + ); + } +); + +SelectComponent.displayName = 'SelectComponent'; + +export default SelectComponent; diff --git a/src/components/other/search/SearchComponent.jsx b/src/components/forms/search/SearchComponent.jsx similarity index 98% rename from src/components/other/search/SearchComponent.jsx rename to src/components/forms/search/SearchComponent.jsx index 0d238cb..0a5e57e 100644 --- a/src/components/other/search/SearchComponent.jsx +++ b/src/components/forms/search/SearchComponent.jsx @@ -12,7 +12,7 @@ import { import { useGetSearchData } from '../../../context/hooks/useGetSearchData'; import SearchResults from './SearchResults'; import MDBox from '../../../layout/REUSABLE_COMPONENTS/MDBOX'; -import SearchForm from '../../forms/SearchForm'; +import SearchForm from '../SearchForm'; import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; import SettingsIcon from '@mui/icons-material/Settings'; import { useCardStore, useMode } from '../../../context'; diff --git a/src/components/other/search/SearchResults.jsx b/src/components/forms/search/SearchResults.jsx similarity index 100% rename from src/components/other/search/SearchResults.jsx rename to src/components/forms/search/SearchResults.jsx diff --git a/src/components/other/search/SearchSettings.jsx b/src/components/forms/search/SearchSettings.jsx similarity index 100% rename from src/components/other/search/SearchSettings.jsx rename to src/components/forms/search/SearchSettings.jsx diff --git a/src/components/forms/selectors/CollectionStatisticsSelector.jsx b/src/components/forms/selectors/CollectionStatisticsSelector.jsx new file mode 100644 index 0000000..4fd43f8 --- /dev/null +++ b/src/components/forms/selectors/CollectionStatisticsSelector.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { FormControl } from '@mui/material'; + +import SelectComponent from '../reusable/Select'; +import { useStatisticsStore, useMode } from '../../../context'; +import { StyledChartBox } from '../../../pages/pageStyles/StyledComponents'; + +const CollectionStatisticsSelector = () => { + const { selectedStat, setSelectedStat } = useStatisticsStore(); + const { theme } = useMode(); + + const statisticsOptions = [ + { value: 'highPoint', label: 'High Point' }, + { value: 'lowPoint', label: 'Low Point' }, + { value: 'twentyFourHourAverage', label: '24 Hour Average' }, + { value: 'average', label: 'Average' }, + { value: 'volume', label: 'Volume' }, + { value: 'volatility', label: 'Volatility' }, + ]; + + const handleChange = (event) => setSelectedStat(event.target.value); + + return ( + + + + + + ); +}; + +export default CollectionStatisticsSelector; diff --git a/src/components/forms/selectors/ThemeSelector.jsx b/src/components/forms/selectors/ThemeSelector.jsx new file mode 100644 index 0000000..25e6fca --- /dev/null +++ b/src/components/forms/selectors/ThemeSelector.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { FormControl, InputLabel } from '@mui/material'; +import { Controller } from 'react-hook-form'; +import { useSnackbar } from 'notistack'; + +import SelectComponent from '../reusable/Select'; +import { useFormContext, useMode } from '../../../context'; +import { StyledChartBox } from '../../../pages/pageStyles/StyledComponents'; +import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; + +const ThemeSelector = ({ setTheme }) => { + const { theme } = useMode(); + const { formMethods } = useFormContext(); + const { enqueueSnackbar } = useSnackbar(); + + const { + control, + handleSubmit, + formState: { errors }, + } = formMethods; + + const themeOptions = [ + { value: 'light', label: 'Light Theme' }, + { value: 'dark', label: 'Dark Theme' }, + { value: 'system', label: 'System Theme' }, + ]; + + const onFormSubmit = (data) => { + setTheme(data.themeRange); + enqueueSnackbar('Theme updated successfully', { variant: 'success' }); + }; + + return ( + + + Theme Range + ( + + )} + /> + + + ); +}; + +export default ThemeSelector; diff --git a/src/components/forms/selectors/TimeRangeSelector.jsx b/src/components/forms/selectors/TimeRangeSelector.jsx new file mode 100644 index 0000000..ef4f184 --- /dev/null +++ b/src/components/forms/selectors/TimeRangeSelector.jsx @@ -0,0 +1,192 @@ +import { useMemo } from 'react'; +import { useFormContext, useMode } from '../../../context'; +import useSelectedCollection from '../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import { StyledChartBox } from '../../../pages/pageStyles/StyledComponents'; +import { FormControl, InputLabel } from '@mui/material'; +import { Controller } from 'react-hook-form'; +import SelectComponent from '../reusable/Select'; +import useTimeRange from './useTimeRange'; + +const TimeRangeSelector = ({ showSnackbar }) => { + const { theme } = useMode(); + const { timeRangeOptions, onFormSubmit, control, errors } = + useTimeRange(showSnackbar); + + return ( + + + Time Range + ( + + )} + /> + + + ); +}; +export default TimeRangeSelector; + +// // const TimeRangeSelector = ({ showSnackbar }) => { +// // const { theme } = useMode(); +// // const { formMethods, onSubmit } = useFormContext(); +// // const { selectedCollection } = useSelectedCollection(); +// // const averagedChartData = selectedCollection?.averagedChartData; + +// // const timeRangeOptions = useMemo(() => { +// // const options = []; +// // averagedChartData?.forEach((value, key) => { +// // options.push({ +// // value: key, +// // label: key.toUpperCase(), +// // }); +// // }); +// // return options; +// // }, [averagedChartData]); + +// // const { +// // control, +// // handleSubmit, +// // formState: { errors }, +// // } = formMethods; + +// // const onFormSubmit = (data) => { +// // onSubmit(data, 'timeRangeSelector') +// // .then(() => { +// // showSnackbar( +// // { +// // title: 'Success', +// // description: `Now viewing chart data for ${data?.timeRange}`, +// // }, +// // 'success' +// // ); +// // }) +// // .catch((error) => { +// // showSnackbar( +// // { +// // title: 'Error', +// // description: `Failed to view chart data for ${data?.timeRange}: ${error}`, +// // }, +// // 'error' +// // ); +// // }); +// // }; + +// // return ( +// // +// // +// // Time Range +// // ( +// // +// // )} +// // /> +// // +// // +// // ); +// // }; + +// // export default TimeRangeSelector; +// // import React, { useMemo } from 'react'; +// // import { useFormContext, Controller } from 'react-hook-form'; +// // import { FormControl, InputLabel } from '@mui/material'; +// // import { useMode } from '../../../../context'; +// // import useSelectedCollection from '../../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +// // import SelectComponent from '../../../REUSABLE_COMPONENTS/SelectComponent'; +// // import StyledChartBox from '../../../REUSABLE_COMPONENTS/StyledChartBox'; + +// const TimeRangeSelector = ({ showSnackbar }) => { +// const { theme } = useMode(); +// const { formMethods, onSubmit } = useFormContext(); +// const { selectedCollection } = useSelectedCollection(); +// const averagedChartData = selectedCollection?.averagedChartData; + +// // Convert the Map to an array of options for the select component +// const timeRangeOptions = useMemo(() => { +// return Array.from(averagedChartData?.entries() || []).map( +// ([key, value]) => ({ +// value: key, +// label: value.name.toUpperCase(), +// }) +// ); +// }, [averagedChartData]); + +// const { +// control, +// handleSubmit, +// formState: { errors }, +// } = formMethods; + +// const onFormSubmit = (data) => { +// onSubmit(data, 'timeRangeSelector') +// .then(() => { +// showSnackbar( +// { +// title: 'Success', +// description: `Now viewing chart data for ${data?.timeRange}`, +// }, +// 'success' +// ); +// }) +// .catch((error) => { +// showSnackbar( +// { +// title: 'Error', +// description: `Failed to view chart data for ${data?.timeRange}: ${error}`, +// }, +// 'error' +// ); +// }); +// }; + +// return ( +// +// +// Time Range +// ( +// +// )} +// /> +// +// +// ); +// }; + +// export default TimeRangeSelector; diff --git a/src/components/forms/selectors/useTimeRange.jsx b/src/components/forms/selectors/useTimeRange.jsx new file mode 100644 index 0000000..497d24a --- /dev/null +++ b/src/components/forms/selectors/useTimeRange.jsx @@ -0,0 +1,54 @@ +import { useMemo, useCallback } from 'react'; +import { useFormContext } from '../../../context'; +import useSelectedCollection from '../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; + +function useTimeRange(showSnackbar) { + const { formMethods, onSubmit } = useFormContext(); + const { selectedCollection } = useSelectedCollection(); + const averagedChartData = selectedCollection?.averagedChartData; + + const timeRangeOptions = useMemo(() => { + return Object.keys(averagedChartData || {}).map((key) => ({ + value: key, + label: key.toUpperCase(), + })); + }, [averagedChartData]); + + // Watching the "timeRange" field to get its current value + const selectedTimeRange = formMethods.watch('timeRange', '24hr'); + + const onFormSubmit = useCallback( + (data) => { + onSubmit(data, 'timeRangeSelector') + .then(() => { + showSnackbar( + { + title: 'Success', + description: `Now viewing chart data for ${data?.timeRange}`, + }, + 'success' + ); + }) + .catch((error) => { + showSnackbar( + { + title: 'Error', + description: `Failed to view chart data for ${data?.timeRange}: ${error}`, + }, + 'error' + ); + }); + }, + [onSubmit, showSnackbar] + ); + + return { + timeRangeOptions, + onFormSubmit, + control: formMethods.control, + errors: formMethods.formState.errors, + selectedTimeRange, // Including the selected time range in the hook's return value + }; +} + +export default useTimeRange; diff --git a/src/components/other/InputComponents/CardNameInput.js b/src/components/other/InputComponents/CardNameInput.js deleted file mode 100644 index 2323ea1..0000000 --- a/src/components/other/InputComponents/CardNameInput.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Input } from '@mui/material'; -import { useCardStore } from '../../../context/CardContext/CardStore'; - -const CardNameInput = ({ value, setValue, handleChange }) => { - const { handleRequest } = useCardStore(); - return ( - { - if (event.key === 'Enter') { - handleRequest(value); - } - }} - value={value} - /> - ); -}; - -export default CardNameInput; diff --git a/src/components/other/InputComponents/UpdateStatusBox.jsx b/src/components/other/InputComponents/UpdateStatusBox.jsx deleted file mode 100644 index eb0df63..0000000 --- a/src/components/other/InputComponents/UpdateStatusBox.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Box, Typography } from '@mui/material'; -import { useCombinedContext } from '../../../context/CombinedProvider'; -import { useCookies } from 'react-cookie'; - -const UpdateStatusBox = ({ socket }) => { - const { allCollectionData, listOfMonitoredCards, listOfSimulatedCards } = - useCombinedContext(); - const [currentTime, setCurrentTime] = useState(new Date()); - const [updateStatus, setUpdateStatus] = useState('Waiting for updates...'); - const [cookies] = useCookies(['authUser']); - - useEffect(() => { - const timeInterval = setInterval(() => { - setCurrentTime(new Date()); - }, 1000); - - const handleStatusUpdate = (statusUpdate) => { - setUpdateStatus(statusUpdate.message || 'Waiting for updates...'); - }; - - if (socket) { - socket.on('STATUS_UPDATE', handleStatusUpdate); - } - - // Cleanup function - return () => { - clearInterval(timeInterval); - if (socket) { - socket.off('STATUS_UPDATE', handleStatusUpdate); - } - }; - }, [socket]); - - const sendUpdateRequest = () => { - if (socket) { - socket.emit('STATUS_UPDATE_REQUEST', { - message: 'Requesting status update...', - data: listOfMonitoredCards, - }); - } - }; - - // Styling for dark theme - const styles = { - container: { - padding: '15px', - border: '2px solid #444', - borderRadius: '8px', - backgroundColor: '#222', - color: '#fff', - // margin: '20px auto', - boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', - fontFamily: '"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', - maxWidth: '400px', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - alignItems: 'center', - height: '100%', // Adjust height here - width: '100%', // Adjust width here - }, - statusBox: { - marginTop: '15px', - padding: '10px', - background: '#333', - borderRadius: '6px', - border: '1px solid #555', - }, - button: { - padding: '10px 20px', - marginTop: '10px', - border: 'none', - borderRadius: '5px', - cursor: 'pointer', - backgroundColor: '#5CDB95', - color: 'white', - fontWeight: 'bold', - fontSize: '14px', - letterSpacing: '1px', - outline: 'none', - }, - }; - - return ( - - - Current Time: {currentTime.toLocaleTimeString()} - -
- Update Status: {updateStatus} -
- -
- ); -}; - -export default UpdateStatusBox; diff --git a/src/components/other/InputComponents/UpdateStatusBox2.jsx b/src/components/other/InputComponents/UpdateStatusBox2.jsx deleted file mode 100644 index 263fed2..0000000 --- a/src/components/other/InputComponents/UpdateStatusBox2.jsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Snackbar, Typography, Box, Button } from '@mui/material'; -import { useCookies } from 'react-cookie'; -import { useCombinedContext, useMode } from '../../../context'; -import { StyledChartBox } from '../../../pages/pageStyles/StyledComponents'; - -const UpdateStatusBox2 = ({ socket }) => { - const { listOfMonitoredCards, handleSendAllCardsInCollections } = - useCombinedContext(); - const [currentTime, setCurrentTime] = useState(new Date()); - const [updateStatus, setUpdateStatus] = useState('Waiting for cron...'); - const [cookies] = useCookies(['authUser']); - const [snackbarData, setSnackbarData] = useState({ - open: false, - message: '', - }); - - const userId = cookies?.authUser?.userId; - const { theme } = useMode(); - const openSnackbar = (message) => { - setSnackbarData({ open: true, message }); - }; - - useEffect(() => { - const timeInterval = setInterval(() => { - setCurrentTime(new Date()); - }, 1000); - - const handleStatusUpdate = (statusUpdate) => { - setUpdateStatus( - (prevStatus) => - statusUpdate.message || prevStatus || 'Waiting for updates...' - ); - }; - - socket?.on('INITIAL_RESPONSE', handleStatusUpdate); - - return () => { - clearInterval(timeInterval); - socket?.off('INITIAL_RESPONSE', handleStatusUpdate); - }; - }, [socket]); // Assuming `socket` is stable and doesn't change on every render - - const handleTriggerCronJob = () => { - if (userId && listOfMonitoredCards.length > 0) { - handleSendAllCardsInCollections(userId, listOfMonitoredCards); - openSnackbar('Triggered the cron job.'); - } - }; - - return ( - - - Current Time: {currentTime.toLocaleTimeString()} - - - Update Status: {updateStatus} - - - setSnackbarData({ ...snackbarData, open: false })} - message={snackbarData.message} - /> - - ); -}; - -export default UpdateStatusBox2; diff --git a/src/components/other/dataDisplay/CardCountDisplay.jsx b/src/components/other/dataDisplay/CardCountDisplay.jsx deleted file mode 100644 index c43a229..0000000 --- a/src/components/other/dataDisplay/CardCountDisplay.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { Grid, Typography } from '@mui/material'; -import { styled } from '@mui/material/styles'; -import PropTypes from 'prop-types'; - -// Styled components -const StyledGrid = styled(Grid)(({ theme }) => ({ - padding: theme.spacing(1), - backgroundColor: theme.palette.backgroundA.lightest, - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[2], - textAlign: 'center', -})); - -const CardCountDisplay = ({ quantity, label, className }) => { - const totalItems = quantity && quantity.totalItems ? quantity.totalItems : 0; - - return ( - - - - {label}: {totalItems} - - - - ); -}; - -CardCountDisplay.propTypes = { - quantity: PropTypes.shape({ - totalItems: PropTypes.number, - }), - label: PropTypes.string, - className: PropTypes.string, -}; - -export default CardCountDisplay; diff --git a/src/components/other/dataDisplay/CartSummary.js b/src/components/other/dataDisplay/CartSummary.js deleted file mode 100644 index 27d2b9e..0000000 --- a/src/components/other/dataDisplay/CartSummary.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Box, Typography } from '@mui/material'; - -const CartSummary = ({ quantity, totalCost }) => { - return ( - - - Items: {quantity} - - - Grand Total: ${totalCost} - - - ); -}; - -export default CartSummary; diff --git a/src/components/other/dataDisplay/CartTotal.jsx b/src/components/other/dataDisplay/CartTotal.jsx deleted file mode 100644 index 6d52cfd..0000000 --- a/src/components/other/dataDisplay/CartTotal.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { Typography } from '@mui/material'; - -const CartTotal = ({ total }) => ( - - {`Total: $${total}`} {/* Ensure this is a string or number */} - -); - -export default CartTotal; diff --git a/src/components/other/dataDisplay/ProgressCircle.jsx b/src/components/other/dataDisplay/ProgressCircle.jsx deleted file mode 100644 index 8652369..0000000 --- a/src/components/other/dataDisplay/ProgressCircle.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Box, useTheme } from '@mui/material'; -import { tokens } from '../../../assets/tokens'; - -const ProgressCircle = ({ progress = '0.75', size = '40' }) => { - const theme = useTheme(); - const colors = tokens(theme.palette.mode); - const angle = progress * 360; - - return ( - - ); -}; - -export default ProgressCircle; diff --git a/src/components/other/dataDisplay/StatBox.jsx b/src/components/other/dataDisplay/StatBox.jsx deleted file mode 100644 index 1a10c72..0000000 --- a/src/components/other/dataDisplay/StatBox.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Box, Typography, useTheme } from '@mui/material'; -import ProgressCircle from './ProgressCircle'; -import { tokens } from '../../../assets/tokens'; - -const StatBox = ({ title, subtitle, icon, progress, increase }) => { - const theme = useTheme(); - const colors = tokens(theme.palette.mode); - - return ( - - - - {icon} - - {title} - - - - - - - - - - {subtitle} - - - {increase} - - - - ); -}; - -export default StatBox; diff --git a/src/components/other/dataDisplay/StatCard.jsx b/src/components/other/dataDisplay/StatCard.jsx deleted file mode 100644 index 711a732..0000000 --- a/src/components/other/dataDisplay/StatCard.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { Card, CardContent, Typography } from '@mui/material'; - -const StatCard = ({ title, value }) => ( - - - - {title} - - - {value} - - - -); - -export default StatCard; diff --git a/src/components/other/dataDisplay/chart/LinearChart.js b/src/components/other/dataDisplay/chart/LinearChart.js deleted file mode 100644 index d6ad9b6..0000000 --- a/src/components/other/dataDisplay/chart/LinearChart.js +++ /dev/null @@ -1,18 +0,0 @@ -import { ChartConfiguration } from '../../../../context/MAIN_CONTEXT/ChartContext/helpers'; -import ChartErrorBoundary from '../../../reusable/ChartErrorBoundary'; - -const LinearChart = ({ height, specialPoints, timeRange, nivoData }) => { - return ( - - - - ); -}; - -export default LinearChart; diff --git a/src/components/reusable/icons/GlassyIcon.jsx b/src/components/reusable/icons/GlassyIcon.jsx new file mode 100644 index 0000000..dd1dbcd --- /dev/null +++ b/src/components/reusable/icons/GlassyIcon.jsx @@ -0,0 +1,92 @@ +// import React from 'react'; +// import { Box, Icon } from '@mui/material'; +// import StarIcon from '@mui/icons-material/Star'; // You can choose any icon +// const GlassyIcon = ({ +// icon: StarIcon, +// iconColor = '#FFFFFF', +// gradientStartColor, +// gradientEndColor, +// size = 100, +// blurAmount = 75, +// }) => { +// return ( +// +// +// +// +// +// +// +// +// +// +// +// +// ); +// }; + +// export default GlassyIcon; +import React from 'react'; +import { Box } from '@mui/material'; + +const GlassyIcon = ({ Icon, iconColor = '#FFFFFF', size = 40 }) => { + return ( + + {Icon && Icon} + {/* Render the Icon */} + + ); +}; + +export default GlassyIcon; diff --git a/src/context/MAIN_CONTEXT/AuthContext/authContext.js b/src/context/MAIN_CONTEXT/AuthContext/authContext.js index 8cc46a8..46cf135 100644 --- a/src/context/MAIN_CONTEXT/AuthContext/authContext.js +++ b/src/context/MAIN_CONTEXT/AuthContext/authContext.js @@ -16,180 +16,167 @@ import useLogger from '../../hooks/useLogger'; import { defaultContextValue } from '../../constants'; import { Redirect, useNavigate } from 'react-router-dom'; import { useLoading } from '../../hooks/useLoading'; +import useFetchWrapper from '../../hooks/useFetchWrapper'; +import jwt_decode from 'jwt-decode'; export const AuthContext = createContext(defaultContextValue.AUTH_CONTEXT); const REACT_APP_SERVER = process.env.REACT_APP_SERVER; export default function AuthProvider({ children }) { - const { setIsDataLoading } = usePageContext(); - const { startLoading, stopLoading } = useLoading(); // Utilize useLoading hook - const { logEvent } = useLogger('AuthContext'); - const navigate = useNavigate(); // Create navigate instance - const [lastTokenCheck, setLastTokenCheck] = useState(Date.now()); - const checkInterval = 120000; // 2 minutes in milliseconds + const navigate = useNavigate(); const [cookies, setCookie, removeCookie] = useCookies([ 'accessToken', 'refreshToken', - 'user', - 'authUser', - 'userBasicData', - 'userSecurityData', 'isLoggedIn', + 'authUser', 'userId', ]); + const [user, setUser] = useState({ + isLoggedIn: false, + accessToken: '', + userId: '', + }); + + // Helper function to decode JWT and set user state + const decodeAndSetUser = (accessToken) => { + const decoded = jwt_decode(accessToken); + setUser({ + isLoggedIn: true, + accessToken, + userId: decoded.userId, // Or any other way you extract userId from token or cookies + }); + }; + + useEffect(() => { + const { accessToken } = cookies; + if (accessToken) { + decodeAndSetUser(accessToken); + } else { + setUser({ + isLoggedIn: false, + accessToken: '', + userId: '', + }); + } + }, [cookies.accessToken]); + const [responseMessage, setResponseMessage] = useState(''); - const logoutTimerRef = useRef(null); - const executeAuthAction = async (actionType, url, requestData) => { - const loadingID = actionType; - startLoading(loadingID); + const { fetchWrapper } = useFetchWrapper(); + const { logEvent } = useLogger('AuthContext'); + // Helper function to set cookies + const setAuthCookies = (data) => { + const { accessToken, refreshToken, user } = data; + const authData = jwt_decode(accessToken); + + setCookie('accessToken', accessToken, { path: '/' }); + setCookie('refreshToken', refreshToken, { path: '/' }); + setCookie('isLoggedIn', true, { path: '/' }); + // setCookie('userBasicData', basicData, { path: '/' }); + // setCookie('userSecurityData', securityData, { path: '/' }); + setCookie('user', user, { path: '/' }); + setCookie('authUser', authData, { path: '/' }); + setCookie('userId', user._id, { path: '/' }); + navigate('/home'); + }; + + const clearAuthCookies = () => { + // List all cookie names to remove + ['accessToken', 'refreshToken', 'user'].forEach((cookieName) => + removeCookie(cookieName, { path: '/' }) + ); + navigate('/login'); + }; + + const executeAuthAction = async (actionType, url, requestData, loadingId) => { try { - const response = await axios.post( + const responseData = await fetchWrapper( `${REACT_APP_SERVER}/api/users/${url}`, - requestData + 'POST', + requestData, + loadingId ); - setResponseMessage(response.data.message); - // const { data } = handleApiResponse(response.data, 'executeAuthAction'); - if (response.status === 200 || response.status === 201) { - const { - accessToken, - refreshToken, - authData, - basicData, - securityData, - responseData, - refreshData, - } = processResponseData(response.data, actionType); - setCookie('accessToken', accessToken, { path: '/' }); - setCookie('refreshToken', refreshToken, { path: '/' }); - setCookie('isLoggedIn', true, { path: '/' }); - setCookie('userBasicData', basicData, { path: '/' }); - setCookie('userSecurityData', securityData, { path: '/' }); - setCookie('user', responseData.user, { path: '/' }); - setCookie('authUser', authData, { path: '/' }); - setCookie('userId', authData.userId, { path: '/' }); - resetLogoutTimer(); - navigate('/home'); // Add this line for redirection - } + if (!responseData?.data) throw new Error('Invalid response structure'); + setAuthCookies(responseData.data); } catch (error) { - logEvent('Auth error:', error); - } finally { - stopLoading(loadingID); + console.error('Auth action error:', error); + setResponseMessage( + error.message || 'An error occurred during authentication.' + ); } }; - const logout = useCallback(async () => { - startLoading('logout'); - try { - const { userId, refreshToken } = cookies; - await axios.post(`${REACT_APP_SERVER}/api/users/signout`, { - userId, - refreshToken, - }); - Object.keys(cookies).forEach(removeCookie); - clearTimeout(logoutTimerRef.current); - } catch (error) { - console.error('Logout error:', error); - } finally { - stopLoading('logout'); - } - }, [removeCookie, startLoading, stopLoading]); - - const resetLogoutTimer = useCallback(() => { - clearTimeout(logoutTimerRef.current); - logoutTimerRef.current = setTimeout(logout, 2700000); // 45 minutes - }, [logout]); + const logout = useCallback(() => { + clearAuthCookies(); + }, [removeCookie, navigate]); const login = useCallback( async (username, password) => { - logEvent('Logging in...'); - await executeAuthAction('signin', 'signin', { - userSecurityData: { username, password }, - }); + await executeAuthAction( + 'signin', + 'signin', + { + userSecurityData: { username, password }, + }, + 'login' + ); }, [executeAuthAction] ); - const signup = async (securityData, basicData) => { - logEvent('Signing up...'); - await executeAuthAction('signup', 'signup', { - userSecurityData: securityData, - userBasicData: basicData, - }); - }; + const signup = useCallback( + async (username, password, firstName, lastName, email) => { + await executeAuthAction( + 'signup', + 'signup', + { + userSecurityData: { firstName, lastName, username, password, email }, + }, + 'signup' + ); + }, + [executeAuthAction] + ); - const checkTokenValidity = useCallback(async () => { - logEvent('Checking token validity.'); - startLoading('checkTokenValidity'); + const checkTokenValidity = useCallback(() => { + const accessToken = cookies.accessToken; + if (!accessToken) { + logout(); + return; + } try { - const accessToken = cookies.accessToken; - if (!accessToken) return false; - - const response = await axios.get( - `${REACT_APP_SERVER}/api/users/checkToken`, - { headers: { Authorization: `Bearer ${accessToken}` } } - ); - setResponseMessage(response.data.message); - const isValid = response.data.message === 'Token is valid'; - - console.log('Token validity:', response.data); - if (!isValid) { + const { exp } = jwt_decode(accessToken); + const isTokenExpired = Date.now() >= exp * 1000; + if (isTokenExpired) { console.log('Token is invalid, logging out...'); logout(); } - - setLastTokenCheck(Date.now()); - return isValid; } catch (error) { console.error('Token validation error:', error); - logEvent(`Token validation failed: ${error}`); - logout(); // Log out if token validation fails - } finally { - stopLoading('checkTokenValidity'); + logout(); } - }, [cookies.accessToken, logout, startLoading, stopLoading]); + }, [cookies.accessToken, logout]); + // Token validity check could be triggered periodically or on specific events useEffect(() => { - const currentTime = Date.now(); - if (currentTime - lastTokenCheck >= checkInterval) { - checkTokenValidity(); - } - - const timeToNextCheck = lastTokenCheck + checkInterval - currentTime; - const timeoutId = setTimeout( - checkTokenValidity, - timeToNextCheck > 0 ? timeToNextCheck : checkInterval - ); - - return () => clearTimeout(timeoutId); // Cleanup the timeout - }, [checkTokenValidity, lastTokenCheck]); + checkTokenValidity(); + }, [checkTokenValidity]); const contextValue = useMemo( () => ({ - // isLoggedIn: cookies.isLoggedIn, - isLoggedIn: !!cookies.accessToken, - accessToken: cookies.accessToken, - refreshToken: cookies.refreshToken, - responseMessage, - authUser: cookies.authUser, - user: cookies.user, - basicData: cookies.userBasicData, - securityData: cookies.userSecurityData, - userId: cookies.userId, - + ...user, + isLoggedIn: + process.env.AUTH_ENVIRONMENT !== 'disabled' + ? !!cookies.accessToken + : true, login, signup, logout, - checkTokenValidity, - resetLogoutTimer, + responseMessage, }), - [cookies, login, logout, resetLogoutTimer] + [cookies.accessToken, login, signup, logout, responseMessage, user] ); - useEffect(() => { - console.log('AUTH CONTEXT:', contextValue); - }, [contextValue.login, contextValue.logout, contextValue.isLoggedIn]); - return ( {children} ); diff --git a/src/context/MAIN_CONTEXT/CartContext/CartContext.js b/src/context/MAIN_CONTEXT/CartContext/CartContext.js index 26e1543..c7b954b 100644 --- a/src/context/MAIN_CONTEXT/CartContext/CartContext.js +++ b/src/context/MAIN_CONTEXT/CartContext/CartContext.js @@ -58,7 +58,7 @@ export const CartProvider = ({ children }) => { const [selectedCards, setSelectedCards] = useState([]); const cartId = useRef(selectedCart?.cart?._id); const [hasFetchedCart, setHasFetchedCart] = useState(false); - const { startLoading, stopLoading, isLoading } = useLoading(); + const { isLoading } = useLoading(); const updateSelectedCart = useCallback((cart) => { setSelectedCart(cart); setSelectedCards(cart?.cart?.slice(0, 30) || []); @@ -207,10 +207,8 @@ export const CartProvider = ({ children }) => { }, [userId, fetchWrapper, setCartDataAndCookie]); const fetchUserCart = useCallback(async () => { - if (!userId) return; const loadingID = 'fetchUserCart'; - startLoading(loadingID); - + if (!userId || isLoading(loadingID)) return; try { const responseData = await fetchWrapper( `${process.env.REACT_APP_SERVER}/api/users/${userId}/cart`, @@ -224,21 +222,15 @@ export const CartProvider = ({ children }) => { responseData?.status === 201 ) { console.log('SUCCESS: fetching user cart'); - const cachedData = responseCache[loadingID]; - if (cachedData) { - setCartDataAndCookie(cachedData); // Assuming setCartDataAndCookie updates local storage or state with cart data - } - } - if (responseData && responseData?.status !== 200) { - console.error('ERROR: fetching user cart'); - setError(responseData?.data?.message || 'Failed to fetch user cart'); + setCartDataAndCookie(responseData?.data); } } catch (error) { console.error(error); setError(error.message || 'Failed to fetch user cart'); logger.logEvent('Failed to fetch user cart', error.message); } finally { - stopLoading(loadingID); + setHasFetchedCart(true); + // stopLoading(loadingID); } }, [ userId, @@ -246,26 +238,13 @@ export const CartProvider = ({ children }) => { createApiUrl, fetchWrapper, responseCache, - startLoading, - stopLoading, setCartDataAndCookie, setError, logger, ]); - useEffect(() => { - const storedResponse = responseCache['fetchUserCart']; - console.log('Stored response for user cart:', storedResponse); - if (storedResponse) { - // Assuming setCartDataAndCookie is a function that updates the cart's data in state or context and possibly updates cookies - setCartDataAndCookie(storedResponse); // Adjust according to your actual state update mechanism - } - }, [responseCache]); - - useEffect(() => { - if (userId && typeof userId === 'string') { - fetchUserCart(); - } - }, []); + // useEffect(() => { + // if (!hasFetchedCart) fetchUserCart(); + // }, [fetchUserCart, userId, hasFetchedCart, setCartDataAndCookie]); const updateCart = useCallback( async (cartId, updatedCart, method, type) => { if (!userId || !cartId) return; diff --git a/src/context/MAIN_CONTEXT/ChartContext/ChartContext.jsx b/src/context/MAIN_CONTEXT/ChartContext/ChartContext.jsx index 937d33f..63beabd 100644 --- a/src/context/MAIN_CONTEXT/ChartContext/ChartContext.jsx +++ b/src/context/MAIN_CONTEXT/ChartContext/ChartContext.jsx @@ -1,111 +1,36 @@ import { createContext, useContext, useEffect, useMemo, useState } from 'react'; -import { - groupAndAverageData, - convertDataForNivo2, - getUniqueValidData, - getTickValues, - getFilteredData, - formatDateToString, - finalizeNivoData, -} from './helpers'; import { useCollectionStore } from '../CollectionContext/CollectionContext'; import { defaultChartConstants, defaultContextValue } from '../../constants'; -const { TIME_RANGES, TIME_RANGE_PROPS, TIME_RANGES_KEYS } = +const { TIME_RANGES, SELECTED_TIME_RANGE, TIME_RANGE_PROPS, TIME_RANGES_KEYS } = defaultChartConstants; const ChartContext = createContext(defaultContextValue.CHART_CONTEXT); export const ChartProvider = ({ children }) => { const { selectedCollection } = useCollectionStore(); - const [latestData, setLatestData] = useState(null); - const [timeRange, setTimeRange] = useState({ - id: '24hr', - value: '24hr', - name: 'Last 24 Hours', - }); - // Use useEffect to log changes to timeRange - useEffect(() => { - console.log('TimeRange changed to:', timeRange); - }, [timeRange]); // This effect depends on timeRange, so it runs whenever timeRange changes. - - const finalizedNivoData = useMemo(() => { - if (selectedCollection.nivoChartData) { - return finalizeNivoData(selectedCollection?.nivoChartData); - } - }, [latestData]); - - const { tickValues, xFormat } = useMemo(() => { - let format, ticks; - switch (timeRange) { - case '24hr': - format = '%H:%M'; - ticks = 'every hour'; - break; - case '7d': - format = '%b %d'; - ticks = 'every day'; - break; - case '30d': - format = '%b %d'; - ticks = 'every day'; - break; - case '90d': - format = '%b %d'; - ticks = 'every 3 days'; - break; - case '180d': - format = '%b %d'; - ticks = 'every 6 days'; - break; - case '270d': - format = '%b %d'; - ticks = 'every 9 days'; - break; - case '365d': - format = '%b %d'; - ticks = 'every 12 days'; - break; - default: - format = '%b %d'; - ticks = 'every day'; - } - return { tickValues: ticks, xFormat: `time:${format}` }; - }, [timeRange]); const contextValue = useMemo( () => ({ - // currentValue, - latestData, - timeRange, - timeRanges: TIME_RANGES, - // selectedTimeRange: selectedTimeRange, - tickValues, - xFormat, - finalizedNivoData, + // timeRange: selectedCollection.averagedChartData[], + // timeRanges: TIME_RANGES, + // tickValues: tickValues, + // xFormat: xFormat, + nivoChartData: selectedCollection?.nivoChartData, newNivoChartData: selectedCollection?.newNivoChartData, muiChartData: selectedCollection?.muiChartData, - // setSelectedTimeRange, - finalizeNivoData, - groupAndAverageData, - convertDataForNivo2, - // getUniqueValidData, - getTickValues, - getFilteredData, - formatDateToString, - setTimeRange, - setLatestData, - // handleChange, + nivoTestData: selectedCollection?.nivoTestData, + // setTimeRange, }), [ - latestData, - timeRange, - tickValues, - xFormat, - finalizedNivoData, + // timeRange, + // tickValues, + // xFormat, selectedCollection?.nivoChartData, selectedCollection?.newNivoChartData, selectedCollection?.muiChartData, + selectedCollection?.nivoTestData, + // setTimeRange, ] ); return ( diff --git a/src/context/MAIN_CONTEXT/ChartContext/helpers.jsx b/src/context/MAIN_CONTEXT/ChartContext/helpers.jsx index c32f88e..ccfd38e 100644 --- a/src/context/MAIN_CONTEXT/ChartContext/helpers.jsx +++ b/src/context/MAIN_CONTEXT/ChartContext/helpers.jsx @@ -10,6 +10,7 @@ import { useAuthContext } from '../AuthContext/authContext'; import useCollectionManager from '../CollectionContext/useCollectionManager'; import { ResponsiveLine } from '@nivo/line'; import { useLoading } from '../../hooks/useLoading'; +import useSelectedCollection from '../CollectionContext/useSelectedCollection'; export const groupAndAverageData = (data, threshold = 600000, timeRange) => { if (!data || data.length === 0) return []; @@ -76,47 +77,33 @@ export const getAveragedData = (data) => { }); }; -export const getTickValues = (timeRange) => { - console.log('timeRange: ', timeRange); - const mapping = { - 600000: 'every 10 minutes', - 900000: 'every 15 minutes', - 3600000: 'every hour', - 7200000: 'every 2 hours', - 86400000: 'every day', - 604800000: 'every week', - 2592000000: 'every month', - }; - return mapping[timeRange] || 'every day'; // Default to 'every week' if no match -}; - -export const convertDataForNivo2 = (rawData2) => { - if (!Array.isArray(rawData2) || rawData2.length === 0) { - console.error('Invalid or empty rawData provided', rawData2); - return []; - } - - // Assuming rawData2 is an array of objects with 'x' and 'y' properties - const nivoData = rawData2?.map((dataPoint) => { - // Ensure the 'x' value is in ISO date string format - const xValue = - dataPoint[0]?.x instanceof Date - ? dataPoint[0]?.x?.toISOString() - : dataPoint[0]?.x; - const yValue = dataPoint[0]?.y; // Assuming y value is directly usable - - return { x: xValue, y: yValue }; - }); - - // Wrapping the data for a single series. You can add more series similarly - return [ - { - id: 'Your Data', // Replace with a meaningful id - color: 'hsl(252, 70%, 50%)', // Replace with color of your choice or logic for dynamic colors - data: nivoData, - }, - ]; -}; +// export const convertDataForNivo2 = (rawData2) => { +// if (!Array.isArray(rawData2) || rawData2.length === 0) { +// console.error('Invalid or empty rawData provided', rawData2); +// return []; +// } + +// // Assuming rawData2 is an array of objects with 'x' and 'y' properties +// const nivoData = rawData2?.map((dataPoint) => { +// // Ensure the 'x' value is in ISO date string format +// const xValue = +// dataPoint[0]?.x instanceof Date +// ? dataPoint[0]?.x?.toISOString() +// : dataPoint[0]?.x; +// const yValue = dataPoint[0]?.y; // Assuming y value is directly usable + +// return { x: xValue, y: yValue }; +// }); + +// // Wrapping the data for a single series. You can add more series similarly +// return [ +// { +// id: 'Your Data', // Replace with a meaningful id +// color: 'hsl(252, 70%, 50%)', // Replace with color of your choice or logic for dynamic colors +// data: nivoData, +// }, +// ]; +// }; export const finalizeNivoData = (nivoChartData) => { // return nivoData which is the data array, combined with id and color properties @@ -199,7 +186,6 @@ export const useEventHandlers = () => { debounce(setHoveredData, 100), [] ); - const handleMouseMove = useCallback( (point) => { debouncedSetHoveredData( @@ -208,12 +194,10 @@ export const useEventHandlers = () => { }, [debouncedSetHoveredData] ); - const handleMouseLeave = useCallback( () => debouncedSetHoveredData(null), [debouncedSetHoveredData] ); - return { hoveredData, handleMouseMove, handleMouseLeave }; }; const TooltipBox = styled(Box)(({ theme }) => ({ @@ -223,7 +207,7 @@ const TooltipBox = styled(Box)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, })); export const isSpecialPoint = (markers, point) => - markers.some((sp) => sp.x === point.data.x && sp.y === point.data.y); + markers?.some((sp) => sp?.x === point.data.x && sp.y === point.data.y); export const CustomTooltipLayer = ({ points, xScale, yScale, markers }) => ( <> {points?.map((point, index) => { @@ -265,180 +249,3 @@ export const CustomTooltip = ({ point, markers, timeRange }) => { ); }; -export const ChartConfiguration = ({ - markers, - height, - timeRange, - nivoChartData, - loadingID, -}) => { - const { theme } = useMode(); - const { startLoading, stopLoading, isLoading, isAnyLoading } = useLoading(); - - const { tickValues } = useChartContext(); - const { isLoggedIn, userId } = useAuthContext(); - const { selectedCollection } = useCollectionManager(isLoggedIn, userId); - const { handleMouseMove, handleMouseLeave } = useEventHandlers(); - const validMarkers = markers.filter((marker) => marker.value !== undefined); - - const chartProps = useMemo( - () => ({ - data: [nivoChartData], - onMouseMove: handleMouseMove, - onMouseLeave: handleMouseLeave, - // tooltip: ({ point }) => , - tooltip: CustomTooltip, - - color: theme.palette.backgroundA.contrastTextA, - text: { - color: theme.palette.backgroundA.contrastTextA, - fill: theme.palette.backgroundA.contrastTextA, - fontSize: 12, - }, - yFormat: '$.2f', - - colors: theme.palette.backgroundE.dark, - axisBottom: { - tickRotation: 0, - legend: 'Time', - legendOffset: 40, - legendPosition: 'middle', - tickSize: 5, - tickPadding: 5, - tickValues: tickValues, - format: (value) => { - const d = new Date(value); - return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; - }, - }, - axisLeft: { - tickSize: 5, - tickPadding: 5, - tickRotation: 0, - legend: 'Value ($)', - legendOffset: -50, - legendPosition: 'middle', - color: theme.palette.text.primary, - format: (value) => `$${value.toFixed(2)}`, - }, - margin: { top: 20, right: 40, bottom: 50, left: 55 }, - padding: 0.3, - animate: true, - xFormat: 'time:%Y-%m-%d %H:%M:%S', - xScale: { - type: 'time', - format: '%Y-%m-%dT%H:%M:%S.%LZ', // Adjust if necessary to match your input data format - useUTC: false, - precision: 'second', - }, - yScale: { - type: 'linear', - min: 'auto', - max: 'auto', - stacked: true, - reverse: false, - }, - pointSize: 10, - pointBorderWidth: 1, - curve: 'monotoneX', - useMesh: true, - motionConfig: 'gentle', - - stiffness: 90, - damping: 15, - enableSlices: 'x', - // pointBorderColor: { from: 'serieColor', modifiers: [] }, - pointBorderColor: theme.palette.primary.main, - // pointColor: { from: 'color', modifiers: [] }, - markers: validMarkers, - layers: [ - 'grid', - 'markers', - 'areas', - 'lines', - 'slices', - 'points', - 'axes', - 'legends', - ({ points, xScale, yScale, markers }) => ( - - ), - ], - theme: { - axis: { - domain: { - line: { - stroke: theme.palette.backgroundA.contrastTextA, - strokeWidth: 1, - }, - }, - ticks: { - line: { - stroke: theme.palette.backgroundA.contrastTextA, - strokeWidth: 1, - }, - text: { - fill: theme.palette.backgroundA.contrastTextA, - fontSize: 12, - fontWeight: 400, - }, - }, - legend: { - text: { - fill: theme.palette.text.primary, - fontSize: 12, - fontWeight: 500, - }, - }, - }, - grid: { - line: { - stroke: theme.palette.divider, - strokeWidth: 1, - }, - }, - }, - }), - [ - nivoChartData, - handleMouseMove, - handleMouseLeave, - markers, - theme, - tickValues, - ] - ); - - const NivoContainer = ({ children, height }) => ( -
-
-
{children}
-
-
- ); - - // useEffect(() => { - // startLoading(loadingID); - // // Mock async data fetching - // setTimeout(() => stopLoading(), 1000); // Remove this in real scenario - // }, [loadingID, startLoading, stopLoading, fetchWrapper, logger]); - - if ( - isLoading( - 'http://localhost:3001/api/users/65b8e155b4885b451a5071c8/collections/allCollections' - ) - ) { - return Loading chart...; - } - - return ( - - - - ); -}; diff --git a/src/context/MAIN_CONTEXT/CollectionContext/CollectionContext.jsx b/src/context/MAIN_CONTEXT/CollectionContext/CollectionContext.jsx index 0912f80..98c3780 100644 --- a/src/context/MAIN_CONTEXT/CollectionContext/CollectionContext.jsx +++ b/src/context/MAIN_CONTEXT/CollectionContext/CollectionContext.jsx @@ -11,6 +11,8 @@ import { calculateCollectionValue } from './collectionUtility'; import { useAuthContext } from '../AuthContext/authContext'; import useCollectionManager from './useCollectionManager'; import { defaultContextValue } from '../../constants'; +import useSelectedCollection from './useSelectedCollection'; +import { json } from 'react-router-dom'; export const CollectionContext = createContext( defaultContextValue.COLLECTION_CONTEXT @@ -19,114 +21,48 @@ export const CollectionContext = createContext( export const CollectionProvider = ({ children }) => { const { isLoggedIn, authUser, userId } = useAuthContext(); const { - collectionData, - allCollections, selectedCollection, - hasFetchedCollections, - selectedCards, - collectionStatistics, - chartData, - totalPrice, - totalQuantity, - collectionPriceHistory, - allXYValues, - lastSavedPrice, - latestPrice, - newNivoChartData, - error, - isLoading, - - setCollectionData, + allCollections, + showCollections, + selectedCollectionId, + } = useSelectedCollection(); + const { + fetchCollections, createNewCollection, - getAllCollectionsForUser, - updateAndSyncCollection, deleteCollection, + updateCollection, addCardsToCollection, removeCardsFromCollection, - // updateCardsInCollection, - updateChartDataInCollection, - updateSelectedCollection, - setAllCollections, - setSelectedCollection, - setSelectedCards, - getTotalPrice, - checkAndUpdateCardPrices, - } = useCollectionManager(isLoggedIn, userId); + selectedCollectionError, + error, + hasFetchedCollections, + handleError, + setSelectedCollectionError, + } = useCollectionManager(); + + useEffect(() => { + // Check if collections need to be fetched for the logged-in user + if (!hasFetchedCollections && isLoggedIn && userId) { + fetchCollections(); + } + }, []); const contextValue = useMemo( () => ({ - // ...defaultContextValue.COLLECTION_CONTEXT, - // MAIN STATE - collectionData, + ...selectedCollection, + selectedCollection: selectedCollection, + selectedCollectionId, allCollections, - selectedCollection, - selectedCards, - hasFetchedCollections, - // SECONDARY STATE (derived from main state selectedCollection) - collectionStatistics, - chartData, - totalQuantity, - collectionPriceHistory, - allXYValues, - lastSavedPrice, - latestPrice, - newNivoChartData, + showCollections, error, - isLoading, - // STATE SETTERS - setCollectionData, - setAllCollections, - setSelectedCollection, - setSelectedCards, - - // COLLECTION ACTIONS - createUserCollection: createNewCollection, - getAllCollectionsForUser, - updateAndSyncCollection, - deleteCollection, - updateSelectedCollection, - - // CARD ACTIONS - addOneToCollection: (card, collection) => - addCardsToCollection([card], collection), - removeOneFromCollection: (card, cardId, collection) => - removeCardsFromCollection([card], [cardId], collection), - // updateOneInCollection: (collectionId, card, incrementType) => { - // updateCardsInCollection(collectionId, [card], incrementType); - // }, - updateChartDataInCollection, - - // CRON JOB ACTIONS - checkAndUpdateCardPrices, - - // OTHER ACTIONS - getTotalPrice, - // getTotalPrice: () => calculateCollectionValue(selectedCollection), - // toggleCollectionVisibility: () => {}, + selectedCollectionError, }), - [ - allCollections, - selectedCollection, - selectedCards, - newNivoChartData, - setAllCollections, - setSelectedCollection, - setSelectedCards, - createNewCollection, - getAllCollectionsForUser, - updateAndSyncCollection, - deleteCollection, - addCardsToCollection, - removeCardsFromCollection, - // updateCardsInCollection, - updateChartDataInCollection, - calculateCollectionValue, - ] + [selectedCollection, allCollections, showCollections, error] ); - // useEffect(() => { - // console.log('COLLECTION CONTEXT:', contextValue); - // }, [contextValue]); + useEffect(() => { + console.log('COLLECTION CONTEXT:', contextValue); + }, [contextValue]); return ( diff --git a/src/context/MAIN_CONTEXT/CollectionContext/helpers.jsx b/src/context/MAIN_CONTEXT/CollectionContext/helpers.jsx index 2a697bc..102ea76 100644 --- a/src/context/MAIN_CONTEXT/CollectionContext/helpers.jsx +++ b/src/context/MAIN_CONTEXT/CollectionContext/helpers.jsx @@ -1,20 +1,16 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import moment from 'moment'; -import { roundToNearestTenth } from '../../Helpers'; - -export const transformPriceHistoryToXY = (collectionPriceHistory) => { - return collectionPriceHistory?.map((entry) => ({ - x: entry?.timestamp, // x represents the timestamp - y: entry?.num, // y represents the numerical value - label: `Price at ${entry?.timestamp}`, // label can be customized as needed - })); -}; +// export const transformPriceHistoryToXY = (collectionPriceHistory) => { +// return collectionPriceHistory?.map((entry) => ({ +// x: entry?.timestamp, // x represents the timestamp +// y: entry?.num, // y represents the numerical value +// label: `Price at ${entry?.timestamp}`, // label can be customized as needed +// })); +// }; -export const logError = (source, action, error) => { - console.error( - `[${source.toUpperCase()}] Failed to ${action}: ${error.message}` - ); -}; +// export const logError = (source, action, error) => { +// console.error( +// `[${source.toUpperCase()}] Failed to ${action}: ${error.message}` +// ); +// }; /** * Checks if the given object is empty. @@ -27,412 +23,6 @@ export const isEmpty = (obj) => { !Object.entries(obj || {}).length ); }; - -/** - * Validates the given data based on its type and emptiness. - * @param {any} data - Data to validate. - * @param {String} eventName - Event that triggered the function. - * @param {String} functionName - Name of the function. - * @returns {Boolean} True if the data is valid, false otherwise. - */ -export const validateData = (newCollectionInfo, eventName, functionName) => { - console.log('NEW COLLECTION INFO: ', newCollectionInfo); - if (typeof newCollectionInfo.name === 'string') { - if (!newCollectionInfo.name || !newCollectionInfo.description) { - console.error( - `The data passed to ${functionName} from ${eventName} is empty.` - ); - return false; - } - return true; - } - console.error( - `The data passed to ${functionName} from ${eventName} is not an object.` - ); - return false; -}; - -export const validateUserIdAndData = ( - userId, - newCollectionInfo, - actionDescription -) => { - if (!userId) { - logError( - 'validateUserIdAndData', - actionDescription, - new Error('User ID is undefined.') - ); - return false; - } - if ( - !validateData(newCollectionInfo, actionDescription, 'createUserCollection') - ) { - logError( - 'validateUserIdAndData', - actionDescription, - new Error('Validation failed for collection data.') - ); - return false; - } - return true; -}; - -export const filterUniqueDataPoints = (dataArray) => { - const uniqueRecords = new Map(); - - dataArray?.forEach((item) => { - const key = `${item?.label}-${item?.x}-${item?.y}`; - if (!uniqueRecords.has(key)) { - uniqueRecords.set(key, item); - } - }); - - return Array.from(uniqueRecords.values()); -}; -/** - * Calculates the difference between the updated price and the initial price. - * @param {Number} updatedPrice - The updated price. - * @param {Object} selectedCollection - The selected collection. - * @returns {Number} The difference between the updated price and the initial price. - * @example calculatePriceDifference(100, { totalPrice: 200 }); - * calculatePriceDifference(100, { totalPrice: 100 }); - **/ -export const updatePriceHistory = (card, update) => { - const newPriceHistoryEntry = createPriceHistoryObject( - update?.latestPrice?.num - ); - const lastPriceHistoryEntry = - card?.priceHistory[card?.priceHistory?.length - 1]; - - if ( - !lastPriceHistoryEntry || - lastPriceHistoryEntry?.num !== newPriceHistoryEntry?.num - ) { - return [...card.priceHistory, newPriceHistoryEntry]; - } - return card?.priceHistory; -}; - -export const createChartDataEntry = (price) => { - return { - x: moment().format('YYYY-MM-DD HH:mm'), - y: price, - }; -}; - -export const getFilteredChartData = (chartData, updatedTotalPrice) => { - const filteredChartData = { - ...chartData, - allXYValues: filterUniqueDataPoints(chartData?.allXYValues), - }; - return { - ...filteredChartData, - allXYValues: [ - ...filteredChartData.allXYValues, - { - label: `Update - ${new Date().toISOString()}`, - x: new Date().toISOString(), - y: updatedTotalPrice, - }, - ], - }; -}; - -export const replaceCardInArray = (cardsArray, newCard, index) => { - return [ - ...cardsArray.slice(0, index), - newCard, - ...cardsArray.slice(index + 1), - ]; -}; - -export const updateCollectionArray = (collections, newData) => { - const index = collections.findIndex((c) => c._id === newData?._id); - return index === -1 - ? [...collections, newData] - : collections.map((c) => (c._id === newData?._id ? newData : c)); -}; - -export const removeDuplicatesFromCollection = (collection) => { - const uniqueCardsMap = new Map(); - if (!collection?.cards) return collection; - collection?.cards.forEach((card) => { - const uniqueKey = `${card.id}-${card.name}`; - if (!uniqueCardsMap.has(uniqueKey)) { - uniqueCardsMap.set(uniqueKey, card); - } - }); - - return { - ...collection, - cards: Array.from(uniqueCardsMap.values()), - }; -}; - -export const handleError = (condition, errorMessage) => { - if (!condition) { - console.error(errorMessage); - return false; - } - return true; -}; - -export const filterNullPriceHistory = (allCollections) => { - return allCollections.map((collection) => { - const filteredCards = collection.cards.map((card) => { - if (card.priceHistory) { - // Remove nulls and duplicates with less than 24 hours difference - const filteredPriceHistory = card.priceHistory.filter( - (price, index, array) => { - if (!price) return false; // Filter out null values - // Check for duplicates with less than 24 hours difference - const nextPrice = array[index + 1]; - if (nextPrice) { - const timeDiff = - new Date(nextPrice.timestamp) - new Date(price.timestamp); - const lessThan24Hours = timeDiff < 24 * 60 * 60 * 1000; // 24 hours in milliseconds - return !(price.num === nextPrice.num && lessThan24Hours); - } - return true; - } - ); - - return { - ...card, - priceHistory: filteredPriceHistory, - }; - } - return card; - }); - - return { - ...collection, - cards: filteredCards, - }; - }); -}; - -export const filterNullPriceHistoryForCollection = (collection) => { - const filteredCards = collection?.cards?.map((card) => { - if (card.priceHistory) { - // Remove null values, duplicates with less than 24 hours difference, and entries with num = 0 - const filteredPriceHistory = card?.priceHistory?.filter( - (price, index, array) => { - if (!price || price.num === 0) return false; // Filter out null values and num = 0 - - // Check for duplicates with less than 24 hours difference - const nextPrice = array[index + 1]; - if (nextPrice) { - const timeDiff = - new Date(nextPrice.timestamp) - new Date(price.timestamp); - const lessThan24Hours = timeDiff < 24 * 60 * 60 * 1000; // 24 hours in milliseconds - return !(price.num === nextPrice.num && lessThan24Hours); - } - return true; - } - ); - - return { - ...card, - priceHistory: filteredPriceHistory, - }; - } - return card; - }); - - return { - ...collection, - cards: filteredCards, - }; -}; -/** - * Handles the addition of a new card to the collection. - * @param {Array} currentCards - Current array of cards. - * @param {Object} cardToAdd - Card object to add. - * @returns {Array} Updated array of cards. - */ -export const handleCardAddition = (currentCards, cardToAdd) => { - const cardToAddId = String(cardToAdd.id); - const existingCardIndex = currentCards.findIndex((c) => c.id === cardToAddId); - - if (existingCardIndex !== -1) { - // Card already exists in the collection - const existingCard = currentCards[existingCardIndex]; - existingCard.quantity = (existingCard.quantity || 0) + 1; - existingCard.totalPrice = existingCard.price * existingCard.quantity; - existingCard.chart_datasets = [ - ...existingCard.chart_datasets, - createChartDataEntry(existingCard.totalPrice), - ]; - - // Update the card in the currentCards array - currentCards[existingCardIndex] = existingCard; - } else { - // Card does not exist in the collection, add it - const newCard = { - ...cardToAdd, - id: cardToAddId, - quantity: 1, - totalPrice: cardToAdd.price, // Assuming the cardToAdd has a 'price' field - }; - currentCards.push(newCard); - } - - return [...currentCards]; -}; - -/** - * Handles the removal of a card from the collection. - * @param {Array} currentCards - Current array of cards. - * @param {Object} cardToRemove - Card object to remove. - * @returns {Array} Updated array of cards. - */ -export const handleCardRemoval = (currentCards, cardToRemove) => { - const cardToRemoveId = String(cardToRemove.id); - const updatedCards = currentCards.map((card) => { - if (card.id === cardToRemoveId) { - return card.quantity > 1 - ? { - ...card, - quantity: card.quantity - 1, - totalPrice: card.totalPrice - card.price, - } - : null; - } - return card; - }); - - return updatedCards.filter((card) => card !== null); -}; - -/** - * Handles the updating of a card in the collection. - * @param {Array} currentCards - Current array of cards. - * @param {Object} cardToUpdate - Card object to update. - * @returns {Array} Updated array of cards. - * @example handleCardUpdate([{ id: 1, quantity: 1 }], { id: 1, quantity: 2 }); - **/ -export const handleCardUpdate = ( - cards, - cardUpdate, - priceHistory, - collectionId, - cardPrice -) => { - return cards.map((card) => { - if (card.id === cardUpdate.id) { - return getUpdatedCard( - card, - cardUpdate, - priceHistory, - collectionId, - cardPrice - ); - } - return card; - }); -}; -function getUpdatedCard(card, update, priceHistory, collectionId, cardPrice) { - console.log('CARD UPDATE:', update); - console.log('CARD:', card); - const totalPrice = cardPrice * (update?.quantity || card?.quantity); - const quantity = update?.quantity || card?.quantity || 1; - const newChartDataEntry = createChartDataEntry(totalPrice); - - console.log('UPDATE QUANTITY: ', update.quantity); - console.log('CARD QUANTITY: ', card.quantity); - console.log('TOTAL PRICE: ', totalPrice); - console.log('NEW CHARTDAT EN', newChartDataEntry); - - return { - ...card, - ...update, - price: cardPrice || card.price, - quantity: quantity, - collectionId: collectionId, - totalPrice: totalPrice, - lastSavedPrice: { - num: card.price || card.card_prices[0].tcgplayer_price, - timestamp: new Date(), - }, - latestPrice: update.latestPrice, - tag: 'monitored', - chart_datasets: [...card.chart_datasets, newChartDataEntry], - card_prices: update.card_prices, - card_sets: update.card_sets, - card_images: update.card_images, - priceHistory: priceHistory, - }; -} -export const getUpdatedCollectionData = ( - collectionWithCards, - updatedTotalPrice, - updatedCollectionPriceHistory, - updatedTotalQuantity, - updatedCards, - userId -) => { - if (!collectionWithCards) { - console.error('No collection data provided'); - return null; - } - - const { - allCardPrices = [], - description = collectionWithCards.description || '', - name = collectionWithCards.name || '', - collectionPriceHistory = [ - ...collectionWithCards.collectionPriceHistory, - ...updatedCollectionPriceHistory, - ], // collectionPriceHistory is an array of objects with 'num' and 'timestamp' properties - chartData = collectionWithCards.chartData || {}, - cards = collectionWithCards.cards || [], - currentChartDataSets2 = getUniqueFilteredXYValues(chartData?.allXYValues) || - [], - } = collectionWithCards; - if (!Array.isArray(collectionPriceHistory)) { - console.error( - 'collectionPriceHistory is not an array', - collectionPriceHistory - ); - return; - } - return { - allCardPrices, - description, - name, - userId: userId, - totalPrice: updatedTotalPrice, - totalQuantity: updatedTotalQuantity || 0, - quantity: cards?.length, - lastSavedPrice: { - num: collectionWithCards?.totalPrice || 0, - timestamp: new Date(), - }, - latestPrice: { - num: updatedTotalPrice || 0, - timestamp: new Date(), - }, - dailyPriceChange: '', - // getPriceChange(chartData)[0]?.priceChange || '', - currentChartDataSets2: currentChartDataSets2, - // filterUniqueDataPoints( - // chartData?.allXYValues - // // transformPriceHistoryToXY(chartData?.allXYValues) - // ), - collectionPriceHistory: updatedCollectionPriceHistory, - // collectionPriceHistory: [ - // ...collectionPriceHistory, - // newCollectionPriceHistoryObject || { - // num: updatedTotalPrice, - // timestamp: new Date(), - // }, - // ], - cards: updatedCards, - }; -}; - export const constructCardDifferencesPayload = ( oldCollection, newCollection, @@ -477,59 +67,66 @@ export const determineMethod = (operation, cardUpdate, collection) => { return 'POST'; } }; +export const getTotalQuantityOfSelectedCollection = (selectedCollection) => { + if (!selectedCollection) return 'n/a'; + return selectedCollection?.cards?.reduce( + (total, card) => total + card.quantity, + 0 + ); +}; -export const determineEndpointSuffix = (operation) => { - return operation === 'remove' ? 'removeCards' : 'updateCards'; +export const createPriceHistoryObject = (price) => { + return { + num: price, + timestamp: new Date(), + }; }; +export const getUpdatedCollectionPriceHistory = ( + selectedCollection, + updatedPrice +) => { + const updatedPriceHistory = selectedCollection?.collectionPriceHistory || []; + return [...updatedPriceHistory, createPriceHistoryObject(updatedPrice)]; +}; /** - * Creates cards payload. - * @param {Object} collection - The collection to update. - * @param {Object} cardUpdate - The card update object. - * @param {String} operation - The operation to perform. - * @returns {Promise} The updated collection. + * Ensures a value is a number, providing a default if not. + * @param {any} value - Value to check. + * @param {Number} defaultValue - Default value if check fails. + * @returns {Number} Ensured number. */ -export const createCardsPayload = (operation, updatedCards, cardUpdate) => { - if (operation === 'remove') { - const cardIds = updatedCards - .filter((card) => card?.id !== cardUpdate?.id) - .map((card) => ({ id: card?.id, _id: card?._id })); - return { cardIds }; +export const ensureNumber = (value, defaultValue = 0) => { + const num = parseFloat(value); + return isNaN(num) ? defaultValue : num; +}; + +export const getCardPrice = (collection) => + parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); + +export const getAllCardPrices = (cards) => + cards.flatMap((card) => Array(card.quantity).fill(card.price)); + +export const determineCardPrice = (card, update) => { + let price = 0; + // console.log('CARD UPDATE:', update); + if (card?.price !== update?.price) { + price = update?.price; } else { - const allCardsWithIds = updatedCards.map((card) => ({ - id: card?.id, - _id: card?._id, - })); - return { cards: updatedCards, cardIds: allCardsWithIds }; + price = card?.price; } -}; -/** - * Handles the updating of a collection chart variable data - * @param {Object} collection - The collection to update. - * @param {Object} cardUpdate - The card update object. - * @param {String} operation - The operation to perform. - * @returns {Promise} The updated collection. - */ -// export const updateChartData = async ( -// userId, -// collectionId, -// updatedChartData -// ) => { -// const chartDataPayload = { chartData: updatedChartData }; -// const chartDataEndpoint = createApiUrl( -// `${userId}/collections/${collectionId}/updateChartData` -// ); -// return await fetchWrapper(chartDataEndpoint, 'PUT', chartDataPayload); -// }; + return price || card?.card_prices[0]?.tcgplayer_price; +}; -/** - * Handles the updating of a collection. - * @param {Object} collection - The collection to update. - * @param {Object} cardUpdate - The card update object. - * @param {String} operation - The operation to perform. - * @returns {Promise} The updated collection. - */ +export const convertToXYLabelData = (collectionPriceHistory) => { + return collectionPriceHistory?.map((item) => ({ + x: new Date(item?.timestamp).toLocaleDateString(), // Converts timestamp to a readable date string + y: item?.num, + label: `Price: $${item?.num} at ${new Date( + item.timestamp + ).toLocaleTimeString()}`, // Combines price and time for the label + })); +}; export const getPriceChange = (currentChartDataSets2) => { if ( !Array.isArray(currentChartDataSets2) || @@ -586,263 +183,11 @@ export const getPriceChange = (currentChartDataSets2) => { return []; }; -/** - * Filters out duplicate Y values from an array of datasets. - * @param {Array} datasets - An array of dataset objects. - * @returns {Array} Filtered datasets. - */ -export const filterOutDuplicateYValues = (datasets) => { - const seenYValues = new Set(); - return datasets?.filter((data) => { - const yValue = data?.y; - if (seenYValues.has(yValue)) { - return false; - } - seenYValues.add(yValue); - return true; - }); -}; - -/** - * Filters unique XY values with Y not equal to 0. - * @param {Array} allXYValues - Array of XY value objects. - * @returns {Array} Filtered array of XY value objects. - */ -export const getUniqueFilteredXYValues = (allXYValues) => { - // Check if the input is an array and is not null/undefined - if (!Array.isArray(allXYValues)) { - // You can throw an error, return an empty array, or handle it as needed - console.error('Invalid input: allXYValues should be an array', allXYValues); - return []; - } - - const uniqueXValues = new Set(); - return allXYValues - .filter((entry) => { - // Check if entry is an object and has property y with a number value - return ( - entry && - typeof entry === 'object' && - typeof entry.y === 'number' && - entry.y !== 0 - ); - }) - .filter((entry) => { - // Check if entry has property x with a valid value (not null/undefined) - const hasValidX = - entry && 'x' in entry && entry.x !== null && entry.x !== undefined; - if (hasValidX && !uniqueXValues.has(entry.x)) { - uniqueXValues.add(entry.x); - return true; - } - return false; - }); - // console.log('uniqueXValues', uniqueXValues); -}; - -export const getCollectionId = (selectedCollection, allCollections) => { - return selectedCollection?._id || allCollections[0]?._id; -}; - -export const calculatePriceDifference = (updatedPrice, selectedCollection) => { - return updatedPrice - (selectedCollection.chartData?.updatedPrice || 0); -}; - -export const createNewDataSet = (updatedPrice, selectedCollection) => { - return { - data: [ - { - xys: [ - { - label: `Update Number ${ - selectedCollection?.chartData?.datasets?.length + 1 || 1 - }`, - data: { - x: moment().format('YYYY-MM-DD HH:mm'), - y: updatedPrice, - }, - }, - ], - additionalPriceData: { - priceChanged: - calculatePriceDifference(updatedPrice, selectedCollection) !== 0, - initialPrice: selectedCollection?.totalPrice, - updatedPrice: updatedPrice, - priceDifference: calculatePriceDifference( - updatedPrice, - selectedCollection - ), - priceChange: - Math.round( - (calculatePriceDifference(updatedPrice, selectedCollection) / - (selectedCollection?.totalPrice || 1)) * - 100 - ) / 100, - }, - }, - ], +export const createChartDataEntry = (chartDataSets) => { + const chartData = { + id: 'priceHistory', + data: chartDataSets, }; -}; -export const getTotalQuantityOfSelectedCollection = (selectedCollection) => { - if (!selectedCollection) return 'n/a'; - return selectedCollection?.cards?.reduce( - (total, card) => total + card.quantity, - 0 - ); + return [chartData]; }; - -export const createPriceHistoryObject = (price) => { - return { - num: price, - timestamp: new Date(), - }; -}; - -export const getUpdatedCollectionPriceHistory = ( - selectedCollection, - updatedPrice -) => { - const updatedPriceHistory = selectedCollection?.collectionPriceHistory || []; - return [...updatedPriceHistory, createPriceHistoryObject(updatedPrice)]; -}; - -export const getUniqueValidData = (allXYValues) => { - if (!Array.isArray(allXYValues)) { - console.error('Invalid input: allXYValues should be an array', allXYValues); - return []; - } - const uniqueLabels = new Set(); - const uniqueXValues = new Set(); - - return allXYValues - .filter((entry) => { - // Check if entry is valid, y is a number and not zero, and label is unique - return ( - entry && - typeof entry === 'object' && - typeof entry.y === 'number' && - entry.y !== 0 && - entry.y !== null && - entry.y !== undefined && - entry.label && - !uniqueLabels.has(entry.label) - ); - }) - .filter((entry) => { - // Check if x is present, not null, not undefined, and unique - const hasValidX = - entry && 'x' in entry && entry.x !== null && entry.x !== undefined; - if (hasValidX && !uniqueXValues.has(entry.x)) { - uniqueXValues.add(entry.x); - uniqueLabels.add(entry.label); - return true; - } - return false; - }) - .map((entry) => ({ - label: entry.label, - x: entry.x, - y: entry.y, - })); -}; - -export const getFilteredData = (data, timeRange) => { - const cutOffTime = new Date().getTime() - timeRange; - return data - .filter((d) => { - const date = new Date(d.x); - if (isNaN(date.getTime())) { - console.error('Invalid date:', d.x); - return false; - } - return date.getTime() >= cutOffTime; - }) - .map((d) => ({ ...d, y: roundToNearestTenth(d.y) })); -}; - -// export const calculateCollectionValue = (cards) => { -// if ( -// !cards?.cards && -// !Array.isArray(cards) && -// !cards?.name && -// !cards?.restructuredCollection -// ) { -// console.warn('Invalid or missing collection', cards); -// return 0; -// } - -// if (cards?.tag === 'new') { -// return 0; -// } -// if (cards?.restructuredCollection) { -// return cards?.restructuredCollection?.cards.reduce((totalValue, card) => { -// const cardPrice = card?.price || 0; -// const cardQuantity = card?.quantity || 0; -// return totalValue + cardPrice * cardQuantity; -// }, 0); -// } -// if (cards?.cards && Array.isArray(cards?.cards)) { -// return cards?.cards.reduce((totalValue, card) => { -// const cardPrice = card?.price || 0; -// const cardQuantity = card?.quantity || 0; -// return totalValue + cardPrice * cardQuantity; -// }, 0); -// } - -// return cards.reduce((totalValue, card) => { -// const cardPrice = card.price || 0; -// const cardQuantity = card.quantity || 0; -// return totalValue + cardPrice * cardQuantity; -// }, 0); -// }; - -/** - * Ensures a value is a number, providing a default if not. - * @param {any} value - Value to check. - * @param {Number} defaultValue - Default value if check fails. - * @returns {Number} Ensured number. - */ -export const ensureNumber = (value, defaultValue = 0) => { - const num = parseFloat(value); - return isNaN(num) ? defaultValue : num; -}; - -export const getCardPrice = (collection) => - parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); - -export const getAllCardPrices = (cards) => - cards.flatMap((card) => Array(card.quantity).fill(card.price)); - -export const determineCardPrice = (card, update) => { - let price = 0; - // console.log('CARD UPDATE:', update); - if (card?.price !== update?.price) { - price = update?.price; - } else { - price = card?.price; - } - - // if (update?.latestPrice?.num) { - // price = update?.latestPrice?.num; - // } - return price || card?.card_prices[0]?.tcgplayer_price; -}; - -export const convertToXYLabelData = (collectionPriceHistory) => { - return collectionPriceHistory?.map((item) => ({ - x: new Date(item?.timestamp).toLocaleDateString(), // Converts timestamp to a readable date string - y: item?.num, - label: `Price: $${item?.num} at ${new Date( - item.timestamp - ).toLocaleTimeString()}`, // Combines price and time for the label - })); -}; - -// export const convertToXYLabelData = (collectionPriceHistory) => { -// return collectionPriceHistory?.map((entry) => ({ -// label: entry._id, -// x: new Date(entry.timestamp), -// y: entry.num, -// })); -// }; diff --git a/src/context/MAIN_CONTEXT/CollectionContext/nivoTestData.json b/src/context/MAIN_CONTEXT/CollectionContext/nivoTestData.json new file mode 100644 index 0000000..6581212 --- /dev/null +++ b/src/context/MAIN_CONTEXT/CollectionContext/nivoTestData.json @@ -0,0 +1,951 @@ +{ + "nivoTestData": [ + { + "id": "24h", + "color": "#06d6a0", + "data": [ + { + "_id": "65e0367bf212b9272d001d33", + "x": "2023-01-01T16:29:49.224Z", + "y": 7 + }, + { + "_id": "65e0367bf212b9272d001d34", + "x": "2023-01-01T18:19:01.732Z", + "y": 17 + }, + { + "_id": "65e0367bf212b9272d001d35", + "x": "2023-01-01T02:30:20.156Z", + "y": 6 + }, + { + "_id": "65e0367bf212b9272d001d36", + "x": "2023-01-01T18:15:28.722Z", + "y": 12 + }, + { + "_id": "65e0367bf212b9272d001d37", + "x": "2023-01-01T21:21:56.716Z", + "y": 12 + }, + { + "_id": "65e0367bf212b9272d001d38", + "x": "2023-01-01T20:17:21.882Z", + "y": 11 + }, + { + "_id": "65e0367bf212b9272d001d39", + "x": "2023-01-01T15:57:47.441Z", + "y": 16 + }, + { + "_id": "65e0367bf212b9272d001d3a", + "x": "2023-01-01T20:26:16.606Z", + "y": 19 + }, + { + "_id": "65e0367bf212b9272d001d3b", + "x": "2023-01-01T20:23:00.548Z", + "y": 19 + }, + { + "_id": "65e0367bf212b9272d001d3c", + "x": "2023-01-01T19:17:32.114Z", + "y": 17 + }, + { + "_id": "65e0367bf212b9272d001d3d", + "x": "2023-01-01T22:20:46.224Z", + "y": 8 + }, + { + "_id": "65e0367bf212b9272d001d3e", + "x": "2023-01-01T14:38:33.191Z", + "y": 29 + }, + { + "_id": "65e0367bf212b9272d001d3f", + "x": "2023-01-01T08:37:59.386Z", + "y": 10 + }, + { + "_id": "65e0367bf212b9272d001d40", + "x": "2023-01-01T05:48:51.274Z", + "y": 11 + }, + { + "_id": "65e0367bf212b9272d001d41", + "x": "2023-01-01T03:53:42.145Z", + "y": 29 + }, + { + "_id": "65e0367bf212b9272d001d42", + "x": "2023-01-01T12:01:54.989Z", + "y": 28 + }, + { + "_id": "65e0367bf212b9272d001d43", + "x": "2023-01-01T02:07:58.789Z", + "y": 15 + }, + { + "_id": "65e0367bf212b9272d001d44", + "x": "2023-01-01T06:56:06.545Z", + "y": 23 + }, + { + "_id": "65e0367bf212b9272d001d45", + "x": "2023-01-01T09:30:39.879Z", + "y": 15 + }, + { + "_id": "65e0367bf212b9272d001d46", + "x": "2023-01-01T10:25:13.540Z", + "y": 15 + }, + { + "_id": "65e0367bf212b9272d001d47", + "x": "2023-01-01T21:06:14.753Z", + "y": 18 + }, + { + "_id": "65e0367bf212b9272d001d48", + "x": "2023-01-01T01:31:50.231Z", + "y": 4 + }, + { + "_id": "65e0367bf212b9272d001d49", + "x": "2023-01-01T01:01:15.761Z", + "y": 2 + }, + { + "_id": "65e0367bf212b9272d001d4a", + "x": "2023-01-01T09:14:19.574Z", + "y": 25 + } + ] + }, + { + "id": "7d", + "color": "#06d6a0", + "data": [ + { + "_id": "65e0367bf212b9272d001d4b", + "x": "2023-01-04T10:03:51.664Z", + "y": 57 + }, + { + "_id": "65e0367bf212b9272d001d4c", + "x": "2023-01-04T03:35:24.333Z", + "y": 47 + }, + { + "_id": "65e0367bf212b9272d001d4d", + "x": "2023-01-05T00:27:37.682Z", + "y": 37 + }, + { + "_id": "65e0367bf212b9272d001d4e", + "x": "2023-01-02T03:52:55.720Z", + "y": 38 + }, + { + "_id": "65e0367bf212b9272d001d4f", + "x": "2023-01-07T22:34:58.619Z", + "y": 39 + }, + { + "_id": "65e0367bf212b9272d001d50", + "x": "2023-01-01T20:17:14.389Z", + "y": 35 + }, + { + "_id": "65e0367bf212b9272d001d51", + "x": "2023-01-07T03:08:24.313Z", + "y": 42 + } + ] + }, + { + "id": "30d", + "color": "#06d6a0", + "data": [ + { + "_id": "65e0367bf212b9272d001d52", + "x": "2023-01-07T12:25:24.855Z", + "y": 73 + }, + { + "_id": "65e0367bf212b9272d001d53", + "x": "2023-01-08T12:46:40.462Z", + "y": 109 + }, + { + "_id": "65e0367bf212b9272d001d54", + "x": "2023-01-15T22:59:59.934Z", + "y": 77 + }, + { + "_id": "65e0367bf212b9272d001d55", + "x": "2023-01-14T05:38:41.010Z", + "y": 67 + }, + { + "_id": "65e0367bf212b9272d001d56", + "x": "2023-01-21T20:36:45.766Z", + "y": 79 + }, + { + "_id": "65e0367bf212b9272d001d57", + "x": "2023-01-16T11:23:15.434Z", + "y": 118 + }, + { + "_id": "65e0367bf212b9272d001d58", + "x": "2023-01-21T13:48:31.171Z", + "y": 114 + }, + { + "_id": "65e0367bf212b9272d001d59", + "x": "2023-01-21T07:17:11.612Z", + "y": 84 + }, + { + "_id": "65e0367bf212b9272d001d5a", + "x": "2023-01-25T01:30:45.836Z", + "y": 77 + }, + { + "_id": "65e0367bf212b9272d001d5b", + "x": "2023-01-29T14:26:00.771Z", + "y": 116 + }, + { + "_id": "65e0367bf212b9272d001d5c", + "x": "2023-01-20T09:14:10.982Z", + "y": 94 + }, + { + "_id": "65e0367bf212b9272d001d5d", + "x": "2023-01-01T03:09:57.486Z", + "y": 62 + }, + { + "_id": "65e0367bf212b9272d001d5e", + "x": "2023-01-01T00:42:29.794Z", + "y": 63 + }, + { + "_id": "65e0367bf212b9272d001d5f", + "x": "2023-01-09T09:37:41.058Z", + "y": 99 + }, + { + "_id": "65e0367bf212b9272d001d60", + "x": "2023-01-10T15:14:58.119Z", + "y": 119 + }, + { + "_id": "65e0367bf212b9272d001d61", + "x": "2023-01-22T20:08:47.937Z", + "y": 83 + }, + { + "_id": "65e0367bf212b9272d001d62", + "x": "2023-01-28T23:06:21.164Z", + "y": 118 + }, + { + "_id": "65e0367bf212b9272d001d63", + "x": "2023-01-04T10:17:57.065Z", + "y": 85 + }, + { + "_id": "65e0367bf212b9272d001d64", + "x": "2023-01-11T06:40:44.545Z", + "y": 119 + }, + { + "_id": "65e0367bf212b9272d001d65", + "x": "2023-01-09T11:24:31.250Z", + "y": 67 + }, + { + "_id": "65e0367bf212b9272d001d66", + "x": "2023-01-28T15:28:02.717Z", + "y": 64 + }, + { + "_id": "65e0367bf212b9272d001d67", + "x": "2023-01-18T12:45:59.834Z", + "y": 62 + }, + { + "_id": "65e0367bf212b9272d001d68", + "x": "2023-01-30T12:12:14.021Z", + "y": 100 + }, + { + "_id": "65e0367bf212b9272d001d69", + "x": "2023-01-13T21:17:54.144Z", + "y": 77 + }, + { + "_id": "65e0367bf212b9272d001d6a", + "x": "2023-01-05T00:11:50.668Z", + "y": 104 + }, + { + "_id": "65e0367bf212b9272d001d6b", + "x": "2023-01-16T05:28:18.172Z", + "y": 105 + }, + { + "_id": "65e0367bf212b9272d001d6c", + "x": "2023-01-21T04:40:30.785Z", + "y": 110 + }, + { + "_id": "65e0367bf212b9272d001d6d", + "x": "2023-01-02T13:21:20.829Z", + "y": 89 + }, + { + "_id": "65e0367bf212b9272d001d6e", + "x": "2023-01-01T19:48:46.883Z", + "y": 100 + }, + { + "_id": "65e0367bf212b9272d001d6f", + "x": "2023-01-14T16:43:36.185Z", + "y": 110 + } + ] + }, + { + "id": "90d", + "color": "#06d6a0", + "data": [ + { + "_id": "65e0367bf212b9272d001d70", + "x": "2023-01-08T16:42:52.932Z", + "y": 157 + }, + { + "_id": "65e0367bf212b9272d001d71", + "x": "2023-01-21T12:07:07.233Z", + "y": 121 + }, + { + "_id": "65e0367bf212b9272d001d72", + "x": "2023-01-19T10:43:11.171Z", + "y": 143 + }, + { + "_id": "65e0367bf212b9272d001d73", + "x": "2023-02-28T08:52:37.169Z", + "y": 231 + }, + { + "_id": "65e0367bf212b9272d001d74", + "x": "2023-01-02T20:12:44.964Z", + "y": 186 + }, + { + "_id": "65e0367bf212b9272d001d75", + "x": "2023-01-12T18:32:04.676Z", + "y": 130 + }, + { + "_id": "65e0367bf212b9272d001d76", + "x": "2023-03-17T04:19:13.853Z", + "y": 134 + }, + { + "_id": "65e0367bf212b9272d001d77", + "x": "2023-02-09T00:13:57.224Z", + "y": 238 + }, + { + "_id": "65e0367bf212b9272d001d78", + "x": "2023-03-26T04:27:52.773Z", + "y": 185 + }, + { + "_id": "65e0367bf212b9272d001d79", + "x": "2023-01-11T19:27:44.131Z", + "y": 180 + }, + { + "_id": "65e0367bf212b9272d001d7a", + "x": "2023-02-26T05:16:38.911Z", + "y": 216 + }, + { + "_id": "65e0367bf212b9272d001d7b", + "x": "2023-02-28T17:52:46.239Z", + "y": 156 + }, + { + "_id": "65e0367bf212b9272d001d7c", + "x": "2023-01-11T10:30:52.311Z", + "y": 218 + }, + { + "_id": "65e0367bf212b9272d001d7d", + "x": "2023-01-25T16:15:21.943Z", + "y": 235 + }, + { + "_id": "65e0367bf212b9272d001d7e", + "x": "2023-01-10T18:44:00.753Z", + "y": 231 + }, + { + "_id": "65e0367bf212b9272d001d7f", + "x": "2023-03-18T06:18:15.750Z", + "y": 171 + }, + { + "_id": "65e0367bf212b9272d001d80", + "x": "2023-02-08T16:35:49.928Z", + "y": 180 + }, + { + "_id": "65e0367bf212b9272d001d81", + "x": "2023-03-07T00:05:18.463Z", + "y": 121 + }, + { + "_id": "65e0367bf212b9272d001d82", + "x": "2023-01-23T05:46:37.441Z", + "y": 173 + }, + { + "_id": "65e0367bf212b9272d001d83", + "x": "2023-03-30T11:26:47.789Z", + "y": 180 + }, + { + "_id": "65e0367bf212b9272d001d84", + "x": "2023-02-09T22:42:17.084Z", + "y": 147 + }, + { + "_id": "65e0367bf212b9272d001d85", + "x": "2023-02-11T13:32:42.657Z", + "y": 222 + }, + { + "_id": "65e0367bf212b9272d001d86", + "x": "2023-03-31T21:28:32.265Z", + "y": 205 + }, + { + "_id": "65e0367bf212b9272d001d87", + "x": "2023-03-13T04:47:47.514Z", + "y": 187 + }, + { + "_id": "65e0367bf212b9272d001d88", + "x": "2023-02-16T07:55:52.595Z", + "y": 134 + }, + { + "_id": "65e0367bf212b9272d001d89", + "x": "2023-01-19T15:11:25.814Z", + "y": 166 + }, + { + "_id": "65e0367bf212b9272d001d8a", + "x": "2023-03-25T19:17:54.674Z", + "y": 188 + }, + { + "_id": "65e0367bf212b9272d001d8b", + "x": "2023-02-03T07:57:15.254Z", + "y": 171 + }, + { + "_id": "65e0367bf212b9272d001d8c", + "x": "2023-01-28T02:45:32.590Z", + "y": 155 + }, + { + "_id": "65e0367bf212b9272d001d8d", + "x": "2023-01-16T13:17:16.198Z", + "y": 123 + } + ] + }, + { + "id": "180d", + "color": "#06d6a0", + "data": [ + { + "_id": "65e0367bf212b9272d001d8e", + "x": "2023-06-16T08:57:22.573Z", + "y": 365 + }, + { + "_id": "65e0367bf212b9272d001d8f", + "x": "2023-05-26T23:49:41.351Z", + "y": 249 + }, + { + "_id": "65e0367bf212b9272d001d90", + "x": "2023-04-06T03:33:31.736Z", + "y": 421 + }, + { + "_id": "65e0367bf212b9272d001d91", + "x": "2023-04-20T14:01:56.687Z", + "y": 337 + }, + { + "_id": "65e0367bf212b9272d001d92", + "x": "2023-06-17T05:23:57.853Z", + "y": 274 + }, + { + "_id": "65e0367bf212b9272d001d93", + "x": "2023-06-09T01:12:36.727Z", + "y": 276 + }, + { + "_id": "65e0367bf212b9272d001d94", + "x": "2023-02-28T17:55:12.771Z", + "y": 445 + }, + { + "_id": "65e0367bf212b9272d001d95", + "x": "2023-03-05T01:19:28.903Z", + "y": 275 + }, + { + "_id": "65e0367bf212b9272d001d96", + "x": "2023-06-15T05:05:16.161Z", + "y": 402 + }, + { + "_id": "65e0367bf212b9272d001d97", + "x": "2023-02-24T18:12:59.213Z", + "y": 374 + }, + { + "_id": "65e0367bf212b9272d001d98", + "x": "2023-06-06T05:18:44.584Z", + "y": 385 + }, + { + "_id": "65e0367bf212b9272d001d99", + "x": "2023-02-05T04:59:34.927Z", + "y": 380 + }, + { + "_id": "65e0367bf212b9272d001d9a", + "x": "2023-01-13T16:13:50.769Z", + "y": 290 + }, + { + "_id": "65e0367bf212b9272d001d9b", + "x": "2023-06-02T14:57:45.543Z", + "y": 250 + }, + { + "_id": "65e0367bf212b9272d001d9c", + "x": "2023-05-27T03:50:02.096Z", + "y": 398 + }, + { + "_id": "65e0367bf212b9272d001d9d", + "x": "2023-05-21T16:26:41.908Z", + "y": 244 + }, + { + "_id": "65e0367bf212b9272d001d9e", + "x": "2023-06-24T14:58:27.378Z", + "y": 334 + }, + { + "_id": "65e0367bf212b9272d001d9f", + "x": "2023-04-24T18:50:53.667Z", + "y": 275 + }, + { + "_id": "65e0367bf212b9272d001da0", + "x": "2023-03-02T09:00:00.129Z", + "y": 395 + }, + { + "_id": "65e0367bf212b9272d001da1", + "x": "2023-01-24T01:55:53.818Z", + "y": 400 + }, + { + "_id": "65e0367bf212b9272d001da2", + "x": "2023-02-02T14:08:50.782Z", + "y": 279 + }, + { + "_id": "65e0367bf212b9272d001da3", + "x": "2023-06-09T13:50:02.708Z", + "y": 294 + }, + { + "_id": "65e0367bf212b9272d001da4", + "x": "2023-04-15T02:38:40.002Z", + "y": 313 + }, + { + "_id": "65e0367bf212b9272d001da5", + "x": "2023-04-22T13:31:04.751Z", + "y": 386 + }, + { + "_id": "65e0367bf212b9272d001da6", + "x": "2023-03-18T18:44:10.109Z", + "y": 452 + }, + { + "_id": "65e0367bf212b9272d001da7", + "x": "2023-02-25T23:06:45.298Z", + "y": 460 + }, + { + "_id": "65e0367bf212b9272d001da8", + "x": "2023-06-28T16:57:06.078Z", + "y": 346 + }, + { + "_id": "65e0367bf212b9272d001da9", + "x": "2023-03-02T01:43:03.343Z", + "y": 378 + }, + { + "_id": "65e0367bf212b9272d001daa", + "x": "2023-03-13T23:28:51.654Z", + "y": 312 + }, + { + "_id": "65e0367bf212b9272d001dab", + "x": "2023-02-04T03:01:11.296Z", + "y": 309 + } + ] + }, + { + "id": "270d", + "color": "#06d6a0", + "data": [ + { + "_id": "65e0367bf212b9272d001dac", + "x": "2023-09-06T21:07:48.291Z", + "y": 776 + }, + { + "_id": "65e0367bf212b9272d001dad", + "x": "2023-04-03T02:30:14.580Z", + "y": 801 + }, + { + "_id": "65e0367bf212b9272d001dae", + "x": "2023-05-12T12:50:00.317Z", + "y": 636 + }, + { + "_id": "65e0367bf212b9272d001daf", + "x": "2023-03-13T16:50:24.915Z", + "y": 759 + }, + { + "_id": "65e0367bf212b9272d001db0", + "x": "2023-08-30T02:28:08.560Z", + "y": 774 + }, + { + "_id": "65e0367bf212b9272d001db1", + "x": "2023-08-16T03:26:08.462Z", + "y": 721 + }, + { + "_id": "65e0367bf212b9272d001db2", + "x": "2023-07-03T00:48:50.366Z", + "y": 754 + }, + { + "_id": "65e0367bf212b9272d001db3", + "x": "2023-09-09T11:43:19.301Z", + "y": 810 + }, + { + "_id": "65e0367bf212b9272d001db4", + "x": "2023-07-27T09:27:24.438Z", + "y": 871 + }, + { + "_id": "65e0367bf212b9272d001db5", + "x": "2023-01-14T08:55:29.457Z", + "y": 639 + }, + { + "_id": "65e0367bf212b9272d001db6", + "x": "2023-02-11T16:52:14.956Z", + "y": 799 + }, + { + "_id": "65e0367bf212b9272d001db7", + "x": "2023-09-01T06:21:23.862Z", + "y": 812 + }, + { + "_id": "65e0367bf212b9272d001db8", + "x": "2023-07-30T12:02:58.364Z", + "y": 919 + }, + { + "_id": "65e0367bf212b9272d001db9", + "x": "2023-04-12T16:31:54.185Z", + "y": 639 + }, + { + "_id": "65e0367bf212b9272d001dba", + "x": "2023-09-11T09:09:59.263Z", + "y": 516 + }, + { + "_id": "65e0367bf212b9272d001dbb", + "x": "2023-06-01T15:30:21.984Z", + "y": 926 + }, + { + "_id": "65e0367bf212b9272d001dbc", + "x": "2023-03-17T03:56:18.599Z", + "y": 529 + }, + { + "_id": "65e0367bf212b9272d001dbd", + "x": "2023-09-13T12:26:28.589Z", + "y": 567 + }, + { + "_id": "65e0367bf212b9272d001dbe", + "x": "2023-07-08T10:12:13.822Z", + "y": 534 + }, + { + "_id": "65e0367bf212b9272d001dbf", + "x": "2023-09-01T22:37:25.126Z", + "y": 576 + }, + { + "_id": "65e0367bf212b9272d001dc0", + "x": "2023-06-14T06:36:05.456Z", + "y": 746 + }, + { + "_id": "65e0367bf212b9272d001dc1", + "x": "2023-03-31T03:01:06.551Z", + "y": 805 + }, + { + "_id": "65e0367bf212b9272d001dc2", + "x": "2023-04-03T04:06:03.399Z", + "y": 668 + }, + { + "_id": "65e0367bf212b9272d001dc3", + "x": "2023-07-25T16:44:37.046Z", + "y": 671 + }, + { + "_id": "65e0367bf212b9272d001dc4", + "x": "2023-06-21T02:43:16.938Z", + "y": 504 + }, + { + "_id": "65e0367bf212b9272d001dc5", + "x": "2023-08-11T07:42:49.695Z", + "y": 741 + }, + { + "_id": "65e0367bf212b9272d001dc6", + "x": "2023-03-31T09:45:57.143Z", + "y": 646 + }, + { + "_id": "65e0367bf212b9272d001dc7", + "x": "2023-05-17T07:20:23.187Z", + "y": 760 + }, + { + "_id": "65e0367bf212b9272d001dc8", + "x": "2023-04-22T17:30:12.191Z", + "y": 636 + }, + { + "_id": "65e0367bf212b9272d001dc9", + "x": "2023-09-04T05:13:54.684Z", + "y": 700 + } + ] + }, + { + "id": "365d", + "color": "#06d6a0", + "data": [ + { + "_id": "65e0367bf212b9272d001dca", + "x": "2023-07-05T05:46:59.376Z", + "y": 2083 + }, + { + "_id": "65e0367bf212b9272d001dcb", + "x": "2023-11-14T21:27:15.612Z", + "y": 2590 + }, + { + "_id": "65e0367bf212b9272d001dcc", + "x": "2023-11-23T01:30:12.451Z", + "y": 2547 + }, + { + "_id": "65e0367bf212b9272d001dcd", + "x": "2023-07-20T22:20:19.499Z", + "y": 1703 + }, + { + "_id": "65e0367bf212b9272d001dce", + "x": "2023-03-06T15:20:49.104Z", + "y": 2140 + }, + { + "_id": "65e0367bf212b9272d001dcf", + "x": "2023-04-06T10:26:25.653Z", + "y": 1288 + }, + { + "_id": "65e0367bf212b9272d001dd0", + "x": "2023-03-11T13:01:26.115Z", + "y": 2513 + }, + { + "_id": "65e0367bf212b9272d001dd1", + "x": "2023-09-17T13:03:37.796Z", + "y": 2000 + }, + { + "_id": "65e0367bf212b9272d001dd2", + "x": "2023-07-06T02:28:13.363Z", + "y": 2389 + }, + { + "_id": "65e0367bf212b9272d001dd3", + "x": "2023-03-19T11:02:36.118Z", + "y": 1098 + }, + { + "_id": "65e0367bf212b9272d001dd4", + "x": "2023-07-14T23:34:51.103Z", + "y": 1569 + }, + { + "_id": "65e0367bf212b9272d001dd5", + "x": "2023-01-30T15:57:55.788Z", + "y": 1585 + }, + { + "_id": "65e0367bf212b9272d001dd6", + "x": "2023-05-09T15:22:38.699Z", + "y": 2306 + }, + { + "_id": "65e0367bf212b9272d001dd7", + "x": "2023-09-16T06:20:30.882Z", + "y": 1460 + }, + { + "_id": "65e0367bf212b9272d001dd8", + "x": "2023-03-26T16:06:46.514Z", + "y": 1788 + }, + { + "_id": "65e0367bf212b9272d001dd9", + "x": "2023-05-26T01:58:29.601Z", + "y": 2707 + }, + { + "_id": "65e0367bf212b9272d001dda", + "x": "2023-11-14T06:25:15.885Z", + "y": 979 + }, + { + "_id": "65e0367bf212b9272d001ddb", + "x": "2023-02-17T12:17:21.252Z", + "y": 1478 + }, + { + "_id": "65e0367bf212b9272d001ddc", + "x": "2023-08-31T03:27:27.636Z", + "y": 1265 + }, + { + "_id": "65e0367bf212b9272d001ddd", + "x": "2023-12-08T07:41:11.419Z", + "y": 1443 + }, + { + "_id": "65e0367bf212b9272d001dde", + "x": "2023-07-01T14:58:59.667Z", + "y": 2281 + }, + { + "_id": "65e0367bf212b9272d001ddf", + "x": "2023-11-04T09:40:35.839Z", + "y": 2735 + }, + { + "_id": "65e0367bf212b9272d001de0", + "x": "2023-12-17T21:20:39.765Z", + "y": 1618 + }, + { + "_id": "65e0367bf212b9272d001de1", + "x": "2023-02-23T02:10:55.588Z", + "y": 1318 + }, + { + "_id": "65e0367bf212b9272d001de2", + "x": "2023-08-25T10:32:27.736Z", + "y": 1819 + }, + { + "_id": "65e0367bf212b9272d001de3", + "x": "2023-06-18T04:43:08.699Z", + "y": 1544 + }, + { + "_id": "65e0367bf212b9272d001de4", + "x": "2023-07-28T11:59:32.291Z", + "y": 1181 + }, + { + "_id": "65e0367bf212b9272d001de5", + "x": "2023-09-16T21:39:28.298Z", + "y": 2388 + }, + { + "_id": "65e0367bf212b9272d001de6", + "x": "2023-12-27T10:11:02.503Z", + "y": 2119 + }, + { + "_id": "65e0367bf212b9272d001de7", + "x": "2023-12-22T12:49:27.288Z", + "y": 1205 + } + ] + } + ] +} diff --git a/src/context/MAIN_CONTEXT/CollectionContext/useCollectionManager.jsx b/src/context/MAIN_CONTEXT/CollectionContext/useCollectionManager.jsx index 45f51f0..6dad84f 100644 --- a/src/context/MAIN_CONTEXT/CollectionContext/useCollectionManager.jsx +++ b/src/context/MAIN_CONTEXT/CollectionContext/useCollectionManager.jsx @@ -1,492 +1,735 @@ import { useState, useCallback, useEffect, useMemo, useRef } from 'react'; import useLogger from '../../hooks/useLogger'; -import useApiResponseHandler from '../../hooks/useApiResponseHandler'; import { DEFAULT_COLLECTION } from '../../constants'; -// import { calculateCollectionValue } from './collectionUtility'; -import { useLoading } from '../../hooks/useLoading'; import useFetchWrapper from '../../hooks/useFetchWrapper'; -import { shouldFetchCollections } from './collectionUtility'; +import { useAuthContext } from '../..'; +import { useCookies } from 'react-cookie'; +import useLocalStorage from '../../hooks/useLocalStorage'; +import useSelectedCollection from './useSelectedCollection'; -const useCollectionManager = (isLoggedIn, userId) => { - const loadingID = 'fetchCollections'; // For fetchWrapper - const createApiUrl = useCallback( - (path) => - `${process.env.REACT_APP_SERVER}/api/users/${userId}/collections${path}`, - [userId] - ); - const { fetchWrapper, responseCache } = useFetchWrapper(); - const handleApiResponse = useApiResponseHandler(); +const useCollectionManager = () => { + const { fetchWrapper, status } = useFetchWrapper(); + const { userId, isLoggedIn } = useAuthContext(); // Now getting userId from context const logger = useLogger('useCollectionManager'); - const [error, setError] = useState(null); - const { startLoading, stopLoading, isLoading, isAnyLoading } = useLoading(); - const prevUserIdRef = useRef(userId); + const { + selectedCollection, + allCollections, + showCollections, + selectedCollectionId, + customError: selectedCollectionError, - const [collectionData, setCollectionData] = useState({}); - const [allCollections, setAllCollections] = useState( - collectionData?.data || [] - ); - const [selectedCollection, setSelectedCollection] = useState( - collectionData?.data?.[0] || {} - ); - const [selectedCards, setSelectedCards] = useState( - selectedCollection?.cards?.slice(0, 30) || [] - ); + updateCollectionsData, + handleSelectCollection, + resetCollection, + setCustomError: setSelectedCollectionError, + } = useSelectedCollection(); + const [error, setError] = useState(null); const [hasFetchedCollections, setHasFetchedCollections] = useState(false); - const updateSelectedCollection = useCallback((newCollection) => { - setSelectedCollection(newCollection); - // setSelectedCards(newCollection?.cards?.slice(0, 30) || []); - }, []); - // Function to update state based on fetched data - const updateStates = useCallback((data) => { - setCollectionData(data); - setAllCollections(data?.data || []); - setSelectedCollection(data?.data?.[0] || {}); - }, []); - // Watch for changes in responseCache, specifically for the fetchCollections index - useEffect(() => { - const cachedData = responseCache[loadingID]; - if (cachedData) { - updateStates(cachedData); - } - }, [responseCache, loadingID, updateStates]); - /** - * Retrieves all collections for a specific user. - * @param {string} userId - The ID of the user whose collections are to be fetched. - * @returns {Promise} The response from the server containing the collections. - * FUNCTION LOCATIONS: --> - * [ ] @function Main - src/Main.jsx - */ - // const getAllCollectionsForUser = useCallback(async () => { - // const loadingID = 'fetchCollections'; // Define a unique loading ID - // if (!userId || isLoading(loadingID) || hasFetchedCollections) return; - // try { - // const { data, error } = await fetchWrapper( - // createApiUrl('/allCollections'), - // 'GET', - // null, - // loadingID - // ); - // if (error) { - // throw new Error(error); - // } - // const collectionData = JSON.parse(data); - // console.log('GET ALL COLLECTIONS', collectionData); - // setCollectionData({ data: collectionData }); - // setAllCollections(collectionData?.data || []); - // setSelectedCollection(collectionData?.data?.[0] || DEFAULT_COLLECTION); - // setHasFetchedCollections(true); - // } catch (error) { - // setError(error.message || 'Failed to fetch collections'); - // logger.logEvent( - // 'Error fetching collections getAllCollectionsForUser', - // error.message - // ); - // } - // }, [ - // userId, - // hasFetchedCollections, - // createApiUrl, - // fetchWrapper, - // logger, - // isLoading, - // setError, - // setCollectionData, - // setAllCollections, - // setSelectedCollection, - // setHasFetchedCollections, - // ]); - // useEffect(() => { - // // if (isLoggedIn && userId && !hasFetchedCollections) { - // if (userId && !hasFetchedCollections) { - // getAllCollectionsForUser(); - // } - // }, [selectedCollection, setSelectedCollection]); - const getAllCollectionsForUser = useCallback(async () => { - const loadingID = 'fetchCollections'; // Define a unique loading ID - if (!userId || isLoading(loadingID) || hasFetchedCollections) { - return; - } - startLoading(loadingID); // Assuming startLoading is available via useLoading or similar context - + const handleError = useCallback( + (error, actionName) => { + const errorMessage = error.message || 'Failed to perform action'; + setError(errorMessage); + logger.logError(`Error in ${actionName}: ${errorMessage}`, error); + }, + [logger] + ); + const baseUrl = `${process.env.REACT_APP_SERVER}/api/users/${userId}/collections`; + const createApiUrl = useCallback((path) => `${baseUrl}/${path}`, [baseUrl]); + const fetchCollections = useCallback(async () => { + if (!userId || !isLoggedIn || status === 'loading') return; try { - // fetchWrapper should return the data directly or throw an error if unsuccessful const responseData = await fetchWrapper( - createApiUrl('/allCollections'), + createApiUrl('allCollections'), 'GET', null, - loadingID + 'fetchCollections' ); - if ( - (responseData && responseData?.status === 200) || - responseData?.status === 201 - ) { - console.log('SUCCESS: fetching collections'); - const cachedData = responseCache[loadingID]; - if (cachedData) { - updateStates(cachedData); - } - } - if (responseData && responseData?.status !== 200) { - console.error('ERROR: fetching collections'); - setError(responseData?.data?.message || 'Failed to fetch collections'); - } + updateCollectionsData(responseData?.data); + setHasFetchedCollections(true); } catch (error) { - console.error(error); - setError(error.message || 'Failed to fetch collections'); - logger.logEvent( - 'Error fetching collections getAllCollectionsForUser', - error.message - ); - } finally { - stopLoading(loadingID); // Ensure loading is stopped regardless of success or failure - setHasFetchedCollections(true); // Prevent re-fetching by updating state + handleError(error, 'fetchCollections'); } }, [ userId, - hasFetchedCollections, - createApiUrl, + isLoggedIn, + status, fetchWrapper, - logger, - isLoading, - setError, - setCollectionData, - setAllCollections, - setSelectedCollection, - setHasFetchedCollections, - startLoading, // Include startLoading and stopLoading if they are not globally available - stopLoading, + createApiUrl, + updateCollectionsData, + handleError, ]); - useEffect(() => { - const storedResponse = responseCache['fetchCollections']; - console.log('Stored response for collections:', storedResponse); - if (storedResponse) { - // Assuming you have similar functions or state setters as in your provided structure - setCollectionData(storedResponse); - setAllCollections(storedResponse.data); - setSelectedCollection(storedResponse.data[0] || DEFAULT_COLLECTION); - } - }, [responseCache]); - - useEffect(() => { - getAllCollectionsForUser(); - }, [selectedCards]); // Dependency array as needed - /** - * Creates a new collection for a specific user. - * @param {string} userId - The ID of the user for whom the collection is being created. - * @param {Object} collectionData - The data for the new collection. - * @param {string} collectionData.name - The name of the collection. - * @param {string} collectionData.description - The description of the collection. - * @param {Array} [collectionData.cards] - Optional array of cards to be included in the collection. - * @returns {Promise} The response from the server. - * FUNCTION LOCATIONS: --> - * [ ] @function CollectionForm - src/components/forms/CollectionForm.jsx - */ - const createNewCollection = async (coData) => { - const loadingID = 'createNewCollection'; - if (!userId || isLoading(loadingID)) return; - try { - const { data, error } = await fetchWrapper( - createApiUrl('/create'), - 'POST', - coData, - loadingID - ); - if (error) { - throw new Error(error); + const performAction = useCallback( + async (path, method, data, actionName, options = {}) => { + if (!userId || !isLoggedIn || status === 'loading') { + setError('User is not logged in or request is in loading state.'); + return; } - updateSelectedCollection(data.data); // Assuming updateSelectedCollection updates context or state - return data.data; - } catch (error) { - setError(error.message || 'Failed to create new collection'); - logger.logEvent( - 'Error creating new collection', - 'createNewCollection', - error.message - ); - throw error; - } - }; - /** - * Updates and synchronizes a specific collection for a user. - * @param {string} userId - The ID of the user who owns the collection. - * @param {string} collectionId - The ID of the collection to be updated. - * @param {Object} updatedData - The updated data for the collection. - * @param {string} updatedData.name - (Optional) New name of the collection. - * @param {string} updatedData.description - (Optional) New description of the collection. - * @param {Array} updatedData.cards - (Optional) Array of updated cards. - * @returns {Promise} The response from the server. - */ - const updateAndSyncCollection = async (collectionId, updatedData) => { - const loadingID = 'updateAndSyncCollection'; - if (!userId || isLoading(loadingID)) return; - try { - const { data, error } = await fetchWrapper( - createApiUrl(`/${collectionId}`), - 'PUT', - updatedData, - loadingID - ); - if (error) { - throw new Error(error); + if (!selectedCollection) { + setSelectedCollectionError('No collection selected'); + return; } - setAllCollections((prev) => - prev.map((collection) => - collection._id === collectionId ? data.data : collection - ) - ); - updateSelectedCollection(data.data); // Assuming updateSelectedCollection updates context or state - return data.data; - } catch (error) { - setError(error.message || 'Failed to update collection'); - logger.logEvent('updateAndSyncCollection error', error); - throw error; - } - }; - /** - * Deletes a specific collection for a user. - * @param {string} collectionId - The ID of the collection to be deleted. - * @returns {Promise} The response from the server. - */ - const deleteCollection = async (collectionId) => { - const loadingID = 'deleteCollection'; - if (!userId || isLoading(loadingID)) return; - setError(null); - try { - const response = await fetchWrapper( - createApiUrl(`/${collectionId}`), - 'DELETE', - { collectionId }, - loadingID - ); - // const data = handleApiResponse(response, 'addCardsToCollection'); + options.beforeAction?.(); - setAllCollections((prev) => - prev.filter((collection) => collection?._id !== collectionId) + console.log( + 'PERFORM ACTION', + path, + method, + data, + actionName, + options.paramTypes ); - return response; - } catch (error) { - setError(error); - logger.logEvent('deleteCollection error', error); - throw error; - } - }; - /** - * Adds new cards to a specific collection. - * @param {string} userId - The ID of the user who owns the collection. - * @param {string} collectionId - The ID of the target collection. - * @param {Array} cards - Array of card objects to be added. - * @returns {Promise} The response from the server. - */ - const addCardsToCollection = async (cards, collection) => { - const loadingID = 'addCardsToCollection'; - if (!userId || isLoading(loadingID)) return; - setError(null); - const newCards = []; - const updatedCards = []; - for (const card of cards) { - const existingCard = collection?.cards?.find((c) => c.id === card.id); + try { + console.log('URL', path); - if (existingCard) { - // await updateCardsInCollection(collection._id, [card], 'increment'); - existingCard.tag = 'incremented'; - updatedCards.push(existingCard); - } else { - card.tag = 'added'; - newCards.push({ ...card, quantity: 1 }); + await fetchWrapper(path, method, data, actionName); + + options.afterAction?.(); + fetchCollections(); // Refresh collections after any action + } catch (error) { + handleError(error, actionName); } - } + }, + [ + userId, + isLoggedIn, + status, + fetchWrapper, + createApiUrl, + fetchCollections, + handleError, + setSelectedCollectionError, + selectedCollection, + ] + ); - if (newCards.length > 0) { - logger.logEvent('addCardsToCollection ADD', { - newCards, - collection, - }); - const data = await fetchWrapper( - createApiUrl(`/${collection?._id}/add`), - 'POST', - { cards: newCards }, - loadingID - ); - // const data = handleApiResponse(response, 'addCardsToCollection'); - updateSelectedCollection(data.data); - } - if (updatedCards.length > 0) { - logger.logEvent('addCardsToCollection UPDATE', { - updatedCards, - collection, - }); - const data = await fetchWrapper( - createApiUrl(`/${collection._id}/update`), - 'PUT', - { cards: updatedCards, type: 'increment' }, - loadingID + const createNewCollection = (data) => + performAction(createApiUrl('create'), 'POST', data, 'createNewCollection'); + const deleteCollection = (collectionId) => + performAction( + createApiUrl(`delete/${collectionId}`), + 'DELETE', + {}, + 'deleteCollection' + ); + const updateCollection = (collectionId, updatedData) => + performAction( + createApiUrl(`update/${collectionId}`), + 'PUT', + updatedData, + 'updateCollection' + ); + const addCardsToCollection = useCallback( + (newCards, collection) => { + if (selectedCollectionId === 'selectedCollectionId') + return console.log('Collection ID has not been set'); + // CHECK FOR EXISTING CARD IN COLLECTION (newcards[0]) + const existingCard = collection?.cards?.find( + (card) => card.id === newCards[0].id ); - // const data = handleApiResponse(response, 'addCardsToCollection'); - updateSelectedCollection(data.data); - } - }; - /** - * Removes cards from a specific collection. - * @param {string} userId - The ID of the user who owns the collection. - * @param {string} collectionId - The ID of the collection from which cards are being removed. - * @param {Array} cardIds - Array of card object IDs to be removed. - * @returns {Promise} The response from the server. - */ - const removeCardsFromCollection = async (cards, cardIds, collection) => { - const loadingID = 'removeCardsFromCollection'; - if (!userId || isLoading(loadingID)) return; - setError(null); - const collectionId = collection._id; - const cardsToRemove = []; - const cardsToDecrement = []; - for (const card of cards) { - const existingCard = collection?.cards?.find((c) => c.id === card.id); - if (existingCard) { - if (existingCard.quantity > 1) { - existingCard.tag = 'decremented'; - cardsToDecrement.push(existingCard); - } else { - card.tag = 'removed'; - cardsToRemove.push(card); - } - } - } - try { - if (cardsToRemove.length > 0) { - logger.logEvent('removeCardsFromCollection REMOVE', { - collectionId, - cardsToRemove, - }); - const response = await fetchWrapper( - createApiUrl(`/${collectionId}/remove`), - 'DELETE', - { cards: cardsToRemove }, - loadingID - ); - const data = handleApiResponse(response, 'removeCardsFromCollection'); - updateSelectedCollection(data); - } + const options = { + beforeAction: () => { + if (existingCard) { + console.log('Card already exists in collection'); + return; + } + }, + afterAction: () => { + if (existingCard) { + console.log('Card already exists in collection'); + return; + } + }, + paramTypes: { + collectionId: selectedCollectionId, + }, + }; - if (cardsToDecrement.length > 0) { - logger.logEvent('removeCardsFromCollection DECREMENT', { - collectionId, - cardsToDecrement, - }); - const response = await fetchWrapper( - createApiUrl(`/${collectionId}/update`), + if (existingCard) { + // UPDATE EXISTING CARD + performAction( + createApiUrl(`${selectedCollectionId}/cards/update`), 'PUT', - { cards: cardsToDecrement, type: 'decrement' }, - loadingID + { cards: [newCards], type: 'increment' }, + 'addCardsToCollection', + options + ); + } else { + // ADD NEW CARD + performAction( + createApiUrl(`${selectedCollectionId}/cards/add`), + 'POST', + { cards: newCards, type: 'addNew' }, + 'addCardsToCollection', + options ); - const data = handleApiResponse(response, 'removeCardsFromCollection'); - updateSelectedCollection(data); } - } catch (error) { - setError(error); - logger.logEvent('removeCardsFromCollection error', error); - throw error; - } - }; - /** - * Updates the chart data for a specific collection. - * @param {string} userId - The ID of the user who owns the collection. - * @param {string} collectionId - The ID of the collection for which chart data is being updated. - * @param {Object} chartData - The updated chart data for the collection. The structure of this data depends on how chart data is managed in your application. - * @returns {Promise} The response from the server. - */ - const updateChartDataInCollection = async (collectionId, chartData) => { - logger.logEvent('updateChartDataInCollection start', { - collectionId, - chartData, - }); - try { - const response = await fetchWrapper( - createApiUrl(`/${collectionId}/updateChartData`), + // performAction( + // createCardApiUrl('update'), + // 'PUT', + // { cards: newCards }, + // 'addCardsToCollection', + // ['object'] + // ); + }, + [performAction] + ); + const removeCardsFromCollection = useCallback( + (cards, cardIds, collection) => { + performAction( + createApiUrl(`${selectedCollectionId}/cards/remove`), 'PUT', - chartData + { cards, cardIds }, + 'removeCardsFromCollection', + ['object', 'object'] ); - const data = handleApiResponse(response, 'updateChartDataInCollection'); - updateSelectedCollection(data); - - return data; - } catch (error) { - logger.logEvent('updateChartDataInCollection error', error); - throw error; - } - }; - const checkAndUpdateCardPrices = useCallback(async (cards, collection) => { - const response = await fetchWrapper( - createApiUrl('/allCollections/automatedPriceUpdate'), - 'PUT', - { cards, type: 'automated' } - ); - const data = handleApiResponse(response, 'checkAndUpdateCardPrices'); - updateSelectedCollection(data); - }); - useEffect(() => { - if (shouldFetchCollections(prevUserIdRef.current, userId)) { - console.log('getAllCollectionsForUser', userId); - getAllCollectionsForUser(); - } - prevUserIdRef.current = userId; - }, [userId, getAllCollectionsForUser]); // fetchAndUpdateDecks is now stable and won't change unless necessary - + }, + [performAction] + ); return { - // STATE - error, - isLoading, - // MAIN STATE - collectionData, - allCollections, - selectedCollection, - selectedCards, - hasFetchedCollections, - // SECONDARY STATE (derived from main state selectedCollection) - collectionStatistics: selectedCollection?.collectionStatistics, - chartData: selectedCollection?.chartData, - // totalPrice: calculateCollectionValue(selectedCollection), - totalQuantity: selectedCollection?.cards?.length || 0, - collectionPriceHistory: selectedCollection?.collectionPriceHistory, - allXYValues: selectedCollection?.chartData?.allXYValues, - lastSavedPrice: selectedCollection?.lastSavedPrice, - latestPrice: selectedCollection?.latestPrice, - cards: selectedCollection?.cards, - newNivoChartData: selectedCollection?.newNivoChartData, - totalPrice: selectedCollection?.totalPrice, - // STATE SETTERS - setCollectionData, - setAllCollections, - setSelectedCollection, - setSelectedCards, - - // COLLECTION ACTIONS + fetchCollections, createNewCollection, - getAllCollectionsForUser, - updateAndSyncCollection, deleteCollection, - updateSelectedCollection, - - // CARD ACTIONS + updateCollection, addCardsToCollection, + addOneToCollection: (cards, collection) => + addCardsToCollection(cards, collection), removeCardsFromCollection, - - // CRON JOB ACTIONS - checkAndUpdateCardPrices, - - // CHART ACTIONS - updateChartDataInCollection, - - // OTHER - // getTotalPrice: () => calculateCollectionValue(selectedCollection), + removeOneFromCollection: (cards, collection) => + removeCardsFromCollection(cards, collection), + selectedCollection, + allCollections, + showCollections, + selectedCollectionId, + selectedCollectionError, + error, + hasFetchedCollections, + handleError, + setSelectedCollectionError, }; }; export default useCollectionManager; +// const validateParams = useCallback( +// (data, expectedTypes, actionName) => { +// let isValid = true; +// const errors = []; + +// expectedTypes.forEach((type, index) => { +// const actualType = Array.isArray(data[index]) +// ? 'array' +// : typeof data[index]; +// if (actualType !== type) { +// isValid = false; +// errors.push(`Param ${index + 1}: Expected ${type}, got ${actualType}`); +// } +// }); + +// if (!isValid) { +// logger.logError(`[${actionName}] Validation errors: `, errors.join('; ')); +// } +// return isValid; +// }, +// [logger] +// ); +// const createNewCollection = useCallback( +// (data) => { +// performAction( +// createApiUrl('create'), +// 'POST', +// data, +// 'createNewCollection', +// { +// paramTypes: ['object'], +// beforeAction: () => console.log('Creating new collection...'), +// afterAction: () => console.log('New collection created.'), +// } +// ); +// }, +// [performAction, createApiUrl] +// ); +// const deleteCollection = useCallback( +// (collectionId) => { +// performAction( +// createApiUrl('delete'), +// 'DELETE', +// { +// collectionId, +// }, +// 'deleteCollection', +// ['string'] +// ); +// }, +// [performAction] +// ); +// const updateCollection = useCallback( +// (collectionId, updatedData) => { +// performAction( +// createApiUrl('update'), +// 'PUT', +// updatedData, +// 'updateCollection', +// ['string', 'object'] +// ); +// }, +// [performAction] +// ); +// const updateCollection = useCallback( +// (collectionId, updatedData) => { +// performAction( +// `/update/${collectionId}`, +// 'PUT', +// updatedData, +// 'updateCollection', +// ['string', 'object'] +// ); +// }, +// [performAction] +// ); +// const updateCardInCollection = useCallback( +// (collectionId, cardId, updatedData) => { +// if ( +// !validateParams( +// [collectionId, cardId, updatedData], +// ['string', 'string', 'object'] +// ) +// ) { +// logger.logError('Invalid parameter types for updateCardInCollection.'); +// return; +// } +// performAction( +// `/updateCard/${collectionId}/${cardId}`, +// 'PUT', +// updatedData, +// 'updateCardInCollection' +// ); +// }, +// [performAction, logger] +// ); +// /** +// * Updates the chart data for a specific collection. +// * @param {string} userId - The ID of the user who owns the collection. +// * @param {string} collectionId - The ID of the collection for which chart data is being updated. +// * @param {Object} chartData - The updated chart data for the collection. The structure of this data depends on how chart data is managed in your application. +// * @returns {Promise} The response from the server. +// */ +// const updateChartDataInCollection = async (collectionId, chartData) => { +// logger.logEvent('updateChartDataInCollection start', { +// collectionId, +// chartData, +// }); +// try { +// const response = await fetchWrapper( +// createApiUrl(`/${collectionId}/updateChartData`), +// 'PUT', +// chartData +// ); +// // const data = handleApiResponse(response, 'updateChartDataInCollection'); +// // updateSelectedCollection(data); +// // return data; +// } catch (error) { +// logger.logEvent('updateChartDataInCollection error', error); +// throw error; +// } +// }; +// const checkAndUpdateCardPrices = useCallback(async (cards, collection) => { +// const response = await fetchWrapper( +// createApiUrl('/allCollections/automatedPriceUpdate'), +// 'PUT', +// { cards, type: 'automated' } +// ); +// // const data = handleApiResponse(response, 'checkAndUpdateCardPrices'); +// // updateSelectedCollection(data); +// }); +// const createNewCollection = useCallback( +// (data) => { +// if (!validateParams([data], ['object'])) { +// logger.logError('Invalid parameter types for createNewCollection.'); +// return; +// } +// performAction('/create', 'POST', data, 'createNewCollection'); +// }, +// [performAction, logger] +// ); +// const deleteCollection = useCallback( +// (collectionId) => { +// if (!validateParams([collectionId], ['string'])) { +// logger.logError('Invalid parameter types for deleteCollection.'); +// return; +// } +// performAction( +// `/delete/${collectionId}`, +// 'DELETE', +// {}, +// 'deleteCollection' +// ); +// }, +// [performAction, logger] +// ); +// const updateAndSyncCollection = useCallback( +// (collectionId, updatedData) => { +// if (!validateParams([collectionId, updatedData], ['string', 'object'])) { +// logger.logError('Invalid parameter types for updateAndSyncCollection.'); +// return; +// } +// performAction( +// `/update/${collectionId}`, +// 'PUT', +// updatedData, +// 'updateAndSyncCollection' +// ); +// }, +// [performAction, logger] +// ); +// const addCardsToCollection = useCallback( +// (newCards, collection) => { +// console.log('addCardsToCollection', newCards, collection); +// if (!validateParams([[newCards], collection], ['object', 'object'])) { +// logger.logError( +// `Invalid parameter types for addCardsToCollection, showing: ${ +// (typeof newCards, typeof collection) +// }` +// ); +// return; +// } +// performAction( +// '/update', +// 'PUT', +// { cards: newCards }, +// 'addCardsToCollection' +// ); +// }, +// [performAction, logger] +// ); +// // Effect to fetch collections on initial load or userId change +// useEffect(() => { +// fetchCollections(); +// }, [fetchCollections]); +// const { fetchData: fetchCollections, isLoading: isLoadingCollections } = +// useGet({ +// userId, +// isLoggedIn, // This might be omitted if not relevant for the fetch +// hasFetchedFlag: hasFetchedCollections, +// path: '/allCollections', +// setLoadingFlag: setHasFetchedCollections, +// updateStates: (responseData) => { +// // Assuming your updateStates function is defined to update the relevant states +// updateStates(responseData); +// }, +// setError, // Assuming setError is a state setter for storing any fetch errors +// fetchWrapper, // Your custom fetch wrapper function +// createApiUrl, // Function to create the full API URL +// logger, // Your logging function or hook +// }); + +// // To initiate fetching: +// useEffect(() => { +// fetchCollections(); +// }, []); + +// const getAllCollectionsForUser = useCallback(async () => { +// const loadingID = 'fetchCollections'; // Define a unique loading ID +// if (!userId || isLoading(loadingID) || hasFetchedCollections) return; +// try { +// const { data, error } = await fetchWrapper( +// createApiUrl('/allCollections'), +// 'GET', +// null, +// loadingID +// ); +// if (error) { +// throw new Error(error); +// } +// const collectionData = JSON.parse(data); +// console.log('GET ALL COLLECTIONS', collectionData); +// setCollectionData({ data: collectionData }); +// setAllCollections(collectionData?.data || []); +// setSelectedCollection(collectionData?.data?.[0] || DEFAULT_COLLECTION); +// setHasFetchedCollections(true); +// } catch (error) { +// setError(error.message || 'Failed to fetch collections'); +// logger.logEvent( +// 'Error fetching collections getAllCollectionsForUser', +// error.message +// ); +// } +// }, [ +// userId, +// hasFetchedCollections, +// createApiUrl, +// fetchWrapper, +// logger, +// isLoading, +// setError, +// setCollectionData, +// setAllCollections, +// setSelectedCollection, +// setHasFetchedCollections, +// ]); +// useEffect(() => { +// // if (isLoggedIn && userId && !hasFetchedCollections) { +// if (userId && !hasFetchedCollections) { +// getAllCollectionsForUser(); +// } +// }, [selectedCollection, setSelectedCollection]); +// const getAllCollectionsForUser = useCallback(async () => { +// const loadingID = 'fetchCollections'; // Define a unique loading ID +// if (!userId || isLoading(loadingID) || hasFetchedCollections) { +// return; +// } +// try { +// // fetchWrapper should return the data directly or throw an error if unsuccessful +// const responseData = await fetchWrapper( +// createApiUrl('/allCollections'), +// 'GET', +// null, +// loadingID +// ); +// if ( +// (responseData && responseData?.status === 200) || +// responseData?.status === 201 +// ) { +// console.log('SUCCESS: fetching collections'); +// updateStates(responseData); +// } +// } catch (error) { +// console.error(error); +// setError(error.message || 'Failed to fetch collections'); +// logger.logEvent( +// 'Error fetching collections getAllCollectionsForUser', +// error.message +// ); +// } finally { +// setHasFetchedCollections(true); // Prevent re-fetching by updating state +// } +// }, [ +// userId, +// hasFetchedCollections, +// createApiUrl, +// fetchWrapper, +// logger, +// isLoading, +// setError, +// setCollectionData, +// setAllCollections, +// setSelectedCollection, +// setHasFetchedCollections, +// startLoading, // Include startLoading and stopLoading if they are not globally available +// stopLoading, +// ]); +// const createNewCollection = async (coData) => { +// const loadingID = 'createNewCollection'; +// if (!userId || isLoading(loadingID)) return; +// try { +// const { data, error } = await fetchWrapper( +// createApiUrl('/create'), +// 'POST', +// coData, +// loadingID +// ); +// if (error) { +// throw new Error(error); +// } +// updateSelectedCollection(data.data); // Assuming updateSelectedCollection updates context or state +// return data.data; +// } catch (error) { +// setError(error.message || 'Failed to create new collection'); +// logger.logEvent( +// 'Error creating new collection', +// 'createNewCollection', +// error.message +// ); +// throw error; +// } +// }; + +// /** +// * Updates and synchronizes a specific collection for a user. +// * @param {string} userId - The ID of the user who owns the collection. +// * @param {string} collectionId - The ID of the collection to be updated. +// * @param {Object} updatedData - The updated data for the collection. +// * @param {string} updatedData.name - (Optional) New name of the collection. +// * @param {string} updatedData.description - (Optional) New description of the collection. +// * @param {Array} updatedData.cards - (Optional) Array of updated cards. +// * @returns {Promise} The response from the server. +// */ +// const updateAndSyncCollection = async (collectionId, updatedData) => { +// const loadingID = 'updateAndSyncCollection'; +// if (!userId || isLoading(loadingID)) return; +// try { +// const { data, error } = await fetchWrapper( +// createApiUrl(`/${collectionId}`), +// 'PUT', +// updatedData, +// loadingID +// ); +// if (error) { +// throw new Error(error); +// } +// setAllCollections((prev) => +// prev.map((collection) => +// collection._id === collectionId ? data.data : collection +// ) +// ); +// updateSelectedCollection(data.data); // Assuming updateSelectedCollection updates context or state +// return data.data; +// } catch (error) { +// setError(error.message || 'Failed to update collection'); +// logger.logEvent('updateAndSyncCollection error', error); +// throw error; +// } +// }; + +// /** +// * Deletes a specific collection for a user. +// * @param {string} collectionId - The ID of the collection to be deleted. +// * @returns {Promise} The response from the server. +// */ +// const deleteCollection = async (collectionId) => { +// const loadingID = 'deleteCollection'; +// if (!userId || isLoading(loadingID)) return; +// setError(null); +// try { +// const response = await fetchWrapper( +// createApiUrl(`/${collectionId}`), +// 'DELETE', +// { collectionId }, +// loadingID +// ); +// // const data = handleApiResponse(response, 'addCardsToCollection'); + +// setAllCollections((prev) => +// prev.filter((collection) => collection?._id !== collectionId) +// ); +// return response; +// } catch (error) { +// setError(error); +// logger.logEvent('deleteCollection error', error); +// throw error; +// } +// }; +// /** +// * Adds new cards to a specific collection. +// * @param {string} userId - The ID of the user who owns the collection. +// * @param {string} collectionId - The ID of the target collection. +// * @param {Array} cards - Array of card objects to be added. +// * @returns {Promise} The response from the server. +// */ +// const addCardsToCollection = async (cards, collection) => { +// const loadingID = 'addCardsToCollection'; +// if (!userId || isLoading(loadingID)) return; +// setError(null); +// const newCards = []; +// const updatedCards = []; + +// for (const card of cards) { +// const existingCard = collection?.cards?.find((c) => c.id === card.id); + +// if (existingCard) { +// // await updateCardsInCollection(collection._id, [card], 'increment'); +// existingCard.tag = 'incremented'; +// updatedCards.push(existingCard); +// } else { +// card.tag = 'added'; +// newCards.push({ ...card, quantity: 1 }); +// } +// } + +// if (newCards.length > 0) { +// logger.logEvent('addCardsToCollection ADD', { +// newCards, +// collection, +// }); +// const data = await fetchWrapper( +// createApiUrl(`/${collection?._id}/add`), +// 'POST', +// { cards: newCards }, +// loadingID +// ); +// // const data = handleApiResponse(response, 'addCardsToCollection'); +// updateSelectedCollection(data.data); +// } +// if (updatedCards.length > 0) { +// logger.logEvent('addCardsToCollection UPDATE', { +// updatedCards, +// collection, +// }); +// const data = await fetchWrapper( +// createApiUrl(`/${collection._id}/update`), +// 'PUT', +// { cards: updatedCards, type: 'increment' }, +// loadingID +// ); +// // const data = handleApiResponse(response, 'addCardsToCollection'); +// updateSelectedCollection(data.data); +// } +// }; +// /** +// * Removes cards from a specific collection. +// * @param {string} userId - The ID of the user who owns the collection. +// * @param {string} collectionId - The ID of the collection from which cards are being removed. +// * @param {Array} cardIds - Array of card object IDs to be removed. +// * @returns {Promise} The response from the server. +// */ +// const removeCardsFromCollection = async (cards, cardIds, collection) => { +// const loadingID = 'removeCardsFromCollection'; +// if (!userId || isLoading(loadingID)) return; +// setError(null); +// const collectionId = collection._id; +// const cardsToRemove = []; +// const cardsToDecrement = []; +// for (const card of cards) { +// const existingCard = collection?.cards?.find((c) => c.id === card.id); +// if (existingCard) { +// if (existingCard.quantity > 1) { +// existingCard.tag = 'decremented'; +// cardsToDecrement.push(existingCard); +// } else { +// card.tag = 'removed'; +// cardsToRemove.push(card); +// } +// } +// } +// try { +// if (cardsToRemove.length > 0) { +// logger.logEvent('removeCardsFromCollection REMOVE', { +// collectionId, +// cardsToRemove, +// }); +// const response = await fetchWrapper( +// createApiUrl(`/${collectionId}/remove`), +// 'DELETE', +// { cards: cardsToRemove }, +// loadingID +// ); +// const data = handleApiResponse(response, 'removeCardsFromCollection'); +// updateSelectedCollection(data); +// } + +// if (cardsToDecrement.length > 0) { +// logger.logEvent('removeCardsFromCollection DECREMENT', { +// collectionId, +// cardsToDecrement, +// }); +// const response = await fetchWrapper( +// createApiUrl(`/${collectionId}/update`), +// 'PUT', +// { cards: cardsToDecrement, type: 'decrement' }, +// loadingID +// ); +// const data = handleApiResponse(response, 'removeCardsFromCollection'); +// updateSelectedCollection(data); +// } +// } catch (error) { +// setError(error); +// logger.logEvent('removeCardsFromCollection error', error); +// throw error; +// } +// }; // const getAllCollectionsForUser = useCallback(async () => { // if (!userId || hasFetchedCollections) return; diff --git a/src/context/MAIN_CONTEXT/CollectionContext/useCollectionStats.jsx b/src/context/MAIN_CONTEXT/CollectionContext/useCollectionStats.jsx new file mode 100644 index 0000000..3b6f7cb --- /dev/null +++ b/src/context/MAIN_CONTEXT/CollectionContext/useCollectionStats.jsx @@ -0,0 +1,84 @@ +import { useEffect, useState } from 'react'; +import useSelectedCollection from './useSelectedCollection'; + +const useCollectionStats = () => { + const { allCollections } = useSelectedCollection(); + const [collectionStats, setCollectionStats] = useState({}); + const [metaStats, setMetaStats] = useState({}); + const totals = []; + const quantities = []; + useEffect(() => { + const stats = {}; + for (const collection of allCollections) { + const { + totalPrice, + totalQuantity, // Fixed typo from 'toalQuantity' to 'totalQuantity' + nivoChartData, + newNivoChartData, + nivoTestData, + averagedChartData, + chartData, + muiChartData, + name, + descriptions, + lastUpdated, + collectionPriceHistory, + dailyCollectionPriceHistory, + createdAt, + collectionStatistics, + id, // Assuming 'id' is available in 'collection' for mapping + } = collection; + + const { avgPrice, highPoint, lowPoint, percentageChange, priceChange } = + collectionStatistics; + + totals.push(totalPrice); + quantities.push(totalQuantity); + + stats[collection.id] = { + totalPrice, + totalQuantity, + nivoChartData, + newNivoChartData, + nivoTestData, + averagedChartData, + chartData, + muiChartData, + name, + descriptions, + lastUpdated, + collectionPriceHistory, + dailyCollectionPriceHistory, + createdAt, + avgPrice, + highPoint, + lowPoint, + percentageChange, + priceChange, + collectionStatistics, + }; + } + + setCollectionStats(stats); + console.log('COLLECTION STATS RECORDED: ', stats); + }, []); // Dependency array ensures this effect runs only when allCollections changes + + const calculateAndSetMetaStats = () => { + const metaStats = {}; + metaStats.totalValue = totals.reduce((acc, total) => acc + total, 0); + metaStats.totalQuantity = quantities.reduce( + (acc, quantity) => acc + quantity, + 0 + ); + setMetaStats(metaStats); + console.log('META STATS RECORDED: ', metaStats); + return metaStats; + }; + + useEffect(() => { + calculateAndSetMetaStats(); + }, []); + return { collectionStats, metaStats }; +}; + +export default useCollectionStats; diff --git a/src/context/MAIN_CONTEXT/CollectionContext/useSelectedCollection.jsx b/src/context/MAIN_CONTEXT/CollectionContext/useSelectedCollection.jsx new file mode 100644 index 0000000..d6150de --- /dev/null +++ b/src/context/MAIN_CONTEXT/CollectionContext/useSelectedCollection.jsx @@ -0,0 +1,508 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + DEFAULT_COLLECTION, + SELECTED_COLLECTION_ID, + DEFAULT_CARDS_COUNT, +} from '../../constants'; +import useLocalStorage from '../../hooks/useLocalStorage'; +import jsonData from './nivoTestData.json'; +import _set from 'lodash/set'; + +function useSelectedCollection() { + const { nivoTestData } = jsonData; + + const [collections, setCollections] = useLocalStorage('collections', { + allIds: [], + byId: { + [SELECTED_COLLECTION_ID]: + DEFAULT_COLLECTION.addMultipleDefaultCards(DEFAULT_CARDS_COUNT), + }, + selectedId: SELECTED_COLLECTION_ID, + showCollections: true, + nivoTestData: nivoTestData, + }); + const [selectedCollectionId, setSelectedCollectionId] = useState(null); + const [collectionsVisible, setCollectionsVisible] = useState(true); + const [customError, setCustomError] = useState(null); + const prevSelectedCollectionIdRef = useRef(null); + + useEffect(() => { + prevSelectedCollectionIdRef.current = selectedCollectionId; + }, [selectedCollectionId]); + const isCollectionShown = useMemo( + () => collections.selectedId !== null, + [collections.selectedId] + ); + const getSelectedCollection = useMemo( + () => collections.byId[collections.selectedId] || DEFAULT_COLLECTION, + [collections.byId, collections.selectedId] + ); + const handleSelectCollection = useCallback( + (collection) => { + console.log('SELECTED COLLECTION ID', collection._id); + const prevSelectedCollectionId = prevSelectedCollectionIdRef.current; + console.log('Previous selected collection ID:', prevSelectedCollectionId); + setSelectedCollectionId(collection._id); + setCustomError(null); + setCollectionsVisible(false); // Hide collection list to show the selected collection's details. + const collectionId = collection._id; + if (!collections.byId[collectionId]) { + setCustomError('Invalid collection selected'); + return; + } + setCollections((prev) => ({ + ...prev, + selectedId: collectionId, + showCollections: false, + })); + setCustomError(null); + }, + [collections.byId, setCollections, setSelectedCollectionId] + ); + const toggleShowCollections = useCallback(() => { + setCollections((prev) => ({ + ...prev, + showCollections: !prev.showCollections, + })); + }, [setCollections]); + const handleBackToCollections = useCallback(() => { + setCollections((prev) => ({ + ...prev, + selectedId: null, + showCollections: true, + })); + setCustomError(null); + }, [setCollections]); + const updateCollectionField = useCallback( + (collectionId, fieldPath, value) => { + setCollections((prev) => + _set({ ...prev }, `byId.${collectionId}.${fieldPath}`, value) + ); + }, + [setCollections] + ); + const resetCollection = useCallback(() => { + setCollections((prev) => ({ + ...prev, + selectedId: null, + showCollections: true, + })); + setCustomError(null); + }, [setCollections]); + const updateCollectionsData = useCallback( + (newCollections) => { + console.log('updateCollectionsData', newCollections); + setCollections((prev) => { + const updatedById = { ...prev.byId }; + newCollections?.forEach((collection) => { + updatedById[collection._id] = collection; + }); + return { + ...prev, + byId: updatedById, + allIds: Object.keys(updatedById), + }; + }); + }, + [setCollections] + ); + const addNewCollection = useCallback( + (newCollection) => { + // const newId = new Date().getTime().toString(); // Simple unique ID generation + updateCollectionsData([{ ...newCollection, _id: newCollection._id }]); + }, + [updateCollectionsData] + ); + const removeCollection = useCallback( + (collectionId) => { + setCollections((prev) => { + const { [collectionId]: _, ...remainingById } = prev.byId; + return { + ...prev, + allIds: prev.allIds.filter((id) => id !== collectionId), + byId: remainingById, + selectedId: prev.selectedId === collectionId ? null : prev.selectedId, + }; + }); + }, + [setCollections] + ); + + const prevCollectionsRef = useRef(); + + // useEffect to log changes + useEffect(() => { + if (prevCollectionsRef.current) { + console.log('Collections data updated:', collections); + } + prevCollectionsRef.current = collections; + }, [collections]); // Dependency array ensures this runs only when collections change + + return { + selectedCollectionId: collections.selectedId, + selectedCollection: getSelectedCollection, + allCollections: Object.values(collections.byId), + showCollections: collectionsVisible, + nivoTestData: nivoTestData, + handleSelectCollection, + handleBackToCollections, + updateCollectionField, + resetCollection, + updateCollectionsData, + customError, + isCollectionShown, + toggleShowCollections, + addNewCollection, + removeCollection, + setCustomError, + prevSelectedCollectionId: prevSelectedCollectionIdRef.current, + }; +} + +export default useSelectedCollection; + +// import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +// import { +// DEFAULT_COLLECTION, +// SELECTED_COLLECTION_ID, +// DEFAULT_CARDS_COUNT, +// } from '../../constants'; +// import useLocalStorage from '../../hooks/useLocalStorage'; +// import _set from 'lodash/set'; // Assuming lodash is installed +// function useSelectedCollection() { +// const prevSelectedCollectionRef = useRef(SELECTED_COLLECTION_ID); +// const [collections, setCollections] = useLocalStorage('collections', { +// allCollections: [DEFAULT_COLLECTION], +// selectedCollection: DEFAULT_COLLECTION, +// allIds: [SELECTED_COLLECTION_ID], +// selectedId: SELECTED_COLLECTION_ID, +// byId: { +// [SELECTED_COLLECTION_ID]: +// DEFAULT_COLLECTION.addMultipleDefaultCards(DEFAULT_CARDS_COUNT), +// }, +// prevId: SELECTED_COLLECTION_ID, +// showCollections: true, +// }); +// const [selectedCollectionId, setSelectedCollectionId] = useLocalStorage( +// 'selectedCollectionId', +// SELECTED_COLLECTION_ID +// ); +// const [customError, setCustomError] = useState(null); +// const isCollectionShown = useMemo( +// () => collections.selectedId !== null, +// [collections.selectedId] +// ); +// const getSelectedCollection = useMemo( +// () => collections.byId[collections.selectedId] || DEFAULT_COLLECTION, +// [collections.byId, collections.selectedId] +// ); +// const handleSelectCollection = useCallback( +// (collection) => { +// const collectionId = collection._id; +// console.log(collectionId); +// if (!collections.byId[collectionId]) { +// setCustomError('Invalid collection selected'); +// return; +// } +// setCollections((prev) => ({ +// ...prev, +// selectedId: collectionId, +// selectedCollection: collections.byId[collectionId], + +// showCollections: false, +// })); +// setCustomError(null); +// }, +// [setCollections] +// ); +// const handleBackToCollections = useCallback(() => { +// setCollections((prev) => ({ +// ...prev, +// selectedId: null, +// selectedCollection: null, +// showCollections: true, +// })); +// setCustomError(null); +// }, [setCollections]); +// const updateCollectionField = useCallback( +// (collectionId, fieldPath, value) => { +// setCollections((prev) => { +// const updatedCollections = { ...prev }; +// _set(updatedCollections.byId, `${collectionId}.${fieldPath}`, value); +// return updatedCollections; +// }); +// }, +// [setCollections] +// ); +// const resetCollection = useCallback(() => { +// setCollections((prev) => ({ +// ...prev, +// selectedId: null, // Reset selected collection ID to null +// showCollections: true, // Optionally, show all collections again +// })); +// setCustomError(null); // Clear any existing errors +// }, [setCollections]); + +// const updateCollectionsData = useCallback( +// (newCollections, collectionsData) => { +// setCollections((prev) => { +// const updatedById = { ...prev.byId, ...collectionsData }; // Assuming collectionsData is a map of {collectionId: collection} +// // Ensure newCollections are properly incorporated into the state +// newCollections.forEach((collection) => { +// updatedById[collection._id] = collection; +// }); +// let newSelectedId = prev.selectedId; +// // Check if the current selected collection needs to be updated +// if ( +// updatedById[newSelectedId] === undefined || +// newSelectedId === null +// ) { +// newSelectedId = newCollections[0]?._id || null; +// } +// return { +// ...prev, +// byId: updatedById, +// allIds: Object.keys(updatedById), +// selectedId: newSelectedId, +// showCollections: !newSelectedId, // Hide or show collections based on whether a new selection is made +// }; +// }); +// }, +// [setCollections] +// ); +// const addNewCollection = useCallback( +// (newCollection) => { +// setCollections((prev) => { +// const newId = new Date().getTime().toString(); // Simple unique ID generation +// return { +// ...prev, +// allIds: [...prev.allIds, newId], +// byId: { ...prev.byId, [newId]: newCollection }, +// }; +// }); +// }, +// [setCollections] +// ); + +// const removeCollection = useCallback( +// (collectionId) => { +// setCollections((prev) => { +// const { [collectionId]: removed, ...remainingCollections } = prev.byId; +// return { +// ...prev, +// allIds: prev.allIds.filter((id) => id !== collectionId), +// byId: remainingCollections, +// selectedId: prev.selectedId === collectionId ? null : prev.selectedId, +// }; +// }); +// }, +// [setCollections] +// ); + +// const toggleShowCollections = useCallback(() => { +// setCollections((prev) => ({ +// ...prev, +// showCollections: !prev.showCollections, +// })); +// }, [setCollections]); +// // useEffect(() => { +// // prevSelectedCollectionRef.current = collections.selectedId; +// // if (prevSelectedCollectionRef.current !== collections.selectedId) { +// // setSelectedCollectionId(collections.selectedId); +// // } +// // }, [collections.selectedId, setSelectedCollectionId]); + +// return { +// selectedCollectionId: collections.selectedId, +// selectedCollection: collections.selectedCollection, +// allCollections: Object.values(collections.byId), +// showCollections: collections.showCollections, +// handleSelectCollection, +// handleBackToCollections, +// updateCollectionField, +// addNewCollection, +// removeCollection, +// toggleShowCollections, +// customError, +// setCustomError, +// }; +// } + +// export default useSelectedCollection; + +// // function useSelectedCollection() { +// // const [selectedCollectionId, setSelectedCollectionId] = useLocalStorage( +// // 'selectedCollectionId', +// // SELECTED_COLLECTION_ID +// // ); +// // const [allCollectionIds, setAllCollectionIds] = useLocalStorage( +// // 'allCollectionIds', +// // [SELECTED_COLLECTION_ID] +// // ); +// // const [showCollections, setShowCollections] = useLocalStorage( +// // 'showCollections', +// // true +// // ); +// // const isCollectionShown = useMemo( +// // () => selectedCollectionId !== null, +// // [selectedCollectionId] +// // ); // Use useMemo for derived state + +// // const [collectionViewState, setCollectionViewState] = useState({ +// // mode: { +// // showListOfAllCollections: !isCollectionShown, +// // showSelectedCollection: isCollectionShown, +// // }, +// // visible: { +// // selected: selectedCollectionId, +// // all: allCollectionIds, +// // }, +// // toggleViewState: (state) => { +// // setCollectionViewState((prev) => ({ +// // ...prev, +// // mode: state, +// // })); +// // }, +// // }); +// // const [selectedCollection, setSelectedCollection] = useLocalStorage( +// // 'selectedCollection', +// // DEFAULT_COLLECTION.addMultipleDefaultCards(5) +// // ); +// // const [allCollections, setAllCollections] = useLocalStorage( +// // 'allCollections', +// // [DEFAULT_COLLECTION.addMultipleDefaultCards(5)] +// // ); +// // const [collectionData, setCollectionData] = useLocalStorage( +// // 'collectionData', +// // {} +// // ); +// // const [customError, setCustomError] = useState(null); + +// // const prevSelectedCollectionRef = useRef(DEFAULT_COLLECTION); + +// // const handleSelectCollection = useCallback( +// // (collectionId) => { +// // if (!collectionId) { +// // setCustomError('Invalid collection selected'); +// // return; +// // } +// // const collection = +// // allCollections.find((c) => c._id === collectionId) || +// // DEFAULT_COLLECTION; +// // setSelectedCollection(collection); +// // setSelectedCollectionId(collection._id); +// // setShowCollections(false); +// // setCustomError(null); +// // prevSelectedCollectionRef.current = collection; +// // }, +// // [ +// // allCollections, +// // setSelectedCollection, +// // setSelectedCollectionId, +// // setShowCollections, +// // setCustomError, +// // ] +// // ); + +// // const handleBackToCollections = useCallback(() => { +// // setSelectedCollectionId(null); +// // setSelectedCollection(DEFAULT_COLLECTION); +// // setShowCollections(true); +// // setCustomError(null); +// // }, [ +// // setSelectedCollectionId, +// // setSelectedCollection, +// // setShowCollections, +// // setCustomError, +// // ]); +// // const updateCollectionField = useCallback( +// // (fieldPath, value) => { +// // setSelectedState((prevCollection) => { +// // const updatedCollection = { ...prevCollection }; +// // let currentField = updatedCollection; +// // const keys = fieldPath.split('.'); +// // keys.forEach((key, index) => { +// // if (index === keys.length - 1) { +// // currentField[key] = value; +// // } else { +// // if (!currentField[key]) currentField[key] = {}; +// // currentField = currentField[key]; +// // } +// // }); +// // return updatedCollection; +// // }); +// // }, +// // [setSelectedState] +// // ); + +// // useEffect(() => { +// // // console.log('SELECTED COLLECTION ID:', selectedCollectionId); +// // // console.log('SELECTED COLLECTION:', selectedCollection); +// // // console.log('COLLECTION SHOWN:', isCollectionShown); +// // setShowCollections(isCollectionShown); +// // }, [ +// // selectedCollection, +// // selectedCollectionId, +// // isCollectionShown, +// // setShowCollections, +// // ]); +// // useEffect(() => { +// // const collection = localStorage.getItem('selectedCollection'); +// // setSelectedCollection(collection ? JSON.parse(collection) : null); +// // }, []); +// // const [selectedCollection, setSelectedCollection] = useLocalStorage( +// // 'selectedCollection', +// // DEFAULT_COLLECTION.addMultipleDefaultCards(5) +// // ); +// // const [allCollections, setAllCollections] = useLocalStorage( +// // 'allCollections', +// // [DEFAULT_COLLECTION.addMultipleDefaultCards(5)] +// // ); +// // const [collectionData, setCollectionData] = useLocalStorage( +// // 'collectionData', +// // {} +// // ); +// // const [selectedCollectionId, setSelectedCollectionId] = useLocalStorage( +// // 'selectedCollectionId', +// // null +// // ); +// // const [showCollections, setShowCollections] = useLocalStorage( +// // 'showCollections', +// // true +// // ); +// // const prevSelectedCollectionRef = useRef(DEFAULT_COLLECTION); +// // const [error, setError] = useState(null); + +// // const setSelectedState = useCallback( +// // (collection) => { +// // prevSelectedCollectionRef.current = selectedCollection; // Store current collection as previous +// // setSelectedCollection(collection); // Update selected collection +// // }, +// // [selectedCollection, setSelectedCollection, setSelectedCollectionId] +// // ); + +// // const handleSelectCollection = useCallback( +// // (collection) => { +// // setShowCollections(false); +// // setSelectedState(collection); +// // setSelectedCollectionId(collection._id); +// // }, +// // [ +// // setSelectedState, +// // setSelectedCollectionId, +// // setShowCollections, +// // setCustomError, +// // ] +// // ); +// // const handleBackToCollections = useCallback(() => { +// // console.log('BACK TO COLLECTIONS'); +// // setSelectedState(DEFAULT_COLLECTION.addMultipleDefaultCards(5)); +// // setSelectedCollectionId(null); +// // setShowCollections(true); +// // setCustomError(null); +// // }, [ +// // setSelectedCollectionId, +// // setSelectedState, +// // setShowCollections, +// // setCustomError, +// // ]); diff --git a/src/context/MAIN_CONTEXT/DeckContext/DeckContext.js b/src/context/MAIN_CONTEXT/DeckContext/DeckContext.js index 7f29619..c44cacb 100644 --- a/src/context/MAIN_CONTEXT/DeckContext/DeckContext.js +++ b/src/context/MAIN_CONTEXT/DeckContext/DeckContext.js @@ -20,6 +20,7 @@ import useFetchWrapper from '../../hooks/useFetchWrapper.jsx'; import useLogger from '../../hooks/useLogger.jsx'; import useApiResponseHandler from '../../hooks/useApiResponseHandler.jsx'; import { useLoading } from '../../hooks/useLoading.jsx'; +import { DEFAULT_DECK } from '../../constants.jsx'; export const DeckContext = createContext(defaultContextValue); @@ -30,8 +31,8 @@ export const DeckProvider = ({ children }) => { const { fetchWrapper, responseCache } = useFetchWrapper(); const prevUserIdRef = useRef(userId); const [deckData, setDeckData] = useState({}); - const [allDecks, setAllDecks] = useState([]); - const [selectedDeck, setSelectedDeck] = useState({}); + const [allDecks, setAllDecks] = useState([DEFAULT_DECK]); + const [selectedDeck, setSelectedDeck] = useState(DEFAULT_DECK); const [selectedCards, setSelectedCards] = useState(selectedDeck?.cards || []); const [hasFetchedDecks, setHasFetchedDecks] = useState(false); const [error, setError] = useState(null); @@ -51,17 +52,9 @@ export const DeckProvider = ({ children }) => { setSelectedDeck(newDeck); setSelectedCards(newDeck?.cards || []); }, []); - // const userIdRef = useRef(userId); - - // useEffect(() => { - // userIdRef.current = userId; // Update ref when userId changes - // }, [userId]); - - // Function to fetch and update decks const fetchAndUpdateDecks = useCallback(async () => { const loadingID = 'fetchAndUpdateDecks'; if (!userId || isLoading(loadingID) || hasFetchedDecks) return; - startLoading(loadingID); try { const responseData = await fetchWrapper( createApiUrl('/allDecks'), @@ -75,21 +68,13 @@ export const DeckProvider = ({ children }) => { responseData?.status === 201 ) { console.log('SUCCESS: fetching decks'); - const cachedData = responseCache[loadingID]; - if (cachedData) { - updateStates(cachedData); - } - } - if (responseData && responseData?.status !== 200) { - console.error('ERROR: fetching decks'); - setError(responseData?.data?.message || 'Failed to fetch decks'); + updateStates(responseData); } } catch (error) { console.error(error); setError(error.message || 'Failed to fetch decks'); logger.logEvent('Failed to fetch decks', error.message); } finally { - stopLoading(loadingID); setHasFetchedDecks(true); } }, [ @@ -108,27 +93,6 @@ export const DeckProvider = ({ children }) => { setSelectedDeck, setHasFetchedDecks, ]); - - useEffect(() => { - fetchAndUpdateDecks(); - }, []); - useEffect(() => { - const storedResponse = responseCache['fetchAndUpdateDecks']; - console.log('Stored response:', storedResponse); - if (storedResponse) { - setDeckData(storedResponse); - setAllDecks(storedResponse.data); - setSelectedDeck(storedResponse.data[0] || {}); - } - }, [responseCache]); - - /** - * Updates the details of a specific deck. - * @param {string} deckId - The ID of the deck to be updated. - * @param {Object} updatedInfo - The updated deck data. - * @returns {Promise} The response from the server. - * @todo Update the deck in local state. - */ const updateDeckDetails = async (deckId, updatedInfo) => { const loadingID = 'updateDeckDetails'; setError(null); @@ -167,12 +131,6 @@ export const DeckProvider = ({ children }) => { console.error('Error updating deck details:', error); } }; - /** - * Creates a new deck for a user. - * @param {string} userId - The ID of the user creating the collection. - * @param {Object} newDeckInfo - The data for the new collection. - * @returns {Promise} The response from the server. - */ const createUserDeck = async (userId, newDeckInfo) => { const loadingID = 'createUserDeck'; setError(null); @@ -204,11 +162,6 @@ export const DeckProvider = ({ children }) => { console.error(`Failed to create a new deck: ${error.message}`); } }; - /** - * Deletes a specific collection for a user. - * @param {string} collectionId - The ID of the collection to be deleted. - * @returns {Promise} The response from the server. - */ const deleteUserDeck = async (deckId) => { const loadingID = 'deleteUserDeck'; setError(null); @@ -236,12 +189,6 @@ export const DeckProvider = ({ children }) => { throw error; } }; - /** - * Adds new cards to a specific deck. - * @param {Array} cards - Array of card objects to be added. - * @param {Object} deck - The deck to which the cards will be added. - * @returns {Promise} The response from the server. - */ const addCardToDeck = async (cards, deck) => { const loadingID = 'addCardToDeck'; setError(null); @@ -290,13 +237,6 @@ export const DeckProvider = ({ children }) => { updateSelectedDeck(data.deck); // Assuming a function to update the current deck } }; - /** - * Removes cards from a specific deck. - * @param {Array} cards - Array of card objects in the deck. - * @param {Array} cardIds - Array of card object IDs to be removed. - * @param {Object} deck - The deck object from which cards are being removed. - * @returns {Promise} The response from the server. - */ const removeCardFromDeck = async (cards, cardIds, deck) => { const loadingID = 'removeCardFromDeck'; setError(null); diff --git a/src/context/MAIN_CONTEXT/UserContext/UserContext.js b/src/context/MAIN_CONTEXT/UserContext/UserContext.js index edecc85..e26a545 100644 --- a/src/context/MAIN_CONTEXT/UserContext/UserContext.js +++ b/src/context/MAIN_CONTEXT/UserContext/UserContext.js @@ -17,119 +17,74 @@ import useLogger from '../../hooks/useLogger'; export const UserContext = createContext(defaultContextValue.USER_CONTEXT); export const UserProvider = ({ children }) => { + const { userId, isLoggedIn } = useAuthContext(); // Assuming useAuthContext now provides userId directly + const [user, setUser] = useLocalStorage('user', {}); const logger = useLogger('UserProvider'); - const { isLoading, startLoading, stopLoading } = useLoading(); - const { fetchWrapper, responseCache } = useFetchWrapper(); - const { isLoggedIn, userId } = useAuthContext(); // Removed authUser as it's unused + const { isLoading } = useLoading(); + const { fetchWrapper, status } = useFetchWrapper(); + const [hasFetchedUser, setHasFetchedUser] = useState(false); const [error, setError] = useState(null); - const createApiUrl = useCallback( (path) => `${process.env.REACT_APP_SERVER}/api/users/${userId}/${path}`, [userId] ); const fetchUserData = useCallback(async () => { - const loadingID = 'fetchUserData'; - if (!isLoggedIn || !userId || isLoading(loadingID)) return; - startLoading(loadingID); - + if (!userId || !isLoggedIn || status === 'loading') return; try { const responseData = await fetchWrapper( - createApiUrl('userData'), + `${process.env.REACT_APP_SERVER}/api/users/${userId}/userData`, 'GET', null, - loadingID + 'getUserData' ); - - if ( - (responseData && responseData?.status === 200) || - responseData?.status === 201 - ) { - console.log('SUCCESS: fetching user data'); - const cachedData = responseCache[loadingID]; - if (cachedData) { - setUser(cachedData); // Assuming setUser updates local storage or state with user data - } - } - if (responseData && responseData?.status !== 200) { - console.error('ERROR: fetching user data'); - setError(responseData?.data?.message || 'Failed to fetch user data'); - } + setUser(responseData?.data); + setHasFetchedUser(true); } catch (error) { - console.error(error); + console.error('Error fetching user data:', error); setError(error.message || 'Failed to fetch user data'); logger.logEvent('Failed to fetch user data', error.message); - } finally { - stopLoading(loadingID); - } - }, [ - isLoggedIn, - userId, - isLoading, - createApiUrl, - fetchWrapper, - responseCache, - startLoading, - stopLoading, - setUser, - setError, - logger, - ]); - useEffect(() => { - const storedResponse = responseCache['fetchUserData']; - console.log('Stored response for user data:', storedResponse); - if (storedResponse) { - // Assuming setUser is a function that updates the user's data in state or context - setUser(storedResponse.data); // Adjust according to your actual state update mechanism } - }, [responseCache]); + }, [userId, isLoggedIn, fetchWrapper, setUser, logger]); useEffect(() => { fetchUserData(); }, []); const updateUser = useCallback( - async (updatedUser) => { - const loadingID = 'updateUserData'; - if (isLoading(loadingID)) return; - - startLoading(loadingID); - const url = createApiUrl('userData/update'); + async (updatedUserData) => { + if (!userId || !isLoggedIn || isLoading('updateUserData')) return; try { - const responseData = await fetchWrapper( - url, + await fetchWrapper( + `${process.env.REACT_APP_SERVER}/api/users/${userId}/updateUserData`, 'PUT', - updatedUser, - loadingID + updatedUserData, + 'updateUserData' ); - - console.log('Response from server for update user:', responseData); - if (responseData && responseData.data) { - setUser(responseData.data); // Update user data in local storage - } + fetchUserData(); // Refetch user data to ensure UI is up-to-date + setError(null); } catch (error) { - console.error('Error updating user data:', error); - } finally { - stopLoading(loadingID); + setError('Failed to update user data'); + logger.error('Error updating user data:', error); } }, - [fetchWrapper, createApiUrl, setUser, isLoading, startLoading, stopLoading] + [userId, isLoggedIn, fetchWrapper, fetchUserData, logger, isLoading] ); - // Removed dependency on `user` to prevent unnecessary re-fetching - // useEffect(() => { - // if (isLoggedIn && userId) { - // fetchUserData(); - // } - // }, [fetchUserData, isLoggedIn, userId]); - - const contextValue = { + const values = { + userId, + isLoggedIn, + error, + hasFetchedUser, user, updateUser, getUserData: fetchUserData, }; + const contextValue = + process.env.AUTH_ENVIRONMENT !== 'disabled' ? values : defaultContextValue; + return ( {children} ); diff --git a/src/context/MISC_CONTEXT/AppContext/AppContextProvider.jsx b/src/context/MISC_CONTEXT/AppContext/AppContextProvider.jsx index 00bdb1a..4e20197 100644 --- a/src/context/MISC_CONTEXT/AppContext/AppContextProvider.jsx +++ b/src/context/MISC_CONTEXT/AppContext/AppContextProvider.jsx @@ -12,16 +12,18 @@ import { CartContext } from '../../MAIN_CONTEXT/CartContext/CartContext'; import { CollectionContext } from '../../MAIN_CONTEXT/CollectionContext/CollectionContext'; import { defaultContextValue } from '../../constants'; import useLocalStorage from '../../hooks/useLocalStorage'; +import useSelectedContext from '../../hooks/useSelectedContext'; // Create the combined context export const AppContext = createContext(defaultContextValue.APP_CONTEXT); // Create a provider component that combines the contexts export const AppContextProvider = ({ children }) => { - const [context, setContext] = useState({}); + // const [context, setContext] = useState({}); const Deck = useContext(DeckContext); const Cart = useContext(CartContext); const Collection = useContext(CollectionContext); + const { selectedContext } = useSelectedContext(); const [cardsWithQuantities, setCardsWithQuantities] = useState([]); const [allCardsWithQuantities, setAllCardsWithQuantities] = useLocalStorage( 'allCardsWithQuantities', @@ -32,15 +34,15 @@ export const AppContextProvider = ({ children }) => { const { selectedDeck, allDecks } = Deck; const { cartData } = Cart; const isCardInContext = useCallback( - (selectedCollection, selectedDeck, cartData, context, card) => { + (card) => { const cardsList = { Collection: selectedCollection?.cards, Deck: selectedDeck?.cards, Cart: cartData?.cart, }; - return !!cardsList[context]?.find((c) => c?.id === card?.id); + return !!cardsList[selectedContext]?.find((c) => c?.id === card?.id); }, - [context, selectedCollection, selectedDeck, cartData] + [selectedContext, selectedCollection, selectedDeck, cartData] ); const compileCardsWithQuantities = () => { if (!selectedCollection && !selectedDeck && !cartData) return []; @@ -52,18 +54,13 @@ export const AppContextProvider = ({ children }) => { }, []); const cartCards = cartData?.cart || []; const collectionCards = allCollections?.reduce((acc, collection) => { - if (collection.cards) { + if (collection?.cards) { acc = [...acc, ...collection.cards]; } return acc; }, []); - - // Combine cards from all contexts const combinedCards = [...deckCards, ...cartCards, ...collectionCards]; - // console.log('combinedCards:', combinedCards); - // Calculate quantities and deduplicate const cardQuantities = combinedCards?.reduce((acc, card) => { - // If the card already exists in the accumulator, increment its quantity if (acc[card.id]) { acc[card.id].quantity += card?.quantity; } else { @@ -72,21 +69,18 @@ export const AppContextProvider = ({ children }) => { } return acc; }, {}); - // console.log('cardQuantities:', cardQuantities); - // Convert the accumulator object back into an array of cards const quantities = Object.values(cardQuantities); // console.log('cardsWithQuantities:', quantities); setCardsWithQuantities(quantities); - setAllCardsWithQuantities(quantities); + setAllCardsWithQuantities(combinedCards); return quantities; }; - // useEffect(() => { - // // Call compileCardsWithQuantities here or in response to specific changes - // compileCardsWithQuantities(); - // }, [selectedCollection, selectedDeck, cartData]); // Dependency array based on when you want to recalculate + useEffect(() => { + compileCardsWithQuantities(); + }, []); // Dependency array based on when you want to recalculate // Combine the context values into one object const appContextValues = useMemo( diff --git a/src/context/MISC_CONTEXT/CombinedContext/CombinedProvider.jsx b/src/context/MISC_CONTEXT/CombinedContext/CombinedProvider.jsx index c14a939..968ed00 100644 --- a/src/context/MISC_CONTEXT/CombinedContext/CombinedProvider.jsx +++ b/src/context/MISC_CONTEXT/CombinedContext/CombinedProvider.jsx @@ -1,283 +1,19 @@ -import React, { - createContext, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; -import { useCookies } from 'react-cookie'; -import { - generateListOfMonitoredCards, - updateCardPricesInList, - initialState, -} from './helpers'; -import { useSocketContext } from '../../UTILITIES_CONTEXT/SocketContext/SocketProvider'; -import useCollectionManager from '../../MAIN_CONTEXT/CollectionContext/useCollectionManager'; - -export const CombinedContext = createContext(); - -export const CombinedProvider = ({ children }) => { - const [cookies] = useCookies(['authUser']); - const [state, setState] = useState(initialState); - const user = cookies?.authUser; - const userId = user?.userId; - const { selectedCollection, allCollections, updateOneInCollection } = - useCollectionManager(); - const socket = useSocketContext(); - - const setDataFunctions = { - data: (key, data) => setState((prev) => ({ ...prev, [key]: data })), - eventsTriggered: (data) => - setState((prev) => ({ ...prev, eventsTriggered: data })), - }; - - const setLoader = (isLoading) => { - if (typeof isLoading !== 'boolean') { - console.error('Invalid argument type for setLoader: Expected boolean'); - return; - } - setState((prev) => ({ ...prev, isLoading })); - }; - - const handleEvent = (event, data) => { - console.log(`Handling event: ${event}`); - switch (event) { - case 'MESSAGE_TO_CLIENT': - console.log('Received message:', data); - setDataFunctions.data('messageTest', data); - break; - case 'STATUS_UPDATE_CRON': - console.log('Received status update from cron:', data); - if (Array.isArray(data) && data.length > 0) { - setDataFunctions.data('chartData', data); - } - break; - case 'CARD_PRICES_UNCHANGED': - console.log('Received card prices unchanged:', data); - setDataFunctions.data('cardPrices', data.currentPrices); - break; - case 'COLLECTIONS_UPDATED': - console.log('Collections updated:', data); - // if (Array.isArray(data.allCollections)) { - // for (const collection of data.allCollections) { - // updateAllCollectionState(collection); - // } - // setDataFunctions.data('allCollectionData', data.allCollections); - // setDataFunctions.data('allCardPrices', data.updatedCards); - // } - break; - case 'SEND_PRICING_DATA_TO_CLIENT': - console.log('Received pricing data:', data); - handleCardPricesUpdated(data.data.data); - break; - case 'ERROR': - console.error('Received error:', data); - setDataFunctions.data('error', data.error); - break; - default: - break; - } - }; - - const handleCardPricesUpdated = async (priceData) => { - console.log('Card prices retrieved:', priceData); - const updatedCardPrices = priceData.data.data; - const currentListOfMonitoredCards = - generateListOfMonitoredCards(allCollections); - const updatedListOfMonitoredCards = updateCardPricesInList( - currentListOfMonitoredCards, - updatedCardPrices - ); - const updatedSelectedCollectionCards = selectedCollection.cards.map( - (card) => { - const updatedCardPrice = updatedListOfMonitoredCards.find( - (updatedCard) => updatedCard.id === card.id - ); - return updatedCardPrice ? { ...card, ...updatedCardPrice } : card; - } - ); - - const updatedCollection = { - ...selectedCollection, - cards: updatedSelectedCollectionCards, - }; - - // Update each card in the collection - for (const card of updatedCollection.cards) { - const updatedCollectionResult = await updateOneInCollection( - updatedCollection._id, - card, - 'update' - ); - setDataFunctions.data('updatedCollectionResult', updatedCollectionResult); - // if (updatedCollectionResult) { - // updateAllCollectionState( - // updatedCollectionResult.filteredRestructuredCollection - // ); - // setDataFunctions.data( - // 'listOfSimulatedCards', - // updatedCollectionResult.filteredRestructuredCollection.cards - // ); - // } - } - - // Update the global state with the new card prices - setDataFunctions.data('allCardPrices', updatedListOfMonitoredCards); - }; - - useEffect(() => { - if (!socket) return; - const eventHandlers = { - MESSAGE_TO_CLIENT: handleEvent, - STATUS_UPDATE_CRON: handleEvent, - CARD_PRICES_UNCHANGED: handleEvent, - COLLECTIONS_UPDATED: handleEvent, - SEND_PRICING_DATA_TO_CLIENT: handleEvent, - ERROR: handleEvent, - }; - Object.entries(eventHandlers).forEach(([event, handler]) => { - socket.on(event, handler); - }); - return () => { - Object.keys(eventHandlers).forEach((event) => { - socket.off(event, eventHandlers[event]); - }); - }; - }, [socket]); - - const listOfMonitoredCards = useMemo( - () => generateListOfMonitoredCards([allCollections]), - [allCollections] - ); - - useEffect(() => { - if (state.eventsTriggered) { - console.log(`Handling event: ${state.eventsTriggered.eventName}`); - } - }, [state?.eventsTriggered]); - - useEffect(() => { - if (selectedCollection) { - setDataFunctions.data('collectionData', selectedCollection); - } - }, [selectedCollection]); - - useEffect(() => { - if ( - allCollections && - JSON.stringify(allCollections) !== JSON.stringify(state.allCollectionData) - ) { - setDataFunctions.data('allCollectionData', allCollections); - } - }, [allCollections]); - - useEffect(() => { - if (user) { - setDataFunctions.data('userData', user); - } - }, [user]); - - useEffect(() => { - if (listOfMonitoredCards) { - setDataFunctions.data( - 'retrievedListOfMonitoredCards', - listOfMonitoredCards - ); - } - }, [listOfMonitoredCards]); - - const handleCronRequest = (userId, selectedList) => { - socket?.emit('START_CRON_JOB', { userId, selectedList }); - }; - - const handleSend = (message) => { - if (!message) return console.error('Message content is missing.'); - socket?.emit('MESSAGE_FROM_CLIENT', { message, data: message }); - }; - - const handleSendAllCardsInCollections = (userId, selectedList) => { - if (!userId || !selectedList) return; - socket?.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { - userId, - data: { selectedList }, - }); - }; - - const handleRequestCronStop = (userId) => { - if (!userId) return console.error('Missing userId for cron job stop.'); - socket?.emit('REQUEST_CRON_STOP', { userId }); - }; - - const handlePricesActivateCron = ( - userId, - selectedList, - cardsWithChangedPrice - ) => { - if (!userId || !selectedList || !cardsWithChangedPrice) return; - socket?.emit('REQUEST_PRICES_ACTIVATE_CRON', { - userId, - data: { userId, selectedList, cardsWithChangedPrice }, - }); - }; - const contextValue = useMemo( - () => ({ - ...state, - listOfMonitoredCards, - setLoader, - handleCronRequest, - handleSend, - handleSendAllCardsInCollections, - handleRequestCronStop, - handlePricesActivateCron, - socket, - }), - [state, listOfMonitoredCards, socket] - ); - - // useEffect(() => { - // console.log('COMBINED CONTEXT:', state); - // }, [ - // userId, - // state.allCollectionData, - // state.updatedCollection, - // state.cardPrices, - // ]); - - return ( - - {children} - - ); -}; - -export const useCombinedContext = () => { - const context = useContext(CombinedContext); - if (context === undefined) { - throw new Error( - 'useCombinedContext must be used within a CombinedProvider' - ); - } - return context; -}; - // import React, { // createContext, -// useCallback, // useContext, // useEffect, // useMemo, // useState, // } from 'react'; // import { useCookies } from 'react-cookie'; -// import { CollectionContext } from '../MAIN_CONTEXT/CollectionContext/CollectionContext'; -// import { filterNullPriceHistoryForCollection } from '../MAIN_CONTEXT/CollectionContext/helpers'; // import { // generateListOfMonitoredCards, // updateCardPricesInList, // initialState, // } from './helpers'; -// import { useUnsavedCardsEffect } from '../hooks/useUnsavedCardsEffect'; -// import { useSocketContext } from '../UTILITIES_CONTEXT/SocketContext/SocketProvider'; +// import { useSocketContext } from '../../UTILITIES_CONTEXT/SocketContext/SocketProvider'; +// import useCollectionManager from '../../MAIN_CONTEXT/CollectionContext/useCollectionManager'; + // export const CombinedContext = createContext(); // export const CombinedProvider = ({ children }) => { @@ -285,54 +21,14 @@ export const useCombinedContext = () => { // const [state, setState] = useState(initialState); // const user = cookies?.authUser; // const userId = user?.userId; -// const { -// selectedCollection, -// updateCollection, -// allCollections, -// getNewTotalPrice, -// updateCollectionState, -// updateAllCollectionState, -// getUpdatedCollection, -// } = useContext(CollectionContext); +// const { selectedCollection, allCollections, updateOneInCollection } = +// useCollectionManager(); // const socket = useSocketContext(); -// const createStateUpdaterFunction = (key) => -// useCallback( -// (data) => { -// setState((prev) => { -// let newData; - -// // validateData(data, key, 'createStateUpdaterFunction'); -// if (Array.isArray(data)) { -// newData = [...data]; -// } else if (typeof data === 'object' && data !== null) { -// newData = { ...data }; -// } else { -// newData = data; -// } -// return { ...prev, [key]: newData }; -// }); -// }, -// [setState] -// ); - // const setDataFunctions = { -// data: createStateUpdaterFunction('chartData'), -// userData: createStateUpdaterFunction('userData'), -// messageTest: createStateUpdaterFunction('messageTest'), -// chartData: createStateUpdaterFunction('chartData'), -// listOfSimulatedCards: createStateUpdaterFunction('listOfSimulatedCards'), -// cardPrices: createStateUpdaterFunction('cardPrices'), -// allCardPrices: createStateUpdaterFunction('allCardPrices'), -// retrievedListOfMonitoredCards: createStateUpdaterFunction( -// 'retrievedListOfMonitoredCards' -// ), -// cronData: createStateUpdaterFunction('cronData'), -// error: createStateUpdaterFunction('error'), -// collectionData: createStateUpdaterFunction('collectionData'), -// allCollectionData: createStateUpdaterFunction('allCollectionData'), -// emittedResponses: createStateUpdaterFunction('emittedResponses'), -// eventsTriggered: createStateUpdaterFunction('eventsTriggered'), +// data: (key, data) => setState((prev) => ({ ...prev, [key]: data })), +// eventsTriggered: (data) => +// setState((prev) => ({ ...prev, eventsTriggered: data })), // }; // const setLoader = (isLoading) => { @@ -343,104 +39,55 @@ export const useCombinedContext = () => { // setState((prev) => ({ ...prev, isLoading })); // }; -// useEffect(() => { -// if (state.eventsTriggered) { -// console.log(`Handling event: ${state.eventsTriggered.eventName}`); -// } -// }, [state?.eventsTriggered]); - -// // ----------- XXX ----------- - -// const listOfMonitoredCards = useMemo( -// () => generateListOfMonitoredCards([allCollections]), -// [allCollections] -// ); - -// const handleReceive = (message) => { -// console.log('Received message:', message); -// setDataFunctions.messageTest(message); -// }; -// const handleEventResponse = (newData) => { -// const { message, data } = newData; -// console.log('EVENT_RESPONSE:', message, data); -// setDataFunctions.eventsTriggered(data); -// }; -// const handleStatusUpdateCron = (newData) => { -// const { message, data } = newData; -// // console.log('[STATUS_UPDATE_CRON]', message, data); -// if (!Array.isArray(data) || !data.data || data.data.length === 0) { -// return null; -// } -// setDataFunctions.data(data); -// }; -// const handleCollectionsUpdated = async (data) => { -// const { message, updatedCards, allCollections } = data; -// console.log('message', message); -// console.log('updatedCards', updatedCards); -// console.log('allCollections', allCollections); -// // console.log('updatedCards', updatedCards); - -// // Update the selected collection with new card prices -// // const updatedSelectedCollectionCards = selectedCollection.cards.map( -// // (card) => { -// // const updatedCardPrice = updatedCards.find( -// // (updatedCard) => updatedCard.id === card.id -// // ); -// // return updatedCardPrice ? { ...card, ...updatedCardPrice } : card; -// // } -// // ); -// if (!allCollections) return; -// if (!Array.isArray(allCollections)) return; -// // const updatedCardsLocalAndRemote = useUnsavedCardsEffect( -// // allCollections, -// // userId -// // ); -// // console.log('UPDATED CARDS', updatedCardsLocalAndRemote); -// for (const collection of allCollections) { -// updateAllCollectionState(collection); +// const handleEvent = (event, data) => { +// console.log(`Handling event: ${event}`); +// switch (event) { +// case 'MESSAGE_TO_CLIENT': +// console.log('Received message:', data); +// setDataFunctions.data('messageTest', data); +// break; +// case 'STATUS_UPDATE_CRON': +// console.log('Received status update from cron:', data); +// if (Array.isArray(data) && data.length > 0) { +// setDataFunctions.data('chartData', data); +// } +// break; +// case 'CARD_PRICES_UNCHANGED': +// console.log('Received card prices unchanged:', data); +// setDataFunctions.data('cardPrices', data.currentPrices); +// break; +// case 'COLLECTIONS_UPDATED': +// console.log('Collections updated:', data); +// // if (Array.isArray(data.allCollections)) { +// // for (const collection of data.allCollections) { +// // updateAllCollectionState(collection); +// // } +// // setDataFunctions.data('allCollectionData', data.allCollections); +// // setDataFunctions.data('allCardPrices', data.updatedCards); +// // } +// break; +// case 'SEND_PRICING_DATA_TO_CLIENT': +// console.log('Received pricing data:', data); +// handleCardPricesUpdated(data.data.data); +// break; +// case 'ERROR': +// console.error('Received error:', data); +// setDataFunctions.data('error', data.error); +// break; +// default: +// break; // } -// setDataFunctions.data(allCollections); -// setDataFunctions.allCardPrices(updatedCards); -// }; -// const handlePricesUnchanged = (data) => { -// const { message, currentPrices } = data; -// console.log('message', message); -// // console.log('currentPrices', currentPrices); -// setDataFunctions.cardPrices(currentPrices); -// }; -// const handleError = (errorData) => { -// console.log('ERROR RECEIVED', errorData); -// const { status, message, error } = errorData; -// console.log('ERROR RECEIVED'); -// console.log('ERROR STATUS: ', status); -// console.log('ERROR MESSAGE: ', message); -// console.log('ERROR DATA: ', error); -// setDataFunctions.error(error); // }; + // const handleCardPricesUpdated = async (priceData) => { // console.log('Card prices retrieved:', priceData); // const updatedCardPrices = priceData.data.data; -// const userId = user?.id; - // const currentListOfMonitoredCards = // generateListOfMonitoredCards(allCollections); -// console.log( -// `[currentListOfMonitoredCards: $${getNewTotalPrice( -// currentListOfMonitoredCards -// )}] | `, -// currentListOfMonitoredCards -// ); // const updatedListOfMonitoredCards = updateCardPricesInList( // currentListOfMonitoredCards, // updatedCardPrices // ); -// console.log( -// `[updatedListOfMonitoredCards: $${getNewTotalPrice( -// updatedListOfMonitoredCards -// )}] | `, -// updatedListOfMonitoredCards -// ); - // const updatedSelectedCollectionCards = selectedCollection.cards.map( // (card) => { // const updatedCardPrice = updatedListOfMonitoredCards.find( @@ -455,208 +102,149 @@ export const useCombinedContext = () => { // cards: updatedSelectedCollectionCards, // }; -// const filteredUpdatedCollection = -// filterNullPriceHistoryForCollection(updatedCollection); - -// try { -// // Update each card in the collection -// for (const card of filteredUpdatedCollection.cards) { -// const updatedCollectionResult = await getUpdatedCollection( -// filteredUpdatedCollection, -// card, // No specific card to update -// 'update', // Operation type -// userId -// ); - -// if (updatedCollectionResult) { -// // console.log( -// // 'UPDATED COLLECTION RESULT IN COMBINED:', -// // updatedCollectionResult.filteredRestructuredCollection -// // ); -// updateCollectionState( -// updatedCollectionResult.filteredRestructuredCollection -// ); -// setDataFunctions.listOfSimulatedCards( -// updatedCollectionResult.filteredRestructuredCollection.cards -// ); -// } -// } -// } catch (error) { -// console.error('Failed to update collection:', error); +// // Update each card in the collection +// for (const card of updatedCollection.cards) { +// const updatedCollectionResult = await updateOneInCollection( +// updatedCollection._id, +// card, +// 'update' +// ); +// setDataFunctions.data('updatedCollectionResult', updatedCollectionResult); +// // if (updatedCollectionResult) { +// // updateAllCollectionState( +// // updatedCollectionResult.filteredRestructuredCollection +// // ); +// // setDataFunctions.data( +// // 'listOfSimulatedCards', +// // updatedCollectionResult.filteredRestructuredCollection.cards +// // ); +// // } // } // // Update the global state with the new card prices -// setDataFunctions.allCardPrices(updatedListOfMonitoredCards); +// setDataFunctions.data('allCardPrices', updatedListOfMonitoredCards); // }; // useEffect(() => { // if (!socket) return; -// const eventHandlers = new Map([ -// ['MESSAGE_TO_CLIENT', handleReceive], -// ['STATUS_UPDATE_CRON', handleStatusUpdateCron], -// ['CARD_PRICES_UNCHANGED', handlePricesUnchanged], -// ['COLLECTIONS_UPDATED', handleCollectionsUpdated], -// ['SEND_PRICING_DATA_TO_CLIENT', handleCardPricesUpdated], -// ['EVENT_RESPONSE', handleEventResponse], -// ['ERROR', handleError], -// ]); -// eventHandlers.forEach((handler, event) => { +// const eventHandlers = { +// MESSAGE_TO_CLIENT: handleEvent, +// STATUS_UPDATE_CRON: handleEvent, +// CARD_PRICES_UNCHANGED: handleEvent, +// COLLECTIONS_UPDATED: handleEvent, +// SEND_PRICING_DATA_TO_CLIENT: handleEvent, +// ERROR: handleEvent, +// }; +// Object.entries(eventHandlers).forEach(([event, handler]) => { // socket.on(event, handler); // }); // return () => { -// eventHandlers.forEach((_, event) => { -// socket.off(event); +// Object.keys(eventHandlers).forEach((event) => { +// socket.off(event, eventHandlers[event]); // }); // }; -// }, [socket, state, setDataFunctions]); - -// // ----------- DATA PROCESSING & HANDLERS ----------- - -// const handleSocketInteraction = { -// requestData: {}, -// sendAction: { -// message: (message) => { -// if (!message) return console.error('Message content is missing.'); -// socket?.emit('MESSAGE_FROM_CLIENT', { message, data: message }); -// }, -// stopCronJob: (userId) => { -// if (!userId) return console.error('Missing userId for cron job stop.'); -// socket?.emit('REQUEST_CRON_STOP', { userId }); -// }, -// checkAndUpdateCardPrices: ( -// userId, -// listOfMonitoredCards -// // retrievedListOfMonitoredCards -// ) => { -// if (!userId) -// return console.error('Missing userId or listOfMonitoredCards.'); -// if (!listOfMonitoredCards || listOfMonitoredCards.length === 0) -// return console.error('Missing retrievedListOfMonitoredCards.'); -// let attempt2; -// if (!Array.isArray(listOfMonitoredCards)) { -// console.warn( -// 'INITIAL LISTOFMONITOREDCARDSVALUE NOT AN ARRAY, ATTEMPTING TO RETREIVE AND TRY AGAIN', -// listOfMonitoredCards -// ); -// attempt2 = generateListOfMonitoredCards(allCollections); -// console.log('ATTEMPT 2', attempt2); - -// if (!attempt2 || attempt2.length === 0) { -// console.error( -// 'ATTEMPT 2 FAILED, listOfMonitoredCards IS NOT AN ARRAY' -// ); -// return; -// } -// } -// console.log( -// 'SENDING CHECK AND UPDATE CARD PRICES', -// listOfMonitoredCards -// ); -// const selectedList = listOfMonitoredCards -// ? listOfMonitoredCards -// : attempt2; -// socket?.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { -// userId, -// data: { -// selectedList, -// }, -// }); -// }, -// checkPriceUpdates: ( -// userId, -// listOfMonitoredCards, -// allCollections, -// cardsWithChangedPrice -// ) => { -// if (!userId) -// return console.log('Missing userId or listOfMonitoredCards.'); -// if (!listOfMonitoredCards) -// return console.log('Missing retrievedListOfMonitoredCards.'); -// if (!allCollections) return console.log('Missing allCollections.'); -// const selectedList = listOfMonitoredCards; -// socket.emit('REQUEST_PRICES_ACTIVATE_CRON', { -// userId, -// data: { -// userId, -// selectedList, -// allCollections, -// cardsWithChangedPrice, -// }, -// }); -// }, -// triggerCronJob: (userId, listOfMonitoredCards) => { -// if (!userId) -// return console.error('Missing userId for cron job trigger.'); - -// // Emit the START_CRON_JOB event with userId and listOfMonitoredCards -// socket.emit('START_CRON_JOB', { userId, listOfMonitoredCards }); -// }, -// }, -// }; -// const confirm = (message) => window.confirm(message); +// }, [socket]); + +// const listOfMonitoredCards = useMemo( +// () => generateListOfMonitoredCards([allCollections]), +// [allCollections] +// ); + +// useEffect(() => { +// if (state.eventsTriggered) { +// console.log(`Handling event: ${state.eventsTriggered.eventName}`); +// } +// }, [state?.eventsTriggered]); + // useEffect(() => { -// // Update the collectionData state when selectedCollection changes -// setDataFunctions.collectionData(selectedCollection); +// if (selectedCollection) { +// setDataFunctions.data('collectionData', selectedCollection); +// } // }, [selectedCollection]); + // useEffect(() => { -// if (allCollections) { -// if ( -// JSON.stringify(allCollections) !== -// JSON.stringify(state.allCollectionData) -// ) { -// setDataFunctions.allCollectionData(allCollections); -// } +// if ( +// allCollections && +// JSON.stringify(allCollections) !== JSON.stringify(state.allCollectionData) +// ) { +// setDataFunctions.data('allCollectionData', allCollections); // } // }, [allCollections]); + // useEffect(() => { // if (user) { -// // console.log('userId', user.userId); -// setDataFunctions.userData(user); +// setDataFunctions.data('userData', user); // } // }, [user]); + // useEffect(() => { // if (listOfMonitoredCards) { -// // console.log('userId', user.userId); -// setDataFunctions.retrievedListOfMonitoredCards(listOfMonitoredCards); +// setDataFunctions.data( +// 'retrievedListOfMonitoredCards', +// listOfMonitoredCards +// ); // } -// }, [user, listOfMonitoredCards]); -// // ----------- CONTEXT VALUE ----------- -// const value = useMemo( +// }, [listOfMonitoredCards]); + +// const handleCronRequest = (userId, selectedList) => { +// socket?.emit('START_CRON_JOB', { userId, selectedList }); +// }; + +// const handleSend = (message) => { +// if (!message) return console.error('Message content is missing.'); +// socket?.emit('MESSAGE_FROM_CLIENT', { message, data: message }); +// }; + +// const handleSendAllCardsInCollections = (userId, selectedList) => { +// if (!userId || !selectedList) return; +// socket?.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { +// userId, +// data: { selectedList }, +// }); +// }; + +// const handleRequestCronStop = (userId) => { +// if (!userId) return console.error('Missing userId for cron job stop.'); +// socket?.emit('REQUEST_CRON_STOP', { userId }); +// }; + +// const handlePricesActivateCron = ( +// userId, +// selectedList, +// cardsWithChangedPrice +// ) => { +// if (!userId || !selectedList || !cardsWithChangedPrice) return; +// socket?.emit('REQUEST_PRICES_ACTIVATE_CRON', { +// userId, +// data: { userId, selectedList, cardsWithChangedPrice }, +// }); +// }; +// const contextValue = useMemo( // () => ({ // ...state, -// ...setDataFunctions, // listOfMonitoredCards, -// confirm, // setLoader, -// handleCronRequest: handleSocketInteraction.sendAction.triggerCronJob, -// handleSend: handleSocketInteraction.sendAction.message, -// handleSendAllCardsInCollections: -// handleSocketInteraction.sendAction.checkAndUpdateCardPrices, -// handleRequestCronStop: handleSocketInteraction.sendAction.stopCronJob, -// handlePricesActivateCron: -// handleSocketInteraction.sendAction.checkPriceUpdates, -// handleSocketInteraction, +// handleCronRequest, +// handleSend, +// handleSendAllCardsInCollections, +// handleRequestCronStop, +// handlePricesActivateCron, // socket, -// isDelaying: state.isDelaying, -// isCronJobTriggered: state.isCronJobTriggered, // }), -// [state, socket] +// [state, listOfMonitoredCards, socket] // ); -// // Log combined context value for debugging -// useEffect(() => { -// console.log('COMBINED CONTEXT:', state); -// }, [ -// userId, -// setDataFunctions.allCollectionData, -// state.updatedCollection, -// // state.allCollectionData, -// // state.collectionData, -// state.cardPrices, -// ]); +// // useEffect(() => { +// // console.log('COMBINED CONTEXT:', state); +// // }, [ +// // userId, +// // state.allCollectionData, +// // state.updatedCollection, +// // state.cardPrices, +// // ]); // return ( -// +// // {children} // // ); @@ -671,3 +259,415 @@ export const useCombinedContext = () => { // } // return context; // }; + +// // import React, { +// // createContext, +// // useCallback, +// // useContext, +// // useEffect, +// // useMemo, +// // useState, +// // } from 'react'; +// // import { useCookies } from 'react-cookie'; +// // import { CollectionContext } from '../MAIN_CONTEXT/CollectionContext/CollectionContext'; +// // import { filterNullPriceHistoryForCollection } from '../MAIN_CONTEXT/CollectionContext/helpers'; +// // import { +// // generateListOfMonitoredCards, +// // updateCardPricesInList, +// // initialState, +// // } from './helpers'; +// // import { useUnsavedCardsEffect } from '../hooks/useUnsavedCardsEffect'; +// // import { useSocketContext } from '../UTILITIES_CONTEXT/SocketContext/SocketProvider'; +// // export const CombinedContext = createContext(); + +// // export const CombinedProvider = ({ children }) => { +// // const [cookies] = useCookies(['authUser']); +// // const [state, setState] = useState(initialState); +// // const user = cookies?.authUser; +// // const userId = user?.userId; +// // const { +// // selectedCollection, +// // updateCollection, +// // allCollections, +// // getNewTotalPrice, +// // updateCollectionState, +// // updateAllCollectionState, +// // getUpdatedCollection, +// // } = useContext(CollectionContext); +// // const socket = useSocketContext(); + +// // const createStateUpdaterFunction = (key) => +// // useCallback( +// // (data) => { +// // setState((prev) => { +// // let newData; + +// // // validateData(data, key, 'createStateUpdaterFunction'); +// // if (Array.isArray(data)) { +// // newData = [...data]; +// // } else if (typeof data === 'object' && data !== null) { +// // newData = { ...data }; +// // } else { +// // newData = data; +// // } +// // return { ...prev, [key]: newData }; +// // }); +// // }, +// // [setState] +// // ); + +// // const setDataFunctions = { +// // data: createStateUpdaterFunction('chartData'), +// // userData: createStateUpdaterFunction('userData'), +// // messageTest: createStateUpdaterFunction('messageTest'), +// // chartData: createStateUpdaterFunction('chartData'), +// // listOfSimulatedCards: createStateUpdaterFunction('listOfSimulatedCards'), +// // cardPrices: createStateUpdaterFunction('cardPrices'), +// // allCardPrices: createStateUpdaterFunction('allCardPrices'), +// // retrievedListOfMonitoredCards: createStateUpdaterFunction( +// // 'retrievedListOfMonitoredCards' +// // ), +// // cronData: createStateUpdaterFunction('cronData'), +// // error: createStateUpdaterFunction('error'), +// // collectionData: createStateUpdaterFunction('collectionData'), +// // allCollectionData: createStateUpdaterFunction('allCollectionData'), +// // emittedResponses: createStateUpdaterFunction('emittedResponses'), +// // eventsTriggered: createStateUpdaterFunction('eventsTriggered'), +// // }; + +// // const setLoader = (isLoading) => { +// // if (typeof isLoading !== 'boolean') { +// // console.error('Invalid argument type for setLoader: Expected boolean'); +// // return; +// // } +// // setState((prev) => ({ ...prev, isLoading })); +// // }; + +// // useEffect(() => { +// // if (state.eventsTriggered) { +// // console.log(`Handling event: ${state.eventsTriggered.eventName}`); +// // } +// // }, [state?.eventsTriggered]); + +// // // ----------- XXX ----------- + +// // const listOfMonitoredCards = useMemo( +// // () => generateListOfMonitoredCards([allCollections]), +// // [allCollections] +// // ); + +// // const handleReceive = (message) => { +// // console.log('Received message:', message); +// // setDataFunctions.messageTest(message); +// // }; +// // const handleEventResponse = (newData) => { +// // const { message, data } = newData; +// // console.log('EVENT_RESPONSE:', message, data); +// // setDataFunctions.eventsTriggered(data); +// // }; +// // const handleStatusUpdateCron = (newData) => { +// // const { message, data } = newData; +// // // console.log('[STATUS_UPDATE_CRON]', message, data); +// // if (!Array.isArray(data) || !data.data || data.data.length === 0) { +// // return null; +// // } +// // setDataFunctions.data(data); +// // }; +// // const handleCollectionsUpdated = async (data) => { +// // const { message, updatedCards, allCollections } = data; +// // console.log('message', message); +// // console.log('updatedCards', updatedCards); +// // console.log('allCollections', allCollections); +// // // console.log('updatedCards', updatedCards); + +// // // Update the selected collection with new card prices +// // // const updatedSelectedCollectionCards = selectedCollection.cards.map( +// // // (card) => { +// // // const updatedCardPrice = updatedCards.find( +// // // (updatedCard) => updatedCard.id === card.id +// // // ); +// // // return updatedCardPrice ? { ...card, ...updatedCardPrice } : card; +// // // } +// // // ); +// // if (!allCollections) return; +// // if (!Array.isArray(allCollections)) return; +// // // const updatedCardsLocalAndRemote = useUnsavedCardsEffect( +// // // allCollections, +// // // userId +// // // ); +// // // console.log('UPDATED CARDS', updatedCardsLocalAndRemote); +// // for (const collection of allCollections) { +// // updateAllCollectionState(collection); +// // } +// // setDataFunctions.data(allCollections); +// // setDataFunctions.allCardPrices(updatedCards); +// // }; +// // const handlePricesUnchanged = (data) => { +// // const { message, currentPrices } = data; +// // console.log('message', message); +// // // console.log('currentPrices', currentPrices); +// // setDataFunctions.cardPrices(currentPrices); +// // }; +// // const handleError = (errorData) => { +// // console.log('ERROR RECEIVED', errorData); +// // const { status, message, error } = errorData; +// // console.log('ERROR RECEIVED'); +// // console.log('ERROR STATUS: ', status); +// // console.log('ERROR MESSAGE: ', message); +// // console.log('ERROR DATA: ', error); +// // setDataFunctions.error(error); +// // }; +// // const handleCardPricesUpdated = async (priceData) => { +// // console.log('Card prices retrieved:', priceData); +// // const updatedCardPrices = priceData.data.data; +// // const userId = user?.id; + +// // const currentListOfMonitoredCards = +// // generateListOfMonitoredCards(allCollections); +// // console.log( +// // `[currentListOfMonitoredCards: $${getNewTotalPrice( +// // currentListOfMonitoredCards +// // )}] | `, +// // currentListOfMonitoredCards +// // ); +// // const updatedListOfMonitoredCards = updateCardPricesInList( +// // currentListOfMonitoredCards, +// // updatedCardPrices +// // ); +// // console.log( +// // `[updatedListOfMonitoredCards: $${getNewTotalPrice( +// // updatedListOfMonitoredCards +// // )}] | `, +// // updatedListOfMonitoredCards +// // ); + +// // const updatedSelectedCollectionCards = selectedCollection.cards.map( +// // (card) => { +// // const updatedCardPrice = updatedListOfMonitoredCards.find( +// // (updatedCard) => updatedCard.id === card.id +// // ); +// // return updatedCardPrice ? { ...card, ...updatedCardPrice } : card; +// // } +// // ); + +// // const updatedCollection = { +// // ...selectedCollection, +// // cards: updatedSelectedCollectionCards, +// // }; + +// // const filteredUpdatedCollection = +// // filterNullPriceHistoryForCollection(updatedCollection); + +// // try { +// // // Update each card in the collection +// // for (const card of filteredUpdatedCollection.cards) { +// // const updatedCollectionResult = await getUpdatedCollection( +// // filteredUpdatedCollection, +// // card, // No specific card to update +// // 'update', // Operation type +// // userId +// // ); + +// // if (updatedCollectionResult) { +// // // console.log( +// // // 'UPDATED COLLECTION RESULT IN COMBINED:', +// // // updatedCollectionResult.filteredRestructuredCollection +// // // ); +// // updateCollectionState( +// // updatedCollectionResult.filteredRestructuredCollection +// // ); +// // setDataFunctions.listOfSimulatedCards( +// // updatedCollectionResult.filteredRestructuredCollection.cards +// // ); +// // } +// // } +// // } catch (error) { +// // console.error('Failed to update collection:', error); +// // } + +// // // Update the global state with the new card prices +// // setDataFunctions.allCardPrices(updatedListOfMonitoredCards); +// // }; + +// // useEffect(() => { +// // if (!socket) return; +// // const eventHandlers = new Map([ +// // ['MESSAGE_TO_CLIENT', handleReceive], +// // ['STATUS_UPDATE_CRON', handleStatusUpdateCron], +// // ['CARD_PRICES_UNCHANGED', handlePricesUnchanged], +// // ['COLLECTIONS_UPDATED', handleCollectionsUpdated], +// // ['SEND_PRICING_DATA_TO_CLIENT', handleCardPricesUpdated], +// // ['EVENT_RESPONSE', handleEventResponse], +// // ['ERROR', handleError], +// // ]); +// // eventHandlers.forEach((handler, event) => { +// // socket.on(event, handler); +// // }); +// // return () => { +// // eventHandlers.forEach((_, event) => { +// // socket.off(event); +// // }); +// // }; +// // }, [socket, state, setDataFunctions]); + +// // // ----------- DATA PROCESSING & HANDLERS ----------- + +// // const handleSocketInteraction = { +// // requestData: {}, +// // sendAction: { +// // message: (message) => { +// // if (!message) return console.error('Message content is missing.'); +// // socket?.emit('MESSAGE_FROM_CLIENT', { message, data: message }); +// // }, +// // stopCronJob: (userId) => { +// // if (!userId) return console.error('Missing userId for cron job stop.'); +// // socket?.emit('REQUEST_CRON_STOP', { userId }); +// // }, +// // checkAndUpdateCardPrices: ( +// // userId, +// // listOfMonitoredCards +// // // retrievedListOfMonitoredCards +// // ) => { +// // if (!userId) +// // return console.error('Missing userId or listOfMonitoredCards.'); +// // if (!listOfMonitoredCards || listOfMonitoredCards.length === 0) +// // return console.error('Missing retrievedListOfMonitoredCards.'); +// // let attempt2; +// // if (!Array.isArray(listOfMonitoredCards)) { +// // console.warn( +// // 'INITIAL LISTOFMONITOREDCARDSVALUE NOT AN ARRAY, ATTEMPTING TO RETREIVE AND TRY AGAIN', +// // listOfMonitoredCards +// // ); +// // attempt2 = generateListOfMonitoredCards(allCollections); +// // console.log('ATTEMPT 2', attempt2); + +// // if (!attempt2 || attempt2.length === 0) { +// // console.error( +// // 'ATTEMPT 2 FAILED, listOfMonitoredCards IS NOT AN ARRAY' +// // ); +// // return; +// // } +// // } +// // console.log( +// // 'SENDING CHECK AND UPDATE CARD PRICES', +// // listOfMonitoredCards +// // ); +// // const selectedList = listOfMonitoredCards +// // ? listOfMonitoredCards +// // : attempt2; +// // socket?.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { +// // userId, +// // data: { +// // selectedList, +// // }, +// // }); +// // }, +// // checkPriceUpdates: ( +// // userId, +// // listOfMonitoredCards, +// // allCollections, +// // cardsWithChangedPrice +// // ) => { +// // if (!userId) +// // return console.log('Missing userId or listOfMonitoredCards.'); +// // if (!listOfMonitoredCards) +// // return console.log('Missing retrievedListOfMonitoredCards.'); +// // if (!allCollections) return console.log('Missing allCollections.'); +// // const selectedList = listOfMonitoredCards; +// // socket.emit('REQUEST_PRICES_ACTIVATE_CRON', { +// // userId, +// // data: { +// // userId, +// // selectedList, +// // allCollections, +// // cardsWithChangedPrice, +// // }, +// // }); +// // }, +// // triggerCronJob: (userId, listOfMonitoredCards) => { +// // if (!userId) +// // return console.error('Missing userId for cron job trigger.'); + +// // // Emit the START_CRON_JOB event with userId and listOfMonitoredCards +// // socket.emit('START_CRON_JOB', { userId, listOfMonitoredCards }); +// // }, +// // }, +// // }; +// // const confirm = (message) => window.confirm(message); +// // useEffect(() => { +// // // Update the collectionData state when selectedCollection changes +// // setDataFunctions.collectionData(selectedCollection); +// // }, [selectedCollection]); +// // useEffect(() => { +// // if (allCollections) { +// // if ( +// // JSON.stringify(allCollections) !== +// // JSON.stringify(state.allCollectionData) +// // ) { +// // setDataFunctions.allCollectionData(allCollections); +// // } +// // } +// // }, [allCollections]); +// // useEffect(() => { +// // if (user) { +// // // console.log('userId', user.userId); +// // setDataFunctions.userData(user); +// // } +// // }, [user]); +// // useEffect(() => { +// // if (listOfMonitoredCards) { +// // // console.log('userId', user.userId); +// // setDataFunctions.retrievedListOfMonitoredCards(listOfMonitoredCards); +// // } +// // }, [user, listOfMonitoredCards]); +// // // ----------- CONTEXT VALUE ----------- +// // const value = useMemo( +// // () => ({ +// // ...state, +// // ...setDataFunctions, +// // listOfMonitoredCards, +// // confirm, +// // setLoader, +// // handleCronRequest: handleSocketInteraction.sendAction.triggerCronJob, +// // handleSend: handleSocketInteraction.sendAction.message, +// // handleSendAllCardsInCollections: +// // handleSocketInteraction.sendAction.checkAndUpdateCardPrices, +// // handleRequestCronStop: handleSocketInteraction.sendAction.stopCronJob, +// // handlePricesActivateCron: +// // handleSocketInteraction.sendAction.checkPriceUpdates, +// // handleSocketInteraction, +// // socket, +// // isDelaying: state.isDelaying, +// // isCronJobTriggered: state.isCronJobTriggered, +// // }), +// // [state, socket] +// // ); + +// // // Log combined context value for debugging +// // useEffect(() => { +// // console.log('COMBINED CONTEXT:', state); +// // }, [ +// // userId, +// // setDataFunctions.allCollectionData, +// // state.updatedCollection, +// // // state.allCollectionData, +// // // state.collectionData, +// // state.cardPrices, +// // ]); + +// // return ( +// // +// // {children} +// // +// // ); +// // }; + +// // export const useCombinedContext = () => { +// // const context = useContext(CombinedContext); +// // if (context === undefined) { +// // throw new Error( +// // 'useCombinedContext must be used within a CombinedProvider' +// // ); +// // } +// // return context; +// // }; diff --git a/src/context/MISC_CONTEXT/CombinedContext/helpers.jsx b/src/context/MISC_CONTEXT/CombinedContext/helpers.jsx index cbc789c..f14b432 100644 --- a/src/context/MISC_CONTEXT/CombinedContext/helpers.jsx +++ b/src/context/MISC_CONTEXT/CombinedContext/helpers.jsx @@ -1,95 +1,95 @@ -export const initialState = { - data: {}, - messageTest: {}, - userData: {}, - collectionData: {}, - allCollectionsUpdated: {}, - allCollectionData: {}, - cronData: {}, - cardPrices: {}, - eventsTriggered: null, - allCardPrices: {}, - retrievedListOfMonitoredCards: {}, - listOfMonitoredCards: {}, - listOfSimulatedCards: {}, - emittedResponses: [], - error: null, - isDelaying: false, // Added isDelaying to initialState as it is referred in your code - isCronJobTriggered: false, // Added isCronJobTriggered to initialState as it is referred in your code -}; +// export const initialState = { +// data: {}, +// messageTest: {}, +// userData: {}, +// collectionData: {}, +// allCollectionsUpdated: {}, +// allCollectionData: {}, +// cronData: {}, +// cardPrices: {}, +// eventsTriggered: null, +// allCardPrices: {}, +// retrievedListOfMonitoredCards: {}, +// listOfMonitoredCards: {}, +// listOfSimulatedCards: {}, +// emittedResponses: [], +// error: null, +// isDelaying: false, // Added isDelaying to initialState as it is referred in your code +// isCronJobTriggered: false, // Added isCronJobTriggered to initialState as it is referred in your code +// }; -export const generateListOfMonitoredCards = (allCollections) => { - if (!allCollections) return []; +// export const generateListOfMonitoredCards = (allCollections) => { +// if (!allCollections) return []; - const cardsWithCollectionId = allCollections?.flatMap((collection) => - collection?.cards?.map((card) => ({ - ...card, - collectionId: collection._id, - })) - ); +// const cardsWithCollectionId = allCollections?.flatMap((collection) => +// collection?.cards?.map((card) => ({ +// ...card, +// collectionId: collection._id, +// })) +// ); - const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card?.id)); +// const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card?.id)); - return Array.from(uniqueCardIds).map((id) => { - const originalCard = cardsWithCollectionId.find((card) => card?.id === id); - return { - ...originalCard, - }; - }); -}; -export const updateCardPricesInList = (listOfMonitoredCards, cardPrices) => { - return listOfMonitoredCards.map((originalCard) => { - const updatedCardInfo = - cardPrices?.find((price) => price.id === originalCard.id) || {}; - if (updatedCardInfo.latestPrice?.num !== originalCard.latestPrice?.num) { - return { - ...originalCard, - ...updatedCardInfo, - quantity: originalCard?.quantity, - price: updatedCardInfo?.latestPrice?.num || originalCard?.price, - lastSavedPrice: { - num: updatedCardInfo?.lastSavedPrice?.num || originalCard?.price, - timestamp: - updatedCardInfo?.latestPrice?.timestamp || new Date().toISOString(), - }, - priceHistory: [ - ...originalCard.priceHistory, - updatedCardInfo?.latestPrice, - ], - }; - } +// return Array.from(uniqueCardIds).map((id) => { +// const originalCard = cardsWithCollectionId.find((card) => card?.id === id); +// return { +// ...originalCard, +// }; +// }); +// }; +// export const updateCardPricesInList = (listOfMonitoredCards, cardPrices) => { +// return listOfMonitoredCards.map((originalCard) => { +// const updatedCardInfo = +// cardPrices?.find((price) => price.id === originalCard.id) || {}; +// if (updatedCardInfo.latestPrice?.num !== originalCard.latestPrice?.num) { +// return { +// ...originalCard, +// ...updatedCardInfo, +// quantity: originalCard?.quantity, +// price: updatedCardInfo?.latestPrice?.num || originalCard?.price, +// lastSavedPrice: { +// num: updatedCardInfo?.lastSavedPrice?.num || originalCard?.price, +// timestamp: +// updatedCardInfo?.latestPrice?.timestamp || new Date().toISOString(), +// }, +// priceHistory: [ +// ...originalCard.priceHistory, +// updatedCardInfo?.latestPrice, +// ], +// }; +// } - return originalCard; - }); -}; -// Regular function to process unsaved cards -export const processUnsavedCards = ( - allCollections, - userId, - externalOperationHandler, - externalCollectionUpdate -) => { - if (!allCollections || !userId) return; - console.log('allCollections:', allCollections); - const unsavedCollections = allCollections - ?.map((collection) => { - const unsavedCardsInCollection = collection?.cards?.filter( - (card) => card.tag === 'unsaved' - ); - return { - collectionId: collection._id, - unsavedCards: unsavedCardsInCollection, - }; - }) - .filter((collection) => collection.unsavedCards.length > 0); +// return originalCard; +// }); +// }; +// // Regular function to process unsaved cards +// export const processUnsavedCards = ( +// allCollections, +// userId, +// externalOperationHandler, +// externalCollectionUpdate +// ) => { +// if (!allCollections || !userId) return; +// console.log('allCollections:', allCollections); +// const unsavedCollections = allCollections +// ?.map((collection) => { +// const unsavedCardsInCollection = collection?.cards?.filter( +// (card) => card.tag === 'unsaved' +// ); +// return { +// collectionId: collection._id, +// unsavedCards: unsavedCardsInCollection, +// }; +// }) +// .filter((collection) => collection.unsavedCards.length > 0); - const unsavedCardsPromises = unsavedCollections?.flatMap((collection) => - collection?.unsavedCards?.map((card) => - externalOperationHandler(card, 'update', collection, userId).then(() => - externalCollectionUpdate(collection, null, 'update', userId) - ) - ) - ); +// const unsavedCardsPromises = unsavedCollections?.flatMap((collection) => +// collection?.unsavedCards?.map((card) => +// externalOperationHandler(card, 'update', collection, userId).then(() => +// externalCollectionUpdate(collection, null, 'update', userId) +// ) +// ) +// ); - return Promise.all(unsavedCardsPromises); -}; +// return Promise.all(unsavedCardsPromises); +// }; diff --git a/src/context/SECONDARY_CONTEXT/CronJobContext/CronJobContext.jsx b/src/context/SECONDARY_CONTEXT/CronJobContext/CronJobContext.jsx index 262fad1..972d94f 100644 --- a/src/context/SECONDARY_CONTEXT/CronJobContext/CronJobContext.jsx +++ b/src/context/SECONDARY_CONTEXT/CronJobContext/CronJobContext.jsx @@ -10,39 +10,39 @@ export const useCronJobContext = () => useContext(CronJobContext); export const CronJobProvider = ({ children }) => { const { user, userId } = useUserContext(); const { selectedCollection } = useCollectionStore(); // Assuming this is where you get your selectedCollection - const { handleSendAllCardsInCollections, listOfMonitoredCards } = - useCombinedContext(); + // const { handleSendAllCardsInCollections, listOfMonitoredCards } = + // useCombinedContext(); const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState( new Date().getTime() ); - useEffect(() => { - const handleTriggerCronJob = () => { - const currentTime = new Date().getTime(); - const timeDifference = currentTime - lastCronJobTriggerTime; + // useEffect(() => { + // const handleTriggerCronJob = () => { + // const currentTime = new Date().getTime(); + // const timeDifference = currentTime - lastCronJobTriggerTime; - if ( - timeDifference >= 120000 && - listOfMonitoredCards.length > 5 && - selectedCollection?.chartData?.allXYValues?.length > 10 - ) { - setLastCronJobTriggerTime(currentTime); - if (userId) { - console.log('Triggering cron job actions'); - handleSendAllCardsInCollections(userId, listOfMonitoredCards); - } - } - }; + // if ( + // timeDifference >= 120000 && + // listOfMonitoredCards.length > 5 && + // selectedCollection?.chartData?.allXYValues?.length > 10 + // ) { + // setLastCronJobTriggerTime(currentTime); + // if (userId) { + // console.log('Triggering cron job actions'); + // handleSendAllCardsInCollections(userId, listOfMonitoredCards); + // } + // } + // }; - const interval = setInterval(handleTriggerCronJob, 120000); // Trigger every 2 minutes (120000 ms) - return () => clearInterval(interval); - }, [ - lastCronJobTriggerTime, - user, - listOfMonitoredCards, - selectedCollection, - handleSendAllCardsInCollections, - ]); + // const interval = setInterval(handleTriggerCronJob, 120000); // Trigger every 2 minutes (120000 ms) + // return () => clearInterval(interval); + // }, [ + // lastCronJobTriggerTime, + // user, + // listOfMonitoredCards, + // selectedCollection, + // handleSendAllCardsInCollections, + // ]); return ( { - const { - allCollections, - allXYValues, - selectedCollection, - hasFetchedCollections, - } = useCollectionStore(); + const { allXYValues, hasFetchedCollections } = useCollectionStore(); + const { selectedCollection, allCollections } = useSelectedCollection(); const { timeRange } = useChartContext(); if (!Array.isArray(allCollections)) { @@ -39,8 +36,8 @@ export const StatisticsProvider = ({ children }) => { const statsByCollectionId = useMemo( () => validCollections - ? allCollections.reduce((acc, collection) => { - acc[collection._id] = calculateStatsForCollection( + ? allCollections?.reduce((acc, collection) => { + acc[collection?._id] = calculateStatsForCollection( collection, timeRange ); @@ -55,7 +52,7 @@ export const StatisticsProvider = ({ children }) => { return 0; // Ensure a default numeric value } return allCollections?.reduce((acc, collection) => { - const collectionPrice = parseFloat(collection.totalPrice); + const collectionPrice = parseFloat(collection?.totalPrice); return acc + (isNaN(collectionPrice) ? 0 : collectionPrice); }, 0); }, [allCollections]); @@ -64,9 +61,9 @@ export const StatisticsProvider = ({ children }) => { () => validCollections ? allCollections - .flatMap((collection) => collection.cards || []) + .flatMap((collection) => collection?.cards || []) .sort((a, b) => b.price - a.price) - .slice(0, 7) + .slice(0, 30) : [], [allCollections] ); @@ -119,17 +116,17 @@ export const StatisticsProvider = ({ children }) => { return createMarkers(selectedCollection); }, [allCollections]); // Add dependencies as necessary, e.g., someSelectedCollectionId - const chartData = useMemo( - () => - validCollections - ? allCollections.map((collection) => ({ - id: collection.id, - value: collection.totalPrice, - label: collection.name, - })) - : [], - [allCollections] - ); + // const chartData = useMemo( + // () => + // validCollections + // ? allCollections.map((collection) => ({ + // id: collection._id, + // value: collection.totalPrice, + // label: collection.name, + // })) + // : [], + // [allCollections] + // ); const contextValue = useMemo( () => ({ @@ -146,7 +143,7 @@ export const StatisticsProvider = ({ children }) => { // SECONDARY DATA totalValue, topFiveCards, - chartData, + // chartData, // SECONDARY FUNCTIONS calculateTotalPriceOfAllCollections, // calculateStatsByCollectionId, @@ -160,7 +157,7 @@ export const StatisticsProvider = ({ children }) => { markers, totalValue, topFiveCards, - chartData, + // chartData, setSelectedStat, timeRange, ] diff --git a/src/context/UTILITIES_CONTEXT/FormContext/FormContext.jsx b/src/context/UTILITIES_CONTEXT/FormContext/FormContext.jsx index 56cf6c5..3a697cc 100644 --- a/src/context/UTILITIES_CONTEXT/FormContext/FormContext.jsx +++ b/src/context/UTILITIES_CONTEXT/FormContext/FormContext.jsx @@ -19,43 +19,38 @@ import { import { defaultContextValue } from '../../constants'; import { ZodError } from 'zod'; import { useAuthContext } from '../../MAIN_CONTEXT/AuthContext/authContext'; -import { usePageContext } from '../PageContext/PageContext'; import { useCardStoreHook } from '../../hooks/useCardStore'; import useCollectionManager from '../../MAIN_CONTEXT/CollectionContext/useCollectionManager'; import { useDeckStore } from '../../MAIN_CONTEXT/DeckContext/DeckContext'; import { useChartContext } from '../../MAIN_CONTEXT/ChartContext/ChartContext'; -function objectToBoolean(obj) { - return Object.keys(obj).reduce((acc, key) => { - acc[key] = false; - return acc; - }, {}); -} +import { useLoading } from '../../hooks/useLoading'; +const initializeFormState = (schema) => + Object.keys(schema).reduce((acc, key) => ({ ...acc, [key]: false }), {}); + const FormContext = createContext(defaultContextValue.FORM_CONTEXT); export const FormProvider = ({ children }) => { const { signup, login, isLoggedIn, userId } = useAuthContext(); - const { setIsFormDataLoading } = usePageContext(); const { handleRequest, setSearchSettings, searchSettings } = useCardStoreHook(); const { createNewCollection, updateAndSyncCollection, selectedCollection } = - useCollectionManager(isLoggedIn, userId); + useCollectionManager(); const { updateDeckDetails, deleteUserDeck, createUserDeck } = useDeckStore(); - const { timeRange, setTimeRange, timeRanges } = useChartContext(); + const { setTimeRange } = useChartContext(); - const [forms, setForms] = useState({}); + // ** * * * * * * * * * * * * * * * * * * * |/\ /\ /\| ** * * * * * * * * * * * * * * * * * * * + // ** || | | | || ** + // ** CONTEXT ACTIONS + // ** ----------------- ** + // ** DYNAMIC FORM SETUP ** + // ** || | | | || ** + // ** * * * * * * * * * * * * * * * * * * * |\/ \/ \/| ** * * * * * * * * * * * * * * * * * * * - const [currentForm, setCurrentForm] = useState({}); - const methods = useForm({ - mode: 'onTouched', - resolver: zodResolver(formSchemas[Object.keys(formSchemas)[0]]), - defaultValues: defaultValues[Object.keys(formSchemas)[0]], - }); - const initialValues = getDefaultValuesFromSchema( - formSchemas[Object.keys(formSchemas)[0]] + const [forms, setForms] = useState({}); + const { isFormDataLoading, startLoading, stopLoading } = useLoading(); + const [currentSchemaKey, setCurrentSchemaKey] = useState( + Object.keys(formSchemas)[0] ); - const values = useRef(initialValues); - const touched = useRef(objectToBoolean(initialValues)); - const dirty = useRef(objectToBoolean(initialValues)); const initializeFormStates = () => { return Object.keys(formSchemas).reduce((acc, key) => { acc[key] = defaultValues[key]; // Assuming defaultValues is a correctly mapped object @@ -63,6 +58,34 @@ export const FormProvider = ({ children }) => { }, {}); }; const [formStates, setFormStates] = useState(initializeFormStates()); + const methods = useForm({ + resolver: zodResolver(formSchemas[currentSchemaKey]), + defaultValues: getDefaultValuesFromSchema(formSchemas[currentSchemaKey]), + }); + // Use useCallback to ensure these functions are memoized + const setFormSchema = useCallback( + (schemaKey) => { + setCurrentSchemaKey(schemaKey); + const schema = formSchemas[schemaKey]; + const defaultValues = getDefaultValuesFromSchema(schema); + methods.reset(defaultValues); + }, + [methods] + ); + const initialValues = getDefaultValuesFromSchema( + formSchemas[Object.keys(formSchemas)[0]] + ); + const values = useRef(initialValues); + const touched = useRef(initializeFormState(formSchemas)); + const dirty = useRef(initializeFormState(formSchemas)); + const handleTimeRangeChange = useCallback( + (timeRangeValue) => { + console.log('TIME RANGE CHANGE', timeRangeValue); + // Example: setTimeRange could update the context state and trigger re-renders or API calls as needed + setTimeRange(timeRangeValue); + }, + [setTimeRange] + ); const handleFieldChange = (formId, field, value) => { setFormStates((prev) => ({ ...prev, @@ -72,84 +95,99 @@ export const FormProvider = ({ children }) => { }, })); }; - const onSubmit = async (formData, formId, additionalData) => { - setIsFormDataLoading(true); - const validation = handleZodValidation(formData, formSchemas[formId]); - console.log('isValid:', validation.isValid); + const toggleAuthMode = () => { + setCurrentSchemaKey((prevForm) => + prevForm === 'loginForm' ? 'signupForm' : 'loginForm' + ); + }; - if (validation.isValid) { + const formHandlers = { + signupForm: (formData) => + signup( + formData.firstName, + formData.lastName, + formData.username, + formData.password, + formData.email + ), + loginForm: (formData) => login(formData.username, formData.password), + updateUserDataForm: (formData) => console.log(formData), + updateCollectionForm: (formData, additionalData) => + updateAndSyncCollection(additionalData, formData), + createCollectionForm: (formData, additionalData) => + createNewCollection(additionalData, formData), + updateDeckForm: (formData, additionalData) => updateDeckDetails(formData), + addDeckForm: (formData, additionalData) => console.log(formData), + searchForm: (formData, additionalData) => setSearchSettings(formData), + timeRangeSelector: (formData, additionalData) => + handleTimeRangeChange(formData), + searchSettingsSelector: (formData, additionalData) => + setSearchSettings(formData), + rememberMeForm: (formData) => { + // Implement remember me form submission logic here + console.log('Remember Me Form Data:', formData); + }, + authSwitch: (formData) => { + console.log('Auth Switch Form Data:', formData); + + toggleAuthMode(); + }, + }; + const onSubmit = useCallback( + async (formData) => { + startLoading(currentSchemaKey); + + const formHandler = formHandlers[currentSchemaKey]; try { - if (formId === 'signupForm') { - console.log('Submitting signup form:', formData); - await signup( - formData.firstName, - formData.lastName, - formData.username, - formData.password, - formData.email - ); - } else if (formId === 'loginForm') { - console.log('Submitting login form:', formData); - await login(formData.username, formData.password); - } else if (formId === 'updateUserDataForm') { - console.log('Submitting update user data form:', formData); - // await updateUserData(formData); - } else if (formId === 'updateCollectionForm') { - console.log('Updating collection:', formData); - console.log('Selected collection:', additionalData); - - const updatedData = { - name: formData.name, - description: formData.description, - }; - await updateAndSyncCollection(additionalData, updatedData); - } else if (formId === 'addCollectionForm') { - console.log('Adding collection:', formData); - const newData = { - name: formData.name, - description: formData.description, - }; - await createNewCollection(newData); - } else if (formId === 'updateDeckForm') { - console.log('Updating deck:', formData); - await updateDeckDetails(formData); - } else if (formId === 'addDeckForm') { - console.log('Adding deck:', formData); - await createUserDeck(formData.name, formData.description); - } else if (formId === 'searchForm') { - console.log('Submitting search form:', formData); - await handleRequest(formData.searchTerm); // Use handleRequest for the search form - } else if (formId === 'TimeRangeSchema') { - console.log('Submitting TimeRange form:', formData); - setTimeRange(formData.timeRange); - // handleTimeRangeChange(formData); - } else if (formId === 'searchSettingsForm') { - console.log('Submitting SearchSettings form:', formData); - setSearchSettings(formData); - } else if (formId === 'defaultForm') { - console.log('Submitting default form:', formData); - } - console.log(`${formId} form submitted successfully`, formData); - // Reset form logic here if needed + console.log(`Submitting form: ${currentSchemaKey}`, formData); + await formHandler(formData); } catch (error) { - console.error('Error submitting form:', error); + console.error(`Error submitting ${currentSchemaKey}:`, error); } finally { - setIsFormDataLoading(false); + stopLoading(currentSchemaKey); } - } else { - console.error('Form validation failed:', validation.errors); - // Optionally, display validation.errors using your UI logic - setIsFormDataLoading(false); - } - }; - const onChange = (formData, formId) => { - console.log('Form data changed:', formData, formId); - const validation = handleZodValidation(formData, formSchemas[formId]); + }, + [ + currentSchemaKey, + handleTimeRangeChange, + setSearchSettings, + startLoading, + stopLoading, + formHandlers, + ] + ); + useEffect(() => { + setFormSchema(currentSchemaKey); + }, [currentSchemaKey, setFormSchema]); + // const setFormSchema = useCallback( + // (formId) => { + // const schema = formSchemas[formId]; + // const defaultValues = getDefaultValuesFromSchema(schema); + // methods.reset({ ...defaultValues }); // Reset form with new default values + // methods.clearErrors(); // Clear any existing errors + // methods.setValue('formId', formId); // Optionally set formId as a form value + // }, + // [methods] + // ); + // useEffect(() => { + // setFormSchema(initialFormKey); // Initialize form with the first schema + // }, [setFormSchema, initialFormKey]); + + const onChange = (formData, currentSchemaKey) => { + console.log('Form data changed:', formData, currentSchemaKey); + const validation = handleZodValidation( + formData, + formSchemas[currentSchemaKey] + ); console.log('isValid:', validation.isValid); - if (validation.isValid && formId === 'searchForm') { + if (validation.isValid && currentSchemaKey === 'searchForm') { console.log('Form data is valid:', validation.data); handleRequest(validation.data.searchTerm); // Use handleRequest for the search form } + if (formData === false) { + console.log('Signup Mode:', validation.data.signupMode); + toggleAuthMode(); + } }; const handleSetAllForms = useCallback(() => { const allFormConfigs = Object.keys(formSchemas).map((formId) => ({ @@ -167,74 +205,100 @@ export const FormProvider = ({ children }) => { [handleRequest] ); const handleChange = useCallback((e) => { + // Check if the event target has the expected structure + if (!e.target || typeof e.target !== 'object') { + console.error('handleChange called without a valid event target'); + return; + } + const { name, value, type, checked } = e.target; + + // Ensure that the event target has a 'name' property + if (typeof name === 'undefined') { + console.error( + 'handleChange called on an element without a "name" attribute' + ); + return; + } console.log('e.target:', e.target); const fieldValue = type === 'checkbox' ? checked : value; values.current[name] = fieldValue; console.log('Form field changed:', name, fieldValue); - formStates.current[currentForm] = { - ...formStates.current[currentForm], - [name]: fieldValue, - }; + console.log('Search form field changed:', name, fieldValue); + if (name === 'searchTerm' && typeof fieldValue === 'string') { + console.log('Form data is valid:', fieldValue); + handleRequest(fieldValue); // Use handleRequest for the search form + } + + handleFieldChange(e.target.formId, name, fieldValue); }, []); const handleFocus = useCallback((e) => { const { name } = e.target; touched.current[name] = true; - console.log('Form field focused:', name); - // More logic here if needed }, []); const handleBlur = useCallback((e) => { const { name } = e.target; dirty.current[name] = true; - console.log('Form field blurred:', name); - // Validation logic here }, []); - useEffect(() => { console.log('SETTING ALL FORMS:', formSchemas); handleSetAllForms(formSchemas); }, [handleSetAllForms]); + useEffect(() => { + methods.reset(getDefaultValuesFromSchema(formSchemas[currentSchemaKey])); + }, [currentSchemaKey, methods]); + const contextValue = useMemo( () => ({ ...methods, - forms, - formMethods: methods, - formStates, - currentForm, - values, + formStates: methods.control._formValues, // Directly use form state from useForm touched, dirty, initialValues, + isFormDataLoading: isFormDataLoading, + currentSchemaKey, + currentForm: currentSchemaKey, + handleTimeRangeChange, handleSearchTermChange, handleFieldChange, handleChange, handleFocus, handleBlur, - - setCurrentForm, - onSubmit, + setFormSchema, + setCurrentForm: setFormSchema, + onSubmit: methods.handleSubmit(onSubmit), onChange, - + // onChange: methods.handleChange(onChange), toggleForm: (formId) => { - setCurrentForm(formId); + console.log('TOGGLE FORM:', formId); + setFormSchema(formId); }, }), [ methods, - forms, formStates, - currentForm, + values, + touched, + dirty, + initialValues, + formSchemas, + isFormDataLoading, + + handleTimeRangeChange, + handleSearchTermChange, + handleFieldChange, handleChange, handleFocus, handleBlur, - setCurrentForm, + setFormSchema, onSubmit, onChange, + handleSetAllForms, ] ); @@ -244,3 +308,134 @@ export const FormProvider = ({ children }) => { }; export const useFormContext = () => useContext(FormContext); +// switch (formId) { +// case 'signupForm': +// await signup( +// formData.firstName, +// formData.lastName, +// formData.username, +// formData.password, +// formData.email +// ); +// break; +// case 'loginForm': +// await login(formData.username, formData.password); +// break; +// case 'updateUserDataForm': +// // await updateUserData(formData); +// break; +// case 'updateCollectionForm': +// await updateAndSyncCollection(additionalData, formData); +// break; +// case 'addCollectionForm': +// await createNewCollection(formData); +// break; +// case 'updateDeckForm': +// await updateDeckDetails(formData); +// break; +// case 'addDeckForm': +// await createUserDeck(formData.name, formData.description); +// break; +// case 'searchForm': +// await handleRequest(formData.searchTerm); +// break; +// case 'timeRangeSelector': +// console.log('Time range selected:', formData.timeRange); +// handleTimeRangeChange(formData.timeRange); // Directly use formData.timeRange +// break; +// case 'searchSettingsSelector': +// setSearchSettings(formData); +// break; +// default: +// console.error('Unhandled form submission:', formId); +// } +// Function to dynamically set the current form and its schema +// const setCurrentForm = useCallback((formId) => { +// setCurrentFormId(formId); +// }, []); +// const useFormMethods = useCallback((formId) => { +// const schema = formSchemas[formId] || formSchemas.default; +// return useForm({ +// resolver: zodResolver(schema), +// defaultValues: schema.safeParse({}), // Assuming you have a parse method to get default values from Zod schema +// }); +// }, []); +// Initialize useForm with the first form schema as default +// const initialFormKey = Object.keys(formSchemas)[0]; +// const methods = useForm({ +// resolver: zodResolver(formSchemas[initialFormKey]), +// defaultValues: getDefaultValuesFromSchema(formSchemas[initialFormKey]), +// }); +// const [forms, setForms] = useState({}); + +// const [currentForm, setCurrentForm] = useState({}); +// const onSubmit = useCallback(async (formData, formId, additionalData) => { + +// setIsFormDataLoading(true); +// // const validation = handleZodValidation(formData, formSchemas[formId]); +// // console.log('isValid:', validation.isValid); +// try { + +// try { +// if (formId === 'signupForm') { +// console.log('Submitting signup form:', formData); +// await signup( +// formData.firstName, +// formData.lastName, +// formData.username, +// formData.password, +// formData.email +// ); +// } else if (formId === 'loginForm') { +// console.log('Submitting login form:', formData); +// await login(formData.username, formData.password); +// } else if (formId === 'updateUserDataForm') { +// console.log('Submitting update user data form:', formData); +// // await updateUserData(formData); +// } else if (formId === 'updateCollectionForm') { +// console.log('Updating collection:', formData); +// console.log('Selected collection:', additionalData); + +// const updatedData = { +// name: formData.name, +// description: formData.description, +// }; +// await updateAndSyncCollection(additionalData, updatedData); +// } else if (formId === 'addCollectionForm') { +// console.log('Adding collection:', formData); +// const newData = { +// name: formData.name, +// description: formData.description, +// }; +// await createNewCollection(newData); +// } else if (formId === 'updateDeckForm') { +// console.log('Updating deck:', formData); +// await updateDeckDetails(formData); +// } else if (formId === 'addDeckForm') { +// console.log('Adding deck:', formData); +// await createUserDeck(formData.name, formData.description); +// } else if (formId === 'searchForm') { +// console.log('Submitting search form:', formData); +// await handleRequest(formData.searchTerm); // Use handleRequest for the search form +// } else if (formId === 'TimeRangeSchema') { +// console.log('Submitting TimeRange form:', formData); +// setTimeRange(formData.timeRange); +// // handleTimeRangeChange(formData); +// } else if (formId === 'searchSettingsForm') { +// console.log('Submitting SearchSettings form:', formData); +// setSearchSettings(formData); +// } else if (formId === 'defaultForm') { +// console.log('Submitting default form:', formData); +// } +// console.log(`${formId} form submitted successfully`, formData); +// // Reset form logic here if needed +// } catch (error) { +// console.error('Error submitting form:', error); +// } finally { +// setIsFormDataLoading(false); +// } +// } else { +// console.error('Form validation failed:', validation.errors); +// // Optionally, display validation.errors using your UI logic +// setIsFormDataLoading(false); +// } diff --git a/src/context/UTILITIES_CONTEXT/FormContext/schemas.jsx b/src/context/UTILITIES_CONTEXT/FormContext/schemas.jsx index 46a66a3..523500e 100644 --- a/src/context/UTILITIES_CONTEXT/FormContext/schemas.jsx +++ b/src/context/UTILITIES_CONTEXT/FormContext/schemas.jsx @@ -1,5 +1,6 @@ // schemas.js import { ZodError, z } from 'zod'; +import { defaultChartConstants } from '../../constants'; const GenericStringConstraint = z.string().min(8).max(18); const EmailConstraint = z @@ -38,6 +39,12 @@ const loginForm = z.object({ username: UsernameConstraint, password: PasswordConstraint, }); +const rememberMeFormSchema = z.object({ + rememberMe: z.boolean().default(false), +}); +const authSwitchSchema = z.object({ + signupMode: z.boolean().default(false), +}); // Update User Data Form Schema const updateUserDataForm = z.object({ firstName: nameConstraint, @@ -98,17 +105,43 @@ const customerInfoForm = z.object({ phone: z.string().min(1, { message: 'Phone is required' }), email: z.string().email({ message: 'Invalid email address' }), }); -const TimeRangeSchema = z.object({ - timeRange: z.object({ - id: z.string( - 'Invalid time range ID. Please select a valid time range from the list.' - ), - value: z.string( - 'Invalid time range value. Please select a valid time range from the list.' - ), - name: z.string( - 'Invalid time range name. Please select a valid time range from the list.' - ), +// const TimeRangeSchema = z.object({ +// timeRange: z.object({ +// id: z.string( +// 'Invalid time range ID. Please select a valid time range from the list.' +// ), +// value: z.string( +// 'Invalid time range value. Please select a valid time range from the list.' +// ), +// name: z.string( +// 'Invalid time range name. Please select a valid time range from the list.' +// ), +// }), +// }); + +// const timeRangeSchema = z.object({ +// timeRange: z.enum(['24hr', '7d', '30d', '90d', '180d', '270d', '365d']), +// }); +const timeRangeOptionSchema = z.object({ + id: z.string( + 'Invalid time range ID. Please select a valid time range from the list.' + ), + value: z.string(), + name: z.string(), + data: z.array( + z.object({ + x: z.number(), + y: z.number(), + }) + ), +}); + +// const timeRangeSchema = z.object({ +// timeRange: timeRangeOptionSchema, +// }); +const timeRangeSelectorSchema = z.object({ + timeRange: z.string().nonempty({ + message: 'You must select a time range.', }), }); // Define a schema for a single filter's values @@ -138,15 +171,11 @@ const filterValueSchema = z.enum([ 'Water', 'Wind', ]); - -// Define a schema for a single filter object const filterSchema = z.object({ label: z.string(), name: z.string(), values: z.array(filterValueSchema), }); - -// Define a schema for the initialState object const initialStateSchema = z.object({ name: z.string(), race: z.string().optional(), @@ -154,13 +183,10 @@ const initialStateSchema = z.object({ attribute: z.string().optional(), level: z.string().optional(), }); - -// Define the schema for the entire data structure const dataSchema = z.object({ initialState: initialStateSchema, filters: z.array(filterSchema), }); - // Usage example: const data = { initialState: { @@ -226,6 +252,9 @@ const data = { export const formSchemas = { signupForm, loginForm, + rememberMeForm: rememberMeFormSchema, + authSwitch: authSwitchSchema, + updateUserDataForm, addCollectionForm, updateCollectionForm, @@ -233,7 +262,7 @@ export const formSchemas = { updateDeckForm, searchForm, customerInfoForm, - TimeRangeSchema, + timeRangeSelector: timeRangeSelectorSchema, searchSettingsSelector: dataSchema, }; export const handleZodValidation = (formData, schema) => { @@ -336,13 +365,9 @@ export const defaultValues = { email: '', }, // SELECTOR FORMS - TimeRangeSchema: { - // timeRange: '24hr', - timeRange: { - id: '', - value: '', - name: '', - }, + timeRangeSelector: { + initialState: defaultChartConstants.TIME_RANGES[0], + filters: defaultChartConstants.TIME_RANGES, }, searchSettingsSelector: { initialData: { diff --git a/src/context/UTILITIES_CONTEXT/PageContext/PageContext.jsx b/src/context/UTILITIES_CONTEXT/PageContext/PageContext.jsx index cbd23f8..ea2af24 100644 --- a/src/context/UTILITIES_CONTEXT/PageContext/PageContext.jsx +++ b/src/context/UTILITIES_CONTEXT/PageContext/PageContext.jsx @@ -11,81 +11,131 @@ import SplashPage2 from '../../../pages/SplashPage2'; import useSnackBar from '../../hooks/useSnackBar'; import { defaultContextValue } from '../../constants'; import { DynamicSnackbar } from '../../../layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; +import { useLoading } from '../../hooks/useLoading'; const PageContext = createContext(defaultContextValue.PAGE_CONTEXT); export const PageProvider = ({ children }) => { + const { + isLoading, + isAnyLoading, + startLoading, + stopLoading, + setError, + error, + clearLoading, + } = useLoading(); const { snackbar, handleSnackBar, handleCloseSnackbar } = useSnackBar(); - // const [activelyLoading, setActivelyLoading] = useState(false); // [false, setActivelyLoading - const [loadingStatus, setLoadingStatus] = useState({ - isLoading: false, - isDataLoading: false, - isFormDataLoading: false, - isPageLoading: false, - loadingTimeoutExpired: false, - error: null, - loadingType: '', - }); - useEffect(() => { - const isCompleted = Object.values(loadingStatus).every((status) => !status); - if (isCompleted) { - handleSnackBar( - `Loading ${loadingStatus.loadingType} completed`, - 'success' - ); + + const handleLoadingCompletion = () => { + if (!isAnyLoading()) { + handleSnackBar('Loading completed', 'success'); } - }, [loadingStatus, handleSnackBar]); - const setLoading = (type, status) => { - setLoadingStatus((prevStatus) => ({ - ...prevStatus, - [type]: status, - loadingType: status ? type : '', - })); }; const returnDisplay = () => { - if (loadingStatus.error) { - return ; + if (error) { + return ; } - switch (loadingStatus.loadingType) { - case 'isLoading': - case 'isDataLoading': - case 'isFormDataLoading': - return ; - case 'isPageLoading': - return ; - default: - return null; + if (isLoading('isPageLoading')) { + return ; + } else if (isAnyLoading()) { + return ; } + return null; }; - const contextValue = useMemo( () => ({ - loadingStatus, - error: loadingStatus.error, - setActivelyLoading: (status) => setLoading('isLoading', status), + startLoading, + stopLoading, + setError, + error, + isLoading, + clearLoading, returnDisplay, - setError: (error) => setLoadingStatus((prev) => ({ ...prev, error })), - setPageError: (error) => - setLoadingStatus((prev) => ({ ...prev, error, isPageLoading: false })), - setIsLoading: (status) => setLoading('isLoading', status), - setIsDataLoading: (status) => setLoading('isDataLoading', status), - setIsFormDataLoading: (status) => setLoading('isFormDataLoading', status), - setIsPageLoading: (status) => setLoading('isPageLoading', status), - setLoadingTimeoutExpired: (status) => - setLoadingStatus((prev) => ({ - ...prev, - loadingTimeoutExpired: status, - })), - handleLoadingTimeout: () => - setLoadingStatus((prev) => ({ - ...prev, - loadingTimeoutExpired: true, - isPageLoading: false, - })), + // You can expose any additional functionalities from useLoading as needed. }), - [loadingStatus] + [ + startLoading, + stopLoading, + setError, + error, + isLoading, + clearLoading, + returnDisplay, + ] ); + // const { snackbar, handleSnackBar, handleCloseSnackbar } = useSnackBar(); + // // const [activelyLoading, setActivelyLoading] = useState(false); // [false, setActivelyLoading + // const [loadingStatus, setLoadingStatus] = useState({ + // isLoading: false, + // isDataLoading: false, + // isFormDataLoading: false, + // isPageLoading: false, + // loadingTimeoutExpired: false, + // error: null, + // loadingType: '', + // }); + // useEffect(() => { + // const isCompleted = Object.values(loadingStatus).every((status) => !status); + // if (isCompleted) { + // handleSnackBar( + // `Loading ${loadingStatus.loadingType} completed`, + // 'success' + // ); + // } + // }, [loadingStatus, handleSnackBar]); + // const setLoading = (type, status) => { + // setLoadingStatus((prevStatus) => ({ + // ...prevStatus, + // [type]: status, + // loadingType: status ? type : '', + // })); + // }; + // const returnDisplay = () => { + // if (loadingStatus.error) { + // return ; + // } + // switch (loadingStatus.loadingType) { + // case 'isLoading': + // case 'isDataLoading': + // case 'isFormDataLoading': + // return ; + // case 'isPageLoading': + // return ; + // default: + // return null; + // } + // }; + + // const contextValue = useMemo( + // () => ({ + // loadingStatus, + // error: loadingStatus.error, + // setActivelyLoading: (status) => setLoading('isLoading', status), + // returnDisplay, + // setError: (error) => setLoadingStatus((prev) => ({ ...prev, error })), + // setPageError: (error) => + // setLoadingStatus((prev) => ({ ...prev, error, isPageLoading: false })), + // setIsLoading: (status) => setLoading('isLoading', status), + // setIsDataLoading: (status) => setLoading('isDataLoading', status), + // setIsFormDataLoading: (status) => setLoading('isFormDataLoading', status), + // setIsPageLoading: (status) => setLoading('isPageLoading', status), + // setLoadingTimeoutExpired: (status) => + // setLoadingStatus((prev) => ({ + // ...prev, + // loadingTimeoutExpired: status, + // })), + // handleLoadingTimeout: () => + // setLoadingStatus((prev) => ({ + // ...prev, + // loadingTimeoutExpired: true, + // isPageLoading: false, + // })), + // }), + // [loadingStatus] + // ); + return ( {children}{' '} diff --git a/src/context/UTILITIES_CONTEXT/VisibilityContext.jsx b/src/context/UTILITIES_CONTEXT/VisibilityContext.jsx new file mode 100644 index 0000000..083acc1 --- /dev/null +++ b/src/context/UTILITIES_CONTEXT/VisibilityContext.jsx @@ -0,0 +1,38 @@ +// VisibilityContext.js +import React, { createContext, useContext, useState } from 'react'; + +const VisibilityContext = createContext(); + +export const useVisibilityContext = () => useContext(VisibilityContext); + +export const VisibilityProvider = ({ children }) => { + const [isCollectionVisible, setCollectionVisibility] = useState(true); + const [dialogStates, setDialogStates] = useState({ + isAddCollectionDialogOpen: false, + isSelectionErrorDialogOpen: false, + }); + + const toggleCollectionVisibility = () => { + setCollectionVisibility(!isCollectionVisible); + }; + + const toggleDialog = (dialogName) => { + setDialogStates((prevState) => ({ + ...prevState, + [dialogName]: !prevState[dialogName], + })); + }; + + return ( + + {children} + + ); +}; diff --git a/src/context/constants.jsx b/src/context/constants.jsx index 292e226..d2cbba3 100644 --- a/src/context/constants.jsx +++ b/src/context/constants.jsx @@ -1,5 +1,10 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import { createNewPriceEntry } from './Helpers'; +import jsonData from './MAIN_CONTEXT/CollectionContext/nivoTestData.json'; +const { nivoTestData } = jsonData; +import user from './user'; + +// import { lightTheme, indigoTheme } from '../assets/themes/colors'; // ! DEFAULT VALUES FOR CARD OBJECTS // Default values for common API data fields const defaultCommonAPIFields = { @@ -109,36 +114,178 @@ const defaultCollectionStatistics = { }, }, }; + +// const defaultPriceEntry = { +// num: 0, +// timestamp: new Date(), +// }; const defaultChartData = { - name: '', - userId: null, - allXYValues: [], + id: '24hr', + color: 'hsl(252, 70%, 50%)', + data: [{ x: new Date(), y: 0 }], }; -const defaultPriceEntry = { - num: 0, +// const defaultPriceHistory = [defaultPriceEntry]; +// ! DEFAULT VALUES FOR COLLECTION OBJECTS +// const defaultCollection = { +// userId: null, +// name: '', +// description: '', +// totalPrice: 0, +// quantity: 0, +// totalQuantity: 0, +// dailyPriceChange: 0, +// dailyPercentageChange: '0%', +// latestPrice: defaultPriceEntry, +// lastSavedPrice: defaultPriceEntry, +// dailyCollectionPriceHistory: defaultPriceHistory, +// collectionPriceHistory: defaultPriceHistory, +// chartData: { +// name: '', +// allXYValues: [{ x: new Date(), y: 0 }], +// }, +// nivoChartData: defaultChartData, +// newNivoChartData: defaultChartData, +// collectionStatistics: defaultCollectionStatistics, +// cards: [defaultCardObject], +// }; +// Simulating Mongoose-like schemas on the client-side +const defaultCardImage = { + image_url: 'https://example.com/default-image.png', + image_url_small: 'https://example.com/default-image-small.png', +}; + +const defaultPriceEntry = (price = 0) => ({ + num: price, timestamp: new Date(), +}); + +const defaultCardPrice = { + cardmarket_price: 0, + tcgplayer_price: 0, + ebay_price: 0, + amazon_price: 0, }; -const defaultPriceHistory = [defaultPriceEntry]; -// ! DEFAULT VALUES FOR COLLECTION OBJECTS + +// Function to create a default card object +const createDefaultCard = () => ({ + name: '', + id: '', + type: '', + frameType: '', + desc: '', + atk: 0, + def: 0, + level: 0, + race: '', + attribute: '', + archetype: [], + card_sets: [], + card_images: [defaultCardImage], + card_prices: [defaultCardPrice], + tag: '', + watchList: false, + price: 0, + quantity: 1, + image: defaultCardImage.image_url, + totalPrice: 0, + latestPrice: defaultPriceEntry(), + lastSavedPrice: defaultPriceEntry(), + priceHistory: [defaultPriceEntry()], + dailyPriceHistory: [defaultPriceEntry()], + chart_datasets: [defaultChartData], + nivoChartData: defaultChartData, + cardVariants: [], + variant: null, // Assuming this would be set to the ID of a selected variant + rarity: '', + contextualQuantity: { + SearchHistory: 0, + Deck: 0, + Collection: 0, + Cart: 0, + }, + contextualTotalPrice: { + SearchHistory: 0, + Deck: 0, + Collection: 0, + Cart: 0, + }, + refs: { + decks: [], + collections: [], + carts: [], + // Add more contexts as needed + }, +}); + +// Example usage +const defaultCard = createDefaultCard(); +console.log(defaultCard); + const defaultCollection = { - userId: null, name: '', description: '', - totalPrice: 0, - quantity: 0, - totalQuantity: 0, dailyPriceChange: 0, - dailyPercentageChange: '0%', - latestPrice: defaultPriceEntry, - lastSavedPrice: defaultPriceEntry, - dailyCollectionPriceHistory: defaultPriceHistory, - collectionPriceHistory: defaultPriceHistory, - chartData: defaultChartData, + dailyPercentageChange: '', + newTotalPrice: 0, + collectionStatistics: { + highPoint: 0, + lowPoint: 0, + avgPrice: 0, + percentageChange: 0, + priceChange: 0, + twentyFourHourAverage: { + startDate: new Date(), + endDate: new Date(), + lowPoint: 0, + highPoint: 0, + priceChange: 0, + percentageChange: 0, + priceIncreased: false, + }, + average: 0, + volume: 0, + volatility: 0, + general: { + totalPrice: 0, + topCard: '', + topCollection: '', + }, + }, + latestPrice: { + price: 0, + date: new Date(), + }, + lastSavedPrice: { + price: 0, + date: new Date(), + }, + dailyCollectionPriceHistory: [], + collectionPriceHistory: [ + { + timestamp: new Date(), + priceChanges: [ + { + collectionName: '', + cardName: '', + oldPrice: 0, + newPrice: 0, + priceDifference: 0, + message: '', + }, + ], + priceDifference: 0, + }, + ], + priceChangeHistory: [], + chartData: { + name: '', + userId: '', + allXYValues: [], + }, nivoChartData: [ { id: '', color: '', - // TODO: set x as defauly value for dates data: [{ x: new Date(), y: 0 }], }, ], @@ -149,9 +296,26 @@ const defaultCollection = { data: [{ x: new Date(), y: 0 }], }, ], - collectionStatistics: defaultCollectionStatistics, - cards: [defaultCardObject], + nivoTestData: [nivoTestData], + averagedChartData: new Map(), + muiChartData: [], + cards: [], + addDefaultCard: function () { + const newCard = createDefaultCard(); + this.cards.push(newCard); + this.updateTotalPrice(); + }, + updateTotalPrice: function () { + this.newTotalPrice = this.cards.reduce( + (acc, card) => acc + card.totalPrice, + 0 + ); + }, + addMultipleDefaultCards: function (numberOfCards) { + Array.from({ length: numberOfCards }).forEach(() => this.addDefaultCard()); + }, }; + // ! DEFAULT VALUES FOR DECK OBJECTS const defaultDeck = { userId: null, @@ -160,9 +324,9 @@ const defaultDeck = { totalPrice: 0, quantity: 0, totalQuantity: 0, - tags: [], + tags: [''], color: '', - cards: [], + cards: [defaultCard], }; // ! DEFAULT VALUES FOR CART OBJECTS const defaultCart = { @@ -192,11 +356,45 @@ export const DEFAULT_DECK = defaultDeck; export const DEFAULT_ALLDECKS_ARRAY = [defaultDeck]; export const DEFAULT_CART = defaultCart; export const DEFAULT_USER = defaultUser; +export const SELECTED_COLLECTION_ID = 'selectedCollectionId'; +export const DEFAULT_CARDS_COUNT = 5; // FUNCTIONS export const createNewCardObject = () => { return { ...DEFAULT_CARD_OBJECT }; }; +// ! DEFAULT VALUES FOR CUSTOM DYNAMIC FIELD OBJECTS +export const defaultCustomField = { + name: '', + value: '', +}; +export const defaultCustomFields = [defaultCustomField]; + +const themeRanges = [ + { + id: 'light', + name: 'Light', + value: 'light', + data: {}, + }, + { + id: 'dark', + name: 'Dark', + value: 'dark', + data: {}, + }, + { + id: 'indigo', + name: 'Indigo', + value: 'indigo', + data: {}, + }, +]; +// ! DEFAULT SYSTEM VALUES +export const defaultSystemConstants = { + THEME_RANGES: themeRanges, +}; + // ! DEFAULT VALUES FOR CONTEXT OBJECTS ---------------------- // ! DEFAULT VALUES FOR PAGE CONTEXT const defaultPageContextValues = { @@ -270,9 +468,12 @@ const defaultCardContextValues = { // ! DEFAULT VALUES FOR COLLECTION CONTEXT const defaultCollectionContextValues = { // MAIN STATE - allCollections: [DEFAULT_COLLECTION], - selectedCollection: DEFAULT_COLLECTION, - selectedCards: DEFAULT_CARDS_ARRAY, + collectionData: { + data: [DEFAULT_COLLECTION.addDefaultCard()], + }, + allCollections: [DEFAULT_COLLECTION.addDefaultCard()], + selectedCollection: DEFAULT_COLLECTION.addDefaultCard(), + selectedCards: DEFAULT_COLLECTION.cards, // SECONDARY STATE (derived from main state selectedCollection) collectionStatistics: DEFAULT_COLLECTION_STATISTICS, @@ -286,8 +487,9 @@ const defaultCollectionContextValues = { cards: DEFAULT_COLLECTION.cards || [], // STATE SETTERS + setCollectionData: () => {}, setAllCollections: () => {}, - setSelectedCollection: () => {}, + handleSelectCollection: () => {}, setSelectedCards: () => {}, // COLLECTION ACTIONS @@ -356,22 +558,25 @@ const defaultCartContextValues = { }; // ! DEFAULT VALUES FOR USER CONTEXT -const defaultUserProps = { - userId: null, - firstName: '', - lastName: '', +const defaultUserProps = + process.env.AUTH_ENVIRONMENT === 'disabled' + ? user + : { + userId: null, + firstName: '', + lastName: '', - username: '', - password: '', - email: '', + username: '', + password: '', + email: '', - collections: [], - totalNumberOfCollections: 0, - decks: [], - totalNumberOfDecks: 0, - cart: [], - totalNumberOfCardsInCart: 0, -}; + collections: [], + totalNumberOfCollections: 0, + decks: [], + totalNumberOfDecks: 0, + cart: [], + totalNumberOfCardsInCart: 0, + }; const defaultUserContextValues = { ...defaultUserProps, @@ -426,12 +631,25 @@ const defaultStatisticsContextValues = { getTopCollection: () => {}, calculateStatsByCollectionId: () => {}, }; +const defaultXYValues = [{ x: new Date(), y: 0 }]; +const defaultDataPoint = { + label: '', + x: new Date(), + y: 0, +}; // default values for LINEAR CHART from nivo +const defaultTimeRanges = [ + { + id: '', + color: '', + data: defaultDataPoint, + }, +]; const defaultNivoChartData = [ { id: '', color: '', - data: [{ x: new Date(), y: 0 }], + data: defaultDataPoint, }, ]; // default values for PIE CHART from MUI @@ -466,15 +684,45 @@ const timeRangeProps = { '65cecbc3bc231bd7d96598ef', ], }; -const timeRanges = [ - { id: '24hr', name: 'Last 24 Hours', value: '24hr' }, - { id: '7d', name: 'Last 7 Days', value: '7d' }, - { id: '30d', name: 'Last Month', value: '30d' }, - { id: '90d', name: 'Last 3 Months', value: '90d' }, - { id: '180d', name: 'Last 6 Months', value: '180d' }, - { id: '270d', name: 'Last 9 Months', value: '270d' }, - { id: '365d', name: 'Last Year', value: '365d' }, -]; +// const timeRanges = [ +// { +// id: '24hr', +// name: 'Last 24 Hours', +// value: '24hr', +// data: defaultNivoChartData, +// }, +// { id: '7d', name: 'Last 7 Days', value: '7d', data: [defaultNivoChartData] }, +// { +// id: '30d', +// name: 'Last 30 Days', +// value: '30d', +// data: defaultNivoChartData, +// }, +// { +// id: '90d', +// name: 'Last 90 Days', +// value: '90d', +// data: defaultNivoChartData, +// }, +// { +// id: '180d', +// name: 'Last 180 Days', +// value: '180d', +// data: defaultNivoChartData, +// }, +// { +// id: '270d', +// name: 'Last 270 Days', +// value: '270d', +// data: defaultNivoChartData, +// }, +// { +// id: '365d', +// name: 'Last 365 Days', +// value: '365d', +// data: defaultNivoChartData, +// }, +// ]; const allChartData = (timeRangeProps) => { for (const id of timeRangeProps.ids) { timeRangeProps.data.push({ x: new Date(), y: 0 }); @@ -493,9 +741,19 @@ const createNewNivoChartData = () => { export const defaultChartConstants = { HEIGHT_TO_WIDTH_RATIO: 7 / 10, DEFAULT_THRESHOLD: 600000, - TIME_RANGES: timeRanges, + TIME_RANGES: [ + { id: '24hr', name: 'Last 24 Hours', value: '24hr', data: [] }, + { id: '7d', name: 'Last 7 Days', value: '7d', data: [] }, + { id: '30d', name: 'Last 30 Days', value: '30d', data: [] }, + { id: '90d', name: 'Last 90 Days', value: '90d', data: [] }, + { id: '180d', name: 'Last 180 Days', value: '180d', data: [] }, + { id: '270d', name: 'Last 270 Days', value: '270d', data: [] }, + { id: '365d', name: 'Last 365 Days', value: '365d', data: [] }, + ], + NIVO_CHART_DATA: createNewNivoChartData(), + // SELECTED_TIME_RANGE: timeRanges[0], // TIME_RANGE: timeRanges[0], - TIME_RANGES_KEYS: timeRanges.map((range) => range.id), + TIME_RANGES_KEYS: ['24hr', '7d', '30d', '90d', '180d', '270d', '365d'], TIME_RANGE_PROPS: timeRangeProps, // TICK_VALUES: ({ ticks } = getFormatAndTicks(timeRangeProps[0])), // X_FORMAT: ({ format } = getFormatAndTicks(timeRangeProps[0])), @@ -548,7 +806,7 @@ const defaultChartContextValues = { // ...defaultChartConstants, // Data latestData: null, - timeRange: defaultChartConstants.TIME_RANGE, + selectedRange: defaultChartConstants.SELECTED_TIME_RANGE, timeRanges: defaultChartConstants.TIME_RANGES, finalizedNivoData: null, currentValue: null, diff --git a/src/context/hooks/useCardActions.jsx b/src/context/hooks/useCardActions.jsx index 3d8a87c..333029f 100644 --- a/src/context/hooks/useCardActions.jsx +++ b/src/context/hooks/useCardActions.jsx @@ -28,35 +28,48 @@ export const useCardActions = ( max: page === 'DeckBuilder' ? 3 : undefined, // Limit to 3 for Deck context }); + /** @internal Place an object in an array */ + const placeObjectInArray = useCallback((object) => { + const objectInArray = [object]; + return objectInArray; + }, []); + const performAction = useCallback( (action) => { - if (context === 'Collection' && !selectedCollection) { - // Show alert and open dialog if no collection is selected - handleSnackBar('Please select a collection.', 'error'); // Use handleSnackBar to show a snackbar - openDialog('Collection'); // Open the 'Collection' dialog - return; - } - if (context === 'Deck' && !selectedDeck) { - // Show alert and open dialog if no deck is selected - handleSnackBar('Please select a deck.', 'error'); // Use handleSnackBar to show a snackbar - openDialog('Deck'); // Open the 'Deck' dialog - return; - } + // if (context === 'Collection' && !selectedCollection) { + // // Show alert and open dialog if no collection is selected + // handleSnackBar('Please select a collection.', 'error'); // Use handleSnackBar to show a snackbar + // openDialog('Collection'); // Open the 'Collection' dialog + // return; + // } + // if (context === 'Deck' && !selectedDeck) { + // // Show alert and open dialog if no deck is selected + // handleSnackBar('Please select a deck.', 'error'); // Use handleSnackBar to show a snackbar + // openDialog('Deck'); // Open the 'Deck' dialog + // return; + // } // Increment or decrement based on the action const updateQuantity = (actionType) => { actionType === 'add' ? increment(card.id) : decrement(card.id); }; + const array = placeObjectInArray(data); + console.log('DATA =>=>=> ', data); // Action functions for different contexts const actionFunctions = { Collection: { add: () => { + console.log('array === ', array); updateQuantity('add'); - addOneToCollection(data, selectedCollection); + addOneToCollection(array, selectedCollection); }, remove: () => { updateQuantity('remove'); - removeOneFromCollection(data, data?.id, selectedCollection); + removeOneFromCollection( + placeObjectInArray(data), + placeObjectInArray(data?.id), + selectedCollection + ); }, }, Deck: { @@ -82,32 +95,36 @@ export const useCardActions = ( }; try { - actionFunctions[context][action]?.(); + actionFunctions[context][action](); onSuccess?.(); } catch (error) { onFailure?.(error); } }, [ - context, - card, - selectedCollection, - selectedDeck, + addOneToCart, addOneToCollection, - removeOneFromCollection, addOneToDeck, - removeOneFromDeck, - addOneToCart, + context, + data, + decrement, + increment, + onFailure, + openDialog, + page, + placeObjectInArray, removeOneFromCart, + removeOneFromCollection, + removeOneFromDeck, + selectedCollection, + selectedDeck, onSuccess, - onFailure, - increment, - decrement, + handleSnackBar, ] ); return { - count: data.quantity, + count: data?.quantity, performAction, closeDialog, }; diff --git a/src/context/hooks/useCardStore.jsx b/src/context/hooks/useCardStore.jsx index 9a7769b..1a7b209 100644 --- a/src/context/hooks/useCardStore.jsx +++ b/src/context/hooks/useCardStore.jsx @@ -5,6 +5,7 @@ import useLogger from './useLogger'; import useApiResponseHandler from './useApiResponseHandler'; import useLocalStorage from './useLocalStorage'; // Ensure this is the correct path to your hook import { defaultContextValue } from '../constants'; +import { useLoading } from './useLoading'; const { CARD_CONTEXT } = defaultContextValue; function debounce(func, wait) { @@ -34,7 +35,16 @@ export const useCardStoreHook = () => { const [searchData, setSearchData] = useLocalStorage('searchData', []); const [isDataValid, setIsDataValid] = useState(searchData.length > 0); const [initialLoad, setInitialLoad] = useState(true); // New state to track the initial load - const [loadingSearchResults, setLoadingSearchResults] = useState(false); + const { + isLoading, + isAnyLoading, + startLoading, + stopLoading, + setError, + error, + clearLoading, + } = useLoading(); + // const [loadingSearchResults, setLoadingSearchResults] = useState(false); const [cardsVersion, setCardsVersion] = useState(0); // New state for tracking data version const [searchSettings, setSearchSettings] = useLocalStorage( 'searchSettings', @@ -71,7 +81,7 @@ export const useCardStoreHook = () => { const handleRequest = useCallback( debounce(async (searchParams) => { - setLoadingSearchResults(true); + startLoading('isSearchLoading'); try { logger.logEvent('handleRequest start', { searchParams, userId }); const requestBody = { @@ -102,7 +112,7 @@ export const useCardStoreHook = () => { clearSearchData(); resetForm(); } finally { - setLoadingSearchResults(false); // Set loading to false once the request is complete + stopLoading('isSearchLoading'); // Set loading to false once the request is complete } }, 100), [] @@ -128,7 +138,9 @@ export const useCardStoreHook = () => { setIsDataValid, clearSearchData, handleRequest, - loadingSearchResults, - setLoadingSearchResults, + loadingSearchResults: isLoading('isSearchLoading'), + setLoadingSearchResults: () => { + startLoading('isSearchLoading'); + }, }; }; diff --git a/src/context/hooks/useCollectionVisibility.jsx b/src/context/hooks/useCollectionVisibility.jsx index 536ae5d..5ac09c7 100644 --- a/src/context/hooks/useCollectionVisibility.jsx +++ b/src/context/hooks/useCollectionVisibility.jsx @@ -1,37 +1,35 @@ -import { useState, useCallback } from 'react'; -import { useCollectionStore } from '../MAIN_CONTEXT/CollectionContext/CollectionContext'; +// import { useState, useCallback } from 'react'; +// import { useCollectionStore } from '../MAIN_CONTEXT/CollectionContext/CollectionContext'; +// import useCollectionManager from '../MAIN_CONTEXT/CollectionContext/useCollectionManager'; -const useCollectionVisibility = () => { - const { setSelectedCollection, allCollections } = useCollectionStore(); - const [showCollections, setShowCollections] = useState(true); - const [selectedCollectionId, setSelectedCollectionId] = useState(null); +// const useCollectionVisibility = () => { +// const { setSelectedCollection, allCollections } = useCollectionManager(); +// const [showCollections, setShowCollections] = useState(true); +// const [selectedCollectionId, setSelectedCollectionId] = useState(null); - const handleSelectCollection = useCallback( - (collectionId) => { - const selected = allCollections.find( - (collection) => collection._id === collectionId - ); - if (selected) { - setSelectedCollection(selected); // Keep this to maintain compatibility with other parts of your code - setSelectedCollectionId(collectionId); // Track the ID of the selected collection - setShowCollections(false); - } - }, - [allCollections, setSelectedCollection] - ); +// // This function is triggered when a collection is selected. +// const handleSelectCollection = useCallback((collection) => { +// console.log('SELECTED COLLECTION ID', collection._id); +// setSelectedCollectionId(collection._id); +// setSelectedCollection(collection); +// setShowCollections(false); // Hide collection list to show the selected collection's details. +// }, []); - const handleBackToCollections = useCallback(() => { - setShowCollections(true); - setSelectedCollectionId(null); // Clear the selected collection ID - setSelectedCollection(null); // Optionally clear the selected collection - }, [setSelectedCollection]); +// const handleBackToCollections = useCallback(() => { +// console.log('BACK TO COLLECTIONS', allCollections, selectedCollectionId); +// setShowCollections(true); // Show the collection list again. - return { - showCollections, - handleSelectCollection, - handleBackToCollections, - selectedCollectionId, // Expose the selectedCollectionId for use in other components - }; -}; +// setSelectedCollectionId(null); +// setSelectedCollection(null); +// }, []); -export default useCollectionVisibility; +// return { +// showCollections, +// handleSelectCollection, +// handleBackToCollections, +// setShowCollections, +// selectedCollectionId, // Expose the selectedCollectionId for use in other components +// }; +// }; + +// export default useCollectionVisibility; diff --git a/src/context/hooks/useCounter.jsx b/src/context/hooks/useCounter.jsx index 143cf7f..9f95c48 100644 --- a/src/context/hooks/useCounter.jsx +++ b/src/context/hooks/useCounter.jsx @@ -31,8 +31,8 @@ const useCounter = (inputData, options = {}) => { }; const totalCount = isArray - ? data.reduce((acc, item) => acc + item.quantity, 0) - : data.quantity; + ? data.reduce((acc, item) => acc + item?.quantity, 0) + : data?.quantity; const increment = (itemId) => modifyCount(itemId, 1); const decrement = (itemId) => modifyCount(itemId, -1); @@ -65,7 +65,7 @@ const useCounter = (inputData, options = {}) => { increment, decrement, setQuantity, - quantity: data.quantity, + quantity: data?.quantity, }; }; diff --git a/src/context/hooks/useDialogState.jsx b/src/context/hooks/useDialogState.jsx new file mode 100644 index 0000000..1b644cf --- /dev/null +++ b/src/context/hooks/useDialogState.jsx @@ -0,0 +1,31 @@ +// hooks/useDialogState.js +import { useState, useCallback } from 'react'; + +const useDialogState = (initialState = {}) => { + const [state, setState] = useState(initialState); + + const toggleDialog = useCallback((dialogName) => { + setState((prevState) => ({ + ...prevState, + [dialogName]: !prevState[dialogName], + })); + }, []); + + const openDialog = useCallback((dialogName) => { + setState((prevState) => ({ + ...prevState, + [dialogName]: true, + })); + }, []); + + const closeDialog = useCallback((dialogName) => { + setState((prevState) => ({ + ...prevState, + [dialogName]: false, + })); + }, []); + + return { dialogState: state, toggleDialog, openDialog, closeDialog }; +}; + +export default useDialogState; diff --git a/src/context/hooks/useFetchWrapper.jsx b/src/context/hooks/useFetchWrapper.jsx index cb3e42b..d2b24a0 100644 --- a/src/context/hooks/useFetchWrapper.jsx +++ b/src/context/hooks/useFetchWrapper.jsx @@ -1,7 +1,76 @@ +// import { useState, useCallback } from 'react'; +// import useLogger from './useLogger'; +// import useLocalStorage from './useLocalStorage'; +// import { useLoading } from './useLoading'; + +// const useFetchWrapper = () => { +// const [status, setStatus] = useState('idle'); // 'idle', 'loading', 'success', 'error' +// const [data, setData] = useState(null); +// const [responseCache, setResponseCache] = useLocalStorage('apiResponses', {}); +// const [error, setError] = useState(null); +// const { logEvent } = useLogger('useFetchWrapper'); +// const { startLoading, stopLoading, isLoading } = useLoading(); + +// const fetchWrapper = useCallback( +// async (url, method = 'GET', body = null, loadingID) => { +// setStatus('loading'); +// startLoading(loadingID); +// try { +// const headers = { 'Content-Type': 'application/json' }; +// const options = { +// method, +// headers, +// ...(body && { body: JSON.stringify(body) }), +// }; +// const response = await fetch(url, options); +// const contentType = response.headers.get('content-type'); +// let responseData; + +// if (contentType && !contentType.includes('application/json')) { +// // Handle non-JSON responses first +// responseData = await response.text(); +// } else { +// // Fallback to JSON if the content type is application/json +// responseData = await response.json(); +// } +// if (!response.ok) { +// throw new Error(`An error occurred: ${response.statusText}`); +// } +// setStatus('success'); +// setData(responseData); +// setResponseCache((prevCache) => ({ +// ...prevCache, +// [loadingID]: responseData, // Use loadingID as the key +// })); + +// return responseData; +// } catch (error) { +// setError(error.toString()); +// setStatus('error'); +// logEvent('fetch error', { url, error: error.toString() }); +// } finally { +// stopLoading(loadingID); +// } +// }, +// [setResponseCache, startLoading, stopLoading, logEvent] +// ); +// return { +// status, +// data, +// responseCache, +// error, +// fetchWrapper, +// isLoading, +// }; +// }; + +// export default useFetchWrapper; import { useState, useCallback } from 'react'; import useLogger from './useLogger'; import useLocalStorage from './useLocalStorage'; import { useLoading } from './useLoading'; +import { useSnackbar } from 'notistack'; +import CircularProgress from '@mui/material/CircularProgress'; const useFetchWrapper = () => { const [status, setStatus] = useState('idle'); // 'idle', 'loading', 'success', 'error' @@ -10,25 +79,36 @@ const useFetchWrapper = () => { const [error, setError] = useState(null); const { logEvent } = useLogger('useFetchWrapper'); const { startLoading, stopLoading, isLoading } = useLoading(); + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); const fetchWrapper = useCallback( async (url, method = 'GET', body = null, loadingID) => { - setData(null); - setError(null); setStatus('loading'); startLoading(loadingID); - const headers = { 'Content-Type': 'application/json' }; - const options = { - method, - headers, - ...(body && { body: JSON.stringify(body) }), - }; + // Show loading snackbar + const snackbarKey = enqueueSnackbar('Loading...', { + variant: 'info', + persist: true, // Keep the snackbar open + action: (key) => , + }); try { + const headers = { 'Content-Type': 'application/json' }; + const options = { + method, + headers, + ...(body && { body: JSON.stringify(body) }), + }; const response = await fetch(url, options); - const responseData = await response.json(); // Assuming the server always returns JSON + const contentType = response.headers.get('content-type'); + let responseData; + if (contentType && !contentType.includes('application/json')) { + responseData = await response.text(); + } else { + responseData = await response.json(); + } if (!response.ok) { throw new Error(`An error occurred: ${response.statusText}`); } @@ -36,37 +116,37 @@ const useFetchWrapper = () => { setData(responseData); setResponseCache((prevCache) => ({ ...prevCache, - [loadingID]: responseData, // Use loadingID as the key + [loadingID]: responseData, })); - logEvent('fetch completed', { - url, - status: response.status, - data: responseData, - }); - } catch (err) { + + return responseData; + } catch (error) { + setError(error.toString()); setStatus('error'); - setError(err.message || 'An error occurred. Awkward..'); - logEvent('fetch error', { url, error: err.message }); + logEvent('fetch error', { url, error: error.toString() }); } finally { stopLoading(loadingID); + closeSnackbar(snackbarKey); // Close the loading snackbar } }, - [setResponseCache, logEvent, startLoading, stopLoading] - ); - const getLoadingState = useCallback( - (loadingID = 'globalLoading') => isLoading(loadingID), - [isLoading] + [ + setResponseCache, + startLoading, + stopLoading, + logEvent, + enqueueSnackbar, + closeSnackbar, + ] ); - const responseData = { - status, // Include the fetch status + + return { + status, data, responseCache, error, fetchWrapper, - getLoadingState, + isLoading, }; - - return responseData; }; export default useFetchWrapper; diff --git a/src/context/hooks/useGet.jsx b/src/context/hooks/useGet.jsx new file mode 100644 index 0000000..fadecfa --- /dev/null +++ b/src/context/hooks/useGet.jsx @@ -0,0 +1,61 @@ +import { useState, useCallback } from 'react'; +import useFetchWrapper from './useFetchWrapper'; +import useLogger from './useLogger'; +import { useLoading } from './useLoading'; + +const useGet = ({ + userId, + isLoggedIn, + hasFetchedFlag, + path, + setLoadingFlag, + updateStates, + setError, + fetchWrapper, + createApiUrl, + logger, +}) => { + const [isLoading, setIsLoading] = useState(false); + + const fetchData = useCallback(async () => { + if (!userId || isLoading || hasFetchedFlag) return; + + const loadingID = `fetch_${path}`; + setIsLoading(true); + try { + const responseData = await fetchWrapper( + createApiUrl(path), + 'GET', + null, + loadingID + ); + + if (responseData && [200, 201].includes(responseData?.status)) { + logger.logEvent(`SUCCESS: fetching ${path}`); + updateStates(responseData); + setLoadingFlag(true); // Update the hasFetched flag for this data type + } + } catch (error) { + console.error(`ERROR: fetching ${path}`, error); + setError(error.message || `Failed to fetch ${path}`); + logger.logEvent(`ERROR: fetching ${path}`, error.message); + } finally { + setIsLoading(false); + } + }, [ + userId, + isLoading, + hasFetchedFlag, + path, + setLoadingFlag, + updateStates, + setError, + fetchWrapper, + createApiUrl, + logger, + ]); + + return { fetchData, isLoading }; +}; + +export default useGet; diff --git a/src/context/hooks/useIsFirstRender.jsx b/src/context/hooks/useIsFirstRender.jsx new file mode 100644 index 0000000..cd9151e --- /dev/null +++ b/src/context/hooks/useIsFirstRender.jsx @@ -0,0 +1,21 @@ +// useIsFirstRender.js +import { useRef, useEffect } from 'react'; + +/** + * Custom hook to determine if the current render is the first one. + * + * @returns {boolean} True for the first render, false otherwise. + */ +export function useIsFirstRender() { + // Use useRef to hold a mutable object that persists for the lifetime of the component + const isFirstRender = useRef(true); + + // useEffect with an empty dependency array to run only once after the initial render + useEffect(() => { + // After the first render, set isFirstRender to false + isFirstRender.current = false; + }, []); // Empty dependency array means this effect runs once after the initial render + + // Return the current value (true on first render, false afterwards) + return isFirstRender.current; +} diff --git a/src/context/hooks/useLoading.jsx b/src/context/hooks/useLoading.jsx index 41dc11d..60aa343 100644 --- a/src/context/hooks/useLoading.jsx +++ b/src/context/hooks/useLoading.jsx @@ -1,28 +1,96 @@ import { useCallback, useState } from 'react'; +const GENERAL_LOADING_IDS = new Set([ + 'isLoading', + 'isDataLoading', + 'isFormDataLoading', + 'isPageLoading', + 'isSearchLoading', +]); // Define general loading IDs +const GENERAL_LOADING_STATES = { + isLoading: 'isLoading', + isDataLoading: 'isDataLoading', + isFormDataLoading: 'isFormDataLoading', + isPageLoading: 'isPageLoading', + ifIsSearchLoading: 'isSearchLoading', +}; // Define general loading states +const ADDITIONAL_LOADING_STATES = { + loadingTimeoutExpired: 'loadingTimeoutExpired', + error: 'error', +}; // Define additional loading states +const DEFAULT_VALUES = { + loadingStatus: { + isLoading: false, + isDataLoading: false, + isFormDataLoading: false, + isPageLoading: false, + isSearchLoading: false, + error: null, + loadingTimeoutExpired: false, + loadingType: '', + }, + error: null, + // setActivelyLoading: () => {}, + // returnDisplay: () => null, + // setLoading: () => {}, + // setError: () => {}, + // setPageError: () => {}, + // setIsLoading: () => {}, + // setIsDataLoading: () => {}, + // setIsFormDataLoading: () => {}, + // setIsPageLoading: () => {}, + // setLoadingTimeoutExpired: () => {}, +}; // Define default values /** * A hook to manage loading states within components. * It supports tracking multiple loading processes at once. */ export const useLoading = () => { - // State to keep track of loading identifiers and their statuses - const [loadingStates, setLoadingStates] = useState({}); - + const [loadingStates, setLoadingStates] = useState({ + isLoading: false, + isDataLoading: false, + isFormDataLoading: false, + isPageLoading: false, + isSearchLoading: false, + error: null, + loadingTimeoutExpired: false, + loadingType: '', + }); + const [apiLoadingStates, setApiLoadingStates] = useState({ + login: false, + }); + const [loadingQueue, setLoadingQueue] = useState([]); + const [error, setError] = useState(null); /** * Starts a loading process. * @param {string} id - A unique identifier for the loading process. */ const startLoading = useCallback((id) => { - setLoadingStates((prevStates) => ({ ...prevStates, [id]: true })); + if (Object.keys(GENERAL_LOADING_STATES).includes(id)) { + // Corrected check for object keys + setLoadingStates((prevStates) => ({ ...prevStates, [id]: true })); + } else { + setApiLoadingStates((prevStates) => ({ ...prevStates, [id]: true })); + } + setLoadingQueue((prevQueue) => [...prevQueue, id]); }, []); /** * Stops a loading process. * @param {string} id - A unique identifier for the loading process. */ - const stopLoading = useCallback((id) => { - setLoadingStates((prevStates) => ({ ...prevStates, [id]: false })); - }, []); + const stopLoading = useCallback( + (id) => { + if (Object.keys(GENERAL_LOADING_STATES).includes(id)) { + // Corrected check for object keys + setLoadingStates((prevStates) => ({ ...prevStates, [id]: false })); + } else if (apiLoadingStates[id] !== undefined) { + setApiLoadingStates((prevStates) => ({ ...prevStates, [id]: false })); + } + setLoadingQueue((prevQueue) => prevQueue.filter((item) => item !== id)); + }, + [apiLoadingStates] + ); /** * Checks if a specific loading process is active. @@ -31,9 +99,9 @@ export const useLoading = () => { */ const isLoading = useCallback( (id) => { - return !!loadingStates[id]; + return loadingStates[id] || apiLoadingStates[id]; }, - [loadingStates] + [loadingStates, apiLoadingStates] ); /** @@ -41,8 +109,51 @@ export const useLoading = () => { * @returns {boolean} - True if any loading process is active, false otherwise. */ const isAnyLoading = useCallback(() => { - return Object.values(loadingStates).some((state) => state); - }, [loadingStates]); + return ( + Object.values(loadingStates).some((state) => state) || + Object.values(apiLoadingStates).some((state) => state) + ); + }, [loadingStates, apiLoadingStates]); + + /** + * Uses isAnythingLoading to determine if any loading processes are active, and if they are, returns a queue of loading identifiers. + * @returns {string[]} - A queue of loading identifiers. + */ + const getLoadingQueue = useCallback(() => { + return loadingQueue; + }, [loadingQueue]); + + /** + * Clears the loading queue. + */ + const clearLoading = useCallback(() => { + setLoadingStates({ + isLoading: false, + isDataLoading: false, + isFormDataLoading: false, + isPageLoading: false, + loadingTimeoutExpired: false, + error: null, + loadingType: '', + }); + setApiLoadingStates({}); + setLoadingQueue([]); + setError(null); + }, []); - return { startLoading, stopLoading, isLoading, isAnyLoading }; + return { + isDataLoading: loadingStates.isDataLoading, + isFormDataLoading: loadingStates.isFormDataLoading, + isPageLoading: loadingStates.isPageLoading, + isSearchLoading: loadingStates.isSearchLoading, + loadingTimeoutExpired: loadingStates.loadingTimeoutExpired, + loadingQueue: getLoadingQueue(), + startLoading, + stopLoading, + isLoading, + isAnyLoading, + getLoadingQueue, + setError, + clearLoading, + }; }; diff --git a/src/context/hooks/useLocalStorage.jsx b/src/context/hooks/useLocalStorage.jsx index 88ae07e..621f3ca 100644 --- a/src/context/hooks/useLocalStorage.jsx +++ b/src/context/hooks/useLocalStorage.jsx @@ -2,14 +2,15 @@ import { useState, useEffect, useRef } from 'react'; function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(() => { + if (typeof window === 'undefined') { + return initialValue; + } + try { - // Get from local storage by key const item = window.localStorage.getItem(key); - // Parse stored json or if none return initialValue return item ? JSON.parse(item) : initialValue; } catch (error) { - // If error also return initialValue - console.error(error); + console.error(`Error reading localStorage key "${key}":`, error); return initialValue; } }); @@ -17,39 +18,49 @@ function useLocalStorage(key, initialValue) { const isMounted = useRef(false); useEffect(() => { + // Skip the initial effect run on mount if (!isMounted.current) { isMounted.current = true; return; } try { - const item = window.localStorage.getItem(key); - if (item) { - setStoredValue(JSON.parse(item)); - } + // React to external changes in local storage + const onStorageChange = (event) => { + if (event.key === key) { + setStoredValue(JSON.parse(event.newValue)); + } + }; + + window.addEventListener('storage', onStorageChange); + return () => window.removeEventListener('storage', onStorageChange); } catch (error) { - console.log(error); + console.error('Listening for local storage changes failed:', error); } - - return () => { - isMounted.current = false; - }; }, [key]); + // Set value to localStorage and update local state const setValue = (value) => { + if (typeof window == 'undefined') { + console.warn( + `Tried setting localStorage key "${key}" even though environment is not a client` + ); + return; + } + try { - // Allow value to be a function so we have the same API as useState const valueToStore = value instanceof Function ? value(storedValue) : value; - // Save state - setStoredValue(valueToStore); - // Save to local storage + window.localStorage.setItem(key, JSON.stringify(valueToStore)); - // Optionally trigger an event for changes in local storage - window.dispatchEvent(new Event('local-storage')); + setStoredValue(valueToStore); + + // Optionally, for cross-tab communication, you might want to trigger a storage event manually + window.dispatchEvent( + new Event('storage', { key, newValue: JSON.stringify(valueToStore) }) + ); } catch (error) { - // A more advanced implementation would handle the error case - console.error(error); + console.error(`Error setting localStorage key "${key}":`, error); } }; diff --git a/src/context/hooks/useLogger.jsx b/src/context/hooks/useLogger.jsx index 5992416..68396ed 100644 --- a/src/context/hooks/useLogger.jsx +++ b/src/context/hooks/useLogger.jsx @@ -30,6 +30,11 @@ const useLogger = (componentName, options = {}) => { } }; + // Function to log errors + const logError = (error) => { + console.error(`[${componentName}] Error:`, error); + }; + useEffect(() => { console.log(`[${componentName}] Mounted`, options); mountedRef.current = true; @@ -51,7 +56,7 @@ const useLogger = (componentName, options = {}) => { logEvent('State Change', newState); }; - return { logEvent, setStateAndLog }; + return { logEvent, setStateAndLog, logError }; }; export default useLogger; diff --git a/src/context/hooks/useMasterCardList.jsx b/src/context/hooks/useMasterCardList.jsx new file mode 100644 index 0000000..ce7cbbb --- /dev/null +++ b/src/context/hooks/useMasterCardList.jsx @@ -0,0 +1,43 @@ +// import { useContext, useState, useEffect, useCallback } from 'react'; +// import { DeckContext } from '../../MAIN_CONTEXT/DeckContext/DeckContext'; +// import { CartContext } from '../../MAIN_CONTEXT/CartContext/CartContext'; +// import { CollectionContext } from '../../MAIN_CONTEXT/CollectionContext/CollectionContext'; +// import useLocalStorage from '../../hooks/useLocalStorage'; + +// const useMasterCardList = () => { +// const [masterCardList, setMasterCardList] = useState([]); +// const [allCardsWithQuantities, setAllCardsWithQuantities] = useLocalStorage( +// 'allCardsWithQuantities', +// [] +// ); +// const Deck = useContext(DeckContext); +// const Cart = useContext(CartContext); +// const Collection = useContext(CollectionContext); +// const [cardsWithQuantities, setCardsWithQuantities] = useState([]); +// const { selectedCollection, allCollections } = Collection; +// const { selectedDeck, allDecks } = Deck; +// const { cartData } = Cart; + +// const compileCardsWithQuantities = () => { +// if (!selectedCollection && !selectedDeck && !cartData) return []; +// const deckCards = allDecks?.reduce((acc, deck) => { +// if (deck.cards) { +// acc = [...acc, ...deck.cards]; +// } +// return acc; +// }, []); +// const cartCards = cartData?.cart || []; +// const collectionCards = allCollections?.reduce((acc, collection) => { +// if (collection?.cards) { +// acc = [...acc, ...collection.cards]; +// } +// return acc; +// }, []); +// return { +// cardsWithQuantities, +// allCardsWithQuantities /* any other values or functions to expose */, +// }; +// }; + + + diff --git a/src/context/hooks/useScreenWidth.jsx b/src/context/hooks/useScreenWidth.jsx index 2485559..a68f043 100644 --- a/src/context/hooks/useScreenWidth.jsx +++ b/src/context/hooks/useScreenWidth.jsx @@ -6,17 +6,21 @@ export default function useScreenWidth() { window.innerWidth < 1024 ); // Example breakpoint const [isLargeScreen, setIsLargeScreen] = useState(window.innerWidth < 1440); // Example breakpoint + const [isXLargeScreen, setIsXLargeScreen] = useState( + window.innerWidth < 1920 + ); // Example breakpoint useEffect(() => { function handleResize() { setIsSmallScreen(window.innerWidth < 768); // Update based on your responsive breakpoint setIsMediumScreen(window.innerWidth < 1024); // Update based on your responsive breakpoint setIsLargeScreen(window.innerWidth < 1440); // Update based on your responsive breakpoint + setIsXLargeScreen(window.innerWidth < 1920); // Update based on your responsive breakpoint } window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); - return { isSmallScreen, isMediumScreen, isLargeScreen }; + return { isSmallScreen, isMediumScreen, isLargeScreen, isXLargeScreen }; } diff --git a/src/context/hooks/useSelectionDialog.jsx b/src/context/hooks/useSelectionDialog.jsx index 4d9ee01..35feb18 100644 --- a/src/context/hooks/useSelectionDialog.jsx +++ b/src/context/hooks/useSelectionDialog.jsx @@ -1,15 +1,17 @@ import { useState, useCallback } from 'react'; import { useCollectionStore } from '../MAIN_CONTEXT/CollectionContext/CollectionContext'; import { useDeckStore } from '../MAIN_CONTEXT/DeckContext/DeckContext'; +import useSelectedCollection from '../MAIN_CONTEXT/CollectionContext/useSelectedCollection'; export const useSelectionDialog = ( context, - selectedCollection, + // selectedCollection, selectedDeck, - allCollections, + // allCollections, allDecks ) => { - const { setSelectedCollection } = useCollectionStore(); + const { selectedCollection, allCollections, handleSelectCollection } = + useSelectedCollection(); const { setSelectedDeck } = useDeckStore(); const [selectDialogOpen, setSelectDialogOpen] = useState(false); const [itemsForSelection, setItemsForSelection] = useState([]); @@ -25,7 +27,7 @@ export const useSelectionDialog = ( const handleSelection = (item) => { context === 'Collection' - ? setSelectedCollection(item) + ? handleSelectCollection(item) : setSelectedDeck(item); setSelectDialogOpen(false); }; diff --git a/src/context/index.js b/src/context/index.js index f1435c7..940c2b1 100644 --- a/src/context/index.js +++ b/src/context/index.js @@ -5,13 +5,13 @@ export { useCardStore } from './MAIN_CONTEXT/CardContext/CardContext'; export { useCollectionStore } from './MAIN_CONTEXT/CollectionContext/CollectionContext'; export { useModalContext } from './UTILITIES_CONTEXT/ModalContext/ModalContext'; export { useUserContext } from './MAIN_CONTEXT/UserContext/UserContext'; -export { useCombinedContext } from './MISC_CONTEXT/CombinedContext/CombinedProvider'; -export { useSocketContext } from './UTILITIES_CONTEXT/SocketContext/SocketProvider'; +// export { useCombinedContext } from './MISC_CONTEXT/CombinedContext/CombinedProvider'; +// export { useSocketContext } from './UTILITIES_CONTEXT/SocketContext/SocketProvider'; export { useSidebarContext } from './UTILITIES_CONTEXT/SideBarContext/SideBarProvider'; export { useChartContext } from './MAIN_CONTEXT/ChartContext/ChartContext'; export { useAppContext } from './MISC_CONTEXT/AppContext/AppContextProvider'; export { usePopoverContext } from './UTILITIES_CONTEXT/PopoverContext/PopoverContext'; -export { useCronJobContext } from './SECONDARY_CONTEXT/CronJobContext/CronJobContext'; +// export { useCronJobContext } from './SECONDARY_CONTEXT/CronJobContext/CronJobContext'; export { useStatisticsStore } from './SECONDARY_CONTEXT/StatisticsContext/StatisticsContext'; export { useCardImages } from './MISC_CONTEXT/CardImagesContext/CardImagesContext'; export { useAuthContext } from './MAIN_CONTEXT/AuthContext/authContext'; @@ -19,6 +19,7 @@ export { usePageContext } from './UTILITIES_CONTEXT/PageContext/PageContext'; export { useFormContext } from './UTILITIES_CONTEXT/FormContext/FormContext'; export { useMode } from './UTILITIES_CONTEXT/ColorModeContext/useMode'; export { useConfiguratorContext } from './UTILITIES_CONTEXT/ConfiguratorContext/ConfiguratorContext'; +export { useVisibilityContext } from './UTILITIES_CONTEXT/VisibilityContext'; // Contexts export { default as ErrorBoundary } from './ErrorBoundary'; @@ -29,16 +30,18 @@ export { CardProvider } from './MAIN_CONTEXT/CardContext/CardContext'; export { CollectionProvider } from './MAIN_CONTEXT/CollectionContext/CollectionContext'; export { ModalProvider } from './UTILITIES_CONTEXT/ModalContext/ModalContext'; export { UserProvider } from './MAIN_CONTEXT/UserContext/UserContext'; -export { CombinedProvider } from './MISC_CONTEXT/CombinedContext/CombinedProvider'; + +// export { CombinedProvider } from './MISC_CONTEXT/CombinedContext/CombinedProvider'; export { ColorModeProvider } from './UTILITIES_CONTEXT/ColorModeContext/ColorModeProvider'; -export { SocketProvider } from './UTILITIES_CONTEXT/SocketContext/SocketProvider'; +// export { SocketProvider } from './UTILITIES_CONTEXT/SocketContext/SocketProvider'; export { SidebarProvider } from './UTILITIES_CONTEXT/SideBarContext/SideBarProvider'; export { ChartProvider } from './MAIN_CONTEXT/ChartContext/ChartContext'; export { AppContextProvider } from './MISC_CONTEXT/AppContext/AppContextProvider'; export { PopoverProvider } from './UTILITIES_CONTEXT/PopoverContext/PopoverContext'; -export { CronJobProvider } from './SECONDARY_CONTEXT/CronJobContext/CronJobContext'; +// export { CronJobProvider } from './SECONDARY_CONTEXT/CronJobContext/CronJobContext'; export { StatisticsProvider } from './SECONDARY_CONTEXT/StatisticsContext/StatisticsContext'; export { FormProvider } from './UTILITIES_CONTEXT/FormContext/FormContext'; export { PageProvider } from './UTILITIES_CONTEXT/PageContext/PageContext'; export { CardImagesProvider } from './MISC_CONTEXT/CardImagesContext/CardImagesContext'; export { ConfiguratorProvider } from './UTILITIES_CONTEXT/ConfiguratorContext/ConfiguratorContext'; +export { VisibilityProvider } from './UTILITIES_CONTEXT/VisibilityContext'; diff --git a/src/context/user.jsx b/src/context/user.jsx new file mode 100644 index 0000000..3259493 --- /dev/null +++ b/src/context/user.jsx @@ -0,0 +1,42 @@ +const user = { + allCollections: [], + allDecks: [], + cart: { + _id: '65d1f50b62d6e0ac413a084f', + userId: '65b8e155b4885b451a5071c8', + totalPrice: 0, + totalQuantity: 0, + }, + createdAt: '2024-02-18T12:16:11.618Z', + quantity: 0, + totalPrice: 0, + totalQuantity: 0, + updatedAt: '2024-02-18T12:16:11.618Z', + userId: '65b8e155b4885b451a5071c8', + _id: '65d1f50b62d6e0ac413a084f', + searchHistory: [], + userBasicData: { + _id: '65b8e155b4885b451a5071c7', + firstName: 'Hinata', + lastName: 'Hyuuga', + }, + firstName: 'Hinata', + lastName: 'Hyuuga', + userSecurityData: { + _id: '65b8e155b4885b451a5071c5', + username: 'Hinata_Hyuuga', + email: 'HXH@gmail.com', + }, + email: 'HXH@gmail.com', + role_data: { + name: 'admin', + capabilities: ['create', 'read', 'update', 'delete'], + _id: '65b8e155b4885b451a5071c6', + }, + capabilities: ['create', 'read', 'update', 'delete'], + name: 'admin', + username: 'Hinata_Hyuuga', + __v: 21, +}; + +export default user; diff --git a/src/layout/Containers/PageLayout.jsx b/src/layout/Containers/PageLayout.jsx index 95abcf3..1e0f8c3 100644 --- a/src/layout/Containers/PageLayout.jsx +++ b/src/layout/Containers/PageLayout.jsx @@ -10,12 +10,16 @@ function PageLayout({ background, backCol, children }) { return ( - + {children} diff --git a/src/layout/REUSABLE_COMPONENTS/DashboardBox.jsx b/src/layout/REUSABLE_COMPONENTS/DashboardBox.jsx new file mode 100644 index 0000000..432a4c6 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/DashboardBox.jsx @@ -0,0 +1,11 @@ +import { Box } from '@mui/material'; +import { styled } from '@mui/system'; + +const DashboardBox = styled(Box)(({ theme }) => ({ + backgroundColor: '#2d2d34', + borderRadius: '1rem', + boxShadow: '0.15rem 0.2rem 0.15rem 0.1rem rgba(0, 0, 0, .8)', + flexGrow: 1, +})); + +export default DashboardBox; diff --git a/src/layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar.jsx b/src/layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar.jsx index fd420ff..c92cb3b 100644 --- a/src/layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar.jsx +++ b/src/layout/REUSABLE_COMPONENTS/HOC/DynamicSnackbar.jsx @@ -1,11 +1,62 @@ import * as React from 'react'; -import { Transition } from 'react-transition-group'; +// const withDynamicSnackbar = (WrappedComponent) => (props) => { +// const { enqueueSnackbar, closeSnackbar } = useSnackbar(); +// // const { handleSnackBar } = useSnackBar(); // Obtain enqueueSnackbar function from useSnackBar hook + import { Snackbar, IconButton, Box, Grow } from '@mui/material'; +import { useSnackbar } from 'notistack'; import { Close as CloseIcon } from '@mui/icons-material'; import styled from 'styled-components'; -import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; -import { useMode } from '../../../context'; -import { useSnackbar } from 'notistack'; + +// eslint-disable-next-line react/display-name +const withDynamicSnackbar = (WrappedComponent) => (props) => { + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + + const showSnackbar = (message, variant = 'success') => { + enqueueSnackbar(message, { + variant, + action: (key) => ( + closeSnackbar(key)}> + + + ), + }); + }; + + return ; +}; + +export { withDynamicSnackbar }; +// import { Transition } from 'react-transition-group'; +// import { Snackbar, IconButton, Box, Grow } from '@mui/material'; +// import { Close as CloseIcon } from '@mui/icons-material'; +// import styled from 'styled-components'; +// import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; +// import { useMode } from '../../../context'; +// import { useSnackbar } from 'notistack'; + +// const DynamicSnackbar = ({ open, message, variant, onClose }) => { +// const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + +// // Use enqueueSnackbar for showing messages +// React.useEffect(() => { +// if (open && message?.title) { +// enqueueSnackbar(message.title, { +// variant, +// action: (key) => ( +// closeSnackbar(key)}> +// +// +// ), +// }); +// } +// }, [open, message, variant, enqueueSnackbar, closeSnackbar]); + +// // Since snackbar management is now handled by notistack, the component itself doesn't need to render anything. +// return null; +// }; + +// export default DynamicSnackbar; // import useSnackBar from '../../../context/hooks/useSnackBar'; // const DynamicSnackbar = ({ open, message, variant, onClose, loading }) => { @@ -73,82 +124,79 @@ import { useSnackbar } from 'notistack'; // // ); // }; -const DynamicSnackbar = ({ open, message, variant, onClose }) => { - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - // Use enqueueSnackbar for showing messages - React.useEffect(() => { - if (open && message?.title) { - enqueueSnackbar(message.title, { - variant, - action: (key) => ( - closeSnackbar(key)}> - - - ), - }); - } - }, [open, message, variant, enqueueSnackbar, closeSnackbar]); +// DynamicSnackbar.displayName = 'DynamicSnackbar'; // Add display name - // Since snackbar management is now handled by notistack, the component itself doesn't need to render anything. - return null; -}; +// eslint-disable-next-line react/display-name +// HOC to provide snackbar functionality to wrapped components +// const withDynamicSnackbar = (WrappedComponent) => (props) => { +// const { enqueueSnackbar, closeSnackbar } = useSnackbar(); -export default DynamicSnackbar; +// // Function to show snackbar +// const showSnackbar = (message, variant = 'success') => { +// enqueueSnackbar(message, { +// variant, +// action: (key) => ( +// closeSnackbar(key)}> +// +// +// ), +// }); +// }; -DynamicSnackbar.displayName = 'DynamicSnackbar'; // Add display name +// return ; +// }; -// eslint-disable-next-line react/display-name -const withDynamicSnackbar = (WrappedComponent) => (props) => { - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - // const { handleSnackBar } = useSnackBar(); // Obtain enqueueSnackbar function from useSnackBar hook +// const withDynamicSnackbar = (WrappedComponent) => (props) => { +// const { enqueueSnackbar, closeSnackbar } = useSnackbar(); +// // const { handleSnackBar } = useSnackBar(); // Obtain enqueueSnackbar function from useSnackBar hook - const [snackbarOpen, setSnackbarOpen] = React.useState(false); - const [snackbarMessage, setSnackbarMessage] = React.useState({ - title: '', - description: '', - }); - const [snackbarVariant, setSnackbarVariant] = React.useState('success'); - const [loading, setLoading] = React.useState(false); +// const [snackbarOpen, setSnackbarOpen] = React.useState(false); +// const [snackbarMessage, setSnackbarMessage] = React.useState({ +// title: '', +// description: '', +// }); +// const [snackbarVariant, setSnackbarVariant] = React.useState('success'); +// const [loading, setLoading] = React.useState(false); - const showSnackbar = (message, variant = 'success') => { - console.log('showSnackbar:', message, variant); - const destructuredMessage = { - title: message.title, - description: message.description, - }; - setSnackbarMessage(destructuredMessage); - setSnackbarVariant(variant); - setSnackbarOpen(true); - }; +// const showSnackbar = (message, variant = 'success') => { +// console.log('showSnackbar:', message, variant); +// const destructuredMessage = { +// title: message.title, +// description: message.description, +// }; +// setSnackbarMessage(destructuredMessage); +// setSnackbarVariant(variant); +// setSnackbarOpen(true); +// }; - const hideSnackbar = () => { - setSnackbarOpen(false); - }; +// const hideSnackbar = () => { +// setSnackbarOpen(false); +// }; - return ( - - - - - ); -}; +// return ( +// +// +// +// +// ); +// }; -export { DynamicSnackbar, withDynamicSnackbar }; +// export { withDynamicSnackbar }; // const positioningStyles = { // entering: 'translateX(0)', @@ -171,7 +219,7 @@ const grey = { 900: '#212121', }; -const StyledSnackbar = styled(Box)( +const StyledSnackbar = styled(Snackbar)( ({ theme, variant }) => ` position: fixed; z-index: 5500; diff --git a/src/layout/REUSABLE_COMPONENTS/HOC/withDynamicSnackbar.jsx b/src/layout/REUSABLE_COMPONENTS/HOC/withDynamicSnackbar.jsx new file mode 100644 index 0000000..0e7098a --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/HOC/withDynamicSnackbar.jsx @@ -0,0 +1,76 @@ +import { Snackbar, IconButton, Box, Grow } from '@mui/material'; +import { useSnackbar } from 'notistack'; +import { Close as CloseIcon } from '@mui/icons-material'; + +// eslint-disable-next-line react/display-name +const withDynamicSnackbar = (WrappedComponent) => (props) => { + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + + const showSnackbar = (message, variant = 'success') => { + enqueueSnackbar(message, { + variant, + action: (key) => ( + closeSnackbar(key)}> + + + ), + }); + }; + + return ; +}; + +export { withDynamicSnackbar }; + +const positioningStyles = { + entering: 'translateX(0)', + entered: 'translateX(0)', + exiting: 'translateX(500px)', + exited: 'translateX(500px)', + unmounted: 'translateX(500px)', +}; + +// const [snackbarOpen, setSnackbarOpen] = React.useState(false); +// const [snackbarMessage, setSnackbarMessage] = React.useState({ +// title: '', +// description: '', +// }); +// const [snackbarVariant, setSnackbarVariant] = React.useState('success'); +// const [loading, setLoading] = React.useState(false); + +// const showSnackbar = (message, variant = 'success') => { +// console.log('showSnackbar:', message, variant); +// const destructuredMessage = { +// title: message.title, +// description: message.description, +// }; +// setSnackbarMessage(destructuredMessage); +// setSnackbarVariant(variant); +// setSnackbarOpen(true); +// }; + +// const hideSnackbar = () => { +// setSnackbarOpen(false); +// }; + +// return ( +// +// +// +// +// ); +// }; diff --git a/src/layout/REUSABLE_COMPONENTS/Icons.jsx b/src/layout/REUSABLE_COMPONENTS/Icons.jsx new file mode 100644 index 0000000..5cc02f0 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/Icons.jsx @@ -0,0 +1,10 @@ +const allIconsMap = { + // Add all icons here + 'attach-money': AttachMoneyIcon, + collections: CollectionsIcon, + 'attach-money': AttachMoneyIcon, + 'format-list-numbered': FormatListNumberedIcon, + 'trending-up': TrendingUpIcon, + 'pie-chart': PieChartIcon, + 'emoji-events': EmojiEventsIcon, +}; diff --git a/src/layout/REUSABLE_COMPONENTS/ProgressCircle.jsx b/src/layout/REUSABLE_COMPONENTS/ProgressCircle.jsx new file mode 100644 index 0000000..9170135 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/ProgressCircle.jsx @@ -0,0 +1,26 @@ +import { Box, useTheme } from '@mui/material'; +import { useMode } from '../../context'; + +const ProgressCircle = ({ progress = '0.75', size = '40' }) => { + const { theme } = useMode(); + const colors = theme.palette.chartTheme; + const primary = colors.primary.default; + const blue = colors.blueAccent.default; + const green = colors.greenAccent.default; + + const angle = progress * 360; + return ( + + ); +}; + +export default ProgressCircle; diff --git a/src/layout/REUSABLE_COMPONENTS/SkeletonVariants.jsx b/src/layout/REUSABLE_COMPONENTS/SkeletonVariants.jsx new file mode 100644 index 0000000..a4eef10 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/SkeletonVariants.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { useMode } from '../../context'; +import useSkeletonLoader from '../collection/collectionGrids/cards-datatable/useSkeletonLoader'; + +const HeroSectionSkeleton = () => { + const { theme } = useMode(); + const { SkeletonLoader } = useSkeletonLoader(); + + return ( + + + {/* Title Skeleton */} + + + {/* Subtitle Skeleton */} + + + {/* Image Slider Skeleton */} + + + + ); +}; + +export default HeroSectionSkeleton; diff --git a/src/layout/REUSABLE_COMPONENTS/StatBox.jsx b/src/layout/REUSABLE_COMPONENTS/StatBox.jsx new file mode 100644 index 0000000..61f51e3 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/StatBox.jsx @@ -0,0 +1,48 @@ +import { Box, Typography, useTheme } from '@mui/material'; +import ProgressCircle from './ProgressCircle'; +import { useMode } from '../../context'; +import MDBox from './MDBOX'; + +const StatBox = ({ title, subtitle, icon, progress, increase }) => { + const { theme } = useMode(); + const colors = theme.palette.chartTheme; + const primary = colors.primary.default; + const blue = colors.blueAccent.default; + const green = colors.greenAccent.default; + const greenliht = colors.greenAccent.light; + + const grey = colors.grey.default; + + return ( + + + + {icon} + + {title} + + + + + + + + + {subtitle} + + + {increase} + + + + ); +}; + +export default StatBox; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/Badge.jsx b/src/layout/REUSABLE_COMPONENTS/unique/Badge.jsx new file mode 100644 index 0000000..7d3af62 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/Badge.jsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { Unicon } from '../'; // Presuming Unicon is a valid component + +console.log('unicon', Unicon); + +const Badge = ({ + size = 'md', + fill, + icon, + isPrimary, + isAccent, + isDefault, + theme, // Assuming theme is now passed as a prop + ...rest +}) => { + console.log('icon', icon); + + // Conditional badge styles based on props + const badgeStyle = { + fill: isPrimary + ? theme.colorPrimary + : isAccent + ? theme.colorAccent + : isDefault + ? theme.colorDefaultBackground + : fill || 'transparent', + }; + + const textStyle = { + color: isPrimary + ? theme.colorPrimaryText + : isAccent + ? theme.colorAccentText + : isDefault + ? theme.colorDefaultText + : 'inherit', + }; + + // Size-based badge styles + const sizeStyle = { + width: + size === 'sm' + ? theme.lenSm1 + : size === 'md' + ? theme.lenSm2 + : size === 'lg' + ? theme.lenSm3 + : size === 'xl' + ? theme.lenLg1 + : size === 'xxl' + ? theme.lenXl1 + : 'auto', + height: + size === 'sm' + ? theme.lenSm1 + : size === 'md' + ? theme.lenSm2 + : size === 'lg' + ? theme.lenSm3 + : size === 'xl' + ? theme.lenLg1 + : size === 'xxl' + ? theme.lenXl1 + : 'auto', + }; + + // Combining the wrapper styles + const wrapperStyle = { + position: 'relative', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + ...sizeStyle, + }; + + const svgWrapperStyle = { + position: 'absolute', + zIndex: 0, + top: 0, + left: 0, + width: '100%', + height: '100%', + }; + + const iconStyle = { + position: 'relative', + zIndex: 2, + }; + + return ( + + + + + + + + {icon && ( + + {/* Assuming Unicon accepts style prop if needed */} + {icon} + + )} + + + ); +}; + +export default Badge; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/ChartWrapper.jsx b/src/layout/REUSABLE_COMPONENTS/unique/ChartWrapper.jsx new file mode 100644 index 0000000..cff6709 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/ChartWrapper.jsx @@ -0,0 +1,20 @@ +import React from 'react'; + +const ChartWrapper = ({ theme, children, ...rest }) => { + const chartStyle = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: theme.lenLg1, + height: theme.heightChartMd, + width: '100%', + }; + + return ( +
+ {children} +
+ ); +}; + +export default ChartWrapper; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/FixedHeightCardWrapper.jsx b/src/layout/REUSABLE_COMPONENTS/unique/FixedHeightCardWrapper.jsx new file mode 100644 index 0000000..7946989 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/FixedHeightCardWrapper.jsx @@ -0,0 +1,18 @@ +import { Card } from '@mui/material'; +import React from 'react'; + +const FixedHeightCardWrapper = ({ theme, children, ...rest }) => { + const cardStyle = { + height: theme.heightCardMd, + minHeight: theme.heightCardMd, + maxHeight: theme.heightCardMd, + }; + + return ( + + {children} + + ); +}; + +export default FixedHeightCardWrapper; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/IconStatWrapper.jsx b/src/layout/REUSABLE_COMPONENTS/unique/IconStatWrapper.jsx new file mode 100644 index 0000000..fb730ba --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/IconStatWrapper.jsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { Card, Icon, Box, Typography } from '@mui/material'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { useMode } from '../../../context'; + +const StyledCard = styled(Card)` + display: flex; + align-items: center; + justify-content: space-between; + padding: ${({ theme }) => `${theme.lenMd1} ${theme.lenMd3}`}; +`; + +const TextContainer = styled(Box)` + display: flex; + flex-direction: column; + justify-content: center; + margin-left: ${({ theme }) => theme.lenMd1}; + text-align: left; + flex: 1; +`; + +// Pre-calculated colors passed as props +const StyledLabel = styled(Typography)` + font-size: ${({ theme }) => theme.lenMd3}; + color: ${({ textColor }) => textColor}; + word-wrap: break-word; +`; + +const StyledValue = styled(Typography)` + font-size: ${({ theme }) => theme.lenLg2}; + color: ${({ textColor }) => textColor}; + word-wrap: break-word; +`; + +const StyledIcon = styled(Icon)` + font-size: 3rem; + color: ${({ iconColor }) => iconColor}; +`; + +const IconContainer = styled(Box)` + display: flex; + align-items: center; + justify-content: center; + width: 4rem; + height: 4rem; + border-radius: 50%; + background-color: ${({ backgroundColor }) => backgroundColor}; + color: ${({ theme }) => theme.colorPrimaryText}; +`; + +const IconStatWrapper = ({ label, value, icon = 'bars', isPrimary }) => { + const { theme } = useMode(); + + // Pre-calculate colors based on isPrimary + const labelColor = isPrimary ? theme.colorForDark2 : theme.colorLabel; + const valueColor = isPrimary ? theme.colorForDark1 : theme.colorText; + const iconColor = isPrimary ? theme.colorPrimary : theme.colorPrimaryText; + const backgroundColor = isPrimary ? theme.colorPrimary : 'transparent'; + + return ( + + + + + {icon} + + + + + {label} + + + {value} + + + + + ); +}; + +IconStatWrapper.propTypes = { + label: PropTypes.string.isRequired, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + icon: PropTypes.string, + isPrimary: PropTypes.bool, +}; + +export default IconStatWrapper; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/IconWrapper.jsx b/src/layout/REUSABLE_COMPONENTS/unique/IconWrapper.jsx new file mode 100644 index 0000000..fd7d275 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/IconWrapper.jsx @@ -0,0 +1,15 @@ +import { Icon } from '@mui/material'; +import React from 'react'; +import styled from 'styled-components'; + +const StyledIcon = styled(Icon)(({ theme }) => ({ + // color: theme.palette.chartTheme.primary.main, + fontSize: 'inherit', + // marginRight: '1rem', + // verticalAlign: 'middle', +})); +const IconWrapper = ({ children }) => { + return {children}; +}; + +export default IconWrapper; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/InfoStackWrapper.jsx b/src/layout/REUSABLE_COMPONENTS/unique/InfoStackWrapper.jsx new file mode 100644 index 0000000..2b245e4 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/InfoStackWrapper.jsx @@ -0,0 +1,72 @@ +import React from 'react'; + +// Assuming theme and media query values need to be passed as props or derived from context +const InfoStackWrapper = ({ + label, + value, + hideBottomMargin, + isValueFirst, + alignItems = 'center', + theme, + ...rest +}) => { + // Inline styles replacing the styled-components + const wrapperStyle = { + display: 'flex', + alignItems: alignItems, + justifyContent: 'center', + flexDirection: 'column', + marginBottom: !hideBottomMargin ? theme.lenMd1 : '0', + }; + + // Responsiveness can be handled via useState and useEffect with window listeners or a context providing screen size + const getValueFontSize = () => { + if (window.innerWidth >= theme.screenWidthSm) { + return theme.lenLg1; + } else if (window.innerWidth >= theme.screenWidthXs) { + return theme.lenMd3; + } + return theme.lenMd2; + }; + + const [valueFontSize, setValueFontSize] = React.useState(getValueFontSize()); + + React.useEffect(() => { + const handleResize = () => { + setValueFontSize(getValueFontSize()); + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [theme]); // Assuming theme is stable, otherwise it should be included in dependencies properly + + const valueStyle = { + marginBottom: isValueFirst ? theme.lenSm1 : '0', + color: theme.colorText, + fontSize: valueFontSize, + }; + + const labelStyle = { + color: theme.colorLabel, + fontSize: theme.lenMd1, + marginBottom: !isValueFirst ? theme.lenSm1 : '0', + }; + + return ( +
+ {isValueFirst ? ( + <> +
{value}
+
{label}
+ + ) : ( + <> +
{label}
+
{value}
+ + )} +
+ ); +}; + +export default InfoStackWrapper; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/Overlay.jsx b/src/layout/REUSABLE_COMPONENTS/unique/Overlay.jsx new file mode 100644 index 0000000..cb88f33 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/Overlay.jsx @@ -0,0 +1,32 @@ +import React from 'react'; + +const Overlay = ({ isVisible, position = 'fixed', zIndex = 2, ...rest }) => { + // Defining the base and conditional styles + const baseStyle = { + position: position, + zIndex: zIndex, + top: 0, + left: 0, + width: '100%', + height: '100%', + background: 'rgba(0, 0, 0, 0.5)', + transitionProperty: 'visibility, opacity', + transitionDuration: '0.5s', + visibility: 'hidden', + opacity: 0, + }; + + const visibleStyle = isVisible + ? { + visibility: 'visible', + opacity: 1, + } + : {}; + + // Merging the base styles with conditional styles based on `isVisible` + const finalStyle = { ...baseStyle, ...visibleStyle }; + + return
; +}; + +export default Overlay; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/SimpleButton.jsx b/src/layout/REUSABLE_COMPONENTS/unique/SimpleButton.jsx new file mode 100644 index 0000000..2891fe3 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/SimpleButton.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { rgba } from 'polished'; + +const SimpleButton = ({ + theme, + children, + isPrimary, + isAccent, + isDefault, + isDisabled, + ...rest +}) => { + const baseStyle = { + position: 'relative', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + minWidth: '140px', + padding: `1.035rem ${theme.lenMd1}`, + borderRadius: theme.borderRadius, + transitionProperty: 'color, background, box-shadow', + transitionDuration: '0.35s', + background: theme.colorDefaultBackground, + color: theme.colorDefaultText, + boxShadow: isDefault + ? `0 0 0 4px ${rgba(theme.colorDefaultBackground || 'white', 0.74)}` + : 'none', + ...(isPrimary && { + background: theme.colorPrimary, + color: theme.colorPrimaryText, + boxShadow: `0 0 0 4px ${rgba(theme.colorPrimary || 'white', 0.4)}`, + }), + ...(isAccent && { + background: theme.colorAccent, + color: theme.colorAccentText, + boxShadow: `0 0 0 4px ${rgba(theme.colorAccent || 'white', 0.4)}`, + }), + ...(isDisabled && { + background: theme.colorDisabledBackground, + color: theme.colorDisabledText, + cursor: 'not-allowed', + }), + }; + + const buttonHoverStyle = { + position: 'absolute', + zIndex: 1, + top: 0, + left: 0, + width: '100%', + height: '100%', + borderRadius: theme.borderRadius, + background: 'rgba(0, 0, 0, 0.075)', + opacity: 0, + pointerEvents: 'none', + transition: 'opacity 0.35s', + }; + + const buttonTextStyle = { + position: 'relative', + zIndex: 2, + }; + + return ( + + ); +}; + +export default SimpleButton; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/SimpleCard.jsx b/src/layout/REUSABLE_COMPONENTS/unique/SimpleCard.jsx new file mode 100644 index 0000000..23dc327 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/SimpleCard.jsx @@ -0,0 +1,152 @@ +import React from 'react'; +import MDTypography from '../MDTYPOGRAPHY/MDTypography'; +import styled from 'styled-components'; +// const CardContainer = styled.div` +// padding: ${({ theme, hasTitle }) => (hasTitle ? 0 : theme.lenMd3)}; +// margin-bottom: ${({ theme, noBottomMargin }) => +// noBottomMargin ? 0 : theme.lenMd1}; +// border-radius: ${({ theme }) => theme.borderRadius}; +// background: ${({ theme, isPrimary, isAccent }) => +// isPrimary +// ? theme.colorPrimary +// : isAccent +// ? theme.colorAccent +// : theme.colorCardBackground}; +// color: ${({ theme, isPrimary, isAccent }) => +// isPrimary +// ? theme.colorPrimaryText +// : isAccent +// ? theme.colorAccentText +// : theme.colorText}; +// `; +// const Content = styled.div` +// padding: ${({ theme }) => `0 ${theme.lenMd3} ${theme.lenMd3}`}; +// `; + +// const Title = styled.div` +// display: flex; +// align-items: center; +// justify-content: center; +// height: ${({ theme }) => theme.lenXl2}; +// padding: ${({ theme }) => `0 ${theme.lenMd1}`}; +// color: ${({ theme }) => theme.colorLabel}; +// font-size: ${({ theme }) => theme.lenMd2}; +// `; +const getPrimaryStyle = (theme, isPrimary) => ({ + background: isPrimary ? theme.colorPrimary : undefined, + color: isPrimary ? theme.colorPrimaryText : undefined, +}); + +const getAccentStyle = (theme, isAccent) => ({ + background: isAccent ? theme.colorAccent : undefined, + color: isAccent ? theme.colorAccentText : undefined, +}); + +const CardContent = ({ theme, children }) => ( +
{children}
+); + +const CardTitle = ({ theme, children }) => ( +
+ + {children} + +
+); + +const SimpleCard = ({ + theme, + hasTitle, + isPrimary, + isAccent, + noBottomMargin, + children, + cardTitle, + data, + ...rest +}) => { + const cardStyle = { + // display: 'flex', + width: '100%', + padding: hasTitle ? 0 : theme.lenMd1, + marginBottom: noBottomMargin ? 0 : theme.lenMd1, + borderRadius: theme.borderRadius, + background: theme.colorCardBackground, + color: theme.colorText, + ...(isPrimary && getPrimaryStyle(theme, true)), + ...(isAccent && getAccentStyle(theme, true)), + }; + + return ( +
+ {cardTitle && ( + <> + {cardTitle} + {children} + + )} + {!cardTitle && children} +
+ ); +}; + +export default SimpleCard; + +// const CardContent = ({ theme, children }) => ( +//
{children}
+// ); + +// const CardTitle = ({ theme, children }) => ( +//
+// {children} +//
+// ); + +// export const SimpleCard = () => { +// theme, +// children, +// cardTitle, +// isPrimary, +// isAccent, +// noBottomMargin, +// }) => { +// return ( +// +// {cardTitle && ( +// <> +// {cardTitle} +// {children} +// +// )} +// {!cardTitle && children} +// +// ); +// }; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/SimplePieChart.jsx b/src/layout/REUSABLE_COMPONENTS/unique/SimplePieChart.jsx new file mode 100644 index 0000000..3f47a53 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/SimplePieChart.jsx @@ -0,0 +1,89 @@ +import React, { useState, useEffect, useContext } from 'react'; +import Chart from 'react-apexcharts'; +import { ThemeContext } from '../../../context/ThemeContext'; // Adjust the path as necessary +import { Card, InfoStack } from '../../../'; + +const Wrapper = ({ cardTitle, value, label, series }) => { + const theme = useContext(ThemeContext); // Assuming theme is provided through context + + const cardStyle = { + height: theme.heightCardMd, + minHeight: theme.heightCardMd, + maxHeight: theme.heightCardMd, + }; + + const chartStyle = { + position: 'relative', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: theme.lenLg1, + height: theme.heightChartMd, + transform: 'translateX(0)', + marginTop: '2rem', + }; + + const chartWrapperStyle = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + minWidth: '240px', + }; + + const [options, setOptions] = useState({ + chart: { + animations: { + enabled: false, + }, + }, + grid: { + padding: { + top: 40, + left: 0, + right: 0, + bottom: 20, + }, + }, + stroke: { + show: false, + }, + tooltip: { enabled: false }, + legend: { show: false }, + dataLabels: { enabled: false }, + plotOptions: { pie: { donut: { size: '75%' } } }, + }); + + useEffect(() => { + if (theme) { + setOptions((prevOptions) => ({ + ...prevOptions, + colors: [ + theme.colorPrimary, + theme.colorAccent, + theme.colorDefaultBackground, + ], + })); + } + }, [theme]); + + return ( + + +
+
+ {options.colors && ( + + )} +
+
+
+ ); +}; + +export default Wrapper; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/SimpleSectionHeader.jsx b/src/layout/REUSABLE_COMPONENTS/unique/SimpleSectionHeader.jsx new file mode 100644 index 0000000..4843798 --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/SimpleSectionHeader.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import { useMode } from '../../../context'; + +// Updated SimpleSectionHeader component with additional parameters +const SimpleSectionHeader = ({ + sectionName, + userName, + sectionDescription, + lastUpdated, +}) => { + const { theme } = useMode(); + return ( + + + {sectionName} + + {` ${userName}'s Portfolio`} + + + + {sectionDescription} + + {` ${lastUpdated}`} + + + + ); +}; + +export default SimpleSectionHeader; diff --git a/src/layout/REUSABLE_COMPONENTS/unique/uniqueTheme.jsx b/src/layout/REUSABLE_COMPONENTS/unique/uniqueTheme.jsx new file mode 100644 index 0000000..ff101bc --- /dev/null +++ b/src/layout/REUSABLE_COMPONENTS/unique/uniqueTheme.jsx @@ -0,0 +1,73 @@ +import { rgba } from 'polished'; + +const colorTextForDark = rgba('white', 0.96); +const colorLabel = '#A4A3A6'; +const colorText = '#343239'; +const defaultColorTheme = { + id: 'whiteGreen', + borderRadius: '0.75rem', + colorBackground: '#f0f0f9', + colorNavbar: '#fff', + colorNavbarLabel: colorLabel, + colorNavbarLink: colorText, + colorText: colorText, + colorLabel: colorLabel, + colorBorder: '#f0f0f9', + colorPrimary: '#18b984', + + // colorPrimary: '#06D6A0', + colorPrimaryText: colorTextForDark, + colorAccent: '#5fe7bb', + colorAccentText: colorTextForDark, + colorCardBackground: '#ffffff', + colorDefaultBackground: '#e3e3e8', + colorDefaultText: '#73707C', + colorDisabledBackground: '#d5d5e3', + colorDisabledText: '#bebed0', + colorCode: '#100f10', + colorChartShading: '#696969', + boxShadowLogo: 'none', +}; +const uniqueTheme = { + ...defaultColorTheme, + // screenWidthXs: '480px', + // screenWidthSm: '576px', + // screenWidthMd: '768px', + // screenWidthLg: '992px', + gutterWidth: '0.5rem', + containerMaxWidthXs: '100%', + containerMaxWidthSm: '100%', + containerMaxWidthMd: '100%', + containerMaxWidthLg: '1080px', + containerMaxWidthXl: '1200px', + screenWidthXs: '576px', + screenWidthSm: '768px', + screenWidthMd: '992px', + screenWidthLg: '1200px', + screenWidthXl: '1920px', + sidebarWidth: '56px', + sidebarWidthMd: '220px', + colorForDark1: rgba('white', 0.96), + colorForDark2: rgba('white', 0.65), + colorForDark3: rgba('white', 0.45), + fontFamily: 'Poppins', + fontWeightBold: 600, + fontWeightSemibold: 500, + fontWeightRegular: 400, + heightCardMd: '360px', + heightChartMd: '150px', + lenSm1: '0.25rem', + lenSm2: '0.5rem', + lenSm3: '0.75rem', + lenMd1: '1rem', + lenMd2: '1.25rem', + lenMd3: '1.5rem', + lenLg1: '2rem', + lenLg2: '3rem', + lenLg3: '4rem', + lenXl1: '5rem', + lenXl2: '6rem', + lenXl3: '7rem', +}; + +export default uniqueTheme; diff --git a/src/layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents.jsx b/src/layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents.jsx new file mode 100644 index 0000000..c8754bc --- /dev/null +++ b/src/layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents.jsx @@ -0,0 +1,264 @@ +import { + Box, + Container, + Dialog, + DialogActions, + DialogContent, + Paper, + TextField, +} from '@mui/material'; +import styled from 'styled-components'; +import MDButton from '../REUSABLE_COMPONENTS/MDBUTTON'; +import rgba from '../../assets/themes/functions/rgba'; + +// COLOR PALETTE: #777 - opaque +export const StyledContainerBoxPrimary = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + width: '100%', + minWidth: '100%', + marginTop: theme.spacing(2), + padding: theme.spacing(3), + borderRadius: theme.shape.borderRadius, + background: theme.palette.backgroundB.dark, + boxShadow: theme.shadows[10], + marginBottom: theme.spacing(4), + transition: 'all 0.3s ease-in-out', // smooth all transitions +})); +// COLOR PALETTE: #8ec7b6 - opaque +export const StyledContainerBoxSecondary = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + marginTop: theme.spacing(2), + padding: theme.spacing(3), + borderRadius: theme.shape.borderRadius, + background: theme.palette.backgroundE.light, + boxShadow: theme.shadows[10], + marginBottom: theme.spacing(4), + transition: 'all 0.3s ease-in-out', // smooth all transitions +})); +// COLOR PALETTE: #4cceac - transparent +export const StyledContainerBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + marginTop: theme.spacing(2), + padding: theme.spacing(3), + borderRadius: theme.shape.borderRadius, + background: theme.palette.backgroundD.dark, + boxShadow: theme.shadows[10], + marginBottom: theme.spacing(4), + transition: 'all 0.3s ease-in-out', // smooth all transitions +})); +// COLOR PALETTE: #4cceac - transparent +export const StyledPaperPrimary = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[4], + backgroundColor: theme.palette.backgroundA.lightest, + color: theme.palette.text.primary, + display: 'flex', + flexDirection: 'column', +})); +// COLOR PALETTE: #333 - transparent +export const StyledPaper = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + width: '100%', + justifyContent: 'center', + mx: 'auto', + padding: '1rem', + background: theme.palette.backgroundC.dark, + // maxWidth: '1200px', + borderRadius: '8px', + boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', + transition: 'all 0.3s ease-in-out', // smooth all transitions +})); +// BUTTON: #4cceac - transparent +export const StyledButton = styled(MDButton)(({ theme }) => ({ + // background: theme.palette.backgroundE.darker, + // borderColor: theme.palette.backgroundB.darkest, + borderWidth: 2, + flexGrow: 1, + justifySelf: 'bottom', + bottom: 0, + mx: 1, + width: '70%', + '&:hover': { + // color: theme.palette.backgroundA.contrastTextC, + fontWeight: 'bold', + background: theme.palette.backgroundF.dark, + borderColor: theme.palette.backgroundB.darkest, + border: `1px solid ${theme.palette.backgroundB.darkest}`, + }, +})); + +// ! DIALOG STYLES +export const StyledDialog = styled(Dialog)(({ theme }) => ({ + height: '100%', + // display: 'flex', + // flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + // top: 64, + // width: 240, + width: '100%', + padding: 0, + background: theme.palette.backgroundC.dark, + boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', + transition: 'all 0.3s ease-in-out', // smooth all transitions + // mx: 'auto', + // my: 'auto', + '& .MuiDialog-paper': { + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(8), + display: 'flex', + width: '100%', + // height: '100%', + minWidth: '380px', + maxWidth: '600px', + maxHeight: '90vh', + mx: 'auto', + my: 'auto', + flexGrow: 1, + color: theme.palette.text.primary, + boxShadow: theme.shadows[5], + '@media (min-width:380px)': { + p: 0, + // maxWidth: '380px', + }, + '@media (max-width:600px)': { + margin: theme.spacing(2), + }, + // '& .MuiDialog-paperScrollPaper': { + // maxHeight: '90vh', // Slightly more space + // }, + }, + '&.MuiDialogActions-root': { + display: 'flex', + justifyContent: 'flex-end', + }, +})); + +export const DialogPaper = styled(Paper)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + // margin: 'auto', + padding: 0, + maxWidth: '100%', + maxHeight: '100%', + width: '100%', + // borderRadius: theme.shape.borderRadius, + flexGrow: 1, + margin: '20px auto', + overflow: 'hidden', // Hide unwanted scrollbars +})); +export const DialogContentsBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + flexGrow: 1, + height: '100%', + width: '100%', + // minWidth: '500px', + // borderRadius: theme.shape.borderRadius, + background: theme.palette.backgroundE.lighter, + boxShadow: theme.shadows[10], + transition: 'all 0.3s ease-in-out', // smooth all transitions + '@media (max-width:600px)': { + padding: theme.spacing(2), + }, +})); +export const StyledDialogContent = styled(DialogContent)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + width: '100%', + gap: theme.spacing(2), + padding: theme.spacing(3), + // backgroundColor: theme.palette.backgroundA.lightest, +})); +export const StyledDialogActions = styled(DialogActions)(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + flexGrow: 1, + width: '100%', + gap: theme.spacing(2), + padding: theme.spacing(3), + backgroundColor: theme.palette.backgroundA.lightest, +})); +export const FormBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + mx: 'auto', + padding: '1rem', + background: 'rgba(255, 255, 255, 0.2)', // Adjust for desired translucency + backdropFilter: 'blur(20px)', + boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', + transition: 'all 0.3s ease-in-out', + borderRadius: '20px', + flexGrow: 1, // Make FormBox grow to fill the space + overflow: 'hidden', // Hide unwanted scrollbars +})); +export const FormPaper = styled(Paper)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + p: 8, + flexGrow: 1, + width: '100%', + height: '100%', + background: theme.palette.backgroundE.lighter, + borderRadius: '16px', + // display: 'flex', + // flexDirection: 'column', + // alignItems: 'center', + // justifyContent: 'center', + // width: '100%', + // mx: 'auto', + // padding: '1rem', + // background: theme.palette.backgroundC.dark, + // // maxWidth: '1200px', + // borderRadius: '8px', + // boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', + // transition: 'all 0.3s ease-in-out', // smooth all transitions +})); +export const FormFieldBox = styled(Box)(({ theme }) => ({ + m: theme.spacing(1), + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + flexGrow: 1, // Ensure fields take available space + marginBottom: theme.spacing(2), +})); +export const StyledTextField = styled(TextField)(({ theme }) => ({ + '& .MuiOutlinedInput-root': { + position: 'relative', + transition: 'border-color 0.5s ease', // Add transition for border color + '& .MuiOutlinedInput-notchedOutline': { + borderColor: theme.palette.transparent.main, + }, + '&:hover .MuiOutlinedInput-notchedOutline': { + color: theme.palette.backgroundA.darker, + borderColor: theme.palette.backgroundA.darker, + }, + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: theme.palette.backgroundA.darker, + borderWidth: '2px', // or other width as you like + }, + }, + borderRadius: theme.shape.borderRadius, + color: theme.palette.backgroundA.darkest, + width: '100%', + backgroundColor: theme.palette.backgroundA.lightest, + boxShadow: `0px 2px 4px -1px ${theme.palette.grey[400]}`, + marginBottom: theme.spacing(2), +})); diff --git a/src/layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx b/src/layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx new file mode 100644 index 0000000..cb80c78 --- /dev/null +++ b/src/layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx @@ -0,0 +1,52 @@ +import { Box, Switch } from '@mui/material'; +import styled from 'styled-components'; +import LoginIcon from '@mui/icons-material/Login'; +import PersonAddIcon from '@mui/icons-material/PersonAdd'; + +export const HeroBox = styled(Box)(({ theme }) => ({ + width: '100%', + display: 'flex', + // minHeight: '600px', + alignItems: 'center', + justifyContent: 'center', + transition: 'all 0.3s ease-in-out', // smooth all transitions +})); + +export const AuthModeSwitchBase = styled(Switch)(({ theme }) => ({ + width: 74, + height: 34, + padding: 2, + '& .MuiSwitch-switchBase': { + padding: 0, + '&.Mui-checked': { + transform: 'translateX(40px)', + color: '#fff', + '& + .MuiSwitch-track': { + backgroundColor: '#8796A5', + }, + }, + }, + '& .MuiSwitch-thumb': { + width: 32, + height: 32, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + boxSizing: 'border-box', + }, + '& .MuiSwitch-track': { + borderRadius: 17 / 2, + backgroundColor: '#8796A5', + width: 58, + }, +})); + +export default function AuthModeSwitch({ checked, ...props }) { + return ( + } + checkedIcon={} + {...props} + /> + ); +} diff --git a/src/layout/cart/CartSummary.js b/src/layout/cart/CartSummary.js new file mode 100644 index 0000000..f235171 --- /dev/null +++ b/src/layout/cart/CartSummary.js @@ -0,0 +1,29 @@ +// import React from 'react'; +// import { Box, Typography } from '@mui/material'; + +// const CartSummary = ({ quantity, totalCost }) => { +// return ( +// +// +// Items: {quantity} +// +// +// Grand Total: ${totalCost} +// +// +// ); +// }; + +// export default CartSummary; diff --git a/src/layout/collection/collectionGrids/ChartGridLayout.jsx b/src/layout/collection/collectionGrids/ChartGridLayout.jsx index 7dc858f..14cfa9f 100644 --- a/src/layout/collection/collectionGrids/ChartGridLayout.jsx +++ b/src/layout/collection/collectionGrids/ChartGridLayout.jsx @@ -1,43 +1,94 @@ import React from 'react'; -import { Grid, Card, useMediaQuery } from '@mui/material'; +import { Grid, Card, useMediaQuery, Icon } from '@mui/material'; import MDBox from '../../REUSABLE_COMPONENTS/MDBOX'; -import CollectionPortfolioChartContainer from './CollectionPortfolioChartContainer'; -import DataTable from '.'; -import { useTheme } from '@mui/material/styles'; +import CollectionPortfolioChartContainer from './cards-chart/CollectionPortfolioChartContainer'; +import DataTable from './cards-datatable'; +import { useMode } from '../../../context'; +import DashboardBox from '../../REUSABLE_COMPONENTS/DashboardBox'; +import BoxHeader from '../../REUSABLE_COMPONENTS/BoxHeader'; +import { UpdaterAndStatisticsRow } from './cards-chart/UpdaterAndStatisticsRow'; +import SimpleCard from '../../REUSABLE_COMPONENTS/unique/SimpleCard'; +import uniqueTheme from '../../REUSABLE_COMPONENTS/unique/uniqueTheme'; const ChartGridLayout = ({ selectedCards, removeCard, columns, data }) => { - const theme = useTheme(); + const { theme } = useMode(); const isXs = useMediaQuery(theme.breakpoints.down('sm')); const isLg = useMediaQuery(theme.breakpoints.up('lg')); - - // Simplified determination of tableSize based on the screen size const tableSize = isXs ? 'less' : isLg ? 'large' : 'small'; - + console.log({ selectedCards, tableSize, columns, data }); // Debug log // A function to render MDBox Card container for reusability const renderCardContainer = (content) => ( - - - - {content} - - + + + {/* */} + {/* */} + {content} + {/* */} + ); return ( - - - {/* PORTFOLIO CHARTS */} - + + + + + table_chart} + sideText="+4%" + /> + {renderCardContainer( )} - - {/* PORTFOLIO CARD LIST TABLE */} - + + + + + + + table_chart} + sideText="+4%" + /> + {renderCardContainer( { tableSize={tableSize} /> )} - + ); diff --git a/src/layout/collection/collectionGrids/CollectionListStats.jsx b/src/layout/collection/collectionGrids/CollectionListStats.jsx index c616081..72ef20e 100644 --- a/src/layout/collection/collectionGrids/CollectionListStats.jsx +++ b/src/layout/collection/collectionGrids/CollectionListStats.jsx @@ -1,21 +1,24 @@ import { Grid, Skeleton } from '@mui/material'; -import TotalValueOfCollectionsDisplay from '../../../components/other/dataDisplay/TotalValueOfCollectionsDisplay'; -import TopFiveExpensiveCards from '../../../components/other/dataDisplay/TopFiveExpensiveCards'; +import TotalValueOfCollectionsDisplay from '../sub-components/TotalValueOfCollectionsDisplay'; +import TopFiveExpensiveCards from '../sub-components/TopFiveExpensiveCards'; import { useStatisticsStore } from '../../../context'; +import SimpleCard from '../../REUSABLE_COMPONENTS/unique/SimpleCard'; +import uniqueTheme from '../../REUSABLE_COMPONENTS/unique/uniqueTheme'; const CollectionListStats = () => { const { topFiveCards, totalValue, chartData } = useStatisticsStore(); return ( - {/* */} - + + This is a primary styled card content. + { - const { theme } = useMode(); - const isSmall = useMediaQuery(theme.breakpoints.down('sm')); - const { allCollections, selectedCollection, newNivoChartData } = - useCollectionStore(); - const { selectedTimeRange, timeRange, setTimeRange } = useChartContext(); - - const [selectedChartData, setSelectedChartData] = useState( - newNivoChartData[0] - ); - - const { socket } = useCombinedContext(); - const { stats, markers } = useStatisticsStore(); - console.log('SELECTED timeRange:', timeRange); - useEffect(() => { - console.log('ADJUSTED TIME RANGE ID:', newNivoChartData[0]); - const oneDay = newNivoChartData[0]; - const sevenDays = newNivoChartData[1]; - const thirtyDays = newNivoChartData[2]; - const ninetyDays = newNivoChartData[3]; - const oneEightyDays = newNivoChartData[4]; - const twoSeventyDays = newNivoChartData[5]; - const threeSixtyFiveDays = newNivoChartData[6]; - // console.log('ALL RANES:', oneDay, sevenDays, thirtyDays, ninetyDays); - if (timeRange.id === '24hr') { - setSelectedChartData(oneDay); - } - if (timeRange.id === '7d') { - console.log('SETTING SEVEN DAYS:', sevenDays); - setSelectedChartData(sevenDays); - } - if (timeRange.id === '30d') { - setSelectedChartData(thirtyDays); - } - if (timeRange.id === '90d') { - setSelectedChartData(ninetyDays); - } - if (timeRange.id === '180d') { - setSelectedChartData(oneEightyDays); - } - if (timeRange.id === '270d') { - setSelectedChartData(twoSeventyDays); - } - if (timeRange.id === '365d') { - setSelectedChartData(threeSixtyFiveDays); - } - }, [newNivoChartData, timeRange]); // Dependencies - - if (!selectedChartData || !timeRange) { - return ; - } - console.log('SELECTED CHARTD:', selectedChartData); - - return ( - - table_chart} - sideText="+4%" - /> - - - - - - - - - - - - ); -}; - -export default CollectionPortfolioChartContainer; diff --git a/src/layout/collection/collectionGrids/StatisticsCardsGrid.jsx b/src/layout/collection/collectionGrids/StatisticsCardsGrid.jsx index a0e1d59..2953f8a 100644 --- a/src/layout/collection/collectionGrids/StatisticsCardsGrid.jsx +++ b/src/layout/collection/collectionGrids/StatisticsCardsGrid.jsx @@ -1,15 +1,16 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Grid } from '@mui/material'; import MDBox from '../../REUSABLE_COMPONENTS/MDBOX'; -import ComplexStatisticsCard from '../../../components/other/dataDisplay/ComplexStatisticsCard'; +import ComplexStatisticsCard from '../sub-components/ComplexStatisticsCard'; import LoadingIndicator from '../../../components/reusable/indicators/LoadingIndicator'; +import useSelectedCollection from '../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; const cardData = [ { icon: 'attach_money', title: 'Cards Added', countFunc: (collection) => - collection.cards.reduce((acc, card) => acc + card.quantity, 0), + collection?.cards?.reduce((acc, card) => acc + card.quantity, 0), percentage: { color: 'success', amount: '+55%', label: 'than last week' }, }, { @@ -22,10 +23,10 @@ const cardData = [ icon: 'store', title: 'Most Valuable Card', countFunc: (collection) => { - const mostValuableCard = collection.cards.reduce((acc, card) => + const mostValuableCard = collection?.cards?.reduce((acc, card) => acc.price > card.price ? acc : card ); - return `${mostValuableCard.name} - $${mostValuableCard.price}`; + return `${mostValuableCard?.name} - $${mostValuableCard?.price}`; }, percentage: { color: 'success', amount: '+1%', label: 'than yesterday' }, }, @@ -45,19 +46,19 @@ const gridItemStyle = { height: '100%', }; -const StatisticsCardGrid = ({ selectedCollection }) => { - // ERROR HANDLING - if (!selectedCollection) return ; +const StatisticsCardGrid = () => { + const { allCollections, selectedCollection } = useSelectedCollection(); + return ( - {cardData.map((data, index) => ( + {cardData?.map((data, index) => ( diff --git a/src/layout/collection/collectionGrids/cards-chart/ChartConfigs.jsx b/src/layout/collection/collectionGrids/cards-chart/ChartConfigs.jsx new file mode 100644 index 0000000..ab2b5cd --- /dev/null +++ b/src/layout/collection/collectionGrids/cards-chart/ChartConfigs.jsx @@ -0,0 +1,376 @@ +import { ResponsiveLine } from '@nivo/line'; +import { useChartContext, useMode } from '../../../../context'; +import useSelectedCollection from '../../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import { + CustomTooltipLayer, + useEventHandlers, +} from '../../../../context/MAIN_CONTEXT/ChartContext/helpers'; +import { useEffect, useMemo, useState } from 'react'; +import useCollectionManager from '../../../../context/MAIN_CONTEXT/CollectionContext/useCollectionManager'; +import NivoContainer from './NivoContainer'; +import { mockLineData as data } from '../../data/mockData'; +import PropTypes from 'prop-types'; +// import nivoTestData from './GenerateNivoTestData'; +// const { tickValues, xFormat } = useMemo(() => { +// let format, ticks; +// switch (timeRange) { +// case '24hr': +// format = '%H:%M'; +// ticks = 'every hour'; +// break; +// case '7d': +// format = '%b %d'; +// ticks = 'every day'; +// break; +// case '30d': +// format = '%b %d'; +// ticks = 'every day'; +// break; +// case '90d': +// format = '%b %d'; +// ticks = 'every 3 days'; +// break; +// case '180d': +// format = '%b %d'; +// ticks = 'every 6 days'; +// break; +// case '270d': +// format = '%b %d'; +// ticks = 'every 9 days'; +// break; +// case '365d': +// format = '%b %d'; +// ticks = 'every 12 days'; +// break; +// default: +// format = '%b %d'; +// ticks = 'every day'; +// } +// return { tickValues: ticks, xFormat: `time:${format}` }; +// }, [timeRange]); +// const normalizeData = (data, timeRange) => { +// // Determine the start and end dates based on the timeRange +// let endDate = new Date(); +// let startDate = new Date(); + +// startDate.setDate(endDate.getDate() - parseInt(timeRange, 10)); + +// let normalizedData = []; +// let currentDate = new Date(startDate); + +// while (currentDate <= endDate) { +// let dateString = currentDate.toISOString().split('T')[0]; + +// let dataPoint = data?.find((point) => point.x === dateString); +// normalizedData.push({ +// x: dateString, +// y: dataPoint ? dataPoint.y : null, // Use null for missing data +// }); + +// currentDate.setDate(currentDate.getDate() + 1); +// } + +// return [{ id: 'data', data: normalizedData }]; +// }; +// function getTickValuesAndFormat(timeRange) { +// // Your logic to determine tick values and format based on timeRange... +// return { +// tickValues: 'every day', +// xFormat: '%Y-%m-%d', +// }; +// } +// const normalizeData = (data, timeRange) => { +// let endDate = new Date(); +// let startDate = new Date(); +// startDate.setDate(endDate.getDate() - parseInt(timeRange, 10)); + +// return [ +// { +// id: 'data', +// data: data.map(({ x, y }) => ({ +// x: x || startDate.toISOString().split('T')[0], // Provide a fallback date if necessary +// y: y != null ? y : 0, // Ensure null or undefined values are replaced with 0 +// })), +// }, +// ]; +// }; + +// function getTickValuesAndFormat(timeRange) { +// // Simplified logic for determining tick values and format based on timeRange +// const format = timeRange === '24hr' ? '%H:%M' : '%b %d'; +// const ticks = timeRange === '24hr' ? 'every hour' : 'every day'; +// return { tickValues: ticks, xFormat: `time:${format}` }; +// } +export const ChartConfiguration = ({ + markers, + height, + nivoChartData, + range, + loadingId, +}) => { + const { theme } = useMode(); + const { selectedCollection, nivoTestData } = useSelectedCollection(); + // const { nivoChartData, newNivoChartData } = useCollectionManager(); + const { handleMouseMove, handleMouseLeave } = useEventHandlers(); + // const chartData = useMemo(() => { + // // Access the averaged data based on the selected range + // return selectedCollection.averagedChartData.get(range) || []; + // }, [selectedCollection.averagedChartData, range]); + // const [timeRange, setTimeRange] = useState('24hr'); + // const [chartData, setChartData] = useState([]); + // useEffect(() => { + // // Assuming a function like `normalizeData` exists to format your data correctly based on the time range. + // // This should adjust the data structure to fit what Nivo expects. + // const normalizedData = normalizeData(nivoChartData, timeRange); + // setChartData(normalizedData); + // }, [nivoChartData, timeRange]); + // useEffect(() => { + // let isValidData = true; // Assume data is valid initially + + // // Checks if the data array has valid 'x' and 'y' values + // const validateChartData = (data) => { + // return data.every((series) => + // series.data.every( + // (point) => point.x && point.y !== null && point.y !== undefined + // ) + // ); + // }; + + // // Check if nivoChartData or newNivoChartData has valid structure and content + // if ( + // !nivoChartData || + // !newNivoChartData || + // !validateChartData(nivoChartData) + // ) { + // isValidData = false; // Data is considered invalid if checks fail + // } + + // // Set the chart data based on the validation above + // let activeChartData; + // if (isValidData) { + // // Normalize the actual data if it's valid + // activeChartData = normalizeData(nivoChartData, timeRange); + // } else { + // // Fallback to nivoTestData for the selected time range if data is invalid + // const testData = nivoTestData.find((data) => data.id === timeRange); + // activeChartData = testData ? normalizeData(testData.data, timeRange) : []; + // } + + // setChartData(activeChartData); + // }, [nivoChartData, newNivoChartData, timeRange, nivoTestData]); + + // Filtering valid markers only to avoid any runtime errors. + const validMarkers = useMemo( + () => markers.filter((marker) => marker.value !== undefined), + [markers] + ); + const { tickValues, xFormat } = useMemo(() => { + let format, ticks; + switch (range) { + case '24hr': + format = '%H:%M'; + ticks = 'every hour'; + break; + case '7d': + case '30d': + format = '%b %d'; + ticks = 'every day'; + break; + case '90d': + format = '%b %d'; + ticks = 'every 3 days'; + break; + case '180d': + format = '%b %d'; + ticks = 'every 6 days'; + break; + case '270d': + format = '%b %d'; + ticks = 'every 9 days'; + break; + case '365d': + format = '%b %d'; + ticks = 'every 12 days'; + break; + default: + format = '%b %d'; + ticks = 'every day'; + } + return { tickValues: ticks, xFormat: `time:${format}` }; + }, [range]); + + const chartProps = useMemo( + () => ({ + // data: [nivoChartData], + data: [nivoChartData], + onMouseMove: handleMouseMove, + onMouseLeave: handleMouseLeave, + colors: { datum: 'color' }, // This is the only change from the original code + pointSize: 10, + pointColor: { theme: 'background' }, + pointBorderWidth: 2, + pointBorderColor: { from: 'serieColor' }, + yFormat: '$.2f', + + // colors: theme.palette.backgroundE.dark, + axisBottom: { + tickRotation: 0, + legend: 'Time', + legendOffset: 40, + legendPosition: 'middle', + tickSize: 5, + tickPadding: 5, + tickValues: tickValues, + format: (value) => { + const d = new Date(value); + return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; + }, + }, + axisLeft: { + tickSize: 5, + tickPadding: 5, + tickRotation: 0, + legend: 'Value ($)', + legendOffset: -50, + legendPosition: 'middle', + color: theme.palette.text.primary, + format: (value) => `$${value?.toFixed(2)}`, + }, + margin: { top: 20, right: 40, bottom: 50, left: 55 }, + padding: 0.5, + animate: true, + // xFormat: 'time:%Y-%m-%d %H:%M:%S7Z', + // format: '%Y-%m-%dT%H:%M:%S.%LZ', + + xScale: { + type: 'time', + format: '%Y-%m-%dT%H:%M:%S.%LZ', + precision: 'millisecond', + // format: 'time:%Y-%m-%d', + useUTC: false, + // precision: 'day', + }, + yScale: { + type: 'linear', + min: 'auto', + max: 'auto', + stacked: true, + reverse: false, + }, + curve: 'monotoneX', + useMesh: true, + motionConfig: 'gentle', + + stiffness: 90, + damping: 15, + enableSlices: 'x', + markers: validMarkers, + layers: [ + 'grid', + 'markers', + 'areas', + 'lines', + 'slices', + 'points', + 'axes', + 'legends', + ({ points, xScale, yScale, markers: validMarkers }) => ( + + ), + ], + // theme: { + // axis: theme.nivo.axis, + // legends: theme.nivo.legends, + // tooltip: theme.nivo.tooltip, + // }, + theme: { + axis: { + domain: { + line: { + stroke: theme.palette.chartTheme.grey.lightest, + }, + }, + legend: { + text: { + fill: theme.palette.chartTheme.grey.lightest, + }, + }, + ticks: { + line: { + stroke: theme.palette.chartTheme.grey.lightest, + strokeWidth: 1, + }, + text: { + fill: theme.palette.chartTheme.grey.lightest, + }, + }, + }, + legends: { + text: { + fill: theme.palette.chartTheme.grey.lightest, + }, + }, + tooltip: { + container: { + color: theme.palette.chartTheme.primary.default, + }, + }, + }, + }), + [nivoChartData, markers, theme, tickValues, xFormat] + ); + + return ( + + + + ); +}; + +// Define the structure of each data point +// const pointShape = PropTypes.shape({ +// x: PropTypes.oneOfType([ +// PropTypes.number, +// PropTypes.string, +// PropTypes.instanceOf(Date), +// ]).isRequired, +// y: PropTypes.number.isRequired, +// }); + +// // Define the structure for each data series +// const seriesShape = PropTypes.shape({ +// id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, +// color: PropTypes.string, +// data: PropTypes.arrayOf(pointShape).isRequired, +// }); + +// Now, apply these to ChartConfiguration propTypes +ChartConfiguration.propTypes = { + // markers: PropTypes.arrayOf(PropTypes.object), + // height: PropTypes.number.isRequired, + // range: PropTypes.string.isRequired, + // loadingId: PropTypes.string, + nivoChartData: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + color: PropTypes.string, + data: PropTypes.arrayOf( + PropTypes.shape({ + x: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.instanceOf(Date), + ]).isRequired, + y: PropTypes.number.isRequired, + }) + ).isRequired, + }) + ).isRequired, + // Validate chartData explicitly to match the expected structure + // nivoChartData: PropTypes.arrayOf(seriesShape).isRequired, +}; diff --git a/src/components/reusable/ChartErrorBoundary.jsx b/src/layout/collection/collectionGrids/cards-chart/ChartErrorBoundary.jsx similarity index 100% rename from src/components/reusable/ChartErrorBoundary.jsx rename to src/layout/collection/collectionGrids/cards-chart/ChartErrorBoundary.jsx diff --git a/src/layout/collection/collectionGrids/cards-chart/CollectionPortfolioChartContainer.jsx b/src/layout/collection/collectionGrids/cards-chart/CollectionPortfolioChartContainer.jsx new file mode 100644 index 0000000..261a730 --- /dev/null +++ b/src/layout/collection/collectionGrids/cards-chart/CollectionPortfolioChartContainer.jsx @@ -0,0 +1,165 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Box, useMediaQuery, Grid, Container, Icon } from '@mui/material'; +import { + useChartContext, + useStatisticsStore, + useMode, + useCollectionStore, +} from '../../../../context'; +import { ChartArea } from '../../../../pages/pageStyles/StyledComponents'; +import BoxHeader from '../../../REUSABLE_COMPONENTS/BoxHeader'; +import LinearChart from './LinearChart'; +import { Suspense } from 'react'; +import useSkeletonLoader from '../cards-datatable/useSkeletonLoader'; +import useSelectedCollection from '../../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import useTimeRange from '../../../../components/forms/selectors/useTimeRange'; + +const CollectionPortfolioChartContainer = () => { + const { theme } = useMode(); + const env = process.env.CHART_ENVIRONMENT; + const isSmall = useMediaQuery(theme.breakpoints.down('sm')); + const { selectedCollection } = useSelectedCollection(); + const chartDataVariants = { + averaged: selectedCollection.averagedChartData, + raw: selectedCollection.nivoChartData, + new: selectedCollection.newNivoChartData, + test: selectedCollection.nivoTestData, + }; + const selectedData = chartDataVariants.averaged; + const { selectedTimeRange } = useTimeRange(); + const { stats, markers } = useStatisticsStore(); + const { SkeletonLoader } = useSkeletonLoader(); + // Directly use the averagedChartData map for selecting the data based on timeRange + const selectedChartData = useMemo(() => { + const averagedData = selectedCollection.averagedChartData; + + if (!averagedData || !averagedData[selectedTimeRange]) { + console.error( + 'No averaged chart data available for the selected time range.' + ); + return null; + } + + // Retrieve the specific dataset for the current time range directly + const chartData = averagedData[selectedTimeRange]; + return chartData || null; // Return null if the specific range data is not available + }, [selectedCollection.averagedChartData, selectedTimeRange]); + + if (!selectedChartData) { + return ( + + table_chart} + sideText={new Date().toLocaleString()} + /> + + + + + ); + } + + // const setSelectedChartData = (chartData) => { + // console.log('setSelectedChartData', chartData); + // setTimeRange(chartData); + // }; + // useEffect(() => { + // const timeRangeIdMap = nivoTestData?.reduce((acc, data) => { + // acc[data.id] = data; + // return acc; + // }, {}); + // console.log('TIME RANGE ID MAP:', timeRangeIdMap); + + // const matchedData = timeRangeIdMap[timeRange.id]; + // if (matchedData) { + // console.log('SET SELECTED DATA:', matchedData); + // setSelectedChartData(matchedData); + // // setSelectedChartData(matchedData); + // } + // }, [selectedData, timeRange]); + + // if (!selectedChartData) { + // return ( + // + // table_chart} + // sideText={new Date().toLocaleString()} + // /> + // + // + // + // + // ); + // } + return ( + }> + {/* */} + + + + {/* */} + + ); +}; + +export default CollectionPortfolioChartContainer; +// useEffect(() => { +// console.log('ADJUSTED TIME RANGE ID:', newNivoChartData[0].id); +// const oneDay = newNivoChartData[0].id; +// const sevenDays = newNivoChartData[1].id; +// const thirtyDays = newNivoChartData[2].id; +// const ninetyDays = newNivoChartData[3].id; +// const oneEightyDays = newNivoChartData[4].id; +// const twoSeventyDays = newNivoChartData[5].id; +// const threeSixtyFiveDays = newNivoChartData[6].id; + +// setTimeRangeIds([ +// oneDay, +// sevenDays, +// thirtyDays, +// ninetyDays, +// oneEightyDays, +// twoSeventyDays, +// threeSixtyFiveDays, +// ]); + +// // console.log('ALL RANES:', oneDay, sevenDays, thirtyDays, ninetyDays); +// if (timeRange.id === '24hr') { +// setSelectedChartData(oneDay); +// } +// if (timeRange.id === '7d') { +// console.log('SETTING SEVEN DAYS:', sevenDays); +// setSelectedChartData(sevenDays); +// } +// if (timeRange.id === '30d') { +// setSelectedChartData(thirtyDays); +// } +// if (timeRange.id === '90d') { +// setSelectedChartData(ninetyDays); +// } +// if (timeRange.id === '180d') { +// setSelectedChartData(oneEightyDays); +// } +// if (timeRange.id === '270d') { +// setSelectedChartData(twoSeventyDays); +// } +// if (timeRange.id === '365d') { +// setSelectedChartData(threeSixtyFiveDays); +// } +// }, [newNivoChartData, timeRange]); // Dependencies + +// if (!selectedChartData || !timeRange) { +// return ; +// } diff --git a/src/layout/collection/collectionGrids/cards-chart/GenerateNivoTestData.jsx b/src/layout/collection/collectionGrids/cards-chart/GenerateNivoTestData.jsx new file mode 100644 index 0000000..c31e9c1 --- /dev/null +++ b/src/layout/collection/collectionGrids/cards-chart/GenerateNivoTestData.jsx @@ -0,0 +1,77 @@ +import { addDays, format, parseISO } from 'date-fns'; + +// Function to generate an incremental data set with occasional dips +const generateIncrementalData = (start, end, minY, maxY, interval = 1) => { + let data = []; + let currentValue = Math.floor(Math.random() * (maxY - minY + 1)) + minY; + + for (let date = start; date <= end; date = addDays(date, interval)) { + // Randomly decide if the next point should be a dip + if (Math.random() > 0.8) { + // Create a dip but ensure it's above the minimum + currentValue = Math.max( + currentValue - Math.floor(Math.random() * (maxY / 10)), + minY + ); + } else { + // Increment the value but cap it at maxY + currentValue = Math.min( + currentValue + Math.floor(Math.random() * (maxY / 10)), + maxY + ); + } + + data.push({ + _id: Math.random().toString(36).substr(2, 9), // Simulate an objectId + x: format(date, "yyyy-MM-dd'T'HH:mm:ss'Z'"), + y: currentValue, + }); + } + + return data; +}; + +const generateNivoTestData = () => { + const today = new Date(); + return [ + { + id: '24h', + color: '#06d6a0', + data: generateIncrementalData(addDays(today, -1), today, 2, 30, 1 / 24), + }, + { + id: '7d', + color: '#06d6a0', + data: generateIncrementalData(addDays(today, -7), today, 31, 60), + }, + { + id: '30d', + color: '#06d6a0', + data: generateIncrementalData(addDays(today, -30), today, 61, 120), + }, + { + id: '90d', + color: '#06d6a0', + data: generateIncrementalData(addDays(today, -90), today, 121, 240, 3), + }, + { + id: '180d', + color: '#06d6a0', + data: generateIncrementalData(addDays(today, -180), today, 241, 480, 6), + }, + { + id: '270d', + color: '#06d6a0', + data: generateIncrementalData(addDays(today, -270), today, 481, 960, 9), + }, + { + id: '365d', + color: '#06d6a0', + data: generateIncrementalData(addDays(today, -365), today, 961, 2789, 12), + }, + ]; +}; + +const nivoTestData = generateNivoTestData(); + +export default nivoTestData; diff --git a/src/layout/collection/collectionGrids/cards-chart/LinearChart.js b/src/layout/collection/collectionGrids/cards-chart/LinearChart.js new file mode 100644 index 0000000..edce7fe --- /dev/null +++ b/src/layout/collection/collectionGrids/cards-chart/LinearChart.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ChartWrapper from '../../../REUSABLE_COMPONENTS/unique/ChartWrapper'; +import FixedHeightCardWrapper from '../../../REUSABLE_COMPONENTS/unique/FixedHeightCardWrapper'; +import InfoStackWrapper from '../../../REUSABLE_COMPONENTS/unique/InfoStackWrapper'; +import uniqueTheme from '../../../REUSABLE_COMPONENTS/unique/uniqueTheme'; +import DynamicCollectionDestructuring from '../../data/statList'; +import { ChartConfiguration } from './ChartConfigs'; +import ChartErrorBoundary from './ChartErrorBoundary'; + +const LinearChart = ({ height, specialPoints, timeRange, nivoData }) => { + console.log('NIVODATA ', nivoData); + return ( + + + + ); +}; + +// Define prop types for LinearChart component +LinearChart.propTypes = { + height: PropTypes.number.isRequired, + specialPoints: PropTypes.arrayOf(PropTypes.object), + timeRange: PropTypes.string.isRequired, + nivoData: PropTypes.object.isRequired, +}; + +export default LinearChart; diff --git a/src/layout/collection/collectionGrids/cards-chart/NivoContainer.jsx b/src/layout/collection/collectionGrids/cards-chart/NivoContainer.jsx new file mode 100644 index 0000000..6227f25 --- /dev/null +++ b/src/layout/collection/collectionGrids/cards-chart/NivoContainer.jsx @@ -0,0 +1,9 @@ +const NivoContainer = ({ children, height }) => ( +
+
+
{children}
+
+
+); + +export default NivoContainer; diff --git a/src/layout/collection/sub-components/UpdaterAndStatisticsRow.jsx b/src/layout/collection/collectionGrids/cards-chart/UpdaterAndStatisticsRow.jsx similarity index 56% rename from src/layout/collection/sub-components/UpdaterAndStatisticsRow.jsx rename to src/layout/collection/collectionGrids/cards-chart/UpdaterAndStatisticsRow.jsx index 1b92e21..d10860e 100644 --- a/src/layout/collection/sub-components/UpdaterAndStatisticsRow.jsx +++ b/src/layout/collection/collectionGrids/cards-chart/UpdaterAndStatisticsRow.jsx @@ -1,7 +1,8 @@ import { Grid } from '@mui/material'; -import UpdateStatusBox2 from '../../../components/other/InputComponents/UpdateStatusBox2'; -import TimeRangeSelector from '../../../components/forms/TimeRangeSelector'; -import CollectionStatisticsSelector from '../../../components/forms/CollectionStatisticsSelector'; +// import UpdateStatusBox2 from '../../../components/other/InputComponents/UpdateStatusBox2'; +import TimeRangeSelector from '../../../../components/forms/selectors/TimeRangeSelector'; +import CollectionStatisticsSelector from '../../../../components/forms/selectors/CollectionStatisticsSelector'; +import ThemeSelector from '../../../../components/forms/selectors/ThemeSelector'; /** * Renders a row of components for updating status, selecting time range, @@ -23,23 +24,23 @@ export const UpdaterAndStatisticsRow = ({ container spacing={2} sx={{ - marginTop: 2, - marginBottom: 2, + // marginTop: 2, + // marginBottom: 2, + width: '100%', // Ensure the container takes full width flexDirection: isSmall ? 'column' : 'row', + // m: '0 auto', }} > {/* Update Status Box */} - - {/* */} + + - {/* Time Range Selector */} - + - {/* Collection Statistics Selector */} - + diff --git a/src/layout/collection/collectionGrids/cards-datatable/DataTableHeadCell.jsx b/src/layout/collection/collectionGrids/cards-datatable/DataTableHeadCell.jsx index af77183..7ee33a1 100644 --- a/src/layout/collection/collectionGrids/cards-datatable/DataTableHeadCell.jsx +++ b/src/layout/collection/collectionGrids/cards-datatable/DataTableHeadCell.jsx @@ -52,7 +52,7 @@ function DataTableHeadCell({ width, children, sorted, align, ...rest }) { gap={0.5} // Adds space between text and icon if sorted > { + let sortedValue; + + if (isSorted && column.isSorted) { + sortedValue = column.isSortedDesc ? 'desc' : 'asce'; + } else if (isSorted) { + sortedValue = 'none'; + } else { + sortedValue = false; + } + + return sortedValue; +}; +function DataTable({ + entriesPerPage, + canSearch, + showTotalEntries, + table, + pagination, + isSorted, + noEndBorder, + tableSize, +}) { + // ** New Custom Breakpoints for Tracking Visible Table Data ** + // 800px - hide total price + // 650px = hide quantity + // 500px - hdie check box + const { theme } = useMode(); + const [showTotalPrice, setShowTotalPrice] = useState(window.innerWidth > 800); + const [showQuantity, setShowQuantity] = useState(window.innerWidth > 650); + const [showSelection, setShowSelection] = useState(window.innerWidth > 500); + const [showPrice, setShowPrice] = useState(window.innerWidth > 445); + useEffect(() => { + const handleResize = () => { + setShowTotalPrice(window.innerWidth > 800); + setShowQuantity(window.innerWidth > 650); + setShowSelection(window.innerWidth > 500); + setShowPrice(window.innerWidth > 445); + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + const data = useMemo(() => table.data, [table.data]); + const columns = useMemo(() => { + let baseColumns = [ + // Only include the selection column if showSelection is true + showSelection && { + id: 'selection', + Header: ({ getToggleAllRowsSelectedProps }) => ( + + ), + Cell: ({ row }) => , + }, + { Header: 'Name', accessor: 'name' }, + // { Header: 'Price', accessor: 'price' }, + { + id: 'action', + Header: 'Action', + accessor: 'action', + Cell: ({ value }) => ( + console.log('clicked')} + onSuccess={() => + enqueueSnackbar( + { + title: 'Action successful', + message: `Card added to ${value} successfully.`, + }, + 'success', + null + ) + } + onFailure={(error) => + enqueueSnackbar( + { + title: 'Action failed', + message: `Failed to add card to ${value}.`, + }, + 'error', + error + ) + } + page={'Collection'} + cardSize={'small'} + /> + ), + }, + ]; + if (tableSize !== 'large' && showTotalPrice) { + baseColumns.push({ + Header: 'Total Price', + accessor: 'tPrice', + }); + } + if (tableSize !== 'large' && showQuantity) { + baseColumns.push({ + Header: 'Quantity', + accessor: 'quantity', + }); + } + if (tableSize !== 'large' && showPrice) { + baseColumns.push({ + Header: 'Price', + accessor: 'price', + }); + } + // Filter out any falsey values to remove the conditionally included columns when not shown + return baseColumns.filter(Boolean); + }, [showTotalPrice, showQuantity, showSelection, tableSize, showPrice]); + + const defaultPageSize = useMemo( + () => entriesPerPage.defaultValue, + [entriesPerPage] + ); + const pageSizeOptions = useMemo( + () => entriesPerPage.entries, + [entriesPerPage] + ); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + prepareRow, + page, + canPreviousPage, + canNextPage, + pageOptions, + gotoPage, + nextPage, + previousPage, + setPageSize, + setGlobalFilter, + selectedFlatRows, + toggleAllRowsSelected, + state: { pageIndex, pageSize, globalFilter }, + } = useTable( + { + columns, + data, + initialState: { pageIndex: 0, pageSize: entriesPerPage.defaultValue }, + }, + useGlobalFilter, + useSortBy, + usePagination, + useRowSelect + ); + const [search, setSearch] = useState(globalFilter); + + useEffect(() => { + setGlobalFilter(search || undefined); + }, [search, setGlobalFilter]); + + useEffect(() => { + setPageSize(defaultPageSize); + }, [defaultPageSize, setPageSize]); + + const handleSelectAllClick = (event) => { + toggleAllRowsSelected(event.target.checked); + }; + + let entriesEnd; + if (pageIndex === 0) { + entriesEnd = pageSize; + } else if (pageIndex === pageOptions.length - 1) { + entriesEnd = data.length; + } else { + entriesEnd = pageSize * (pageIndex + 1); + } + + return ( + + + {/* Search and Entries Per Page Options */} + setSearch(e.target.value)} + pageSize={pageSize} + setPageSize={(size) => setPageSize(Number(size))} + pageOptions={pageSizeOptions} + /> + {/* Table */} + + {}} + headerGroups={headerGroups} + isSorted={isSorted} + setSortedValue={setSortedValue} + /> + {/* Table Body */} + + {page.map((row, key) => { + prepareRow(row); + return ( + + {row.cells.map((cell, idx) => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
+ {/* Pagination */} + +
+
+ ); +} + +DataTable.propTypes = { + entriesPerPage: PropTypes.shape({ + defaultValue: PropTypes.number, + entries: PropTypes.arrayOf(PropTypes.number), + }).isRequired, + canSearch: PropTypes.bool, + showTotalEntries: PropTypes.bool, + table: PropTypes.shape({ + columns: PropTypes.array.isRequired, + data: PropTypes.array.isRequired, + }).isRequired, + isSorted: PropTypes.bool, + noEndBorder: PropTypes.bool, +}; +DataTable.defaultProps = { + canSearch: false, + showTotalEntries: true, + isSorted: true, + noEndBorder: false, +}; + +export default DataTable; diff --git a/src/layout/collection/collectionGrids/cards-datatable/useSkeletonLoader.jsx b/src/layout/collection/collectionGrids/cards-datatable/useSkeletonLoader.jsx new file mode 100644 index 0000000..c296376 --- /dev/null +++ b/src/layout/collection/collectionGrids/cards-datatable/useSkeletonLoader.jsx @@ -0,0 +1,209 @@ +import React from 'react'; +import { Skeleton, Box, Stack, Grid } from '@mui/material'; + +// Enhanced skeleton configurations for a wider variety of types +const skeletonVariants = { + title: { + variant: 'text', + width: '60%', + height: 32, // Approximate height of a title + }, + subtitle: { + variant: 'text', + width: '40%', + height: 24, // Approximate height of a subtitle + }, + button: { + variant: 'rectangular', + width: 180, // Approximate width of a button + height: 40, // Approximate height of a button + }, + chart: { + variant: 'rectangular', + width: '100%', + height: 300, + }, + listItem: { + variant: 'rectangular', + width: '100%', + height: 50, + marginBottom: 1, + }, + text: { + variant: 'text', + width: '60%', + height: 40, + }, + card: { + variant: 'rectangular', + width: '100%', + height: 200, + marginBottom: 2, + }, + avatar: { + variant: 'circular', + width: 40, + height: 40, + marginBottom: 1, + }, + dashboardPanel: { + variant: 'rectangular', + width: '100%', + height: 150, + marginBottom: 2, + }, + grid: { + variant: 'rectangular', + width: '100%', + height: 150, // Default height for grid items + }, + gridContainer: { + variant: 'rectangular', + width: '100%', + height: 150, + marginBottom: 2, + }, + gridItem: { + variant: 'rectangular', + width: '100%', + height: 150, + marginBottom: 2, + }, +}; + +/** + * Custom hook to return skeleton loaders based on type, with enhanced styles and variants. + * @returns A skeleton component based on the specified type, with added flexibility for customization. + */ +const useSkeletonLoader = () => { + const SkeletonLoader = ({ + type = 'text', + count = 3, + gridProps = { + container: true, + spacing: 2, + }, + gridItemProps = { + xs: 12, + sm: 6, + md: 3, + lg: 4, + }, + styleProps = {}, + contentProps = { + typeData: [ + { + id: 0, + type: 'avatar', + num: 1, + index: 0, + }, + { + id: 1, + type: 'title', + num: 1, + index: 1, + }, + { + id: 2, + type: 'subtitle', + num: 1, + index: 2, + }, + ], + numOfItems: 3, + types: ['title', 'subtitle', 'avatar'], + }, + ...props + }) => { + // if (type === 'grid') { + // const { variant, width, height } = skeletonVariants.grid; + // const typesKeyMap = contentProps.types.reduce( + // (acc, type) => ({ ...acc, [contentProps.numOfItems]: type }), + // {} + // ); + // console.log(typesKeyMap); + // const selectedVariant = typesKeyMap[count] || variant; + + // // Render skeletons within Grid container for 'grid' type + // return ( + // + // {Array.from({ length: count }, (_, index) => ( + // + // {/* Customize Grid item props as needed */} + // + // + // ))} + // + // ); + // } + // const skeletonVariants = { + // grid: { variant: 'rect', width: 210, height: 118 }, + // text: { variant: 'text', width: '100%', height: 20, marginBottom: 1 }, + // // Add other types as needed... + // }; + + const generateVariantSequence = () => { + return contentProps?.typeData?.flatMap((item) => + Array.from({ length: item.num }, () => item.type) + ); + }; + + if (type === 'grid') { + const variantSequence = generateVariantSequence(); + return ( + + {variantSequence.map((variantType, index) => { + const { variant, width, height } = skeletonVariants[variantType]; + return ( + + + + ); + })} + + ); + } + const { variant, width, height, marginBottom } = + skeletonVariants[type] || skeletonVariants.text; + + // Support for rendering multiple skeletons of the same type, useful for lists + return ( + + {Array.from({ length: count }, (_, index) => ( + + + + ))} + + ); + }; + + return { SkeletonLoader }; +}; + +export default useSkeletonLoader; diff --git a/src/layout/collection/collectionGrids/collections-list/CollectionListItem.jsx b/src/layout/collection/collectionGrids/collections-list/CollectionListItem.jsx index 51664bc..ad57aa1 100644 --- a/src/layout/collection/collectionGrids/collections-list/CollectionListItem.jsx +++ b/src/layout/collection/collectionGrids/collections-list/CollectionListItem.jsx @@ -1,10 +1,14 @@ /* eslint-disable react/display-name */ import React, { memo, useCallback, useRef, useState } from 'react'; -import { CardActionArea, Grid, Tooltip } from '@mui/material'; +import { Card, CardActionArea, Grid, Tooltip } from '@mui/material'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import PropTypes from 'prop-types'; -import { useAuthContext, useMode } from '../../../../context'; +import { + useAuthContext, + useMode, + useVisibilityContext, +} from '../../../../context'; import LongMenu from '../../../../layout/navigation/LongMenu'; import useCollectionVisibility from '../../../../context/hooks/useCollectionVisibility'; import MDBox from '../../../../layout/REUSABLE_COMPONENTS/MDBOX'; @@ -15,134 +19,149 @@ import { } from '../../../../pages/pageStyles/StyledComponents'; import useCollectionManager from '../../../../context/MAIN_CONTEXT/CollectionContext/useCollectionManager'; import CollectionDialog from '../../../../components/dialogs/CollectionDialog'; +import useSelectedCollection from '../../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import useDialogState from '../../../../context/hooks/useDialogState'; +import SimpleCard from '../../../REUSABLE_COMPONENTS/unique/SimpleCard'; +import uniqueTheme from '../../../REUSABLE_COMPONENTS/unique/uniqueTheme'; + const CollectionInfoItem = ({ label, value, sx }) => ( - + + + {label}: {value} + + + {/* {label}: {value} - + */} ); -const CollectionListItem = memo( - ({ collection, statsByCollectionId, roundToNearestTenth }) => { - const { theme } = useMode(); - const { isLoggedIn, userId } = useAuthContext(); - const ref = useRef(null); - const { deleteCollection, setSelectedCollection } = useCollectionManager( - isLoggedIn, - userId - ); - const [showOptions, setShowOptions] = useState(false); - const [isDialogOpen, setIsDialogOpen] = useState(false); - const [currentCollectionData, setCurrentCollectionData] = useState({}); - const handleOpenDialog = useCallback((collection) => { - setCurrentCollectionData(collection); - setIsDialogOpen(true); - }, []); +const CollectionListItem = memo(({ collection }) => { + const { theme } = useMode(); + const roundToNearestTenth = (number) => Math.ceil(number / 10) * 10; + const ref = useRef(null); + const { deleteCollection } = useCollectionManager(); + const { + handleBackToCollections, + showCollections, + selectedCollection, + handleSelectCollection, + toggleShowCollections, + } = useSelectedCollection(); + const { + isCollectionVisible, + toggleCollectionVisibility, + // dialogStates, + // toggleDialog, + } = useVisibilityContext(); - const twentyFourHourChange = statsByCollectionId?.twentyFourHourAverage; - const { handleSelectCollection, showCollections } = - useCollectionVisibility(); - const handleSelect = useCallback( - (collection) => { - return () => { - handleSelectCollection(collection?._id); - setSelectedCollection(collection); - }; - }, - [handleSelectCollection, setSelectedCollection, showCollections] - ); - const handleDelete = async () => { - await deleteCollection(collection?._id); - setShowOptions(false); // Close the options menu after deletion - }; - const renderToolTip = () => ( - -
- - handleOpenDialog(collection)} - onDelete={handleDelete} // Pass the async delete function - onSelect={() => handleSelect(collection)} - onStats={() => console.log('Stats:', collection)} - onView={() => console.log('View:', collection)} - onClose={() => setShowOptions(false)} - collectionId={collection._id} - ref={ref} - /> - -
-
- ); - const renderPercentageChange = useCallback(() => { - const percentageChange = - collection?.collectionStatistics?.percentageChange || 0; - return ( - 0 ? 'success' : 'error'} - sx={{ display: 'flex', alignItems: 'center' }} - > - {percentageChange > 0 ? : } - {percentageChange}% - - ); - }, [collection]); + const { dialogState, openDialog, closeDialog } = useDialogState({ + isEditCollectionDialogOpen: false, + }); + const handleOpenDialog = useCallback((collection) => { + openDialog('editCollectionDialog', collection); + console.log(collection); + }, []); + const handleCloseDialog = useCallback(() => { + closeDialog('editCollectionDialog'); + }, []); - return ( - - - - - - - - - {renderPercentageChange()} - - - - - - {renderToolTip()} - - {isDialogOpen && ( - setIsDialogOpen(false)} - isNew={false} - collectionMode="edit" - collectionData={currentCollectionData} + const handleDelete = async () => { + await deleteCollection(collection?._id); + // setShowOptions(false); // Close the options menu after deletion + }; + const renderToolTip = () => ( + +
+ + handleOpenDialog(collection)} + onDelete={handleDelete} // Pass the async delete function + onSelect={() => handleSelectCollection(collection)} + onHide={() => toggleShowCollections(false)} + onStats={() => console.log('Stats:', collection)} + onView={() => console.log('View:', collection)} + // onClose={() => setShowOptions(false)} + collectionId={collection?._id} + ref={ref} /> - )} - + +
+
+ ); + const renderPercentageChange = useCallback(() => { + const percentageChange = + collection?.collectionStatistics?.percentageChange || 0; + return ( + 0 ? 'success' : 'error'} + sx={{ display: 'flex', alignItems: 'center' }} + > + {percentageChange > 0 ? : } + {percentageChange}% + ); - } -); + }, [collection]); + const handleSelection = useCallback(() => { + handleSelectCollection(collection); + toggleCollectionVisibility(); + }, [collection]); + + return ( + + + + + + + + + {renderPercentageChange()} + + + + + + {renderToolTip()} + + {dialogState.isEditCollectionDialogOpen && ( + handleCloseDialog()} + isNew={false} + collectionMode="edit" + collectionData={{ + name: collection?.name, + description: collection?.description, + }} + /> + )} + + ); +}); CollectionListItem.propTypes = { collection: PropTypes.object.isRequired, diff --git a/src/layout/collection/collectionGrids/collections-list/SelectCollectionHeader.jsx b/src/layout/collection/collectionGrids/collections-list/SelectCollectionHeader.jsx new file mode 100644 index 0000000..3ddb427 --- /dev/null +++ b/src/layout/collection/collectionGrids/collections-list/SelectCollectionHeader.jsx @@ -0,0 +1,114 @@ +import React, { useEffect } from 'react'; +import { Grid, Button, Box } from '@mui/material'; +import { + useAuthContext, + useFormContext, + useMode, + useUserContext, +} from '../../../../context'; +import { Card, Typography } from '@mui/joy'; +import MDButton from '../../../REUSABLE_COMPONENTS/MDBUTTON'; +import CustomButton from '../../../../components/buttons/other/CustomButton'; +import useSkeletonLoader from '../cards-datatable/useSkeletonLoader'; +import IconStatWrapper from '../../../REUSABLE_COMPONENTS/unique/IconStatWrapper'; +import uniqueTheme from '../../../REUSABLE_COMPONENTS/unique/uniqueTheme'; +import SimpleButton from '../../../REUSABLE_COMPONENTS/unique/SimpleButton'; +import styled from 'styled-components'; +import SimpleCard from '../../../REUSABLE_COMPONENTS/unique/SimpleCard'; +import FlexBetween from '../../../REUSABLE_COMPONENTS/FlexBetween'; +import SimpleSectionHeader from '../../../REUSABLE_COMPONENTS/unique/SimpleSectionHeader'; +import MDBox from '../../../REUSABLE_COMPONENTS/MDBOX'; +const StyledButtonWrapper = styled.div` + margin-bottom: 1rem; +`; +const SelectCollectionHeaderSkeleton = () => { + const { SkeletonLoader } = useSkeletonLoader(); + + return ( + + + + + + + + + + + + ); +}; + +const FlexContainer = styled(Box)` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: ${({ theme }) => theme.spacing(1, 2)}; +`; + +const HeaderContainer = styled.div` + flex: 1; + max-width: 25%; +`; + +const ButtonContainer = styled.div` + flex: 1; + display: flex; + justify-content: flex-end; + max-width: 50%; +`; + +const SelectCollectionHeader = ({ openNewDialog }) => { + const { theme } = useMode(); + const { setCurrentForm } = useFormContext(); + const { user } = useUserContext(); + + if (!user) { + return ; + } + + return ( + + + + + + + + { + setCurrentForm('addCollectionForm'); + openNewDialog(); + }} + > + Add New Collection + + + + + ); +}; + +export default SelectCollectionHeader; diff --git a/src/layout/collection/collectionGrids/collections-list/SelectCollectionList.jsx b/src/layout/collection/collectionGrids/collections-list/SelectCollectionList.jsx index 724cb0a..d4dc736 100644 --- a/src/layout/collection/collectionGrids/collections-list/SelectCollectionList.jsx +++ b/src/layout/collection/collectionGrids/collections-list/SelectCollectionList.jsx @@ -1,183 +1,159 @@ // import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; -// import { Box, Card, Collapse, Grid, List, Skeleton } from '@mui/material'; +// import { +// Box, +// Card, +// CardActionArea, +// Collapse, +// Grid, +// List, +// Skeleton, +// } from '@mui/material'; // import PropTypes from 'prop-types'; -// import { roundToNearestTenth } from '../../../../context/Helpers'; -// import { useMode, usePageContext } from '../../../../context'; -// import CollectionListItem from './CollectionListItem'; -// import { StyledSkeletonCard } from '../../../../pages/pageStyles/StyledComponents'; -// import { SelectCollectionListContainer } from '../../../../context/hooks/style-hooks/usePortfolioStyles'; -// import MDBox from '../../../REUSABLE_COMPONENTS/MDBOX'; // import { TransitionGroup } from 'react-transition-group'; -// const MemoizedCollectionListItem = memo(CollectionListItem); - -// function renderItem({ collection, roundToNearestTenth }) { +// import CollectionListItem from './CollectionListItem'; +// import useSelectedCollection from '../../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +// import SimpleCard from '../../../REUSABLE_COMPONENTS/unique/SimpleCard'; +// import uniqueTheme from '../../../REUSABLE_COMPONENTS/unique/uniqueTheme'; +// import FlexBetween from '../../../REUSABLE_COMPONENTS/FlexBetween'; +// // +// // +// const CollectionListItemSkeleton = ({ count }) => { // return ( -// +// +// +// +// +// +// +// +// +// +// +// // ); -// } -// const SelectCollectionList = ({ allCollections, openDialog }) => { -// const { theme } = useMode(); -// const { loadingStatus } = usePageContext(); -// const listRef = useRef(null); +// }; + +// CollectionListItemSkeleton.propTypes = { +// count: PropTypes.number, +// }; + +// CollectionListItemSkeleton.displayName = 'CollectionListItemSkeleton'; + +// const SelectCollectionList = ({ openDialog }) => { +// const { allCollections } = useSelectedCollection(); +// const minListSize = 5; +// const collectionCount = allCollections?.length || 0; +// const skeletonsNeeded = Math.max(0, minListSize - collectionCount); -// useEffect(() => { -// if (listRef.current) { -// let totalHeight = 0; -// Array.from(listRef.current.children).forEach((child) => { -// totalHeight += child.offsetHeight; -// }); -// listRef.current.style.minHeight = `${totalHeight}px`; -// } -// }, [allCollections, loadingStatus]); // Depend on allCollections and loadingStatus -// const renderSkeletons = useCallback( -// (count) => -// Array.from({ length: count }).map((_, index) => ( -// -// -// -// -// -// -// -// -// -// -// -// -// -// )), -// [theme] -// ); // return ( -// +// // // -// {allCollections?.map((collection) => ( -// -// {renderItem({ collection, roundToNearestTenth })} +// {allCollections?.map((collection, index) => ( +// +// +// {skeletonsNeeded > 0 && ( +// +// )} // // ))} -// {allCollections.length < 5 && -// renderSkeletons(5 - allCollections.length)} // // -// +// // ); // }; // SelectCollectionList.propTypes = { // openDialog: PropTypes.func.isRequired, -// allCollections: PropTypes.array.isRequired, // Ensure this is passed or obtained from context // }; // export default SelectCollectionList; import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; -import { Box, Card, Collapse, Grid, List, Skeleton } from '@mui/material'; +import { + Box, + Card, + CardActionArea, + Collapse, + Grid, + List, + Skeleton, +} from '@mui/material'; import PropTypes from 'prop-types'; import { TransitionGroup } from 'react-transition-group'; import CollectionListItem from './CollectionListItem'; -import MDBox from '../../../REUSABLE_COMPONENTS/MDBOX'; -import { useMode } from '../../../../context'; -import LoadingIndicator from '../../../../components/reusable/indicators/LoadingIndicator'; - -const MemoizedCollectionListItem = memo(CollectionListItem); - -function renderItem({ collection, roundToNearestTenth }) { - return ( - - ); -} -const SelectCollectionList = ({ allCollections, openDialog }) => { - const { theme } = useMode(); - const [show, setShow] = useState( - new Array(allCollections.length).fill(false) - ); - const roundToNearestTenth = (number) => Math.ceil(number / 10) * 10; - if (!allCollections) return ; - useEffect(() => { - allCollections?.forEach((_, index) => { - setTimeout(() => { - setShow((show) => { - const updatedShow = [...show]; - updatedShow[index] = true; - return updatedShow; - }); - }, index * 300); // Adjust the delay as needed - }); - }, [allCollections.length]); - - const renderSkeletons = useCallback( - (count) => - Array.from({ length: count }).map((_, index) => ( - - - - - - - - - - +import useSelectedCollection from '../../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import styled from 'styled-components'; +import SimpleCard from '../../../REUSABLE_COMPONENTS/unique/SimpleCard'; +import uniqueTheme from '../../../REUSABLE_COMPONENTS/unique/uniqueTheme'; +const FlexContainer = styled(Box)` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: ${({ theme }) => theme.spacing(1, 2)}; +`; +const CollectionListItemSkeleton = ({ count }) => { + return [...Array(count).keys()].map((index) => ( + + + + + + + + - - - )), - [theme] - ); + + + + + )); +}; + +CollectionListItemSkeleton.propTypes = { + count: PropTypes.number.isRequired, +}; +const SelectCollectionList = ({ openDialog }) => { + const { allCollections } = useSelectedCollection(); + // const [skeletonCount, setSkeletonCount] = useState(0); + const listRef = useRef(); + const minItems = 5; + const numRequired = minItems - (allCollections?.length || 0); + const allSkeletonCollections = [...Array(numRequired).keys()].map((index) => ( + + )); + const combinedCollections = [...allCollections, ...allSkeletonCollections]; return ( - + - {allCollections.map((collection, index) => ( - - {renderItem({ collection, roundToNearestTenth })} + {combinedCollections?.map((collection, index) => ( + + ))} - {allCollections.length < 5 && - renderSkeletons(5 - allCollections.length)} + {/* */} - + ); }; SelectCollectionList.propTypes = { openDialog: PropTypes.func.isRequired, - allCollections: PropTypes.array.isRequired, }; -export default SelectCollectionList; +export default memo(SelectCollectionList); diff --git a/src/layout/collection/collectionGrids/collections-list/StatBoard.jsx b/src/layout/collection/collectionGrids/collections-list/StatBoard.jsx new file mode 100644 index 0000000..c30585a --- /dev/null +++ b/src/layout/collection/collectionGrids/collections-list/StatBoard.jsx @@ -0,0 +1,83 @@ +import { Grid } from '@mui/material'; +// import PieChart from './statItems/PieChart'; +// import TotalPriceStatBox from './statItems/TotalPriceStatBox'; +// import ValuDistributionCircle from './statItems/ValuDistributionCircle'; +// import PricedCardList from './statItems/PricedCardList'; +import SimpleCard from '../../../REUSABLE_COMPONENTS/unique/SimpleCard'; +import uniqueTheme from '../../../REUSABLE_COMPONENTS/unique/uniqueTheme'; +import { ChartArea } from '../../../../pages/pageStyles/StyledComponents'; +import NivoContainer from '../cards-chart/NivoContainer'; +import { useMode } from '../../../../context'; +import ValuDistributionCircle from './statItems/ValuDistributionCircle'; +import PricedCardList from './statItems/PricedCardList'; +import ZenEnso from '../../../../assets/animations/ZenEnso'; +import MDBox from '../../../REUSABLE_COMPONENTS/MDBOX'; +// import ValueDistPieChart from './statItems/ValueDistPieChart'; + +// const StatBoard = () => { +// return ( +// +// +// {item} +// +// +// {item} +// +// +// {item} +// +// +// ); +// }; + +const StatBoard = () => { + const { theme } = useMode(); + return ( + + + {/* + + + {' '} + */} + {/* */} + {/* */} + {/* + + */} + {/* */} + {/* + {/* + + */} + {/* + + */} + + + + + + ); +}; + +export default StatBoard; diff --git a/src/layout/collection/collectionGrids/collections-list/statItems/Header.jsx b/src/layout/collection/collectionGrids/collections-list/statItems/Header.jsx new file mode 100644 index 0000000..8726172 --- /dev/null +++ b/src/layout/collection/collectionGrids/collections-list/statItems/Header.jsx @@ -0,0 +1,28 @@ +import { Typography, Box, useTheme } from '@mui/material'; +import { useMode } from '../../../../../context'; + +const Header = ({ title, subtitle }) => { + const { theme } = useMode(); + const colors = theme.palette.chartTheme; + const grey = theme.palette.chartTheme.grey.darkest; + const lightGrey = theme.palette.chartTheme.grey.lightest; + const greenAccent = colors.greenAccent.light; + + return ( + + + {title} + + + {subtitle} + + + ); +}; + +export default Header; diff --git a/src/layout/collection/collectionGrids/collections-list/statItems/PieChart.jsx b/src/layout/collection/collectionGrids/collections-list/statItems/PieChart.jsx new file mode 100644 index 0000000..0c36f5c --- /dev/null +++ b/src/layout/collection/collectionGrids/collections-list/statItems/PieChart.jsx @@ -0,0 +1,232 @@ +import { mockPieData as data } from '../../../data/mockData'; +import { useMode } from '../../../../../context'; +import { ResponsivePie } from '@nivo/pie'; + +const PieChart = () => { + // const { theme } = useMode(); + // const colors = theme.palette.chartTheme; + // const lightGrey = colors.grey.lightest; + return ( + + ); +}; + +export default PieChart; diff --git a/src/layout/collection/collectionGrids/collections-list/statItems/PricedCardList.jsx b/src/layout/collection/collectionGrids/collections-list/statItems/PricedCardList.jsx new file mode 100644 index 0000000..a255e6c --- /dev/null +++ b/src/layout/collection/collectionGrids/collections-list/statItems/PricedCardList.jsx @@ -0,0 +1,111 @@ +import MDBox from '../../../../REUSABLE_COMPONENTS/MDBOX'; +import BoxHeader from '../../../../REUSABLE_COMPONENTS/BoxHeader'; +import useSelectedCollection from '../../../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import useCollectionStats from '../../../../../context/MAIN_CONTEXT/CollectionContext/useCollectionStats'; +import { Box } from '@mui/material'; +import { useMode } from '../../../../../context'; +import { DataGrid } from '@mui/x-data-grid'; +import prepareTableData from '../../../data/topCards'; + +const PricedCardList = () => { + const { collectionStats, metaStats } = useCollectionStats(); + const { selectedCollection } = useSelectedCollection(); + const { data, columns } = prepareTableData(selectedCollection); + const { theme } = useMode(); + const colors = theme.palette.chartTheme; + const grey = theme.palette.chartTheme.grey.darkest; + const lightGrey = theme.palette.chartTheme.grey.lightest; + + const greenAccent = colors.greenAccent.default; + + // const renderCurrency = (params) => `$${params.value}`; + + // const productColumns = [ + // { + // field: '_id', + // headerName: 'ID', + // flex: 1, + // }, + // { + // field: 'expense', + // headerName: 'Expense', + // flex: 0.5, + // renderCell: renderCurrency, + // }, + // { + // field: 'price', + // headerName: 'Price', + // flex: 0.5, + // renderCell: renderCurrency, + // }, + // ]; + + // const countItems = (params) => params.value.length; + + // const transactionColumns = [ + // { + // field: '_id', + // headerName: 'ID', + // flex: 1, + // }, + // { + // field: 'buyer', + // headerName: 'Buyer', + // flex: 0.67, + // }, + // { + // field: 'amount', + // headerName: 'Amount', + // flex: 0.35, + // renderCell: renderCurrency, + // }, + // { + // field: 'productIds', + // headerName: 'Count', + // flex: 0.1, + // renderCell: countItems, + // }, + // ]; + + // Assuming productData is available in your context or props + // const productData = []; // Placeholder for actual data + + return ( + + + + + + + ); +}; + +export default PricedCardList; diff --git a/src/layout/collection/collectionGrids/collections-list/statItems/TotalPriceStatBox.jsx b/src/layout/collection/collectionGrids/collections-list/statItems/TotalPriceStatBox.jsx new file mode 100644 index 0000000..84508fb --- /dev/null +++ b/src/layout/collection/collectionGrids/collections-list/statItems/TotalPriceStatBox.jsx @@ -0,0 +1,37 @@ +import MonetizationOnIcon from '@mui/icons-material/MonetizationOn'; +import useCollectionStats from '../../../../../context/MAIN_CONTEXT/CollectionContext/useCollectionStats'; +import { useMode } from '../../../../../context'; +import { Box } from '@mui/material'; +import StatBox from '../../../../REUSABLE_COMPONENTS/StatBox'; + +const TotalPriceStatBox = () => { + const { collectionStats, metaStats } = useCollectionStats(); + const { theme } = useMode(); + const colors = theme.palette.chartTheme; + const primary = colors.primary.dark; + const greenAccent = colors.greenAccent.light; + return ( + + + } + /> + + ); +}; + +export default TotalPriceStatBox; diff --git a/src/layout/collection/collectionGrids/collections-list/statItems/ValuDistributionCircle.jsx b/src/layout/collection/collectionGrids/collections-list/statItems/ValuDistributionCircle.jsx new file mode 100644 index 0000000..cc1472b --- /dev/null +++ b/src/layout/collection/collectionGrids/collections-list/statItems/ValuDistributionCircle.jsx @@ -0,0 +1,32 @@ +import { Box, Typography } from '@mui/material'; +import { useMode } from '../../../../../context'; +import ProgressCircle from '../../../../REUSABLE_COMPONENTS/ProgressCircle'; + +const ValuDistributionCircle = () => { + const { theme } = useMode(); + const colors = theme.palette.chartTheme; + const primary = colors.primary.dark; + const greenAccent = colors.greenAccent.default; + + return ( + + + Campaign + + + + + $48,352 revenue generated + + Includes extra misc expenditures and costs + + + ); +}; + +export default ValuDistributionCircle; diff --git a/src/layout/collection/collectionGrids/collections-list/statItems/ValueDistPieChart.jsx b/src/layout/collection/collectionGrids/collections-list/statItems/ValueDistPieChart.jsx new file mode 100644 index 0000000..a2b3d6c --- /dev/null +++ b/src/layout/collection/collectionGrids/collections-list/statItems/ValueDistPieChart.jsx @@ -0,0 +1,27 @@ +// import { Box } from '@mui/material'; +// import Header from './Header'; +// import NivoContainer from '../../cards-chart/NivoContainer'; +// import { useMode } from '../../../../../context'; +// import { ChartArea } from '../../../../../pages/pageStyles/StyledComponents'; +// import ChartErrorBoundary from '../../cards-chart/ChartErrorBoundary'; +// import PieChart from './PieChart'; + +// const ValueDistPieChart = () => { +// const { theme } = useMode(); +// return ( +// +//
+// {/* */} +// +// {/* */} +// +// +// +// {/* */} +// +// {/* */} +// +// ); +// }; + +// export default ValueDistPieChart; diff --git a/src/layout/collection/collectionGrids/index.jsx b/src/layout/collection/collectionGrids/index.jsx deleted file mode 100644 index b178a35..0000000 --- a/src/layout/collection/collectionGrids/index.jsx +++ /dev/null @@ -1,240 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { useMemo, useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import { - useTable, - usePagination, - useGlobalFilter, - useAsyncDebounce, - useSortBy, - useRowSelect, -} from 'react-table'; -// @mui material components -import Table from '@mui/material/Table'; -import TableContainer from '@mui/material/TableContainer'; -import TableRow from '@mui/material/TableRow'; -import Icon from '@mui/material/Icon'; -import DataTableBodyCell from './cards-datatable/DataTableBodyCell'; -import useScreenWidth from '../../../context/hooks/useScreenWidth'; -import { Button, Checkbox, Grid, TableBody, TextField } from '@mui/material'; -import PaginationComponent from './cards-datatable/PaginationComponent'; -import OptionsComponent from './cards-datatable/OptionsComponent'; -import { DataTableHeadComponent } from './cards-datatable/DataTableHeadComponent'; -import BoxHeader from '../../REUSABLE_COMPONENTS/BoxHeader'; -import GenericActionButtons from '../../../components/buttons/actionButtons/GenericActionButtons'; -const setSortedValue = (column, isSorted) => { - let sortedValue; - - if (isSorted && column.isSorted) { - sortedValue = column.isSortedDesc ? 'desc' : 'asce'; - } else if (isSorted) { - sortedValue = 'none'; - } else { - sortedValue = false; - } - - return sortedValue; -}; -function DataTable({ - entriesPerPage, - canSearch, - showTotalEntries, - table, - pagination, - isSorted, - noEndBorder, - tableSize, -}) { - const data = useMemo(() => table.data, [table.data]); - const columns = useMemo(() => { - const baseColumns = [ - { - id: 'selection', - Header: ({ getToggleAllRowsSelectedProps }) => ( - - ), - Cell: ({ row }) => , - }, - { Header: 'Name', accessor: 'name' }, - { Header: 'Price', accessor: 'price' }, - { - id: 'action', - Header: 'Action', - accessor: 'action', - Cell: ({ value }) => ( - - ), - }, - ]; - - if (tableSize !== 'large') { - baseColumns.push( - { - Header: 'Total Price', - accessor: 'tPrice', - }, - { - Header: 'Quantity', - accessor: 'quantity', - } - ); - } - - return baseColumns; - }, [tableSize]); - - const defaultPageSize = useMemo( - () => entriesPerPage.defaultValue, - [entriesPerPage] - ); - const pageSizeOptions = useMemo( - () => entriesPerPage.entries, - [entriesPerPage] - ); - - const { - getTableProps, - getTableBodyProps, - headerGroups, - prepareRow, - page, - canPreviousPage, - canNextPage, - pageOptions, - gotoPage, - nextPage, - previousPage, - setPageSize, - setGlobalFilter, - selectedFlatRows, - toggleAllRowsSelected, - state: { pageIndex, pageSize, globalFilter }, - } = useTable( - { - columns, - data, - initialState: { pageIndex: 0, pageSize: entriesPerPage.defaultValue }, - }, - useGlobalFilter, - useSortBy, - usePagination, - useRowSelect - ); - const [search, setSearch] = useState(globalFilter); - - useEffect(() => { - setGlobalFilter(search || undefined); - }, [search, setGlobalFilter]); - - useEffect(() => { - setPageSize(defaultPageSize); - }, [defaultPageSize, setPageSize]); - - const handleSelectAllClick = (event) => { - toggleAllRowsSelected(event.target.checked); - }; - - let entriesEnd; - if (pageIndex === 0) { - entriesEnd = pageSize; - } else if (pageIndex === pageOptions.length - 1) { - entriesEnd = data.length; - } else { - entriesEnd = pageSize * (pageIndex + 1); - } - - return ( - - {/* SECTION HEADER */} - table_chart} - sideText="+4%" - /> - {/* Search and Entries Per Page Options */} - setSearch(e.target.value)} - pageSize={pageSize} - setPageSize={(size) => setPageSize(Number(size))} - pageOptions={pageSizeOptions} - /> - {/* Table */} - - {}} - headerGroups={headerGroups} - isSorted={isSorted} - setSortedValue={setSortedValue} - /> - {/* Table Body */} - - {page.map((row, key) => { - prepareRow(row); - return ( - - {row.cells.map((cell, idx) => ( - - {cell.render('Cell')} - - ))} - - ); - })} - -
- {/* Pagination */} - -
- ); -} - -DataTable.propTypes = { - entriesPerPage: PropTypes.shape({ - defaultValue: PropTypes.number, - entries: PropTypes.arrayOf(PropTypes.number), - }).isRequired, - canSearch: PropTypes.bool, - showTotalEntries: PropTypes.bool, - table: PropTypes.shape({ - columns: PropTypes.array.isRequired, - data: PropTypes.array.isRequired, - }).isRequired, - isSorted: PropTypes.bool, - noEndBorder: PropTypes.bool, -}; -DataTable.defaultProps = { - canSearch: false, - showTotalEntries: true, - isSorted: true, - noEndBorder: false, -}; - -export default DataTable; diff --git a/src/layout/collection/data/collectionPortfolioData.jsx b/src/layout/collection/data/collectionPortfolioData.jsx index 0c8e2b9..e77ddca 100644 --- a/src/layout/collection/data/collectionPortfolioData.jsx +++ b/src/layout/collection/data/collectionPortfolioData.jsx @@ -3,6 +3,7 @@ import Icon from '@mui/material/Icon'; import MDTypography from '../../REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; import React from 'react'; import GenericActionButtons from '../../../components/buttons/actionButtons/GenericActionButtons'; +import { useSnackbar } from 'notistack'; const Name = ({ name }) => ( ( ); export default function prepareTableData(selectedCards) { const roundToNearestTenth = (value) => Math.round(value * 10) / 10; + const { enqueueSnackbar } = useSnackbar(); const columns = React.useMemo( () => [ { @@ -80,7 +82,33 @@ export default function prepareTableData(selectedCards) { Header: 'Action', accessor: 'action', Cell: ({ value }) => ( - + console.log('clicked')} + onSuccess={() => + enqueueSnackbar( + { + title: 'Action successful', + message: `Card added to ${value} successfully.`, + }, + 'success', + null + ) + } + onFailure={(error) => + enqueueSnackbar( + { + title: 'Action failed', + message: `Failed to add card to ${value}.`, + }, + 'error', + error + ) + } + page={'Collection'} + cardSize={'small'} + /> ), }, ], @@ -92,7 +120,7 @@ export default function prepareTableData(selectedCards) { selectedCards?.map((card) => ({ ...card, tPrice: roundToNearestTenth(card.totalPrice), - action: '', // Assuming you have some action value or logic to insert here + action: card, })), [selectedCards] ); diff --git a/src/layout/collection/data/mockData.jsx b/src/layout/collection/data/mockData.jsx new file mode 100644 index 0000000..9fdf4bf --- /dev/null +++ b/src/layout/collection/data/mockData.jsx @@ -0,0 +1,1131 @@ +export const mockDataTeam = [ + { + id: 1, + name: 'Jon Snow', + email: 'jonsnow@gmail.com', + age: 35, + phone: '(665)121-5454', + access: 'admin', + }, + { + id: 2, + name: 'Cersei Lannister', + email: 'cerseilannister@gmail.com', + age: 42, + phone: '(421)314-2288', + access: 'manager', + }, + { + id: 3, + name: 'Jaime Lannister', + email: 'jaimelannister@gmail.com', + age: 45, + phone: '(422)982-6739', + access: 'user', + }, + { + id: 4, + name: 'Anya Stark', + email: 'anyastark@gmail.com', + age: 16, + phone: '(921)425-6742', + access: 'admin', + }, + { + id: 5, + name: 'Daenerys Targaryen', + email: 'daenerystargaryen@gmail.com', + age: 31, + phone: '(421)445-1189', + access: 'user', + }, + { + id: 6, + name: 'Ever Melisandre', + email: 'evermelisandre@gmail.com', + age: 150, + phone: '(232)545-6483', + access: 'manager', + }, + { + id: 7, + name: 'Ferrara Clifford', + email: 'ferraraclifford@gmail.com', + age: 44, + phone: '(543)124-0123', + access: 'user', + }, + { + id: 8, + name: 'Rossini Frances', + email: 'rossinifrances@gmail.com', + age: 36, + phone: '(222)444-5555', + access: 'user', + }, + { + id: 9, + name: 'Harvey Roxie', + email: 'harveyroxie@gmail.com', + age: 65, + phone: '(444)555-6239', + access: 'admin', + }, +]; + +export const mockDataContacts = [ + { + id: 1, + name: 'Jon Snow', + email: 'jonsnow@gmail.com', + age: 35, + phone: '(665)121-5454', + address: '0912 Won Street, Alabama, SY 10001', + city: 'New York', + zipCode: '10001', + registrarId: 123512, + }, + { + id: 2, + name: 'Cersei Lannister', + email: 'cerseilannister@gmail.com', + age: 42, + phone: '(421)314-2288', + address: '1234 Main Street, New York, NY 10001', + city: 'New York', + zipCode: '13151', + registrarId: 123512, + }, + { + id: 3, + name: 'Jaime Lannister', + email: 'jaimelannister@gmail.com', + age: 45, + phone: '(422)982-6739', + address: '3333 Want Blvd, Estanza, NAY 42125', + city: 'New York', + zipCode: '87281', + registrarId: 4132513, + }, + { + id: 4, + name: 'Anya Stark', + email: 'anyastark@gmail.com', + age: 16, + phone: '(921)425-6742', + address: '1514 Main Street, New York, NY 22298', + city: 'New York', + zipCode: '15551', + registrarId: 123512, + }, + { + id: 5, + name: 'Daenerys Targaryen', + email: 'daenerystargaryen@gmail.com', + age: 31, + phone: '(421)445-1189', + address: '11122 Welping Ave, Tenting, CD 21321', + city: 'Tenting', + zipCode: '14215', + registrarId: 123512, + }, + { + id: 6, + name: 'Ever Melisandre', + email: 'evermelisandre@gmail.com', + age: 150, + phone: '(232)545-6483', + address: '1234 Canvile Street, Esvazark, NY 10001', + city: 'Esvazark', + zipCode: '10001', + registrarId: 123512, + }, + { + id: 7, + name: 'Ferrara Clifford', + email: 'ferraraclifford@gmail.com', + age: 44, + phone: '(543)124-0123', + address: '22215 Super Street, Everting, ZO 515234', + city: 'Evertin', + zipCode: '51523', + registrarId: 123512, + }, + { + id: 8, + name: 'Rossini Frances', + email: 'rossinifrances@gmail.com', + age: 36, + phone: '(222)444-5555', + address: '4123 Ever Blvd, Wentington, AD 142213', + city: 'Esteras', + zipCode: '44215', + registrarId: 512315, + }, + { + id: 9, + name: 'Harvey Roxie', + email: 'harveyroxie@gmail.com', + age: 65, + phone: '(444)555-6239', + address: '51234 Avery Street, Cantory, ND 212412', + city: 'Colunza', + zipCode: '111234', + registrarId: 928397, + }, + { + id: 10, + name: 'Enteri Redack', + email: 'enteriredack@gmail.com', + age: 42, + phone: '(222)444-5555', + address: '4123 Easer Blvd, Wentington, AD 142213', + city: 'Esteras', + zipCode: '44215', + registrarId: 533215, + }, + { + id: 11, + name: 'Steve Goodman', + email: 'stevegoodmane@gmail.com', + age: 11, + phone: '(444)555-6239', + address: '51234 Fiveton Street, CunFory, ND 212412', + city: 'Colunza', + zipCode: '1234', + registrarId: 92197, + }, +]; + +export const mockDataInvoices = [ + { + id: 1, + name: 'Jon Snow', + email: 'jonsnow@gmail.com', + cost: '21.24', + phone: '(665)121-5454', + date: '03/12/2022', + }, + { + id: 2, + name: 'Cersei Lannister', + email: 'cerseilannister@gmail.com', + cost: '1.24', + phone: '(421)314-2288', + date: '06/15/2021', + }, + { + id: 3, + name: 'Jaime Lannister', + email: 'jaimelannister@gmail.com', + cost: '11.24', + phone: '(422)982-6739', + date: '05/02/2022', + }, + { + id: 4, + name: 'Anya Stark', + email: 'anyastark@gmail.com', + cost: '80.55', + phone: '(921)425-6742', + date: '03/21/2022', + }, + { + id: 5, + name: 'Daenerys Targaryen', + email: 'daenerystargaryen@gmail.com', + cost: '1.24', + phone: '(421)445-1189', + date: '01/12/2021', + }, + { + id: 6, + name: 'Ever Melisandre', + email: 'evermelisandre@gmail.com', + cost: '63.12', + phone: '(232)545-6483', + date: '11/02/2022', + }, + { + id: 7, + name: 'Ferrara Clifford', + email: 'ferraraclifford@gmail.com', + cost: '52.42', + phone: '(543)124-0123', + date: '02/11/2022', + }, + { + id: 8, + name: 'Rossini Frances', + email: 'rossinifrances@gmail.com', + cost: '21.24', + phone: '(222)444-5555', + date: '05/02/2021', + }, +]; + +export const mockTransactions = [ + { + txId: '01e4dsa', + user: 'johndoe', + date: '2021-09-01', + cost: '43.95', + }, + { + txId: '0315dsaa', + user: 'jackdower', + date: '2022-04-01', + cost: '133.45', + }, + { + txId: '01e4dsa', + user: 'aberdohnny', + date: '2021-09-01', + cost: '43.95', + }, + { + txId: '51034szv', + user: 'goodmanave', + date: '2022-11-05', + cost: '200.95', + }, + { + txId: '0a123sb', + user: 'stevebower', + date: '2022-11-02', + cost: '13.55', + }, + { + txId: '01e4dsa', + user: 'aberdohnny', + date: '2021-09-01', + cost: '43.95', + }, + { + txId: '120s51a', + user: 'wootzifer', + date: '2019-04-15', + cost: '24.20', + }, + { + txId: '0315dsaa', + user: 'jackdower', + date: '2022-04-01', + cost: '133.45', + }, +]; + +// export const mockBarData = [ +// { +// country: 'AD', +// 'hot dog': 137, +// 'hot dogColor': 'hsl(229, 70%, 50%)', +// burger: 96, +// burgerColor: 'hsl(296, 70%, 50%)', +// kebab: 72, +// kebabColor: 'hsl(97, 70%, 50%)', +// donut: 140, +// donutColor: 'hsl(340, 70%, 50%)', +// }, +// { +// country: 'AE', +// 'hot dog': 55, +// 'hot dogColor': 'hsl(307, 70%, 50%)', +// burger: 28, +// burgerColor: 'hsl(111, 70%, 50%)', +// kebab: 58, +// kebabColor: 'hsl(273, 70%, 50%)', +// donut: 29, +// donutColor: 'hsl(275, 70%, 50%)', +// }, +// { +// country: 'AF', +// 'hot dog': 109, +// 'hot dogColor': 'hsl(72, 70%, 50%)', +// burger: 23, +// burgerColor: 'hsl(96, 70%, 50%)', +// kebab: 34, +// kebabColor: 'hsl(106, 70%, 50%)', +// donut: 152, +// donutColor: 'hsl(256, 70%, 50%)', +// }, +// { +// country: 'AG', +// 'hot dog': 133, +// 'hot dogColor': 'hsl(257, 70%, 50%)', +// burger: 52, +// burgerColor: 'hsl(326, 70%, 50%)', +// kebab: 43, +// kebabColor: 'hsl(110, 70%, 50%)', +// donut: 83, +// donutColor: 'hsl(9, 70%, 50%)', +// }, +// { +// country: 'AI', +// 'hot dog': 81, +// 'hot dogColor': 'hsl(190, 70%, 50%)', +// burger: 80, +// burgerColor: 'hsl(325, 70%, 50%)', +// kebab: 112, +// kebabColor: 'hsl(54, 70%, 50%)', +// donut: 35, +// donutColor: 'hsl(285, 70%, 50%)', +// }, +// { +// country: 'AL', +// 'hot dog': 66, +// 'hot dogColor': 'hsl(208, 70%, 50%)', +// burger: 111, +// burgerColor: 'hsl(334, 70%, 50%)', +// kebab: 167, +// kebabColor: 'hsl(182, 70%, 50%)', +// donut: 18, +// donutColor: 'hsl(76, 70%, 50%)', +// }, +// { +// country: 'AM', +// 'hot dog': 80, +// 'hot dogColor': 'hsl(87, 70%, 50%)', +// burger: 47, +// burgerColor: 'hsl(141, 70%, 50%)', +// kebab: 158, +// kebabColor: 'hsl(224, 70%, 50%)', +// donut: 49, +// donutColor: 'hsl(274, 70%, 50%)', +// }, +// ]; + +export const mockPieData = [ + { + id: 'hack', + label: 'hack', + value: 239, + color: 'hsl(104, 70%, 50%)', + }, + { + id: 'make', + label: 'make', + value: 170, + color: 'hsl(162, 70%, 50%)', + }, + { + id: 'go', + label: 'go', + value: 322, + color: 'hsl(291, 70%, 50%)', + }, + { + id: 'lisp', + label: 'lisp', + value: 503, + color: 'hsl(229, 70%, 50%)', + }, + { + id: 'scala', + label: 'scala', + value: 584, + color: 'hsl(344, 70%, 50%)', + }, +]; + +export const mockLineData = [ + { + id: '7d', + data: [ + { x: '2023-01-01', y: 3.1 }, + { x: '2023-01-02', y: 3.4 }, + { x: '2023-01-03', y: 2.1 }, + { x: '2023-01-04', y: 6.7 }, + { x: '2023-01-05', y: 9.4 }, + { x: '2023-01-06', y: 17.2 }, + { x: '2023-01-07', y: 14.4 }, + ], + }, +]; + +// export const mockGeographyData = [ +// { +// id: 'AFG', +// value: 520600, +// }, +// { +// id: 'AGO', +// value: 949905, +// }, +// { +// id: 'ALB', +// value: 329910, +// }, +// { +// id: 'ARE', +// value: 675484, +// }, +// { +// id: 'ARG', +// value: 432239, +// }, +// { +// id: 'ARM', +// value: 288305, +// }, +// { +// id: 'ATA', +// value: 415648, +// }, +// { +// id: 'ATF', +// value: 665159, +// }, +// { +// id: 'AUT', +// value: 798526, +// }, +// { +// id: 'AZE', +// value: 481678, +// }, +// { +// id: 'BDI', +// value: 496457, +// }, +// { +// id: 'BEL', +// value: 252276, +// }, +// { +// id: 'BEN', +// value: 440315, +// }, +// { +// id: 'BFA', +// value: 343752, +// }, +// { +// id: 'BGD', +// value: 920203, +// }, +// { +// id: 'BGR', +// value: 261196, +// }, +// { +// id: 'BHS', +// value: 421551, +// }, +// { +// id: 'BIH', +// value: 974745, +// }, +// { +// id: 'BLR', +// value: 349288, +// }, +// { +// id: 'BLZ', +// value: 305983, +// }, +// { +// id: 'BOL', +// value: 430840, +// }, +// { +// id: 'BRN', +// value: 345666, +// }, +// { +// id: 'BTN', +// value: 649678, +// }, +// { +// id: 'BWA', +// value: 319392, +// }, +// { +// id: 'CAF', +// value: 722549, +// }, +// { +// id: 'CAN', +// value: 332843, +// }, +// { +// id: 'CHE', +// value: 122159, +// }, +// { +// id: 'CHL', +// value: 811736, +// }, +// { +// id: 'CHN', +// value: 593604, +// }, +// { +// id: 'CIV', +// value: 143219, +// }, +// { +// id: 'CMR', +// value: 630627, +// }, +// { +// id: 'COG', +// value: 498556, +// }, +// { +// id: 'COL', +// value: 660527, +// }, +// { +// id: 'CRI', +// value: 60262, +// }, +// { +// id: 'CUB', +// value: 177870, +// }, +// { +// id: '-99', +// value: 463208, +// }, +// { +// id: 'CYP', +// value: 945909, +// }, +// { +// id: 'CZE', +// value: 500109, +// }, +// { +// id: 'DEU', +// value: 63345, +// }, +// { +// id: 'DJI', +// value: 634523, +// }, +// { +// id: 'DNK', +// value: 731068, +// }, +// { +// id: 'DOM', +// value: 262538, +// }, +// { +// id: 'DZA', +// value: 760695, +// }, +// { +// id: 'ECU', +// value: 301263, +// }, +// { +// id: 'EGY', +// value: 148475, +// }, +// { +// id: 'ERI', +// value: 939504, +// }, +// { +// id: 'ESP', +// value: 706050, +// }, +// { +// id: 'EST', +// value: 977015, +// }, +// { +// id: 'ETH', +// value: 461734, +// }, +// { +// id: 'FIN', +// value: 22800, +// }, +// { +// id: 'FJI', +// value: 18985, +// }, +// { +// id: 'FLK', +// value: 64986, +// }, +// { +// id: 'FRA', +// value: 447457, +// }, +// { +// id: 'GAB', +// value: 669675, +// }, +// { +// id: 'GBR', +// value: 757120, +// }, +// { +// id: 'GEO', +// value: 158702, +// }, +// { +// id: 'GHA', +// value: 893180, +// }, +// { +// id: 'GIN', +// value: 877288, +// }, +// { +// id: 'GMB', +// value: 724530, +// }, +// { +// id: 'GNB', +// value: 387753, +// }, +// { +// id: 'GNQ', +// value: 706118, +// }, +// { +// id: 'GRC', +// value: 377796, +// }, +// { +// id: 'GTM', +// value: 66890, +// }, +// { +// id: 'GUY', +// value: 719300, +// }, +// { +// id: 'HND', +// value: 739590, +// }, +// { +// id: 'HRV', +// value: 929467, +// }, +// { +// id: 'HTI', +// value: 538961, +// }, +// { +// id: 'HUN', +// value: 146095, +// }, +// { +// id: 'IDN', +// value: 490681, +// }, +// { +// id: 'IND', +// value: 549818, +// }, +// { +// id: 'IRL', +// value: 630163, +// }, +// { +// id: 'IRN', +// value: 596921, +// }, +// { +// id: 'IRQ', +// value: 767023, +// }, +// { +// id: 'ISL', +// value: 478682, +// }, +// { +// id: 'ISR', +// value: 963688, +// }, +// { +// id: 'ITA', +// value: 393089, +// }, +// { +// id: 'JAM', +// value: 83173, +// }, +// { +// id: 'JOR', +// value: 52005, +// }, +// { +// id: 'JPN', +// value: 199174, +// }, +// { +// id: 'KAZ', +// value: 181424, +// }, +// { +// id: 'KEN', +// value: 60946, +// }, +// { +// id: 'KGZ', +// value: 432478, +// }, +// { +// id: 'KHM', +// value: 254461, +// }, +// { +// id: 'OSA', +// value: 942447, +// }, +// { +// id: 'KWT', +// value: 414413, +// }, +// { +// id: 'LAO', +// value: 448339, +// }, +// { +// id: 'LBN', +// value: 620090, +// }, +// { +// id: 'LBR', +// value: 435950, +// }, +// { +// id: 'LBY', +// value: 75091, +// }, +// { +// id: 'LKA', +// value: 595124, +// }, +// { +// id: 'LSO', +// value: 483524, +// }, +// { +// id: 'LTU', +// value: 867357, +// }, +// { +// id: 'LUX', +// value: 689172, +// }, +// { +// id: 'LVA', +// value: 742980, +// }, +// { +// id: 'MAR', +// value: 236538, +// }, +// { +// id: 'MDA', +// value: 926836, +// }, +// { +// id: 'MDG', +// value: 840840, +// }, +// { +// id: 'MEX', +// value: 353910, +// }, +// { +// id: 'MKD', +// value: 505842, +// }, +// { +// id: 'MLI', +// value: 286082, +// }, +// { +// id: 'MMR', +// value: 915544, +// }, +// { +// id: 'MNE', +// value: 609500, +// }, +// { +// id: 'MNG', +// value: 410428, +// }, +// { +// id: 'MOZ', +// value: 32868, +// }, +// { +// id: 'MRT', +// value: 375671, +// }, +// { +// id: 'MWI', +// value: 591935, +// }, +// { +// id: 'MYS', +// value: 991644, +// }, +// { +// id: 'NAM', +// value: 701897, +// }, +// { +// id: 'NCL', +// value: 144098, +// }, +// { +// id: 'NER', +// value: 312944, +// }, +// { +// id: 'NGA', +// value: 862877, +// }, +// { +// id: 'NIC', +// value: 90831, +// }, +// { +// id: 'NLD', +// value: 281879, +// }, +// { +// id: 'NOR', +// value: 224537, +// }, +// { +// id: 'NPL', +// value: 322331, +// }, +// { +// id: 'NZL', +// value: 86615, +// }, +// { +// id: 'OMN', +// value: 707881, +// }, +// { +// id: 'PAK', +// value: 158577, +// }, +// { +// id: 'PAN', +// value: 738579, +// }, +// { +// id: 'PER', +// value: 248751, +// }, +// { +// id: 'PHL', +// value: 557292, +// }, +// { +// id: 'PNG', +// value: 516874, +// }, +// { +// id: 'POL', +// value: 682137, +// }, +// { +// id: 'PRI', +// value: 957399, +// }, +// { +// id: 'PRT', +// value: 846430, +// }, +// { +// id: 'PRY', +// value: 720555, +// }, +// { +// id: 'QAT', +// value: 478726, +// }, +// { +// id: 'ROU', +// value: 259318, +// }, +// { +// id: 'RUS', +// value: 268735, +// }, +// { +// id: 'RWA', +// value: 136781, +// }, +// { +// id: 'ESH', +// value: 151957, +// }, +// { +// id: 'SAU', +// value: 111821, +// }, +// { +// id: 'SDN', +// value: 927112, +// }, +// { +// id: 'SDS', +// value: 966473, +// }, +// { +// id: 'SEN', +// value: 158085, +// }, +// { +// id: 'SLB', +// value: 178389, +// }, +// { +// id: 'SLE', +// value: 528433, +// }, +// { +// id: 'SLV', +// value: 353467, +// }, +// { +// id: 'ABV', +// value: 251, +// }, +// { +// id: 'SOM', +// value: 445243, +// }, +// { +// id: 'SRB', +// value: 202402, +// }, +// { +// id: 'SUR', +// value: 972121, +// }, +// { +// id: 'SVK', +// value: 319923, +// }, +// { +// id: 'SVN', +// value: 728766, +// }, +// { +// id: 'SWZ', +// value: 379669, +// }, +// { +// id: 'SYR', +// value: 16221, +// }, +// { +// id: 'TCD', +// value: 101273, +// }, +// { +// id: 'TGO', +// value: 498411, +// }, +// { +// id: 'THA', +// value: 506906, +// }, +// { +// id: 'TJK', +// value: 613093, +// }, +// { +// id: 'TKM', +// value: 327016, +// }, +// { +// id: 'TLS', +// value: 607972, +// }, +// { +// id: 'TTO', +// value: 936365, +// }, +// { +// id: 'TUN', +// value: 898416, +// }, +// { +// id: 'TUR', +// value: 237783, +// }, +// { +// id: 'TWN', +// value: 878213, +// }, +// { +// id: 'TZA', +// value: 442174, +// }, +// { +// id: 'UGA', +// value: 720710, +// }, +// { +// id: 'UKR', +// value: 74172, +// }, +// { +// id: 'URY', +// value: 753177, +// }, +// { +// id: 'USA', +// value: 658725, +// }, +// { +// id: 'UZB', +// value: 550313, +// }, +// { +// id: 'VEN', +// value: 707492, +// }, +// { +// id: 'VNM', +// value: 538907, +// }, +// { +// id: 'VUT', +// value: 650646, +// }, +// { +// id: 'PSE', +// value: 476078, +// }, +// { +// id: 'YEM', +// value: 957751, +// }, +// { +// id: 'ZAF', +// value: 836949, +// }, +// { +// id: 'ZMB', +// value: 714503, +// }, +// { +// id: 'ZWE', +// value: 405217, +// }, +// { +// id: 'KOR', +// value: 171135, +// }, +// ]; diff --git a/src/layout/collection/data/statList.jsx b/src/layout/collection/data/statList.jsx new file mode 100644 index 0000000..978c285 --- /dev/null +++ b/src/layout/collection/data/statList.jsx @@ -0,0 +1,29 @@ +import useSelectedCollection from '../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; + +const data = [ + { + label: '', + value: null, + }, +]; + +const DynamicCollectionDestructuring = () => { + const { allCollections, selectedCollection } = useSelectedCollection(); + const { totalPrice, totalQuantity, collectionStatistics } = + selectedCollection; + const { avgPrice, highPoint, lowPoint, percentageChange, priceChange } = + collectionStatistics; + return ( +
+
{totalPrice}
+
{totalQuantity}
+
{avgPrice}
+
{highPoint}
+
{lowPoint}
+
{percentageChange}
+
{priceChange}
+
+ ); +}; + +export default DynamicCollectionDestructuring; diff --git a/src/layout/collection/data/topCards.jsx b/src/layout/collection/data/topCards.jsx new file mode 100644 index 0000000..f9828d9 --- /dev/null +++ b/src/layout/collection/data/topCards.jsx @@ -0,0 +1,95 @@ +import Icon from '@mui/material/Icon'; +// Images +import MDTypography from '../../REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import React from 'react'; +import GenericActionButtons from '../../../components/buttons/actionButtons/GenericActionButtons'; +import { useSnackbar } from 'notistack'; +const Name = ({ name }) => ( + + {name} + +); +const Price = ({ price }) => ( + + {price} + +); +const TPrice = ({ tPrice }) => ( + + {tPrice} + +); +const Quantity = ({ quantity }) => ( + + {quantity} + +); +export default function prepareTableData(selectedCards) { + const roundToNearestTenth = (value) => Math.round(value * 10) / 10; + const columns = React.useMemo( + () => [ + { + Header: 'Name', + accessor: 'name', + id: 'name', + Cell: ({ value }) => , + }, + { + Header: 'Price', + accessor: 'price', + id: 'price', + Cell: ({ value }) => , + }, + { + Header: 'Quantity', + accessor: 'quantity', + id: 'quantity', + Cell: ({ value }) => , + }, + ], + [] + ); + + const data = React.useMemo(() => { + if (!selectedCards || selectedCards.length === 0) { + return []; + } + // Sort by totalPrice in descending order and take the top 5 + const topFiveCards = selectedCards + ?.sort((a, b) => b.price - a.price) + .slice(0, 5) + .map((card) => ({ + ...card, + tPrice: roundToNearestTenth(card.totalPrice), + action: card, + })); + + return topFiveCards; + }, [selectedCards]); + + return { columns, data }; +} diff --git a/src/layout/collection/index.jsx b/src/layout/collection/index.jsx index 661935b..8c12811 100644 --- a/src/layout/collection/index.jsx +++ b/src/layout/collection/index.jsx @@ -1,14 +1,17 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import propTypes from 'prop-types'; import useCollectionVisibility from '../../context/hooks/useCollectionVisibility'; -import { useCollectionStore, useMode } from '../../context'; +import { + useCollectionStore, + useMode, + useVisibilityContext, +} from '../../context'; import DashboardLayout from '../Containers/DashBoardLayout'; import MDBox from '../REUSABLE_COMPONENTS/MDBOX'; import PageLayout from '../Containers/PageLayout'; import { PortfolioBoxA } from '../../pages/pageStyles/StyledComponents'; import { Grid, Card, Typography } from '@mui/material'; import { useFormContext } from '../../context'; -import SelectCollectionHeader from './sub-components/SelectCollectionHeader'; import CollectionDialog from '../../components/dialogs/CollectionDialog'; import collectionPortfolioData from './data/collectionPortfolioData'; import ChartGridLayout from './collectionGrids/ChartGridLayout'; @@ -16,153 +19,509 @@ import StatisticsCardGrid from './collectionGrids/StatisticsCardsGrid'; import CollectionPortfolioHeader from './sub-components/CollectionPortfolioHeader'; import CollectionListStats from './collectionGrids/CollectionListStats'; import SelectCollectionList from './collectionGrids/collections-list/SelectCollectionList'; +import useSelectedCollection from '../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import useSnackBar from '../../context/hooks/useSnackBar'; +import { withDynamicSnackbar } from '../REUSABLE_COMPONENTS/HOC/DynamicSnackbar'; +import SelectionErrorDialog from '../../components/dialogs/SelectionErrorDialog'; +import useCollectionManager from '../../context/MAIN_CONTEXT/CollectionContext/useCollectionManager'; +import useSkeletonLoader from './collectionGrids/cards-datatable/useSkeletonLoader'; +import { DEFAULT_COLLECTION } from '../../context/constants'; +import useDialogState from '../../context/hooks/useDialogState'; +import SelectCollectionHeader from './collectionGrids/collections-list/SelectCollectionHeader'; +import DashboardBox from '../REUSABLE_COMPONENTS/DashboardBox'; +import StatBoard from './collectionGrids/collections-list/StatBoard'; + +// function SelectCollectionView({ onNewDialog, onCloseDialog, dialogState }) { +// const { theme } = useMode(); +// const { allCollections, selectedCollection } = useSelectedCollection(); +// const { currentForm } = useFormContext(); +// return ( +// +// +// +// +// + +// +// +// +// +// +// {dialogState.isDialogOpen && ( +// +// )} +// {dialogState.isSelectionErrorDialogOpen && ( +// +// )} +// +// +// ); +// } const CollectionPortfolio = () => { const { theme } = useMode(); + const { fetchCollections, hasFetchedCollections } = useCollectionManager(); const { + handleBackToCollections, + // showCollections, selectedCollection, - hasFetchedCollections, + selectedCollectionId, allCollections, - totalQuantity, - removeOneFromCollection, - } = useCollectionStore(); - const { handleBackToCollections } = useCollectionVisibility(); - const [isDialogOpen, setDialogOpen] = useState(false); - const { currentForm } = useFormContext(); - const openNewDialog = useCallback((addOrEdit) => { - setDialogOpen(true); // Correctly opens the dialog + customError, + toggleShowCollections, + } = useSelectedCollection(); + // Initially fetch collections if needed + useEffect(() => { + fetchCollections(); }, []); - const handleDialogToggle = () => setDialogOpen(!isDialogOpen); - const handleCloseDialog = () => setDialogOpen(false); - const [collectionName, setCollectionName] = useState( - selectedCollection?.name || '' - ); - const { columns, data } = collectionPortfolioData( - selectedCollection?.cards, - null, - null, - null + const { currentForm } = useFormContext(); + const { + isCollectionVisible, + toggleCollectionVisibility, + // dialogStates, + // toggleDialog, + } = useVisibilityContext(); + + const { dialogState, openDialog, closeDialog } = useDialogState({ + isAddCollectionDialogOpen: false, + isSelectionErrorDialogOpen: false, + isConfirmationDialogOpen: false, // New dialog state + }); + const [viewState, setViewState] = useState({ + showListOfAllCollections: isCollectionVisible, + showSelectedCollection: !isCollectionVisible, + currentCollection: selectedCollectionId, + }); + const handleAddCollectionDialogToggle = useCallback(() => { + openDialog('isAddCollectionDialogOpen'); + }, [openDialog]); + + const handleCloseAddCollectionDialog = useCallback(() => { + closeDialog('isAddCollectionDialogOpen'); + }, [closeDialog]); + + const handleSelectionErrorDialogToggle = useCallback(() => { + openDialog('isSelectionErrorDialogOpen'); + }, [openDialog]); + + const handleCloseSelectionErrorDialog = useCallback(() => { + closeDialog('isSelectionErrorDialogOpen'); + }, [closeDialog]); + // const handleDialogToggle = () => + // setDialogState((prevState) => ({ + // ...prevState, + // isDialogOpen: !prevState.isDialogOpen, + // })); + // const handleCloseDialog = () => + // setDialogState((prevState) => ({ + // ...prevState, + // isDialogOpen: false, + // })); + // const handleErrorDialog = () => + // setDialogState((prevState) => ({ + // ...prevState, + // isSelectionErrorDialogOpen: true, + // })); + // const handleCloseErrorDialog = () => + // setDialogState((prevState) => ({ + // ...prevState, + // isSelectionErrorDialogOpen: false, + // })); + useEffect(() => { + if (customError === 'Selection Error') { + handleSelectionErrorDialogToggle(); + } else { + handleCloseSelectionErrorDialog(); + } + }, [customError]); + // const handleSelectedCollectionView = useCallback(() => { + // console.log('SWITCHING TO SELECTED COLLECTION VIEW', selectedCollectionId); + // setViewState((prevState) => ({ + // ...prevState, + // showListOfAllCollections: !prevState.showListOfAllCollections, + // showSelectedCollection: !prevState.showSelectedCollection, + // currentCollection: selectedCollectionId, + // })); + // }, [selectedCollectionId]); + const handleDialogToggle = useCallback( + (dialogName) => { + dialogState[dialogName] + ? closeDialog(dialogName) + : openDialog(dialogName); + }, + [dialogState, openDialog, closeDialog] ); + // const handleViewChange = useCallback(() => { + // toggleShowCollections(); + // }, [toggleShowCollections]); + useEffect(() => { + console.log('VIEW STATE CHANGED', viewState); + console.log('ALL COLLECTION', allCollections); + console.log('SELECTED COLLECTION', selectedCollection); + console.log('SELECTED COLLECTION ID', selectedCollectionId); + console.log('IS COLLECTION VISIBLE', isCollectionVisible); + }, [ + viewState, + allCollections, + isCollectionVisible, + selectedCollection, + selectedCollectionId, + ]); + const { columns, data } = collectionPortfolioData(selectedCollection?.cards); + + // if (!hasFetchedCollections) { + // return ( + // + // + // + // {/* Directly use the SkeletonLoader with 'grid' type and customize gridProps as needed */} + // + // + // + // + // ); + // } return ( - {selectedCollection && hasFetchedCollections ? ( + {!isCollectionVisible ? ( + + + + + + + + + + + + + {dialogState.isAddCollectionDialogOpen && ( + + )} + {dialogState.isSelectionErrorDialogOpen && ( + + )} + + + ) : ( + // Selected collection view - {/* HEADER */} - {/* STATISTICS FEATURE GRID */} - + {/* */} - {/* CHARTS */} - ) : ( - - - {/* HEADER */} - - - - - - {/* COLLECTION LIST STATS DISPLAY */} - - - - - - {/* COLLECTION LIST */} - - - - - - - {isDialogOpen && ( - - )} - - )} ); }; -CollectionPortfolio.propTypes = { - selectedCollection: propTypes.object, - hasFetchedCollections: propTypes.bool, - allCollections: propTypes.array, - totalQuantity: propTypes.number, - removeOneFromCollection: propTypes.func, -}; +export default withDynamicSnackbar(CollectionPortfolio); +// { +// viewState.showListOfAllCollections && ( +// +// +// +// +// -CollectionPortfolio.defaultProps = { - selectedCollection: {}, - hasFetchedCollections: false, - allCollections: [], - totalQuantity: 0, - // eslint-disable-next-line @typescript-eslint/no-empty-function - removeOneFromCollection: () => {}, -}; +// +// +// +// +// +// {dialogState.isDialogOpen && ( +// +// )} +// {dialogState.isSelectionErrorDialogOpen && ( +// +// )} +// +// +// ); +// } +// { +// viewState.showSelectedCollection && ( +// +// +// +// { +// handleBackToCollections(); +// handleSelectedCollectionView(); +// }} +// /> +// +// +// +// +// +// +// +// +// ); +// } +// const [isDialogOpen, setDialogOpen] = useState(false); +// const [isSelectionErrorDialogOpen, setSelectionErrorDialogOpen] = +// useState(false); +// const { currentForm } = useFormContext(); +// currentForm === 'addCollectionForm' +// const openNewDialog = useCallback((addOrEdit) => { +// setDialogOpen(true); // Correctly opens the dialog +// }, []); +// const handleDialogToggle = () => setDialogOpen(!isDialogOpen); +// const handleCloseDialog = () => setDialogOpen(false); +// const handleErrorDialog = () => setSelectionErrorDialogOpen(true); +// const handleCloseErrorDialog = () => setSelectionErrorDialogOpen(false); + +// if (!hasFetchedCollections) { +// return ( +// +// +// Loading... +// +// +// ); +// } + +// return ( +// +// +// {showCollections ? ( +// +// +// {/* HEADER */} +// +// +// +// {/* STATISTICS FEATURE GRID */} +// +// {/* */} +// +// {/* CHARTS */} +// +// +// +// +// +// ) : ( +// +// +// {/* HEADER */} +// +// +// +// +// +// {/* COLLECTION LIST STATS DISPLAY */} +// +// +// +// +// +// {/* COLLECTION LIST */} +// +// +// +// +// + +// {isDialogOpen && ( +// +// )} +// {isSelectionErrorDialogOpen && ( +// +// )} +// +// +// )} +// +// +// ); -export default CollectionPortfolio; +// export default withDynamicSnackbar(CollectionPortfolio); diff --git a/src/layout/collection/sub-components/CollectionPortfolioHeader.jsx b/src/layout/collection/sub-components/CollectionPortfolioHeader.jsx index 2b61580..b6705e1 100644 --- a/src/layout/collection/sub-components/CollectionPortfolioHeader.jsx +++ b/src/layout/collection/sub-components/CollectionPortfolioHeader.jsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { IconButton, Box, Grid } from '@mui/material'; +import React, { useEffect } from 'react'; +import { IconButton, Box, Grid, Grow, Card } from '@mui/material'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import CollectionsIcon from '@mui/icons-material/Collections'; import AttachMoneyIcon from '@mui/icons-material/AttachMoney'; @@ -9,55 +9,136 @@ import MDBox from '../../REUSABLE_COMPONENTS/MDBOX'; import MDTypography from '../../REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; import MDAvatar from '../../REUSABLE_COMPONENTS/MDAVATAR'; import { useMode } from '../../../context'; +import useSelectedCollection from '../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +import { TransitionGroup } from 'react-transition-group'; +import useSkeletonLoader from '../collectionGrids/cards-datatable/useSkeletonLoader'; +import { DEFAULT_COLLECTION } from '../../../context/constants'; +import uniqueTheme from '../../REUSABLE_COMPONENTS/unique/uniqueTheme'; +import SimpleCard from '../../REUSABLE_COMPONENTS/unique/SimpleCard'; +import IconStatWrapper from '../../REUSABLE_COMPONENTS/unique/IconStatWrapper'; +import DashboardBox from '../../REUSABLE_COMPONENTS/DashboardBox'; +const SelectCollectionHeaderSkeleton = () => { + const { SkeletonLoader } = useSkeletonLoader(); -const HeaderItem = ({ icon: IconComponent, label, value }) => { - const { theme } = useMode(); + return ( + + + + + + + + + + + + ); +}; +const HeaderItem = ({ icon, label, value, delay }) => { return ( - - - - - - - {label}: - + + + - - {value} - - + ); }; -const CollectionPortfolioHeader = ({ - onBack, - collectionName, - selectedCollection, - totalQuantity, -}) => { +const CollectionPortfolioHeader = ({ onBack, collection, allCollections }) => { const { theme } = useMode(); - if (!selectedCollection) { - return null; + const headerIcons = [ + CollectionsIcon, + AttachMoneyIcon, + FormatListNumberedIcon, + TrendingUpIcon, + ]; + useEffect(() => { + console.log('collection', collection); + }, [collection]); + if ( + !collection || + collection === DEFAULT_COLLECTION || + allCollections.length === 0 + ) { + return onBack(); } + const headerItems = [ + { + // icon: , + icon: 'collections', + label: 'Portfolio Selected', + value: collection?.name || 'Select a collection to view its statistics', + delay: 0, + }, + { + // icon: , + icon: 'attach_money', + label: 'Total Value', + value: + collection?.totalPrice || 'Select a collection to view its statistics', + delay: 200, + }, + { + // icon: , + icon: 'format_list_numbered', + label: 'Number of Unique Cards', + value: + collection?.cards?.length || + 'Select a collection to view its statistics', + delay: 400, + }, + { + // icon: , + icon: 'trending_up', + label: "Today's Performance", + value: + collection?.statistics?.percentChange || + 'Select a collection to view its statistics', + delay: 600, + }, + ]; + return ( - + {/* */} - - - - + {/* */} + {headerItems.map((item, index) => ( + + ))} + {/* */} - + {/* */} + ); }; export default CollectionPortfolioHeader; diff --git a/src/components/other/dataDisplay/ComplexStatisticsCard.jsx b/src/layout/collection/sub-components/ComplexStatisticsCard.jsx similarity index 96% rename from src/components/other/dataDisplay/ComplexStatisticsCard.jsx rename to src/layout/collection/sub-components/ComplexStatisticsCard.jsx index e0c3375..b8062a3 100644 --- a/src/components/other/dataDisplay/ComplexStatisticsCard.jsx +++ b/src/layout/collection/sub-components/ComplexStatisticsCard.jsx @@ -2,8 +2,8 @@ import PropTypes from 'prop-types'; import Card from '@mui/material/Card'; import Divider from '@mui/material/Divider'; import Icon from '@mui/material/Icon'; -import MDBox from '../../../layout/REUSABLE_COMPONENTS/MDBOX/index'; -import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import MDBox from '../../REUSABLE_COMPONENTS/MDBOX/index'; +import MDTypography from '../../REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; import { useMode } from '../../../context'; function ComplexStatisticsCard({ color, title, count, percentage, icon, sx }) { diff --git a/src/components/other/dataDisplay/PieChartStats.jsx b/src/layout/collection/sub-components/PieChartStats.jsx similarity index 100% rename from src/components/other/dataDisplay/PieChartStats.jsx rename to src/layout/collection/sub-components/PieChartStats.jsx diff --git a/src/layout/collection/sub-components/SelectCollectionHeader.jsx b/src/layout/collection/sub-components/SelectCollectionHeader.jsx deleted file mode 100644 index b77abe2..0000000 --- a/src/layout/collection/sub-components/SelectCollectionHeader.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { Grid, Button } from '@mui/material'; -import { useAuthContext, useFormContext, useMode } from '../../../context'; -import { Card, Typography } from '@mui/joy'; -import MDButton from '../../REUSABLE_COMPONENTS/MDBUTTON'; -import CustomButton from '../../../components/buttons/other/CustomButton'; -const SelectCollectionHeader = ({ openNewDialog }) => { - const { theme } = useMode(); - const { basicData } = useAuthContext(); - const { currentForm, setCurrentForm } = useFormContext(); - // Format the current date - const currentDate = new Date().toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric', - }); - return ( - - - - - Collection Portfolio - - {` ${basicData?.firstName}'s Portfolio`} - - - - Last updated: - - {` ${currentDate}`} - - - - - - { - setCurrentForm('addCollectionForm'); - openNewDialog(true); - }} - sx={{ - backgroundColor: theme.palette.backgroundD.lighter, - color: theme.palette.primary.main, - }} - /> - - - ); -}; - -export default SelectCollectionHeader; diff --git a/src/components/other/dataDisplay/StatisticsDisplaySection.jsx b/src/layout/collection/sub-components/StatisticsDisplaySection.jsx similarity index 98% rename from src/components/other/dataDisplay/StatisticsDisplaySection.jsx rename to src/layout/collection/sub-components/StatisticsDisplaySection.jsx index 465c93d..43bb2e9 100644 --- a/src/components/other/dataDisplay/StatisticsDisplaySection.jsx +++ b/src/layout/collection/sub-components/StatisticsDisplaySection.jsx @@ -6,7 +6,7 @@ import { useMode } from '../../../context'; import PieChartIcon from '@mui/icons-material/PieChart'; import AttachMoneyIcon from '@mui/icons-material/AttachMoney'; import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; -import MDBox from '../../../layout/REUSABLE_COMPONENTS/MDBOX'; +import MDBox from '../../REUSABLE_COMPONENTS/MDBOX'; const StatisticCard = styled(Card)(({ theme }) => ({ height: '100%', diff --git a/src/components/other/dataDisplay/TopCardsDisplay.jsx b/src/layout/collection/sub-components/TopCardsDisplay.jsx similarity index 97% rename from src/components/other/dataDisplay/TopCardsDisplay.jsx rename to src/layout/collection/sub-components/TopCardsDisplay.jsx index 9e48bf3..a7b706a 100644 --- a/src/components/other/dataDisplay/TopCardsDisplay.jsx +++ b/src/layout/collection/sub-components/TopCardsDisplay.jsx @@ -14,8 +14,8 @@ import { useCollectionStore } from '../../../context/MAIN_CONTEXT/CollectionCont import { styled } from 'styled-components'; import { MainContainer } from '../../../pages/pageStyles/StyledComponents'; -import CarouselCard from '../../cards/CarouselCard'; -import LoadingIndicator from '../../reusable/indicators/LoadingIndicator'; +import CarouselCard from '../../../components/cards/CarouselCard'; +import LoadingIndicator from '../../../components/reusable/indicators/LoadingIndicator'; import { useMode } from '../../../context'; const StyledStepper = styled(MobileStepper)(({ theme }) => ({ diff --git a/src/layout/collection/sub-components/TopCardsDisplayRow.jsx b/src/layout/collection/sub-components/TopCardsDisplayRow.jsx index b4ec7e8..825d56c 100644 --- a/src/layout/collection/sub-components/TopCardsDisplayRow.jsx +++ b/src/layout/collection/sub-components/TopCardsDisplayRow.jsx @@ -1,5 +1,5 @@ import { Grid } from '@mui/material'; -import TopCardsDisplay from '../../../components/other/dataDisplay/TopCardsDisplay'; +import TopCardsDisplay from './TopCardsDisplay'; export const TopCardsDisplayRow = ({ isSmall, theme }) => ( { const { theme } = useMode(); diff --git a/src/layout/store/StoreSearch.jsx b/src/layout/store/StoreSearch.jsx index d546774..51d2d99 100644 --- a/src/layout/store/StoreSearch.jsx +++ b/src/layout/store/StoreSearch.jsx @@ -2,8 +2,8 @@ import React, { useState } from 'react'; import { useMode } from '../../context'; import MDBox from '../REUSABLE_COMPONENTS/MDBOX'; import PageLayout from '../Containers/PageLayout'; -import SearchComponent from '../../components/other/search/SearchComponent'; import { PortfolioBoxA } from '../../pages/pageStyles/StyledComponents'; +import SearchComponent from '../../components/forms/search/SearchComponent'; const StoreSearch = () => { const { theme } = useMode(); diff --git a/src/pages/CartPage.js b/src/pages/CartPage.js index 03ec0a2..3e91a35 100644 --- a/src/pages/CartPage.js +++ b/src/pages/CartPage.js @@ -3,9 +3,10 @@ import { useCookies } from 'react-cookie'; import { Box, Card, CardContent, Grid, useTheme } from '@mui/material'; import CartContent from '../layout/cart/CartContent'; import { useCartStore, useMode, usePageContext } from '../context'; -import CartSummary from '../components/other/dataDisplay/CartSummary'; import Checkout from '../layout/cart/cartPageContainers/Checkout'; import PageLayout from '../layout/Containers/PageLayout'; +import { useLoading } from '../context/hooks/useLoading'; +import { CartSummary } from '../layout/cart/CartSummary'; const CartPage = () => { const { theme } = useMode(); @@ -18,19 +19,21 @@ const CartPage = () => { cartCardQuantity, totalCost, } = useCartStore(); - const { loadingStatus, returnDisplay, setLoading } = usePageContext(); + const { returnDisplay, setLoading } = usePageContext(); + const { startLoading, stopLoading, setError, isPageLoading } = useLoading(); const calculateTotalPrice = getTotalCost(); // useEffect hook to fetch cart data for user useEffect(() => { const fetchData = async () => { - setLoading('isPageLoading', true); + startLoading('isPageLoading'); try { await fetchCartForUser(); // Assuming fetchUserCart updates cartData } catch (error) { console.error('Error fetching cart data:', error); + setError(error.message || 'Failed to fetch cart data'); } finally { - setLoading('isPageLoading', false); + stopLoading('isPageLoading'); } }; @@ -104,7 +107,7 @@ const CartPage = () => { {/* */} {/* */} - {loadingStatus?.isPageLoading && returnDisplay()} + {isPageLoading && returnDisplay()} {/* {loadingStatus?.isLoading && returnDisplay()} */} { - const { theme } = useMode(); - const { selectedCollection, allCollections } = useCollectionStore(); - const { - loadingStatus, - returnDisplay, - isModalOpen, - modalContent, - closeModal, - } = useLoadingAndModal(); + const { selectedCollection, allCollections, resetCollection } = + useSelectedCollection(); + const { fetchCollections, hasFetchedCollections } = useCollectionManager(); + + const { returnDisplay, isModalOpen, modalContent, closeModal } = + useLoadingAndModal(); + const { isPageLoading } = useLoading(); + const isFirstRender = useIsFirstRender(); + + useEffect(() => { + if (!hasFetchedCollections) { + fetchCollections(); + } + }, []); return ( - {loadingStatus?.isPageLoading && returnDisplay()} + {isPageLoading && returnDisplay()} {!selectedCollection && ( { - const { loadingStatus, returnDisplay, isModalOpen, modalContent } = + const { isPageLoading } = useLoading(); + const { closeModal, returnDisplay, isModalOpen, modalContent } = useLoadingAndModal(); return ( - {loadingStatus?.isPageLoading && returnDisplay()} + {isPageLoading && returnDisplay()} { const { theme } = useMode(); const { tertiaryContent, secondaryContent, mainContentFeatureCard } = @@ -78,20 +53,9 @@ const getHomePageSkeletonConfigs = (tiers) => { }; const HomePage = () => { const { theme } = useMode(); - const breakpoints = theme.breakpoints; - const isSmUp = useMediaQuery(breakpoints.up('sm')); - const isMdUp = useMediaQuery(breakpoints.up('md')); - const isLgUp = useMediaQuery(breakpoints.up('lg')); - const { authUser, basicData } = useAuthContext(); - const { user } = useUserContext(); - const { allCollections, selectedCollection } = useCollectionStore(); - - const isDataLoaded = allCollections && allCollections.length > 0; const { tiers, introText } = pages; - - const initialCardData = isDataLoaded ? allCollections[0].cards[0] : null; - const { cardData } = useCardCronJob(initialCardData); - const { loadingStatus } = usePageContext(); + const { hasFetchedCollections } = useCollectionManager(); + const { allCollections, selectedCollection } = useSelectedCollection(); const { allFeatureData, showDetailsModal, @@ -99,8 +63,8 @@ const HomePage = () => { isModalOpen, modalContent, } = useModalContext(); - const homePageSkeletonConfigs = getHomePageSkeletonConfigs(tiers); const splashRef = useRef(null); + useEffect(() => { if (splashRef.current) { Object.assign(splashRef.current.style, { diff --git a/src/pages/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents.jsx b/src/pages/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents.jsx deleted file mode 100644 index 483f063..0000000 --- a/src/pages/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Box, Container, Paper } from '@mui/material'; -import styled from 'styled-components'; -import MDButton from '../../layout/REUSABLE_COMPONENTS/MDBUTTON'; - -// COLOR PALETTE: #777 - opaque -export const StyledContainerBoxPrimary = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - width: '100%', - minWidth: '100%', - marginTop: theme.spacing(2), - padding: theme.spacing(3), - borderRadius: theme.shape.borderRadius, - background: theme.palette.backgroundB.dark, - boxShadow: theme.shadows[10], - marginBottom: theme.spacing(4), - transition: 'all 0.3s ease-in-out', // smooth all transitions -})); - -// COLOR PALETTE: #8ec7b6 - opaque -export const StyledContainerBoxSecondary = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - marginTop: theme.spacing(2), - padding: theme.spacing(3), - borderRadius: theme.shape.borderRadius, - background: theme.palette.backgroundE.light, - boxShadow: theme.shadows[10], - marginBottom: theme.spacing(4), - transition: 'all 0.3s ease-in-out', // smooth all transitions -})); - -// COLOR PALETTE: #4cceac - transparent -export const StyledContainerBox = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - marginTop: theme.spacing(2), - padding: theme.spacing(3), - borderRadius: theme.shape.borderRadius, - background: theme.palette.backgroundD.dark, - boxShadow: theme.shadows[10], - marginBottom: theme.spacing(4), - transition: 'all 0.3s ease-in-out', // smooth all transitions -})); - -// COLOR PALETTE: #4cceac - transparent -export const StyledPaperPrimary = styled(Paper)(({ theme }) => ({ - padding: theme.spacing(2), - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[4], - backgroundColor: theme.palette.backgroundA.lightest, - color: theme.palette.text.primary, - display: 'flex', - flexDirection: 'column', -})); - -// COLOR PALETTE: #333 - transparent -export const StyledPaper = styled(Box)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - width: '100%', - justifyContent: 'center', - mx: 'auto', - padding: '1rem', - background: theme.palette.backgroundC.dark, - // maxWidth: '1200px', - borderRadius: '8px', - boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', - transition: 'all 0.3s ease-in-out', // smooth all transitions -})); - -// BUTTON: #4cceac - transparent -export const StyledButton = styled(MDButton)(({ theme }) => ({ - // background: theme.palette.backgroundE.darker, - // borderColor: theme.palette.backgroundB.darkest, - borderWidth: 2, - flexGrow: 1, - justifySelf: 'bottom', - bottom: 0, - mx: 1, - width: '70%', - '&:hover': { - // color: theme.palette.backgroundA.contrastTextC, - fontWeight: 'bold', - background: theme.palette.backgroundF.dark, - borderColor: theme.palette.backgroundB.darkest, - border: `1px solid ${theme.palette.backgroundB.darkest}`, - }, -})); diff --git a/src/pages/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx b/src/pages/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx deleted file mode 100644 index 0927a56..0000000 --- a/src/pages/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Box } from '@mui/material'; -import styled from 'styled-components'; - -export const HeroBox = styled(Box)(({ theme }) => ({ - width: '100%', - display: 'flex', - // minHeight: '600px', - alignItems: 'center', - justifyContent: 'center', - transition: 'all 0.3s ease-in-out', // smooth all transitions -})); diff --git a/src/pages/pageStyles/StyledComponents.jsx b/src/pages/pageStyles/StyledComponents.jsx index 8b93226..2f1d46e 100644 --- a/src/pages/pageStyles/StyledComponents.jsx +++ b/src/pages/pageStyles/StyledComponents.jsx @@ -37,16 +37,16 @@ import { SwiperSlide } from 'swiper/react'; // ! APP CONTAINER ----------------------------- export const AppContainer = styled(Container)(({ theme }) => ({ - // display: 'flex', flexDirection: 'column', - // height: '100vh', - // left: -20, margin: 0, padding: 0, justifyContent: 'center', minHeight: '100vh', // Ensures at least the height of the viewport width: '100%', // Ensures it takes the full width minWidth: '100vw', // Ensures at least the width of the viewport + maxWidth: '100vw', + overflowX: 'hidden', + boxSizing: 'border-box' /* Ensures padding is included in width */, // overflow: 'hidden', // Prevents unwanted scrolling if not necessary // [theme.breakpoints.down('sm')]: { // paddingTop: theme.spacing(6), @@ -56,6 +56,7 @@ export const AppContainer = styled(Container)(({ theme }) => ({ // ! TOP BAR STYLES ----------------------------- export const StyledAppBar = styled(AppBar)(({ theme }) => ({ minWidth: '100vw', + maxWidth: '100vw', // minWidth: '100%', border: '2px solid white', // left: -20, @@ -464,9 +465,7 @@ export const ChartArea = styled(Container)(({ theme }) => ({ height: '100%', width: '100%', minHeight: '500px', - // minWidth: '450px', position: 'relative', - // justifyContent: 'space-between', flexGrow: 1, padding: theme.spacing(2), margin: theme.spacing(2), @@ -476,7 +475,7 @@ export const ChartArea = styled(Container)(({ theme }) => ({ justifyContent: 'center', border: `1px solid ${theme.palette.divider}`, borderRadius: theme.shape.borderRadius, - background: theme.palette.backgroundB.lightest, + background: theme.palette.backgroundB.contrastText, })); export const SquareChartContainer = styled(Box)(({ theme }) => ({ position: 'relative', @@ -702,44 +701,6 @@ export const SearchSettingsBox = styled(Box)(({ theme }) => ({ gap: theme.spacing(2), })); -// ! DIALOG STYLES -export const StyledDialog = styled(Dialog)(({ theme }) => ({ - '& .MuiDialog-paper': { - borderRadius: theme.shape.borderRadius, - padding: theme.spacing(4), - display: 'flex', - backgroundColor: theme.palette.backgroundB.lightest, // Choose a subtle color - color: theme.palette.text.primary, - boxShadow: theme.shadows[5], - '@media (max-width:600px)': { - margin: theme.spacing(2), - }, - }, - '& .MuiDialog-paperScrollPaper': { - maxHeight: '90vh', // Slightly more space - }, -})); - -export const StyledDialogContent = styled(DialogContent)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - width: '100%', - gap: theme.spacing(2), - padding: theme.spacing(3), - backgroundColor: theme.palette.backgroundA.lightest, -})); - -export const StyledDialogActions = styled(DialogActions)(({ theme }) => ({ - display: 'flex', - flexDirection: 'row', - flexGrow: 1, - width: '100%', - gap: theme.spacing(2), - padding: theme.spacing(3), - backgroundColor: theme.palette.backgroundA.lightest, -})); - // ! PORTFOLIO TABLE STYLES export const PortfolioTablePaper = styled(Paper)(({ theme }) => ({ maxWidth: 'lg', diff --git a/src/pages/pageStyles/useLoadingAndModal.jsx b/src/pages/pageStyles/useLoadingAndModal.jsx index 2f709e3..eefde2d 100644 --- a/src/pages/pageStyles/useLoadingAndModal.jsx +++ b/src/pages/pageStyles/useLoadingAndModal.jsx @@ -2,11 +2,10 @@ import { useModalContext, usePageContext } from '../../context'; const useLoadingAndModal = () => { - const { loadingStatus, returnDisplay } = usePageContext(); + const { returnDisplay } = usePageContext(); const { isModalOpen, modalContent, closeModal } = useModalContext(); return { - loadingStatus, returnDisplay, isModalOpen, modalContent, diff --git a/src/pages/sections/FeatureCardsSection.jsx b/src/pages/sections/FeatureCardsSection.jsx index d0babcb..c565459 100644 --- a/src/pages/sections/FeatureCardsSection.jsx +++ b/src/pages/sections/FeatureCardsSection.jsx @@ -7,11 +7,11 @@ import { AnimatedFeatureCard } from '../../components/cards/AnimatedFeatureCard' import { useModalContext } from '../../context/UTILITIES_CONTEXT/ModalContext/ModalContext'; import { useResponsiveStyles } from '../../context/hooks/style-hooks/useResponsiveStyles'; import { useMode } from '../../context'; +import pages from '../../assets/data/pages.json'; import { StyledContainerBox, StyledPaper, -} from '../REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; -import pages from '../../assets/data/pages.json'; +} from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; const FeatureCardsSection = () => { const { theme } = useMode(); diff --git a/src/pages/sections/HeroSection.jsx b/src/pages/sections/HeroSection.jsx index b995e44..b877815 100644 --- a/src/pages/sections/HeroSection.jsx +++ b/src/pages/sections/HeroSection.jsx @@ -1,18 +1,8 @@ import React, { useState } from 'react'; -import { - Box, - Typography, - Button, - useTheme, - Grid, - CardMedia, -} from '@mui/material'; -import { useParallax } from 'react-scroll-parallax'; import { useMode, useStatisticsStore } from '../../context'; import MDBox from '../../layout/REUSABLE_COMPONENTS/MDBOX'; import placeHolder from '../../assets/images/placeholder.jpeg'; import { Swiper, SwiperSlide } from 'swiper/react'; -// import SwiperCore, { EffectCoverflow, Pagination, Navigation } from 'swiper'; // Import Swiper styles import 'swiper/css'; @@ -21,231 +11,144 @@ import 'swiper/css/pagination'; import 'swiper/css/navigation'; // Corrected imports for Swiper modules -import { EffectCoverflow, Pagination, Navigation } from 'swiper/modules'; - -const BackgroundBeams = () => { - // Define the SVG paths for the beams here - const beams = [ - 'M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875', - 'M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867', - 'M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859', - 'M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851', - 'M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843', - 'M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835', - 'M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827', - 'M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819', - 'M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811', - 'M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803', - 'M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795', - 'M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787', - 'M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779', - 'M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771', - 'M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763', - 'M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755', - 'M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747', - 'M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739', - 'M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731', - 'M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723', - 'M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715', - 'M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707', - 'M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699', - 'M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691', - 'M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683', - 'M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675', - 'M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667', - 'M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659', - 'M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651', - 'M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643', - 'M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635', - 'M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627', - 'M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619', - 'M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611', - 'M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603', - 'M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595', - 'M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587', - 'M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579', - 'M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571', - 'M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563', - 'M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555', - 'M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547', - 'M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539', - 'M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531', - 'M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523', - 'M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515', - 'M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507', - 'M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499', - 'M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491', - 'M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483', - ]; +import { + EffectCoverflow, + Pagination, + Navigation, + Autoplay, +} from 'swiper/modules'; +import MDTypography from '../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +import HeroSectionSkeleton from '../../layout/REUSABLE_COMPONENTS/SkeletonVariants'; - return ( - - - {beams.map((d, index) => ( - - ))} - - - - - - - - - - ); -}; +// const +// return cards?.map((card, index) => ( +// +// {/* */} +// {`slide_${index}`} +// {/* */} +// +// )); +// }; -const createSliderImages = (cards) => { - return cards?.map((card, index) => ( - - - {`slide_${index}`} - - - )); -}; -const createSlider = (cards) => { - return ( - setActiveCardIndex(swiper.realIndex)} - > - {/* */} - {createSliderImages(cards)} - {/* */} - - ); -}; const HeroSection = () => { const { theme } = useMode(); // const isXs = useMediaQuery(theme.breakpoints.down('sm')); const [activeCardIndex, setActiveCardIndex] = useState(0); const { topFiveCards } = useStatisticsStore(); + // Create 30 default card objects + const defaultCards = new Array(30).fill({}).map((_, index) => ({ + id: `default-${index}`, + name: `Placeholder ${index + 1}`, + description: `Description for Placeholder ${index + 1}`, + image: placeHolder, // Use the placeholder image for all default cards + })); - if (!topFiveCards || !Array.isArray(topFiveCards)) { - return null; - } - + // Combine topFiveCards with defaultCards + const cards = [...topFiveCards, ...defaultCards]; const handleSlideChange = (swiper) => { setActiveCardIndex(swiper.realIndex); }; + if (!cards || !Array.isArray(cards) || !cards[activeCardIndex]) { + return ; + } + return ( - - - {topFiveCards[activeCardIndex]?.name} - - - {topFiveCards[activeCardIndex]?.description} - - + - {createSliderImages(topFiveCards)} - {/* {topFiveCards.map((card, index) => ( - - + {/* Ensure there's always a string value, even if it's empty */} + + {topFiveCards[activeCardIndex]?.name || ''} + + + {/* Same for the description */} + + {topFiveCards[activeCardIndex]?.description || ''} + + + + {cards.map((card, index) => ( + {`slide_${index}`} { objectFit: 'contain', }} /> - - - ))} */} - - + + ))} + + + ); }; export default HeroSection; +// +// {return cards?.map((card, index) => ( +// +// {/* */} +// {`slide_${index}`} +// {/* */} +// +// ))} +// +// +// +// ); +// }; + +// export default HeroSection; // import React, { useState } from 'react'; // import { Box, Typography } from '@mui/material'; // import { Swiper, SwiperSlide } from 'swiper/react'; diff --git a/src/pages/sections/MainContentSection.jsx b/src/pages/sections/MainContentSection.jsx index bd24aa6..b97c8a2 100644 --- a/src/pages/sections/MainContentSection.jsx +++ b/src/pages/sections/MainContentSection.jsx @@ -14,6 +14,7 @@ import { useAuthContext, useCollectionStore, useMode, + usePageContext, useUserContext, } from '../../context'; import MDBox from '../../layout/REUSABLE_COMPONENTS/MDBOX'; @@ -26,18 +27,25 @@ import CardChart from '../../tests/CardChart'; import { StyledContainerBox, StyledPaper, -} from '../REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; +} from '../../layout/REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; import placeHolder from '../../assets/images/placeholder.jpeg'; +import { DEFAULT_COLLECTION } from '../../context/constants'; +import useCollectionManager from '../../context/MAIN_CONTEXT/CollectionContext/useCollectionManager'; +import useSelectedCollection from '../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; const MainContentSection = () => { const { theme } = useMode(); - const { authUser, basicData } = useAuthContext(); + const { authUser } = useAuthContext(); const { user } = useUserContext(); - const { allCollections, selectedCollection } = useCollectionStore(); - const isDataLoaded = allCollections && allCollections.length > 0; + + const { hasFetchedCollections } = useCollectionManager(); + const { allCollections, selectedCollection } = useSelectedCollection(); + // const isDataLoaded = allCollections && allCollections.length > 0; const isMdUp = useMediaQuery(theme.breakpoints.up('md')); - const initialCardData = isDataLoaded ? allCollections[0].cards[0] : null; - const { cardData } = useCardCronJob(initialCardData); + const initialCardsData = hasFetchedCollections + ? allCollections[0]?.cards[0] + : DEFAULT_COLLECTION.addMultipleDefaultCards(5); + const { cardData } = useCardCronJob(initialCardsData); const renderStatItem = (label, value) => ( @@ -46,7 +54,7 @@ const MainContentSection = () => { ); return ( -
+
@@ -82,7 +90,7 @@ const MainContentSection = () => { } /> diff --git a/src/tests/CardChart.jsx b/src/tests/CardChart.jsx index be68ded..7612de5 100644 --- a/src/tests/CardChart.jsx +++ b/src/tests/CardChart.jsx @@ -38,6 +38,7 @@ import { format } from 'date-fns'; import LoadingCardAnimation from '../assets/animations/LoadingCardAnimation'; import styled from 'styled-components'; import MDButton from '../layout/REUSABLE_COMPONENTS/MDBUTTON'; +import { useLoading } from '../context/hooks/useLoading'; // Adjust the padding, margin, and other styles as needed const ChartArea = styled(Box)(({ theme }) => ({ @@ -78,13 +79,9 @@ const CardChart = ({ cardData = initialCardData }) => { width: 0, height: 0, }); - const { setLoading, loadingStatus, returnDisplay } = usePageContext(); + const { returnDisplay } = usePageContext(); + const { isLoading } = useLoading(); const safeCardData = cardData || { dailyPriceHistory: [] }; - if (!cardData || !cardData?.dailyPriceHistory) { - console.log('Card data not found', cardData); - // setLoading('isLoading', true); - } - const dailyPriceHistory = safeCardData?.dailyPriceHistory; useEffect(() => { if (cardData?.imageUrl) { console.log('Setting image url', cardData?.imageUrl); @@ -114,20 +111,11 @@ const CardChart = ({ cardData = initialCardData }) => { ); const renderLoadingAnimation = () => isLgUp && ; - // const renderChart = () => ( - // - // - // - // ); - // Ensure this effect doesn't set loading when not necessary useEffect(() => { - if (nivoReadyData && nivoReadyData.length > 0 && loadingStatus.isLoading) { - setLoading('isLoading', false); + if (isLoading('fetchCollections')) { + console.log('Fetching collections'); } - }, [nivoReadyData, setLoading, loadingStatus.isLoading]); + }, [isLoading('fetchCollections')]); // Add responsive chart dimension handling useEffect(() => { // Example of setting dynamic chart dimensions (could be more complex based on container size) @@ -230,7 +218,7 @@ const CardChart = ({ cardData = initialCardData }) => { - {loadingStatus?.isLoading ? ( + {isLoading('fetchCollections') ? ( returnDisplay() ) : ( diff --git a/src/zcleanup/AddButton.jsx b/src/zcleanup/AddButton.jsx new file mode 100644 index 0000000..66a64d0 --- /dev/null +++ b/src/zcleanup/AddButton.jsx @@ -0,0 +1,71 @@ +// import { LoadingButton } from '@mui/lab'; +// import MDButton from '../../../layout/REUSABLE_COMPONENTS/MDBUTTON'; // Assuming MDButton is used elsewhere or can be removed if not needed +// import AddIcon from '@mui/icons-material/Add'; // Make sure this import is used if you need it elsewhere in your component or remove it if it's unused +// import { AddCircleOutlineOutlined } from '@mui/icons-material'; +// import { useMode } from '../../../context'; +// import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +// import { getContextIcon } from '../../reusable/icons'; +// import { useLoading } from '../../../context/hooks/useLoading'; + +// const getLabelAndVariant = (buttonSize, labelValue, action) => { +// const labelTypeMap = { +// extraSmall: null, +// small: `${action}`, +// medium: `${action}`, +// // large: `${action}` + action === 'add' ? 'to ' : 'from ' + `${labelValue}`, +// large: `${labelValue}`, +// }; +// const buttonVariantMap = { +// extraSmall: 'body4', +// small: 'body3', +// medium: 'body2', +// large: 'body4', +// }; +// return { +// buttonLabel: labelTypeMap[buttonSize], +// buttonVariant: buttonVariantMap[buttonSize], +// }; +// }; +// // Styled add button +// const AddButton = ({ buttonSize, handleCardAction, labelValue, action }) => { +// const { theme } = useMode(); +// // const currentContextIcon = getContextIcon(labelValue); +// const tempLabel = action === 'add' ? 'Add' : 'Remove'; +// const { startLoading, stopLoading, isLoading } = useLoading(); +// const { buttonLabel, buttonVariant } = getLabelAndVariant( +// buttonSize, +// tempLabel, +// // labelValue, +// action +// ); + +// return ( +// handleCardAction('add')} +// startIcon={} +// sx={{ +// width: '100%', +// flexGrow: 1, +// borderRadius: theme.shape.borderRadius, +// maxWidth: '100%', +// backgroundColor: theme.palette.success.main, +// justifyContent: 'center', +// alignItems: 'center', +// display: 'flex', +// }} +// > +// +// {buttonLabel} +// +// +// ); +// }; + +// export default AddButton; diff --git a/src/zcleanup/InputComponents/CardNameInput.js b/src/zcleanup/InputComponents/CardNameInput.js new file mode 100644 index 0000000..ac66225 --- /dev/null +++ b/src/zcleanup/InputComponents/CardNameInput.js @@ -0,0 +1,22 @@ +// import React from 'react'; +// import { Input } from '@mui/material'; +// import { useCardStore } from '../../../context/CardContext/CardStore'; + +// const CardNameInput = ({ value, setValue, handleChange }) => { +// const { handleRequest } = useCardStore(); +// return ( +// { +// if (event.key === 'Enter') { +// handleRequest(value); +// } +// }} +// value={value} +// /> +// ); +// }; + +// export default CardNameInput; diff --git a/src/zcleanup/InputComponents/UpdateStatusBox.jsx b/src/zcleanup/InputComponents/UpdateStatusBox.jsx new file mode 100644 index 0000000..ff2863e --- /dev/null +++ b/src/zcleanup/InputComponents/UpdateStatusBox.jsx @@ -0,0 +1,100 @@ +// import React, { useState, useEffect } from 'react'; +// import { Box, Typography } from '@mui/material'; +// import { useCombinedContext } from '../../../context/CombinedProvider'; +// import { useCookies } from 'react-cookie'; + +// const UpdateStatusBox = ({ socket }) => { +// const { allCollectionData, listOfMonitoredCards, listOfSimulatedCards } = +// useCombinedContext(); +// const [currentTime, setCurrentTime] = useState(new Date()); +// const [updateStatus, setUpdateStatus] = useState('Waiting for updates...'); +// const [cookies] = useCookies(['authUser']); + +// useEffect(() => { +// const timeInterval = setInterval(() => { +// setCurrentTime(new Date()); +// }, 1000); + +// const handleStatusUpdate = (statusUpdate) => { +// setUpdateStatus(statusUpdate.message || 'Waiting for updates...'); +// }; + +// if (socket) { +// socket.on('STATUS_UPDATE', handleStatusUpdate); +// } + +// // Cleanup function +// return () => { +// clearInterval(timeInterval); +// if (socket) { +// socket.off('STATUS_UPDATE', handleStatusUpdate); +// } +// }; +// }, [socket]); + +// const sendUpdateRequest = () => { +// if (socket) { +// socket.emit('STATUS_UPDATE_REQUEST', { +// message: 'Requesting status update...', +// data: listOfMonitoredCards, +// }); +// } +// }; + +// // Styling for dark theme +// const styles = { +// container: { +// padding: '15px', +// border: '2px solid #444', +// borderRadius: '8px', +// backgroundColor: '#222', +// color: '#fff', +// // margin: '20px auto', +// boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', +// fontFamily: '"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', +// maxWidth: '400px', +// display: 'flex', +// flexDirection: 'column', +// justifyContent: 'space-between', +// alignItems: 'center', +// height: '100%', // Adjust height here +// width: '100%', // Adjust width here +// }, +// statusBox: { +// marginTop: '15px', +// padding: '10px', +// background: '#333', +// borderRadius: '6px', +// border: '1px solid #555', +// }, +// button: { +// padding: '10px 20px', +// marginTop: '10px', +// border: 'none', +// borderRadius: '5px', +// cursor: 'pointer', +// backgroundColor: '#5CDB95', +// color: 'white', +// fontWeight: 'bold', +// fontSize: '14px', +// letterSpacing: '1px', +// outline: 'none', +// }, +// }; + +// return ( +// +// +// Current Time: {currentTime.toLocaleTimeString()} +// +//
+// Update Status: {updateStatus} +//
+// +//
+// ); +// }; + +// export default UpdateStatusBox; diff --git a/src/zcleanup/InputComponents/UpdateStatusBox2.jsx b/src/zcleanup/InputComponents/UpdateStatusBox2.jsx new file mode 100644 index 0000000..33f0a74 --- /dev/null +++ b/src/zcleanup/InputComponents/UpdateStatusBox2.jsx @@ -0,0 +1,91 @@ +// import React, { useState, useEffect } from 'react'; +// import { Snackbar, Typography, Box, Button } from '@mui/material'; +// import { useCookies } from 'react-cookie'; +// import { useCombinedContext, useMode } from '../../../context'; +// import { StyledChartBox } from '../../../pages/pageStyles/StyledComponents'; + +// const UpdateStatusBox2 = ({ socket }) => { +// const { listOfMonitoredCards, handleSendAllCardsInCollections } = +// useCombinedContext(); +// const [currentTime, setCurrentTime] = useState(new Date()); +// const [updateStatus, setUpdateStatus] = useState('Waiting for cron...'); +// const [cookies] = useCookies(['authUser']); +// const [snackbarData, setSnackbarData] = useState({ +// open: false, +// message: '', +// }); + +// const userId = cookies?.authUser?.userId; +// const { theme } = useMode(); +// const openSnackbar = (message) => { +// setSnackbarData({ open: true, message }); +// }; + +// useEffect(() => { +// const timeInterval = setInterval(() => { +// setCurrentTime(new Date()); +// }, 1000); + +// const handleStatusUpdate = (statusUpdate) => { +// setUpdateStatus( +// (prevStatus) => +// statusUpdate.message || prevStatus || 'Waiting for updates...' +// ); +// }; + +// socket?.on('INITIAL_RESPONSE', handleStatusUpdate); + +// return () => { +// clearInterval(timeInterval); +// socket?.off('INITIAL_RESPONSE', handleStatusUpdate); +// }; +// }, [socket]); // Assuming `socket` is stable and doesn't change on every render + +// const handleTriggerCronJob = () => { +// if (userId && listOfMonitoredCards.length > 0) { +// handleSendAllCardsInCollections(userId, listOfMonitoredCards); +// openSnackbar('Triggered the cron job.'); +// } +// }; + +// return ( +// +// +// Current Time: {currentTime.toLocaleTimeString()} +// +// +// Update Status: {updateStatus} +// +// +// setSnackbarData({ ...snackbarData, open: false })} +// message={snackbarData.message} +// /> +// +// ); +// }; + +// export default UpdateStatusBox2; diff --git a/src/zcleanup/RemoveButton.jsx b/src/zcleanup/RemoveButton.jsx new file mode 100644 index 0000000..bb2c7ce --- /dev/null +++ b/src/zcleanup/RemoveButton.jsx @@ -0,0 +1,75 @@ +// import { LoadingButton } from '@mui/lab'; +// import MDButton from '../../../layout/REUSABLE_COMPONENTS/MDBUTTON'; +// import RemoveIcon from '@mui/icons-material/Remove'; +// import { useMode } from '../../../context'; +// import { RemoveCircleOutlineOutlined } from '@mui/icons-material'; +// import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +// import { getContextIcon } from '../../reusable/icons'; +// import { useLoading } from '../../../context/hooks/useLoading'; +// const getLabelAndVariant = (buttonSize, labelValue, action) => { +// const labelTypeMap = { +// extraSmall: null, +// small: `${action}`, +// medium: `${action}`, +// large: `${action}` + action === 'add' ? 'to' : 'from' + `${labelValue}`, +// }; +// const buttonVariantMap = { +// extraSmall: 'body4', +// small: 'body3', +// medium: 'body2', +// large: 'body4', +// }; +// return { +// buttonLabel: labelTypeMap[buttonSize], +// buttonVariant: buttonVariantMap[buttonSize], +// }; +// }; +// // Styled remove button +// const RemoveButton = ({ buttonSize, handleCardAction, labelValue, action }) => { +// const { theme } = useMode(); +// const currentContextIcon = getContextIcon(labelValue); +// const { startLoading, stopLoading, isLoading } = useLoading(); +// const { buttonLabel, buttonVariant } = getLabelAndVariant( +// buttonSize, +// labelValue, +// action +// ); + +// return ( +// handleCardAction('remove')} +// // getButtonLabel={getButtonLabel} +// startIcon={} +// sx={{ +// width: '100%', // Button grows to fill the container +// flexGrow: 1, // Grow to fill the parent container +// // minWidth: buttonSize === 'small' ? '25px' : '100px', // Ensure buttons have a minimum width +// backgroundColor: theme.palette.error.main, +// borderRadius: theme.shape.borderRadius, // Use theme values for consistent styling +// // width: '100%', // Button grows to fill the container +// // minWidth: '100px', // Ensure buttons have a minimum width +// maxWidth: '100%', // Ensure buttons have a maximum width +// justifyContent: 'center', +// alignItems: 'center', +// display: 'flex', +// // p: 1, +// '&:hover': { +// backgroundColor: theme.palette.error.dark, +// }, +// }} +// > +// +// {buttonLabel} +// +// +// ); +// }; + +// export default RemoveButton; diff --git a/src/zcleanup/dataDisplay/CardCountDisplay.jsx b/src/zcleanup/dataDisplay/CardCountDisplay.jsx new file mode 100644 index 0000000..7f85461 --- /dev/null +++ b/src/zcleanup/dataDisplay/CardCountDisplay.jsx @@ -0,0 +1,42 @@ +// import React from 'react'; +// import { Grid, Typography } from '@mui/material'; +// import { styled } from '@mui/material/styles'; +// import PropTypes from 'prop-types'; + +// // Styled components +// const StyledGrid = styled(Grid)(({ theme }) => ({ +// padding: theme.spacing(1), +// backgroundColor: theme.palette.backgroundA.lightest, +// borderRadius: theme.shape.borderRadius, +// boxShadow: theme.shadows[2], +// textAlign: 'center', +// })); + +// const CardCountDisplay = ({ quantity, label, className }) => { +// const totalItems = quantity && quantity.totalItems ? quantity.totalItems : 0; + +// return ( +// +// +// +// {label}: {totalItems} +// +// +// +// ); +// }; + +// CardCountDisplay.propTypes = { +// quantity: PropTypes.shape({ +// totalItems: PropTypes.number, +// }), +// label: PropTypes.string, +// className: PropTypes.string, +// }; + +// export default CardCountDisplay; diff --git a/src/zcleanup/dataDisplay/CartTotal.jsx b/src/zcleanup/dataDisplay/CartTotal.jsx new file mode 100644 index 0000000..1f65e9e --- /dev/null +++ b/src/zcleanup/dataDisplay/CartTotal.jsx @@ -0,0 +1,10 @@ +// import React from 'react'; +// import { Typography } from '@mui/material'; + +// const CartTotal = ({ total }) => ( +// +// {`Total: $${total}`} {/* Ensure this is a string or number */} +// +// ); + +// export default CartTotal; diff --git a/src/zcleanup/dataDisplay/StatBox.jsx b/src/zcleanup/dataDisplay/StatBox.jsx new file mode 100644 index 0000000..34fa85d --- /dev/null +++ b/src/zcleanup/dataDisplay/StatBox.jsx @@ -0,0 +1,43 @@ +// import { Box, Typography, useTheme } from '@mui/material'; +// import ProgressCircle from './ProgressCircle'; +// import { tokens } from '../../../assets/tokens'; + +// const StatBox = ({ title, subtitle, icon, progress, increase }) => { +// const theme = useTheme(); +// const colors = tokens(theme.palette.mode); + +// return ( +// +// +// +// {icon} +// +// {title} +// +// +// +// +// +// + +// +// +// {subtitle} +// +// +// {increase} +// +// +// +// ); +// }; + +// export default StatBox; diff --git a/src/zcleanup/dataDisplay/StatCard.jsx b/src/zcleanup/dataDisplay/StatCard.jsx new file mode 100644 index 0000000..a2655b1 --- /dev/null +++ b/src/zcleanup/dataDisplay/StatCard.jsx @@ -0,0 +1,17 @@ +// import React from 'react'; +// import { Card, CardContent, Typography } from '@mui/material'; + +// const StatCard = ({ title, value }) => ( +// +// +// +// {title} +// +// +// {value} +// +// +// +// ); + +// export default StatCard; diff --git a/src/zcleanup/renderFullWidthAddButton.jsx b/src/zcleanup/renderFullWidthAddButton.jsx new file mode 100644 index 0000000..2d231aa --- /dev/null +++ b/src/zcleanup/renderFullWidthAddButton.jsx @@ -0,0 +1,185 @@ +// import React, { useCallback, useEffect, useMemo } from 'react'; +// import { Box } from '@mui/material'; +// import { useMode } from '../../../context'; +// import { getContextIcon } from '../../../components/reusable/icons/index'; +// import AddButton from './AddButton'; +// import RemoveButton from './RemoveButton'; +// import MDTypography from '../../../layout/REUSABLE_COMPONENTS/MDTYPOGRAPHY/MDTypography'; +// import { useCardActions } from '../../../context/hooks/useCardActions'; +// import { useDeckStore } from '../../../context/MAIN_CONTEXT/DeckContext/DeckContext'; +// import { useCartStore } from '../../../context/MAIN_CONTEXT/CartContext/CartContext'; +// import useSelectedCollection from '../../../context/MAIN_CONTEXT/CollectionContext/useSelectedCollection'; +// import useCollectionManager from '../../../context/MAIN_CONTEXT/CollectionContext/useCollectionManager'; +// import { DEFAULT_COLLECTION } from '../../../context/constants'; + +// // Utility function for deriving label and variant +// export const renderFullWidthAddButton = ( +// buttonSize, +// labelValue, +// context, +// card, +// page, +// onClick, +// closeModal, +// onSuccess, +// onFailure, +// cardSize +// ) => { +// const currentContextIcon = getContextIcon(labelValue); +// const stackDirection = buttonSize === 'extraSmall' ? 'column' : 'row'; +// const { addOneToCollection, removeOneFromCollection } = +// useCollectionManager(); +// const { selectedCollection, allCollections, handleSelectCollection } = +// useSelectedCollection(); +// const { +// addOneToDeck, +// removeOneFromDeck, +// selectedDeck, +// allDecks, +// setSelectedDeck, +// } = useDeckStore(); +// const { addOneToCart, removeOneFromCart, cartData } = useCartStore(); +// useEffect(() => { +// if (context === 'Deck' && !selectedDeck && allDecks?.length > 0) { +// console.warn('No deck selected. Defaulting to first deck.'); +// setSelectedDeck(allDecks[0]); +// } +// if ( +// (context === 'Collection' && !selectedCollection) || +// selectedCollection === null || +// selectedCollection === DEFAULT_COLLECTION || +// !allCollections?.length > 0 +// ) { +// console.warn('No collection selected. Defaulting to first collection.'); +// // handleSelectCollection(allCollections[0]); +// } +// }, [ +// context, +// selectedCollection, +// allCollections, +// selectedDeck, +// allDecks, +// cartData, +// ]); +// const addActions = useMemo(() => { +// Collection: addOneToCollection; +// Deck: addOneToDeck; +// Cart: addOneToCart; +// return addActions; +// }, [addOneToCollection, addOneToDeck, addOneToCart]); +// const removeActions = useMemo(() => { +// Collection: removeOneFromCollection; +// Deck: removeOneFromDeck; +// Cart: removeOneFromCart; +// return removeActions; +// }, [removeOneFromCollection, removeOneFromDeck, removeOneFromCart]); + +// const handleCardActionsAtContext = useCallback( +// (context, card) => { +// switch (context) { +// case 'Collection': +// handleSelectCollection(card); +// break; +// case 'Deck': +// setSelectedDeck(card); +// break; +// case 'Cart': +// addOneToCart(card); +// break; +// default: +// break; +// } +// }, +// [addOneToCart, handleSelectCollection, setSelectedDeck] +// ); +// const { performAction, count } = useCardActions( +// context, +// card, +// selectedCollection, +// selectedDeck, +// addOneToCollection, +// removeOneFromCollection, +// addOneToDeck, +// removeOneFromDeck, +// addOneToCart, +// removeOneFromCart, +// onSuccess, +// onFailure, +// page +// ); +// const handleCardAction = useCallback( +// async (actionType) => { +// console.log('SET LOADING FOR ', actionType); +// console.log('SET LOADING RESPONSE FOR CARD ', card); +// // onClick?.(); +// performAction(actionType); +// closeModal?.(); +// try { +// await new Promise((resolve) => setTimeout(resolve, 500)); + +// if (actionType === 'add') { +// console.log(`Adding ${card?.name} to ${labelValue}`); + +// onSuccess?.(); +// } else if (actionType === 'remove') { +// console.log(`Removing ${card?.name} from ${labelValue}`); +// performAction(actionType); + +// onSuccess?.(); +// } +// } catch (error) { +// console.error('Action failed:', error); +// onFailure?.(error); +// } finally { +// console.log('SET LOADING RESPONSE FOR CARD ', card); +// // closeModal(); +// } +// }, +// [card, labelValue, onSuccess, onFailure, closeModal] +// ); + +// return ( +// +// +// {currentContextIcon || ''} +// +// +// +// +// +// +// ); +// };