From 8d6698dc6e21e2f3a1fa2ed198e4e0eb1988445d Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Fri, 27 Oct 2023 01:37:54 -0700 Subject: [PATCH 01/10] add --- .../grids/collectionGrids/CardList.jsx | 3 +- src/context/Auth/authContext.js | 4 +- .../CollectionContext/CollectionContext.jsx | 86 +++++++------------ 3 files changed, 35 insertions(+), 58 deletions(-) diff --git a/src/components/grids/collectionGrids/CardList.jsx b/src/components/grids/collectionGrids/CardList.jsx index 41e0cb0..3f18e51 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -102,7 +102,8 @@ const CardList = ({ selectedCards }) => { {(rowsPerPage > 0 - ? selectedCards.slice( + ? // eslint-disable-next-line no-unsafe-optional-chaining + selectedCards?.slice( page * rowsPerPage, page * rowsPerPage + rowsPerPage ) diff --git a/src/context/Auth/authContext.js b/src/context/Auth/authContext.js index 8a6b524..cc6d43a 100644 --- a/src/context/Auth/authContext.js +++ b/src/context/Auth/authContext.js @@ -122,7 +122,9 @@ export default function AuthProvider({ children }) { setLoginState(false, null, {}); }; + // Add logs to see what is triggering the useEffect useEffect(() => { + console.log('validateToken or cookies.auth changed, triggering useEffect'); const token = new URLSearchParams(window.location.search).get('token') || cookies.auth; if (token) validateToken(token); @@ -153,7 +155,7 @@ export default function AuthProvider({ children }) { login, logout, signup, - setUser, + // setUser, ]); return ( diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index 07a3560..d0b5ad0 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -216,74 +216,48 @@ export const CollectionProvider = ({ children }) => { return isNaN(num) ? defaultValue : num; }; - const fetchAndSetCollections = useCallback(async () => { + // Function to fetch collections + const fetchCollections = useCallback(async (userId) => { if (!userId) { - console.warn('userId is not set, aborting fetchAndSetCollections.'); - return; + console.warn('userId is not set, aborting fetchCollections.'); + return null; } try { console.log('Fetching collections...'); - const collections = await fetchWrapper( + const response = await fetchWrapper( `${BASE_API_URL}/${userId}/collections`, 'GET' ); - + const collections = response?.data?.allCollections; console.log('Fetched collections:', collections); + return collections; + } catch (error) { + // Your error handling logic here + return null; + } + }, []); - if (!collections) { - console.warn('No collections returned from the server.'); - // Consider setting an empty or error state here - return; - } + // Function to set collections + const setCollections = useCallback((collections) => { + if (!collections || !Array.isArray(collections)) { + console.warn('Invalid collections array:', collections); + return; + } - const uniqueCollections = removeDuplicateCollections(collections); - console.log('Unique collections:', uniqueCollections); - - // Filter out invalid collections - const validCollections = uniqueCollections.filter(Boolean); - - if (Array.isArray(validCollections)) { - console.log('Valid collections:', validCollections); - validCollections.forEach((collection) => { - if (collection && collection.allCardPrices) { - collection.totalPrice = calculateTotalFromAllCardPrices( - collection.allCardPrices - ); - } else { - console.warn( - 'Invalid collection or missing allCardPrices:', - collection - ); - } - }); - } else { - console.error('Valid collections is not an array', collections); - } + const uniqueCollections = removeDuplicateCollections(collections); + const validCollections = uniqueCollections.filter(Boolean); - setAllCollections(validCollections); - setCollectionData( - validCollections.length === 0 - ? initialCollectionState - : validCollections[0] - ); - setSelectedCollection( - validCollections.length === 0 - ? initialCollectionState - : validCollections[0] - ); - } catch (error) { - if (error && error.message) { - console.error(`Failed to fetch collections: ${error.message}`); - // Consider setting an error state here, for example: - // setErrorState(error.message); - } else { - console.error('An unexpected error occurred.'); - // Consider setting a generic error state here, for example: - // setErrorState('An unexpected error occurred.'); - } + // Your logic to set collections + }, []); + + // Your original fetchAndSetCollections function, now simplified + const fetchAndSetCollections = useCallback(async () => { + const collections = await fetchCollections(userId); + if (collections) { + setCollections(collections); } - }, [userId]); + }, [userId, fetchCollections, setCollections]); const findCollectionIndex = useCallback( (collections, id) => @@ -751,7 +725,7 @@ export const CollectionProvider = ({ children }) => { useEffect(() => { if (userId) fetchAndSetCollections(); - }, [fetchAndSetCollections, userId]); + }, [userId]); return ( From 0994f5900047926d81ba7cf28024d93cea3d2634 Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Sat, 28 Oct 2023 00:00:34 -0700 Subject: [PATCH 02/10] add --- src/App.js | 11 + src/components/Auth/PrivateRoute.jsx | 31 +- src/components/dialogs/LoginDialog.jsx | 59 +- src/components/dialogs/login.jsx | 92 --- src/components/headings/header/Header.js | 138 ++-- .../headings/header/MenuItemComponent.jsx | 43 ++ .../headings/header/menuItemsData.jsx | 62 ++ .../headings/navigation/MenuItems.js | 164 ++--- .../headings/navigation/SideBar.jsx | 277 ++++---- src/components/headings/navigation/TopBar.jsx | 246 ++++--- .../headings/navigation/useWindowSize.jsx | 20 + src/context/Auth/authContext.js | 610 +++++++++++++++--- .../CollectionContext/CollectionContext.jsx | 71 +- .../CollectionContext/exampleImport.js | 31 +- src/context/UtilityContext/UtilityContext.jsx | 102 +-- src/pages/SplashPage.js | 12 +- 16 files changed, 1214 insertions(+), 755 deletions(-) delete mode 100644 src/components/dialogs/login.jsx create mode 100644 src/components/headings/header/MenuItemComponent.jsx create mode 100644 src/components/headings/header/menuItemsData.jsx create mode 100644 src/components/headings/navigation/useWindowSize.jsx diff --git a/src/App.js b/src/App.js index bfc7c23..67f8b74 100644 --- a/src/App.js +++ b/src/App.js @@ -96,6 +96,17 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { const App = () => { const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); const { isLoading, setIsContextLoading } = useUtilityContext(); + const { user } = useUserContext(); // Assuming 'user' exists and is non-null if the user is logged in + + useEffect(() => { + if (user) { + console.log('Private routes now available'); + } + + return () => { + console.log('Private routes no longer available'); + }; + }, [user]); // useEffect will re-run whenever 'user' changes useEffect(() => { if (!isLoading) { diff --git a/src/components/Auth/PrivateRoute.jsx b/src/components/Auth/PrivateRoute.jsx index 8a7495d..e31b2b0 100644 --- a/src/components/Auth/PrivateRoute.jsx +++ b/src/components/Auth/PrivateRoute.jsx @@ -1,29 +1,18 @@ import React, { useContext } from 'react'; -import { Route, Navigate } from 'react-router-dom'; -import { AuthContext } from '../../context/Auth/authContext'; +import { Navigate } from 'react-router-dom'; +import { useCookies } from 'react-cookie'; +import { useAuthContext } from '../../context/hooks/auth'; -// function PrivateRoute({ children, capability }) { -// const authContext = useContext(AuthContext); -// const isloggedin = authContext.isloggedin; +const PrivateRoute = ({ children }) => { + const authContext = useAuthContext(); -// if (!isloggedin) { -// // Redirect to a login page or another appropriate route -// return ; -// } + // Use react-cookie's useCookies hook to read the 'isloggedin' cookie + const [cookies] = useCookies(['isloggedin']); -// // Check if the user has the required capability (optional) -// // if (capability && !authContext.can(capability)) { -// // // Redirect to a forbidden page or another appropriate route -// // return ; -// // } + const isloggedin = authContext.isloggedin || cookies.isloggedin; -// // Render the protected route with children -// return children; -// } -const PrivateRoute = ({ children }) => { - const authContext = useContext(AuthContext); - const isloggedin = authContext.isloggedin; - return isloggedin ? children : ; + // If isloggedin from either cookie or context is true, proceed to the route + return isloggedin ? children : ; }; export default PrivateRoute; diff --git a/src/components/dialogs/LoginDialog.jsx b/src/components/dialogs/LoginDialog.jsx index 9445f7a..cd9d5ec 100644 --- a/src/components/dialogs/LoginDialog.jsx +++ b/src/components/dialogs/LoginDialog.jsx @@ -1,4 +1,4 @@ -import React, { useState, useContext, useEffect } from 'react'; +import React, { useState, useContext, useEffect, useRef } from 'react'; import { Button, Dialog, @@ -16,7 +16,7 @@ import { useNavigate } from 'react-router-dom'; import { useCookies } from 'react-cookie'; function LoginDialog({ open, onClose, onLogin }) { - const authContext = useAuthContext(); + const authContext = useAuthContext(); // <-- Make sure this line is updated const navigate = useNavigate(); const [username, setUsername] = useState(''); @@ -27,26 +27,38 @@ function LoginDialog({ open, onClose, onLogin }) { const [roleData, setRoleData] = useState('admin'); // Adjusted to handle string value const { toggleColorMode, mode } = useMode(); const [cookies, setCookie, removeCookie] = useCookies(['isloggedin']); + // Flag to track if the component is mounted + const isMounted = useRef(true); + + useEffect(() => { + return () => { + isMounted.current = false; + }; // Cleanup + }, []); const handleSubmit = async (e) => { e.preventDefault(); + try { + let response = null; + + if (signupMode) { + response = await authContext.signup( + username, + password, + email, + name, + roleData + ); + } else { + response = await authContext.login(username, password); + } - if (signupMode) { - await authContext.signup(username, password, email, name, roleData); - } else { - try { - const loggedIn = await authContext.login(username, password); - if (loggedIn) { - setCookie('isloggedin', 'true', { - path: '/', - secure: true, // set to true if using https - sameSite: 'strict', - }); - onLogin(); - } - } catch (error) { - console.error('Login failed:', error); + // Checking if the login or signup was successful + if (response?.loggedIn) { + onLogin(); } + } catch (error) { + console.error('Login failed:', error); } }; @@ -55,10 +67,21 @@ function LoginDialog({ open, onClose, onLogin }) { authContext.logout(); }; + // UseEffect to handle login state change useEffect(() => { - if (authContext.isloggedin && window.location.pathname !== '/profile') { + let isMounted = true; // Single variable for isMounted + + if ( + isMounted && + authContext.isloggedin && + window.location.pathname !== '/profile' + ) { onLogin(); } + + return () => { + isMounted = false; + }; }, [authContext.isloggedin, onLogin, navigate]); return ( diff --git a/src/components/dialogs/login.jsx b/src/components/dialogs/login.jsx deleted file mode 100644 index e564868..0000000 --- a/src/components/dialogs/login.jsx +++ /dev/null @@ -1,92 +0,0 @@ -// // Login.js -// import React, { useState, useContext, useEffect } from 'react'; -// import { useNavigate } from 'react-router-dom'; -// import { When } from 'react-if'; -// import { Button, Dialog, DialogContent, DialogTitle } from '@mui/material'; -// import { AuthContext } from '../../context/Auth/authContext.js'; -// import LoginForm from '../forms/LoginForm.jsx'; -// import SignupSwitch from '../Auth/SignupSwitch.jsx'; - -// function Login() { -// let authContext = useContext(AuthContext); -// let navigate = useNavigate(); - -// const [username, setUsername] = React.useState(''); -// const [password, setPassword] = React.useState(''); -// const [email, setEmail] = React.useState(''); -// const [signupMode, setSignupMode] = React.useState(false); -// const [roleData, setRoleData] = useState(''); -// const [name, setName] = useState(''); - -// const handleSubmit = async (e) => { -// e.preventDefault(); -// if (signupMode) { -// await authContext.signup( -// username, -// password, -// email, -// { name }, -// { name: roleData } -// ); -// } else { -// try { -// console.log('username', username); -// const loggedIn = await authContext.login(username, password); -// if (loggedIn) { -// // Store login status in local storage -// localStorage.setItem('isLoggedIn', 'true'); -// } -// } catch (error) { -// console.error('Login failed:', error); -// } -// } -// }; - -// useEffect(() => { -// if ( -// authContext.isLoggedIn || -// localStorage.getItem('isLoggedIn') === 'true' -// ) { -// navigate('/profile'); -// } -// }, [authContext.isLoggedIn, navigate]); - -// return ( -// -// {signupMode ? 'Sign Up' : 'Login'} -// -// -// -// - -// -// -// -// -// {authContext.error &&

{authContext.error}

} -// {authContext.isLoading &&

Loading...

} -//
-//
-// ); -// } - -// export default Login; diff --git a/src/components/headings/header/Header.js b/src/components/headings/header/Header.js index 717d265..259ede5 100644 --- a/src/components/headings/header/Header.js +++ b/src/components/headings/header/Header.js @@ -1,117 +1,49 @@ -import React, { useState, useEffect } from 'react'; -import { useAuthContext } from '../../../context/hooks/auth'; +import React, { useEffect, useState } from 'react'; import TopBar from '../navigation/TopBar'; import SideBar from '../navigation/SideBar'; import { useSidebarContext } from '../../../context/SideBarProvider'; -import { - Home as HomeIcon, - Store as StoreIcon, - ShoppingCart as CartIcon, - Assessment as AssessmentIcon, - Deck as DeckOfCardsIcon, - Person as LoginIcon, -} from '@mui/icons-material'; -import { Box } from '@mui/system'; +import { useAuthContext } from '../../../context/hooks/auth'; +import { getMenuItemsData } from './menuItemsData'; +import { Box, useMediaQuery, useTheme } from '@mui/material'; +import LoginDialog from '../../dialogs/LoginDialog'; const Header = () => { - const [isMobileView, setIsMobileView] = useState(window.innerWidth <= 598); - const { - isOpen, - toggleSidebar, - sidebarBackgroundColor, - sidebarImage, - setIsOpen, - } = useSidebarContext(); - const menuItemsData = [ - { - title: 'Store', - items: [ - { - name: 'Home', - index: 0, - icon: , - to: '/home', - requiresLogin: false, - }, - { - name: 'Store', - index: 1, - icon: , - to: '/store', - requiresLogin: true, - }, - { - name: 'Deck Builder', - index: 2, - icon: , - to: '/deckbuilder', - requiresLogin: true, - }, - { - name: 'Cart', - index: 3, - icon: , - to: '/cart', - requiresLogin: true, - }, - { - name: 'Collection', - index: 4, - icon: , - to: '/collection', - requiresLogin: true, - }, - { - name: 'Login', - index: 5, - icon: , - to: '/login', - requiresLogin: false, - }, - ], - }, - ]; - const handleDrawerOpen = () => { - if (isMobileView && !isOpen) { - setIsOpen(true); - } - }; + const { isOpen, toggleSidebar, setIsOpen } = useSidebarContext(); + const { isloggedin } = useAuthContext(); + const [isLoginDialogOpen, setLoginDialogOpen] = useState(false); - const handleDrawerClose = () => { - setIsOpen(false); - }; + const theme = useTheme(); + const isMobileView = useMediaQuery(theme.breakpoints.down('sm')); - const updateView = () => { - setIsMobileView(window.innerWidth <= 598); - }; + const filteredMenuItems = getMenuItemsData(isloggedin).filter( + (item) => !item.requiresLogin || isloggedin + ); + // Combine useEffect hooks to a single one useEffect(() => { - window.addEventListener('resize', updateView); - if (!isMobileView && isOpen) { - handleDrawerClose(); - } - if (!isMobileView && !isOpen) { - handleDrawerClose(); - } - if (isMobileView && isOpen) { - handleDrawerOpen(); - } - if (isMobileView && !isOpen) { - handleDrawerClose(); + if (isMobileView) { + setIsOpen(false); } - return () => window.removeEventListener('resize', updateView); - }, [isMobileView]); + }, [isMobileView, setIsOpen]); + + const handleDrawerState = () => toggleSidebar(); + + const handleLoginDialogState = () => setLoginDialogOpen(!isLoginDialogOpen); + + const handleLoginDialogClose = () => setLoginDialogOpen(false); + + const handleLoginSuccess = () => setLoginDialogOpen(false); // Implement additional logic if required return ( <> - {isMobileView && ( // Only render SideBar in mobile view + {isMobileView && ( { }} > )} + ); }; + export default Header; diff --git a/src/components/headings/header/MenuItemComponent.jsx b/src/components/headings/header/MenuItemComponent.jsx new file mode 100644 index 0000000..900d46f --- /dev/null +++ b/src/components/headings/header/MenuItemComponent.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { + styled, + ListItem, + ListItemIcon, + ListItemText, + Typography, +} from '@mui/material'; + +// Create a StyledMenuItem component with extensive styling +const StyledMenuItem = styled(ListItem)(({ theme }) => ({ + cursor: 'pointer', + '&:hover': { + backgroundColor: theme.palette.action.hover, + }, + '& .MuiListItemIcon-root': { + minWidth: '35px', + }, + '& .MuiTypography-root': { + fontSize: '1.2em', + fontWeight: 500, + }, + '& .MuiSvgIcon-root': { + fontSize: '1.5em', + marginRight: theme.spacing(1), + }, +})); + +const MenuItemComponent = ({ item, onClick }) => { + const { icon, to, name } = item; + + return ( + onClick(to)} component="div"> + {icon} + {name}} + /> + + ); +}; + +export default MenuItemComponent; diff --git a/src/components/headings/header/menuItemsData.jsx b/src/components/headings/header/menuItemsData.jsx new file mode 100644 index 0000000..76a6ca1 --- /dev/null +++ b/src/components/headings/header/menuItemsData.jsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { + Home as HomeIcon, + Store as StoreIcon, + ShoppingCart as CartIcon, + Assessment as AssessmentIcon, + Deck as DeckOfCardsIcon, +} from '@mui/icons-material'; + +export const getMenuItemsData = (isloggedin) => { + // Error handling: Check if 'isloggedin' is a boolean + if (typeof isloggedin !== 'boolean') { + console.error("Invalid argument: 'isloggedin' should be a boolean"); + return []; + } + + try { + const baseMenuItems = [ + { name: 'Home', icon: , to: '/home', requiresLogin: false }, + { + name: 'Store', + icon: , + to: '/store', + requiresLogin: !isloggedin, + }, + { + name: 'Deck Builder', + icon: , + to: '/deckbuilder', + requiresLogin: false, + }, + { + name: 'Cart', + icon: , + to: '/cart', + requiresLogin: !isloggedin, + }, + { + name: 'Collection', + icon: , + to: '/collection', + requiresLogin: !isloggedin, + }, + ]; + + // If the user is logged in, set all requiresLogin fields to false + if (isloggedin) { + console.log('isloggedin is true', isloggedin); + return baseMenuItems.map((item) => ({ + ...item, + requiresLogin: false, + })); + } + + return baseMenuItems; + } catch (error) { + console.error('An error occurred in getMenuItemsData:', error); + return []; + } +}; + +export default getMenuItemsData; diff --git a/src/components/headings/navigation/MenuItems.js b/src/components/headings/navigation/MenuItems.js index 4a1c397..bbc8cc4 100644 --- a/src/components/headings/navigation/MenuItems.js +++ b/src/components/headings/navigation/MenuItems.js @@ -26,8 +26,7 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'; import { styled } from '@mui/system'; import LoginDialog from '../../dialogs/LoginDialog'; import { useAuthContext } from '../../../context/hooks/auth'; -import { AuthContext } from '../../../context/Auth/authContext'; -import { StyledListItem, StyledTypography } from './styled'; +import { useCookies } from 'react-cookie'; const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ '& .MuiTypography-root': { @@ -39,24 +38,10 @@ const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ }, })); -const StyledLink = styled(Link)(({ theme }) => ({ - color: '#000', - textDecoration: 'none', - display: 'flex', - alignItems: 'center', - width: '100%', - height: '100%', -})); - const MenuBox = styled(Box)(({ theme }) => ({ - padding: (props) => (props.isLoggedIn ? '1em' : '2em'), + padding: '1em', display: 'flex', - flexDirection: 'column', - [theme.breakpoints.up('sm')]: { - flexDirection: 'row', - justifyContent: 'space-around', - alignItems: 'center', - }, + flexDirection: 'row', })); const menuItemsData = [ @@ -67,7 +52,6 @@ const menuItemsData = [ icon: , to: '/deckbuilder', requiresLogin: false, - // requiresLogin: true, }, { name: 'Cart', icon: , to: '/cart', requiresLogin: true }, { @@ -78,114 +62,84 @@ const menuItemsData = [ }, ]; -const MenuItems = ({ - handleDrawerClose, - variant, - isMenuOpen, - setIsLoginDialogOpen, - isLoginDialogOpen, - menuSections, - handleItemClick, - isMobileView, - selectedItem, - isOpen, - sections, -}) => { - const location = useLocation(); - const previousPageRef = useRef(); - const isSidebar = variant === 'sidebar'; - const isDialogOpen = isLoginDialogOpen === true ? true : false; - const { isloggedin, logout } = useAuthContext(); - const navigate = useNavigate(); // Use the useNavigate hook to navigate programmatically +const MenuItems = ({ handleDrawerClose, onLogin }) => { + const { logout } = useAuthContext(); + const [cookies] = useCookies(['isloggedin']); + const isloggedin = cookies.isloggedin === true; // adjust the condition according to how you store the value in the cookie + + const navigate = useNavigate(); + const isMounted = useRef(true); + const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false); useEffect(() => { - if (location.pathname !== previousPageRef.current) { - console.log('CURRENT PAGE:', location.pathname); - previousPageRef.current = location.pathname; - } - }, [location.pathname]); - // console.log('isloggedin:', isloggedin); - // console.log('isDialogOpen:', isDialogOpen); - // console.log('isLoginDialogOpen:', isLoginDialogOpen); - // console.log('isMenuOpen:', isMenuOpen); - // console.log('isSidebar:', isSidebar); - // console.log('menuSections:', menuSections); - // console.log('sections:', sections); - // console.log('selectedItem:', selectedItem); - // console.log('isOpen:', isOpen); - // console.log('isMobileView:', isMobileView); - const handleLoginClick = () => { - setIsLoginDialogOpen(true); - }; + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); const handleLoginDialogClose = () => { setIsLoginDialogOpen(false); }; - const handleLoginSuccess = () => { - setIsLoginDialogOpen(false); - // Handle additional logic upon successful login if needed + const handleLoginClick = () => { + setIsLoginDialogOpen(true); }; - useEffect(() => { - if (location.pathname !== previousPageRef.current) { - console.log('CURRENT PAGE:', location.pathname); - previousPageRef.current = location.pathname; + + const handleLoginSuccess = () => { + if (isMounted.current) { + setIsLoginDialogOpen(false); + if (onLogin) { + onLogin(); + } } - }, [location.pathname]); + }; const handleMenuItemClick = (to) => { - // Use history.push to navigate to the desired route navigate(to); }; - const renderMenuItems = (items) => { - return items.map((item) => { - // console.log('ITEM:', item); - const { name, icon, to, requiresLogin } = item; - // console.log('REQUIRES LOGIN:', requiresLogin); - // console.log('IS LOGGED IN:', isloggedin); - // console.log('NAME:', name); - // console.log('TO:', to); - - return isloggedin || !requiresLogin ? ( // Allow non-logged in users to access items marked as not requiring login - handleMenuItemClick(to)} - > - {icon} {name} - - ) : null; - }); - }; - + const filteredMenuItems = menuItemsData.filter( + (item) => !item.requiresLogin || isloggedin + ); return ( -
- - {renderMenuItems(menuItemsData)} - - {isloggedin ? ( - - Logout + {filteredMenuItems?.map((item) => { + const { name, icon, to, requiresLogin } = item; + return isloggedin || !requiresLogin ? ( + handleMenuItemClick(to)} + > + {icon} + {name} - ) : ( - - Login - - )} - + ) : null; + })} + {isloggedin ? ( + + Logout + + ) : ( + + Login + + )} -
+ ); }; diff --git a/src/components/headings/navigation/SideBar.jsx b/src/components/headings/navigation/SideBar.jsx index 979346c..ed349b3 100644 --- a/src/components/headings/navigation/SideBar.jsx +++ b/src/components/headings/navigation/SideBar.jsx @@ -1,151 +1,142 @@ -import React, { useState } from 'react'; -import { - Drawer, - List, - ListItem, - ListItemIcon, - Typography, - Divider, - AppBar, - Container, - Toolbar, - Box, -} from '@mui/material'; -import MenuItems from './MenuItems'; -import { useSidebarContext } from '../../../context/SideBarProvider'; -import { useAuthContext } from '../../../context/hooks/auth'; -import { StyledDrawer, StyledListItem, StyledTypography } from './styled'; // Import styled components -import LoginDialog from '../../dialogs/LoginDialog'; -// import theme from '../../../assets/styles/themes'; -import { Home as HomeIcon, Map as MapIcon } from '@mui/icons-material'; -const SideBar = ({ - handleDrawerClose, - isMobileView, - isOpen, - toggleSidebar, - menuSections, -}) => { - const { sidebarBackgroundColor, sidebarImage } = useSidebarContext(); - const [selected, setSelected] = useState('Dashboard'); - const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false); +// import React, { useState } from 'react'; +// import { +// Drawer, +// List, +// ListItem, +// ListItemIcon, +// Typography, +// Divider, +// } from '@mui/material'; +// import { Home as HomeIcon, Map as MapIcon } from '@mui/icons-material'; +// import { useNavigate } from 'react-router-dom'; + +// // Custom Components and Hooks +// import MenuItems from './MenuItems'; +// import { useSidebarContext } from '../../../context/SideBarProvider'; +// import { useAuthContext } from '../../../context/hooks/auth'; + +// // Styled Components +// import { StyledDrawer, StyledListItem, StyledTypography } from './styled'; +// import LoginDialog from '../../dialogs/LoginDialog'; + +// const SideBar = ({ +// handleDrawerClose, +// isMobileView, +// isOpen, +// toggleSidebar, +// menuSections, +// }) => { +// // State +// const [selected, setSelected] = useState('Dashboard'); +// const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false); + +// // Context +// const { sidebarBackgroundColor } = useSidebarContext(); - const handleLoginClick = () => { - setIsLoginDialogOpen(true); - }; +// // Handlers +// const handleLoginClick = () => setIsLoginDialogOpen(true); +// const handleItemClick = (name) => { +// setSelected(name); +// handleDrawerClose(); +// }; - const handleItemClick = (name) => { - setSelected(name); - handleDrawerClose(); - }; +// // Close drawer if not in mobile view +// if (!isMobileView) { +// handleDrawerClose(); +// } + +// return ( +// +// +// + +// {menuSections?.map((section, index) => ( +//
+// +// {section?.title} +// {section?.items?.map((item) => ( +// handleItemClick(item.name)} +// > +// {item.icon} +// {item.name} +// +// ))} +//
+// ))} +//
+// setIsLoginDialogOpen(false)} +// /> +//
+// ); +// }; + +// export default SideBar; +import React from 'react'; +import { Drawer, List, Divider, Hidden } from '@mui/material'; +import LogoutIcon from '@mui/icons-material/Logout'; +import LoginIcon from '@mui/icons-material/Login'; +import MenuItemComponent from '../header/MenuItemComponent'; +import { useAuthContext } from '../../../context/hooks/auth'; +import getMenuItemsData from '../header/menuItemsData'; - if (!isMobileView) { - handleDrawerClose(); - } +const SideBar = ({ handleDrawerState, isOpen, handleLoginDialogState }) => { + const { isloggedin } = useAuthContext(); + const menuItemsData = getMenuItemsData(isloggedin); return ( - // - // - // - // - - {/* - - {menuSections.map((section, index) => ( -
- - {section.title} - {section.items.map((item) => ( - handleItemClick(item.name)} - sx={{ - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - // backgroundColor: theme?.palette?.primary?.main || 'defaultColor', - padding: '0 2em', - }} - > - {item.icon} - {item.name} - - ))} -
- ))} -
*/} - - - {menuSections?.map((section, index) => ( -
- - {section?.title} - {section?.items?.map((item) => ( - handleItemClick(item.name)} - > - {item.icon} - {item.name} - - ))} -
- ))} -
- setIsLoginDialogOpen(false)} - onLogin={() => setIsLoginDialogOpen(false)} - /> -
- //
- //
- //
- //
+ + + + {menuItemsData.map((item) => { + const { name, icon, to, requiresLogin } = item; + return isloggedin || !requiresLogin ? ( + + ) : null; + })} + + {/* Additional items based on login status */} + {isloggedin ? ( + }} + onClick={handleLoginDialogState} + /> + ) : ( + }} + onClick={handleLoginDialogState} + /> + )} + + + ); }; diff --git a/src/components/headings/navigation/TopBar.jsx b/src/components/headings/navigation/TopBar.jsx index ce6ba86..116bfa9 100644 --- a/src/components/headings/navigation/TopBar.jsx +++ b/src/components/headings/navigation/TopBar.jsx @@ -1,115 +1,147 @@ -import React, { useState } from 'react'; -import { - AppBar, - Toolbar, - IconButton, - Typography, - Box, - Container, - List, - Divider, - ListItemIcon, - useTheme, - createTheme, -} from '@mui/material'; +// import React, { useState } from 'react'; +// import { +// AppBar, +// Toolbar, +// IconButton, +// Typography, +// Container, +// } from '@mui/material'; +// import MenuIcon from '@mui/icons-material/Menu'; +// import { Link } from 'react-router-dom'; +// import MenuItems from './MenuItems'; +// import { +// StyledAppBar, +// StyledBox, +// StyledIconButton, +// StyledToolbar, +// } from './styled'; +// import ThemeToggleButton from '../../buttons/ThemeToggleButton'; + +// const TopBar = ({ +// handleDrawerOpen, +// handleDrawerClose, +// isOpen, +// isMobileView, +// menuSections, +// }) => { +// const [selected, setSelected] = useState('Dashboard'); +// const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false); + +// const handleItemClick = (name) => { +// setSelected(name); +// handleDrawerClose(); +// }; + +// // Close the drawer if it's not a mobile view +// if (!isMobileView) { +// handleDrawerClose(); +// } + +// return ( +// +// +// +// +// {isMobileView && ( +// +// +// +// )} +// +// SiteName +// +// +// {!isMobileView && ( +// +// )} +// +// +// +// +// ); +// }; + +// export default TopBar; +// TopBar.js +import React from 'react'; +import { AppBar, Toolbar, IconButton, Hidden } from '@mui/material'; import MenuIcon from '@mui/icons-material/Menu'; -import { Link } from 'react-router-dom'; -import logo from '../../../assets/images/navlogo.png'; -// import theme from '../../../assets/styles/themes'; -import MenuItems from './MenuItems'; -import { Image } from '@mui/icons-material'; -import { - StyledAppBar, - StyledBox, - StyledIconButton, - StyledListItem, - StyledToolbar, - StyledTypography, -} from './styled'; -import ThemeToggleButton from '../../buttons/ThemeToggleButton'; +import LogoutIcon from '@mui/icons-material/Logout'; +import LoginIcon from '@mui/icons-material/Login'; +import MenuItemComponent from '../header/MenuItemComponent'; +import { useAuthContext } from '../../../context/hooks/auth'; +import getMenuItemsData from '../header/menuItemsData'; +import { StyledToolbar } from './styled'; -const TopBar = ({ - handleDrawerOpen, - handleDrawerClose, - logout, - isOpen, - isMobileView, - menuSections, -}) => { - const [selected, setSelected] = useState('Dashboard'); - // const theme = React.useMemo(() => createTheme(themeSettings(mode)), [mode]); - // const theme = useTheme(); - // console.log('theme', theme); - const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false); - const handleLoginClick = () => { - setIsLoginDialogOpen(true); - }; +const TopBar = ({ handleDrawerState, handleLoginDialogState }) => { + const { isloggedin } = useAuthContext(); + const menuItemsData = getMenuItemsData(isloggedin); - const handleItemClick = (name) => { - setSelected(name); - handleDrawerClose(); - }; - if (!isMobileView) { - handleDrawerClose(); - } return ( - - - - - {isMobileView && ( - - - - )} - - - SiteName - - - {!isMobileView && ( - + + + + + + + {menuItemsData.map((item) => { + const { name, icon, to, requiresLogin } = item; + return isloggedin || !requiresLogin ? ( + - )} - - - - + ) : null; + })} + {isloggedin ? ( + }} + onClick={handleLoginDialogState} + /> + ) : ( + }} + onClick={handleLoginDialogState} + /> + )} + + ); }; diff --git a/src/components/headings/navigation/useWindowSize.jsx b/src/components/headings/navigation/useWindowSize.jsx new file mode 100644 index 0000000..c7daa7f --- /dev/null +++ b/src/components/headings/navigation/useWindowSize.jsx @@ -0,0 +1,20 @@ +import { useState, useEffect } from 'react'; + +const useWindowSize = () => { + const [isMobileView, setIsMobileView] = useState(window.innerWidth <= 598); + + useEffect(() => { + const updateView = () => { + setIsMobileView(window.innerWidth <= 598); + }; + + window.addEventListener('resize', updateView); + return () => { + window.removeEventListener('resize', updateView); + }; + }, []); + + return isMobileView; +}; + +export default useWindowSize; diff --git a/src/context/Auth/authContext.js b/src/context/Auth/authContext.js index cc6d43a..8b7bf4d 100644 --- a/src/context/Auth/authContext.js +++ b/src/context/Auth/authContext.js @@ -1,38 +1,414 @@ -import React, { useState, useEffect, useCallback } from 'react'; +// import React, { useState, useEffect, useCallback, useRef } from 'react'; +// import jwt_decode from 'jwt-decode'; +// import axios from 'axios'; +// import { useCookies } from 'react-cookie'; +// import { useUtilityContext } from '../UtilityContext/UtilityContext'; +// // Initialize constants +// const ONE_MINUTE = 60000; +// const AUTH_COOKIE = 'auth'; +// const USER_COOKIE = 'userCookie'; + +// export const AuthContext = React.createContext(); + +// export default function AuthProvider({ children, serverUrl }) { +// // State and Context Setup +// const { directedResponses, fetchDirectedResponses } = useUtilityContext(); +// const [cookies, setCookie, removeCookie] = useCookies(['auth', 'userCookie']); +// const [isLoading, setIsLoading] = useState(false); +// const [isloggedin, setIsloggedin] = useState(false); +// const [user, setUser] = useState({}); +// const [users, setUsers] = useState([]); +// const [error, setError] = useState(null); +// const [token, setToken] = useState(undefined); +// const [loginAttempts, setLoginAttempts] = useState(0); +// const [lastAttemptTime, setLastAttemptTime] = useState(null); + +// const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER; +// const isMounted = useRef(true); + +// // Cleanup +// useEffect(() => { +// return () => { +// isMounted.current = false; +// }; +// }, []); +// // Handle Login Attempts +// useEffect(() => { +// if (loginAttempts >= 2) { +// const timerId = setTimeout(() => { +// setLoginAttempts(0); +// }, 60000); // Reset after 1 minute +// return () => clearTimeout(timerId); +// } +// }, [loginAttempts]); +// // Initialize and Validate Token +// useEffect(() => { +// if (isMounted.current) { +// const queryToken = new URLSearchParams(window.location.search).get( +// 'token' +// ); +// const cookieToken = cookies.auth; +// const activeToken = queryToken || cookieToken; +// if (activeToken && activeToken !== token) { +// validateToken(activeToken); +// } +// } +// }, [validateToken, cookies.auth]); +// // Utility Function to Set Login State +// const setLoginState = useCallback( +// (loggedIn, token, user, error = null) => { +// setCookie('auth', token, { secure: true, sameSite: 'strict' }); +// setCookie('isloggedin', String(loggedIn), { +// secure: true, +// sameSite: 'strict', +// }); +// setCookie(AUTH_COOKIE, token, { secure: true, sameSite: 'strict' }); + +// if (user) { +// setCookie('userCookie', JSON.stringify(user), { +// secure: true, +// sameSite: 'strict', +// }); +// } +// setIsloggedin(loggedIn); +// setToken(token); +// setUser(user); +// setError(error); +// }, +// [setCookie] +// ); + +// const onLogin = async (username, password) => { +// const currentTime = new Date().getTime(); +// const oneMinute = 60000; // 60 seconds * 1000 milliseconds + +// if ( +// loginAttempts < 2 || +// (lastAttemptTime && currentTime - lastAttemptTime > oneMinute) +// ) { +// // If under limit or last attempt was more than a minute ago, proceed +// setLoginAttempts(loginAttempts + 1); +// setLastAttemptTime(currentTime); + +// try { +// const loginResult = await login(username, password); +// if (loginResult?.loggedIn) { +// if (onLogin) { +// onLogin(); // Call the passed down function when login is successful +// } +// } +// setIsloggedin(loginResult?.loggedIn); +// return loginResult; +// } catch (error) { +// console.error('Login failed:', error); +// } +// } else { +// // If over the limit +// setError('Too many login attempts. Please wait for 1 minute.'); +// } +// }; + +// // Reset the login attempts and time after a minute +// useEffect(() => { +// if (loginAttempts >= 2) { +// const timerId = setTimeout(() => { +// setLoginAttempts(0); +// }, 60000); // 1 minute = 60000 milliseconds + +// return () => clearTimeout(timerId); +// } +// }, [loginAttempts]); + +// // In AuthProvider +// const login = async (username, password) => { +// console.log('Login method invoked'); +// setIsLoading(true); + +// try { +// const signInResponse = await axios.post( +// `${REACT_APP_SERVER}/api/users/signin`, +// { username, password }, +// { +// headers: { +// 'Content-Type': 'application/json', +// Authorization: `Bearer ${token}`, +// }, +// } +// ); + +// console.log('Fetching directed responses...'); +// await fetchDirectedResponses(); + +// // Do not call validateToken here. +// return { loggedIn: isloggedin, token }; +// } catch (error) { +// console.error(`Error during login: ${error}`); +// setError('Login failed'); +// setLoginState(false, null, {}, 'Login failed'); +// } finally { +// setIsLoading(false); +// } +// }; + +// const validateToken = useCallback(async () => { +// if (!isMounted.current) return; + +// setIsLoading(true); +// try { +// const latestSignInResponse = directedResponses.find( +// (res) => res.eventType === 'SIGNIN' +// ); + +// if ( +// latestSignInResponse && +// latestSignInResponse.response.data.token !== token +// ) { +// const newToken = latestSignInResponse.response.data.token; +// const decodedUser = jwt_decode(newToken); +// setLoginState(true, newToken, decodedUser); +// } else { +// throw new Error('Token validation failed'); +// } +// } catch (error) { +// console.error(`Error during validateToken: ${error}`); + +// setError('Token validation failed'); +// setLoginState(false, null, {}, 'Token validation failed'); +// } finally { +// setIsLoading(false); +// } +// }, [directedResponses, setLoginState, token]); + +// const signup = async (username, password, email, basic_info, role_data) => { +// setIsLoading(true); +// try { +// const response = await axios.post( +// `${REACT_APP_SERVER}/api/users/signup`, +// { +// login_data: { username, password, email, role_data }, +// basic_info, +// } +// ); +// await validateToken(response.data.token); +// return response.data.token; +// } catch (err) { +// setError('Signup failed'); +// setLoginState(false, null, {}, 'Signup failed'); +// } finally { +// setIsLoading(false); +// } +// }; + +// const logout = () => { +// removeCookie('auth'); +// removeCookie(AUTH_COOKIE); +// setLoginState(false, null, {}); +// console.log('Logout method invoked'); +// }; + +// // In AuthProvider +// useEffect(() => { +// if (isMounted.current) { +// const queryToken = new URLSearchParams(window.location.search).get( +// 'token' +// ); +// const cookieToken = cookies[AUTH_COOKIE]; +// const activeToken = queryToken || cookieToken; + +// if (activeToken && activeToken !== token) { +// validateToken(activeToken); +// } +// } +// }, [validateToken, cookies[AUTH_COOKIE]]); + +// const contextValue = { +// isLoading: isLoading, +// isloggedin: isloggedin, +// user: users.find((u) => u.id === user.id) || user, +// users, +// error, +// login, +// onLogin, // Add this line to pass it down +// logout, +// signup, +// setUser, +// setLoginState, +// validateToken, +// // updateUser, +// }; + +// return ( +// {children} +// ); +// } + +// /** +// * Handler function for processing login data. +// * +// * @param {Object} data - The data received from login API. +// */ +// function processLoginData(data) { +// // Validate the data before processing +// if (!validateData(data, 'Login Response', 'processLoginData')) { +// console.warn('Invalid login data. Aborting processLoginData.'); +// return; +// } + +// // Extract relevant fields from the received data +// const { token, user } = data; + +// if (token && user) { +// // Save token and user information to state or local storage +// localStorage.setItem('authToken', token); +// // Perform other login success logic here +// } else { +// console.error('Missing essential fields in login data.'); +// } +// } + +// /** +// * Handler function for processing signup data. +// * +// * @param {Object} data - The data received from signup API. +// */ +// function processSignupData(data) { +// // Validate the data before processing +// if (!validateData(data, 'Signup Response', 'processSignupData')) { +// console.warn('Invalid signup data. Aborting processSignupData.'); +// return; +// } + +// // Extract relevant fields from the received data +// const { success, newUser } = data; + +// if (success && newUser) { +// // Assume `newUser` contains essential user info +// const { id, username } = newUser; + +// // Save the new user ID or perform other signup success logic here +// // For example, redirect to login or a welcome page +// } else { +// console.error('Missing essential fields in signup data.'); +// } +// } +import React, { useState, useEffect, useCallback, useRef } from 'react'; import jwt_decode from 'jwt-decode'; import axios from 'axios'; import { useCookies } from 'react-cookie'; import { useUtilityContext } from '../UtilityContext/UtilityContext'; +const ONE_MINUTE = 60000; +const AUTH_COOKIE = 'auth'; +const USER_COOKIE = 'userCookie'; +const LOGGED_IN_COOKIE = 'isloggedin'; +const processLoginData = (data) => { + // Validate the data before processing + if (!validateData(data, 'Login Response', 'processLoginData')) { + console.warn('Invalid login data. Aborting processLoginData.'); + return; + } + + // Extract relevant fields from the received data + const token = data?.data?.token; // Navigate through nested object + + if (token) { + // Save token to local storage + localStorage.setItem('authToken', token); + // Decode user information if it's included in the token + const decodedUser = jwt_decode(token); + // Perform other login success logic here + return { token, user: decodedUser }; + } else { + console.error('Missing essential fields in login data.'); + return null; + } +}; + +const isEmpty = (obj) => { + return ( + [Object, Array].includes((obj || {}).constructor) && + !Object.entries(obj || {}).length + ); +}; +const processSignupData = (data) => { + // Validate the data before processing + if (!validateData(data, 'Signup Response', 'processSignupData')) { + console.warn('Invalid signup data. Aborting processSignupData.'); + return; + } + + // Extract relevant fields from the received data + const { success, newUser } = data; + + if (success && newUser) { + // Assume `newUser` contains essential user info + const { id, username } = newUser; + + // Save the new user ID or perform other signup success logic here + // For example, redirect to login or a welcome page + } else { + console.error('Missing essential fields in signup data.'); + } +}; +// Validator function +const validateData = (data, eventName, functionName) => { + const dataType = typeof data; + console.log( + `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` + ); + + if (data === null || data === undefined) { + console.warn( + `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` + ); + return false; + } + + if (isEmpty(data)) { + console.error( + `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` + ); + return false; + } + + return true; +}; + export const AuthContext = React.createContext(); -export default function AuthProvider({ children }) { - const [cookies, setCookie, removeCookie] = useCookies(['auth', 'userCookie']); +export default function AuthProvider({ children, serverUrl }) { + const { directedResponses, fetchDirectedResponses } = useUtilityContext(); + const [cookies, setCookie, removeCookie] = useCookies([ + LOGGED_IN_COOKIE, + AUTH_COOKIE, + USER_COOKIE, + ]); const [isLoading, setIsLoading] = useState(false); const [isloggedin, setIsloggedin] = useState(false); const [user, setUser] = useState({}); const [users, setUsers] = useState([]); const [error, setError] = useState(null); const [token, setToken] = useState(undefined); - const { directedResponses, fetchDirectedResponses } = useUtilityContext(); + const [loginAttempts, setLoginAttempts] = useState(0); + const [lastAttemptTime, setLastAttemptTime] = useState(null); - const REACT_APP_SERVER = process.env.REACT_APP_SERVER; + const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER; + const isMounted = useRef(true); - // const updateUser = useCallback((newUser) => { - // setUsers((prevUsers) => - // prevUsers.map((u) => (u.id === newUser.id ? newUser : u)) - // ); - // }, []); + useEffect( + () => () => { + isMounted.current = false; + }, + [] + ); + // Utilities const setLoginState = useCallback( (loggedIn, token, user, error = null) => { - setCookie('auth', token, { secure: true, sameSite: 'strict' }); - if (user) { - setCookie('userCookie', JSON.stringify(user), { - secure: true, - sameSite: 'strict', - }); - } + updateCookies( + [AUTH_COOKIE, USER_COOKIE, LOGGED_IN_COOKIE], + [token, JSON.stringify(user), String(loggedIn)] + ); setIsloggedin(loggedIn); setToken(token); setUser(user); @@ -41,22 +417,42 @@ export default function AuthProvider({ children }) { [setCookie] ); + const updateCookies = (names, values) => { + names.forEach((name, index) => + setCookie(name, values[index], { secure: true, sameSite: 'strict' }) + ); + }; + + const resetLoginAttempts = () => + loginAttempts >= 2 && setTimeout(() => setLoginAttempts(0), 60000); + useEffect(resetLoginAttempts, [loginAttempts]); + + // useEffect(() => { + // if (loginAttempts >= 2) { + // const timerId = setTimeout(() => { + // setLoginAttempts(0); + // }, 60000); // Reset after 1 minute + // return () => clearTimeout(timerId); + // } + // }, [loginAttempts]); + + // Validate the token const validateToken = useCallback(async () => { + if (!isMounted.current) return; + setIsLoading(true); try { const latestSignInResponse = directedResponses.find( (res) => res.eventType === 'SIGNIN' ); - if (latestSignInResponse) { - const decodedUser = jwt_decode( - latestSignInResponse.response.data.token - ); - setLoginState( - true, - latestSignInResponse.response.data.token, - decodedUser - ); + if ( + latestSignInResponse && + latestSignInResponse.response.data.token !== token + ) { + const newToken = latestSignInResponse.response.data.token; + const decodedUser = jwt_decode(newToken); + setLoginState(true, newToken, decodedUser); } else { throw new Error('Token validation failed'); } @@ -66,97 +462,137 @@ export default function AuthProvider({ children }) { } finally { setIsLoading(false); } - }, [directedResponses, setLoginState]); + }, [directedResponses, setLoginState, token]); - const login = async (username, password) => { - setIsLoading(true); - try { - await axios.post( - `${REACT_APP_SERVER}/api/users/signin`, - { username, password }, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - } + // Initialize and Validate Token + const initializeAndValidateToken = useCallback(() => { + if (isMounted.current) { + const queryToken = new URLSearchParams(window.location.search).get( + 'token' ); + const cookieToken = cookies[AUTH_COOKIE]; + const activeToken = queryToken || cookieToken; - await fetchDirectedResponses(); // Update directedResponses in utilityContext + if (activeToken && activeToken !== token) { + validateToken(activeToken); + } + } + }, [validateToken, cookies[AUTH_COOKIE]]); + + useEffect(initializeAndValidateToken, [cookies[AUTH_COOKIE]]); - validateToken(); - return { - loggedIn: true, - token, - }; + // Utility functions + const safeRequest = useCallback(async (apiEndpoint, data, methodName) => { + try { + if (!validateData(data, apiEndpoint, methodName)) { + throw new Error(`Invalid data sent to API endpoint: ${apiEndpoint}`); + } + const response = await axios.post(apiEndpoint, data); + if (!validateData(response, apiEndpoint, methodName)) { + throw new Error( + `Invalid data received from API endpoint: ${apiEndpoint}` + ); + } + return response.data; } catch (error) { - setError('Login failed'); - setLoginState(false, null, {}, 'Login failed'); - } finally { - setIsLoading(false); + console.error(`[Error] Failed to send request to: ${apiEndpoint}`, error); + setError({ message: error.message, source: methodName }); + return null; + } + }, []); + const safeResponse = useCallback((data, eventName, handler) => { + try { + if (!validateData(data, eventName, handler.name)) { + throw new Error(`Invalid data received for event: ${eventName}`); + } + return handler(data); + } catch (error) { + console.error(`[Error] Failed to handle event: ${eventName}`, error); + setError({ message: error.message, source: eventName }); + return null; + } + }, []); + const login = async (username, password) => { + const requestData = { username, password }; + + const response = await safeRequest( + `${REACT_APP_SERVER}/api/users/signin`, + requestData, + 'login' + ); + + if (response) { + const processedData = safeResponse(response, 'login', processLoginData); + console.log('PROCESSED DATA:', processedData); + if (processedData) { + setLoginState(true, processedData.token, processedData.user); + } } }; + // Action: Handle successful login + const onLogin = (isloggedin, userData) => { + setIsloggedin(isloggedin); + setUser(userData); + }; - const signup = async (username, password, email, basic_info, role_data) => { - setIsLoading(true); - try { - const response = await axios.post( - `${REACT_APP_SERVER}/api/users/signup`, - { - login_data: { username, password, email, role_data }, - basic_info, - } - ); - await validateToken(response.data.token); - return response.data.token; - } catch (err) { - setError('Signup failed'); - setLoginState(false, null, {}, 'Signup failed'); - } finally { - setIsLoading(false); + const signup = async (loginData, basicInfo, otherInfo) => { + const requestData = { + login_data: loginData, + basic_info: basicInfo, + ...otherInfo, + }; + + const response = await safeRequest( + `${REACT_APP_SERVER}/api/signup`, + requestData, + 'signup' + ); + + if (response) { + const processedData = safeResponse(response, 'signup', processSignupData); + if (processedData) { + // Logic to set the login state here + setLoginState(true, processedData.token, processedData.user); + } } }; const logout = () => { removeCookie('auth'); + removeCookie(AUTH_COOKIE); setLoginState(false, null, {}); + console.log('Logout method invoked'); }; - // Add logs to see what is triggering the useEffect + // In AuthProvider useEffect(() => { - console.log('validateToken or cookies.auth changed, triggering useEffect'); - const token = - new URLSearchParams(window.location.search).get('token') || cookies.auth; - if (token) validateToken(token); - }, [validateToken, cookies.auth]); + if (isMounted.current) { + const queryToken = new URLSearchParams(window.location.search).get( + 'token' + ); + const cookieToken = cookies[AUTH_COOKIE]; + const activeToken = queryToken || cookieToken; + + if (activeToken && activeToken !== token) { + validateToken(activeToken); + } + } + }, [validateToken, cookies[AUTH_COOKIE]]); const contextValue = { isLoading, isloggedin, - user: users.find((u) => u.id === user.id) || user, + user, users, error, login, + onLogin, logout, signup, setUser, setLoginState, validateToken, - // updateUser, }; - useEffect(() => { - console.log('AUTH CONTEXT VALUE:', contextValue); - }, [ - // isLoading, - // isloggedin, - // user, - // users, - // error, - login, - logout, - signup, - // setUser, - ]); return ( {children} diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index d0b5ad0..d1220ea 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -91,25 +91,38 @@ function convertData(originalData) { const { datasets } = originalData; if (Array.isArray(datasets) && datasets.length > 0) { - const lastDataset = datasets[datasets.length - 1]; - - if (Array.isArray(lastDataset.data) && lastDataset.data.length > 0) { - lastDataset.data.forEach((dataEntry) => { - dataEntry.xys?.forEach((xyEntry) => { - const { x, y } = xyEntry.data; - if (x && y !== undefined) { - finalDataForChart.push({ x, y }); - } + datasets.forEach((dataset, index) => { + // Loop over all datasets, not just the last one + if (Array.isArray(dataset.data) && dataset.data.length > 0) { + dataset.data.forEach((dataEntry) => { + dataEntry.xys?.forEach((xyEntry) => { + const { label, data } = xyEntry; + // Assume that finalDataForChart has an array for each label + finalDataForChart[label] = finalDataForChart[label] || []; + + data.forEach(({ x, y }) => { + if (x && y !== undefined) { + finalDataForChart[label].push({ x, y }); + } + }); + }); }); - }); - } + } + }); } + // Convert the data into the format expected by Nivo + const nivoData = Object.keys(finalDataForChart).map((label) => ({ + id: label, + data: finalDataForChart[label], + })); + return { ...originalData, - finalDataForChart, + finalDataForChart: nivoData, // Replace this line to return Nivo-formatted data }; } + const isEmpty = (obj) => { return ( [Object, Array].includes((obj || {}).constructor) && @@ -224,7 +237,7 @@ export const CollectionProvider = ({ children }) => { } try { - console.log('Fetching collections...'); + // console.log('Fetching collections...'); const response = await fetchWrapper( `${BASE_API_URL}/${userId}/collections`, 'GET' @@ -248,6 +261,23 @@ export const CollectionProvider = ({ children }) => { const uniqueCollections = removeDuplicateCollections(collections); const validCollections = uniqueCollections.filter(Boolean); + validCollections.forEach((collection) => { + collection.totalPrice = calculateTotalFromAllCardPrices( + collection.allCardPrices + ); + }); + + setAllCollections(validCollections); + setCollectionData( + validCollections.length === 0 + ? initialCollectionState + : validCollections[0] + ); + setSelectedCollection( + validCollections.length === 0 + ? initialCollectionState + : validCollections[0] + ); // Your logic to set collections }, []); @@ -292,12 +322,16 @@ export const CollectionProvider = ({ children }) => { const createApiUrl = (path) => `${BASE_API_URL}/${path}`; const handleApiResponse = (response, method) => { + // Handling POST requests if (method === 'POST' && response.data?.newCollection) { return response.data.newCollection; } - if (method === 'PUT' && response.data?.updatedCollection) { - return response.data.updatedCollection; + + // Handling PUT requests (updating a collection) + if (method === 'PUT' && response.data?.data?.updatedCollection) { + return response.data.data.updatedCollection; } + throw new Error('Unexpected response format'); }; @@ -542,7 +576,8 @@ export const CollectionProvider = ({ children }) => { ); const updateActiveCollection = useCallback( - async (collectionData) => { + async (collectionData, existingChartData = {}) => { + // Added existingChartData as an optional parameter const isCreatingNew = !collectionData?._id; const endpoint = isCreatingNew ? createApiUrl(`${userId}/collections`) @@ -559,11 +594,13 @@ export const CollectionProvider = ({ children }) => { const newChartData = { ...updatedCollection.chartData, xys: [ + ...(existingChartData.xys || []), // Spread existing xys data { label: `Update Number ${ updatedCollection?.chartData?.datasets?.length + 1 || 1 }`, data: [ + ...(existingChartData.data || []), // Spread existing data { x: moment().format('YYYY-MM-DD HH:mm'), y: updatedCollection.totalPrice, @@ -607,7 +644,7 @@ export const CollectionProvider = ({ children }) => { const convertedData = convertData(newChartData); updatedCollection.xys = convertedData; // setXyData(convertedData.finalDataForChart); - xyData.push(convertedData.finalDataForChart); + xyData.push(...convertedData.finalDataForChart); // Spread to add to existing array updateCollectionData(updatedCollection, 'selectedCollection'); updateCollectionData(updatedCollection, 'allCollections'); } catch (error) { diff --git a/src/context/CollectionContext/exampleImport.js b/src/context/CollectionContext/exampleImport.js index 7029c64..63aea53 100644 --- a/src/context/CollectionContext/exampleImport.js +++ b/src/context/CollectionContext/exampleImport.js @@ -8,17 +8,28 @@ export const initialCollectionState = { // Fetch wrapper function export const fetchWrapper = async (url, method, body = null) => { - const options = { - method, - headers: { 'Content-Type': 'application/json' }, - ...(body && { body: JSON.stringify(body) }), - }; - const response = await fetch(url, options); - // console.log('RESPONSE:', response); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + try { + const options = { + method, + headers: { 'Content-Type': 'application/json' }, + ...(body && { body: JSON.stringify(body) }), + }; + const response = await fetch(url, options); + // console.log('RESPONSE:', response); + if (!response.ok) { + const message = `HTTP error! status: ${response.status}`; + console.error(message); + throw new Error(message); + } + return await response.json(); + } catch (error) { + console.error('Fetch Error:', error); + throw error; + // if (!response.ok) { + // throw new Error(`HTTP error! status: ${response.status}`); + // } + // return await response.json(); } - return await response.json(); }; // Remove duplicate collections diff --git a/src/context/UtilityContext/UtilityContext.jsx b/src/context/UtilityContext/UtilityContext.jsx index cce54bc..789243c 100644 --- a/src/context/UtilityContext/UtilityContext.jsx +++ b/src/context/UtilityContext/UtilityContext.jsx @@ -9,70 +9,70 @@ const UtilityContext = createContext(); const UtilityProvider = ({ children }) => { const [isContextLoading, setIsContextLoading] = useState(true); const [directedResponses, setDirectedResponses] = useState([]); // Initialize as an empty array - const fetchWrapper = async (url, method, body = null) => { - try { - const options = { - method, - headers: { 'Content-Type': 'application/json' }, - ...(body && { body: JSON.stringify(body) }), - }; - const response = await fetch(url, options); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return await response.json(); - } catch (error) { - if (error.message === 'Failed to fetch') { - console.error('Network error: ', error); - throw new Error( - 'Network error. Please check your connection and try again.' - ); - } - throw error; - } - }; + // const fetchWrapper = async (url, method, body = null) => { + // try { + // const options = { + // method, + // headers: { 'Content-Type': 'application/json' }, + // ...(body && { body: JSON.stringify(body) }), + // }; + // const response = await fetch(url, options); + // if (!response.ok) { + // throw new Error(`HTTP error! status: ${response.status}`); + // } + // return await response.json(); + // } catch (error) { + // if (error.message === 'Failed to fetch') { + // console.error('Network error: ', error); + // throw new Error( + // 'Network error. Please check your connection and try again.' + // ); + // } + // throw error; + // } + // }; const fetchDirectedResponses = async () => { + let isMounted = true; // Added this flag + try { setIsContextLoading(true); - const data = await fetchWrapper( - `${BASE_API_URL}/directedResponses`, - 'GET' - ); + const response = await axios.get(`${BASE_API_URL}/directedResponses`); + const data = response.data; - if (Array.isArray(data)) { - setDirectedResponses(data); - } else { - console.error('Fetched data is not an array:', data); - setDirectedResponses([]); + if (isMounted) { + // Check if component is still mounted + Array.isArray(data) + ? setDirectedResponses(data) + : setDirectedResponses([]); } - - console.log('Directed Responses:', data); } catch (error) { - console.error('Error fetching directed responses:', error); - alert(`Failed to fetch directed responses. ${error.message}`); - setDirectedResponses([]); + if (isMounted) { + // Check if component is still mounted + console.error('Error:', error); + setDirectedResponses([]); + } } finally { - setIsContextLoading(false); + if (isMounted) { + // Check if component is still mounted + setIsContextLoading(false); + } } }; useEffect(() => { - const fetchData = async () => { - try { - // Fetch user data, validate token, etc. - // ... - // Once done, set loading to false - setIsContextLoading(false); - } catch (error) { - console.error('An error occurred:', error); - } finally { - setIsContextLoading(false); - } - }; + let isMounted = true; // Added this flag + + if (isMounted && isContextLoading) { + // console.log('Loading...'); + } else if (isMounted && !isContextLoading) { + // console.log('Finished Loading'); + } - fetchData(); - }, []); + return () => { + isMounted = false; // Cleanup + }; + }, [isContextLoading]); const contextValue = { isLoading: isContextLoading, diff --git a/src/pages/SplashPage.js b/src/pages/SplashPage.js index d94e3df..44b87bf 100644 --- a/src/pages/SplashPage.js +++ b/src/pages/SplashPage.js @@ -4,7 +4,7 @@ import placeholder from '../assets/images/placeholder.jpeg'; import { useUtilityContext } from '../context/UtilityContext/UtilityContext'; const SplashPage = () => { - const { fetchDirectedResponses } = useUtilityContext(); + // const { fetchDirectedResponses } = useUtilityContext(); useEffect(() => { // Initialize the scene, camera, and renderer @@ -63,10 +63,14 @@ const SplashPage = () => { // Start the animation loop animate(); + console.log('Three.js scene initialized'); // Debug log + }, []); // Empty dependency array ensures this runs once when component mounts - // Fetch the directed responses - fetchDirectedResponses(); - }, [fetchDirectedResponses]); // Add fetchDirectedResponses as a dependency to ensure it is called when the component mounts + // useEffect(() => { + // // Fetch the directed responses + // fetchDirectedResponses(); + // console.log('fetchDirectedResponses called'); // Debug log + // }, [fetchDirectedResponses]); // fetchDirectedResponses as a dependency return
; }; From 03e1e0263d4bdf8ff7dfd22554853d97eb68cb94 Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Sat, 28 Oct 2023 04:44:42 -0700 Subject: [PATCH 03/10] adding before big changes to authcotext --- src/components/dialogs/LoginDialog.jsx | 31 ++++++++++--------- .../grids/collectionGrids/CardList.jsx | 2 +- .../headings/header/MenuItemComponent.jsx | 29 +++++++++++++++-- .../headings/navigation/SideBar.jsx | 14 +++++++-- src/components/headings/navigation/TopBar.jsx | 22 ++++++++++--- src/context/Auth/authContext.js | 28 +++++++++++------ 6 files changed, 92 insertions(+), 34 deletions(-) diff --git a/src/components/dialogs/LoginDialog.jsx b/src/components/dialogs/LoginDialog.jsx index cd9d5ec..4a02438 100644 --- a/src/components/dialogs/LoginDialog.jsx +++ b/src/components/dialogs/LoginDialog.jsx @@ -53,9 +53,14 @@ function LoginDialog({ open, onClose, onLogin }) { response = await authContext.login(username, password); } - // Checking if the login or signup was successful if (response?.loggedIn) { - onLogin(); + // Assuming `userId` is available in the `response`. + const expires = new Date(); + expires.setMinutes(expires.getMinutes() + 45); + setCookie('isloggedin', true, { expires }); + setCookie('userId', response.userId, { expires }); + + onLogin(true, response.userId); // isloggedin is set to true } } catch (error) { console.error('Login failed:', error); @@ -63,26 +68,22 @@ function LoginDialog({ open, onClose, onLogin }) { }; const handleLogout = () => { - removeCookie('isloggedin', { path: '/' }); + // removeCookie('isloggedin', { path: '/' }); + removeCookie('userId', { path: '/' }); // Remove userId cookie authContext.logout(); }; // UseEffect to handle login state change useEffect(() => { - let isMounted = true; // Single variable for isMounted + if (authContext.isloggedin && window.location.pathname !== '/profile') { + const isloggedinFromCookie = cookies['isloggedin']; + const userIdFromCookie = cookies['userId']; // Assuming you've set this cookie - if ( - isMounted && - authContext.isloggedin && - window.location.pathname !== '/profile' - ) { - onLogin(); + if (isloggedinFromCookie && userIdFromCookie) { + onLogin(isloggedinFromCookie, userIdFromCookie); + } } - - return () => { - isMounted = false; - }; - }, [authContext.isloggedin, onLogin, navigate]); + }, [authContext.isloggedin, onLogin, navigate, cookies]); return ( diff --git a/src/components/grids/collectionGrids/CardList.jsx b/src/components/grids/collectionGrids/CardList.jsx index 3f18e51..6ac681e 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -108,7 +108,7 @@ const CardList = ({ selectedCards }) => { page * rowsPerPage + rowsPerPage ) : selectedCards - ).map((card, index) => ( + )?.map((card, index) => ( {card?.name} diff --git a/src/components/headings/header/MenuItemComponent.jsx b/src/components/headings/header/MenuItemComponent.jsx index 900d46f..a11118a 100644 --- a/src/components/headings/header/MenuItemComponent.jsx +++ b/src/components/headings/header/MenuItemComponent.jsx @@ -1,14 +1,17 @@ import React from 'react'; import { styled, - ListItem, + MenuItem, ListItemIcon, ListItemText, Typography, } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; + +// import { Link } from 'react-router-dom'; // Create a StyledMenuItem component with extensive styling -const StyledMenuItem = styled(ListItem)(({ theme }) => ({ +const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ cursor: 'pointer', '&:hover': { backgroundColor: theme.palette.action.hover, @@ -27,10 +30,16 @@ const StyledMenuItem = styled(ListItem)(({ theme }) => ({ })); const MenuItemComponent = ({ item, onClick }) => { + const navigate = useNavigate(); const { icon, to, name } = item; + const handleClick = () => { + navigate(to); // Navigate to the desired path + onClick(); // Perform any additional onClick logic (like closing the drawer) + }; + return ( - onClick(to)} component="div"> + {icon} { ); }; +// const MenuItemComponent = ({ item, onClick }) => { +// const { icon, to, name } = item; + +// return ( +// onClick(to)}> +// {icon} +// {name}} +// /> +// +// ); +// }; + export default MenuItemComponent; diff --git a/src/components/headings/navigation/SideBar.jsx b/src/components/headings/navigation/SideBar.jsx index ed349b3..7a31169 100644 --- a/src/components/headings/navigation/SideBar.jsx +++ b/src/components/headings/navigation/SideBar.jsx @@ -92,7 +92,7 @@ // }; // export default SideBar; -import React from 'react'; +import React, { useState } from 'react'; import { Drawer, List, Divider, Hidden } from '@mui/material'; import LogoutIcon from '@mui/icons-material/Logout'; import LoginIcon from '@mui/icons-material/Login'; @@ -101,9 +101,14 @@ import { useAuthContext } from '../../../context/hooks/auth'; import getMenuItemsData from '../header/menuItemsData'; const SideBar = ({ handleDrawerState, isOpen, handleLoginDialogState }) => { + const [selected, setSelected] = useState('Dashboard'); const { isloggedin } = useAuthContext(); const menuItemsData = getMenuItemsData(isloggedin); + const handleItemClick = (name) => { + setSelected(name); + handleDrawerState(); + }; return ( @@ -130,7 +135,12 @@ const SideBar = ({ handleDrawerState, isOpen, handleLoginDialogState }) => { /> ) : ( }} + item={{ + name: 'Login', + icon: , + to: '', + requiresLogin: false, + }} onClick={handleLoginDialogState} /> )} diff --git a/src/components/headings/navigation/TopBar.jsx b/src/components/headings/navigation/TopBar.jsx index 116bfa9..740aad0 100644 --- a/src/components/headings/navigation/TopBar.jsx +++ b/src/components/headings/navigation/TopBar.jsx @@ -89,7 +89,7 @@ // export default TopBar; // TopBar.js -import React from 'react'; +import React, { useState } from 'react'; import { AppBar, Toolbar, IconButton, Hidden } from '@mui/material'; import MenuIcon from '@mui/icons-material/Menu'; import LogoutIcon from '@mui/icons-material/Logout'; @@ -99,10 +99,19 @@ import { useAuthContext } from '../../../context/hooks/auth'; import getMenuItemsData from '../header/menuItemsData'; import { StyledToolbar } from './styled'; -const TopBar = ({ handleDrawerState, handleLoginDialogState }) => { +const TopBar = ({ + handleDrawerState, + handleLoginDialogState, + handleDrawerClose, +}) => { + const [selected, setSelected] = useState('Dashboard'); const { isloggedin } = useAuthContext(); const menuItemsData = getMenuItemsData(isloggedin); + const handleItemClick = (name) => { + setSelected(name); + handleDrawerState(); + }; return ( @@ -122,7 +131,7 @@ const TopBar = ({ handleDrawerState, handleLoginDialogState }) => { { /> ) : ( }} + item={{ + name: 'Login', + icon: , + to: '', + requiresLogin: false, + }} onClick={handleLoginDialogState} /> )} diff --git a/src/context/Auth/authContext.js b/src/context/Auth/authContext.js index 8b7bf4d..2c5ca49 100644 --- a/src/context/Auth/authContext.js +++ b/src/context/Auth/authContext.js @@ -402,7 +402,19 @@ export default function AuthProvider({ children, serverUrl }) { [] ); - // Utilities + const updateCookies = (names, values) => { + const expires = new Date(); + expires.setMinutes(expires.getMinutes() + 45); + + names.forEach((name, index) => + setCookie(name, values[index], { + expires, + secure: true, + sameSite: 'strict', + }) + ); + }; + const setLoginState = useCallback( (loggedIn, token, user, error = null) => { updateCookies( @@ -417,12 +429,6 @@ export default function AuthProvider({ children, serverUrl }) { [setCookie] ); - const updateCookies = (names, values) => { - names.forEach((name, index) => - setCookie(name, values[index], { secure: true, sameSite: 'strict' }) - ); - }; - const resetLoginAttempts = () => loginAttempts >= 2 && setTimeout(() => setLoginAttempts(0), 60000); useEffect(resetLoginAttempts, [loginAttempts]); @@ -531,7 +537,8 @@ export default function AuthProvider({ children, serverUrl }) { }; // Action: Handle successful login const onLogin = (isloggedin, userData) => { - setIsloggedin(isloggedin); + console.log('onLogin method invoked', isloggedin); + setIsloggedin(true); setUser(userData); }; @@ -563,8 +570,11 @@ export default function AuthProvider({ children, serverUrl }) { setLoginState(false, null, {}); console.log('Logout method invoked'); }; + // Add this in your Header component + useEffect(() => { + console.log('Value of isloggedin from context: ', isloggedin); + }, [isloggedin]); - // In AuthProvider useEffect(() => { if (isMounted.current) { const queryToken = new URLSearchParams(window.location.search).get( From f170f7d730c14b52f0f02ffbb55f0662553fa5f5 Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Fri, 3 Nov 2023 17:29:16 -0700 Subject: [PATCH 04/10] adding recent --- src/App.js | 27 +- src/components/Auth/PrivateRoute.jsx | 10 +- src/components/Auth/auth.jsx | 6 +- src/components/collection/CardPortfolio.jsx | 8 +- src/components/dialogs/LoginDialog.jsx | 44 +- .../dialogs/SelectCollectionDialog.jsx | 6 +- .../grids/collectionGrids/CardList.jsx | 26 +- .../collectionGrids/SelectCollectionList.jsx | 66 +- src/components/headings/header/Header.js | 6 +- .../headings/header/menuItemsData.jsx | 18 +- .../headings/navigation/MenuItems.js | 268 ++--- .../headings/navigation/SideBar.jsx | 8 +- src/components/headings/navigation/TopBar.jsx | 8 +- src/context/Auth/authContext.js | 715 ++++++++------ .../CollectionContext/CollectionContext.jsx | 410 +++----- .../CollectionContext/collectionUtility.jsx | 259 +++++ src/context/CollectionContext/copy.js | 926 ++++++++++++++++++ src/context/DeckContext/DeckContext.js | 4 +- src/context/SideBarProvider.jsx | 6 +- src/context/UserContext/UserContext.js | 20 +- src/context/UtilityContext/UtilityContext.jsx | 54 +- src/pages/CollectionPage.js | 11 - src/pages/HomePage.js | 16 +- 23 files changed, 2088 insertions(+), 834 deletions(-) create mode 100644 src/context/CollectionContext/collectionUtility.jsx create mode 100644 src/context/CollectionContext/copy.js diff --git a/src/App.js b/src/App.js index 67f8b74..17d079e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,11 @@ // External Imports import React, { useEffect, useState } from 'react'; -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import { + BrowserRouter as Router, + Route, + Routes, + useLocation, +} from 'react-router-dom'; import { Helmet } from 'react-helmet'; import styled, { createGlobalStyle } from 'styled-components'; @@ -23,6 +28,7 @@ import CardDeckAnimation from './pages/CardDeckAnimation'; // Context Hooks Imports import { useCombinedContext } from './context/CombinedProvider'; import { useUserContext } from './context/UserContext/UserContext'; +import { useCollectionStore } from './context/hooks/collection'; import { useUtilityContext } from './context/UtilityContext/UtilityContext'; // Styled Components @@ -46,7 +52,7 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { retrievedListOfMonitoredCards, allCollectionsUpdated, } = useCombinedContext(); - + // const { fetchAllCollectionsForUser } = useCollectionStore(); const { user } = useUserContext(); const userId = user?.userID; @@ -94,9 +100,12 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { // Main Component const App = () => { + const { fetchAllCollectionsForUser, allCollections } = useCollectionStore(); const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); const { isLoading, setIsContextLoading } = useUtilityContext(); const { user } = useUserContext(); // Assuming 'user' exists and is non-null if the user is logged in + // const [currentPage, setCurrentPage] = useState(null); // Add this line + // const location = useLocation(); // Add this line to get the current location useEffect(() => { if (user) { @@ -123,6 +132,18 @@ const App = () => { useCronJob(lastCronJobTriggerTime, setLastCronJobTriggerTime); + // Assuming currentPage is a piece of state that changes when the user changes pages + useEffect(() => { + if (user && (!allCollections || allCollections.length === 0)) { + try { + fetchAllCollectionsForUser(); + console.log('Fetched collections because none were present.'); + } catch (err) { + console.error('Failed to fetch collections:', err); + } + } + }, [user, allCollections]); // Add location.pathname to the dependency list + return ( <> @@ -145,6 +166,8 @@ const App = () => {
+ {/* {setCurrentPage(useLocation())} */} + } /> } /> } /> diff --git a/src/components/Auth/PrivateRoute.jsx b/src/components/Auth/PrivateRoute.jsx index e31b2b0..20cebd4 100644 --- a/src/components/Auth/PrivateRoute.jsx +++ b/src/components/Auth/PrivateRoute.jsx @@ -6,13 +6,13 @@ import { useAuthContext } from '../../context/hooks/auth'; const PrivateRoute = ({ children }) => { const authContext = useAuthContext(); - // Use react-cookie's useCookies hook to read the 'isloggedin' cookie - const [cookies] = useCookies(['isloggedin']); + // Use react-cookie's useCookies hook to read the 'isLoggedIn' cookie + const [cookies] = useCookies(['isLoggedIn']); - const isloggedin = authContext.isloggedin || cookies.isloggedin; + const isLoggedIn = authContext.isLoggedIn || cookies.isLoggedIn; - // If isloggedin from either cookie or context is true, proceed to the route - return isloggedin ? children : ; + // If isLoggedIn from either cookie or context is true, proceed to the route + return isLoggedIn ? children : ; }; export default PrivateRoute; diff --git a/src/components/Auth/auth.jsx b/src/components/Auth/auth.jsx index 6d183bf..eb2aacf 100644 --- a/src/components/Auth/auth.jsx +++ b/src/components/Auth/auth.jsx @@ -3,12 +3,12 @@ import { Routes, Route } from 'react-router-dom'; import { AuthContext } from '../../context/Auth/authContext'; function Auth({ children }) { - const { isloggedin, user, can } = useContext(AuthContext); + const { isLoggedIn, user, can } = useContext(AuthContext); - console.log('isloggedin', isloggedin); + console.log('isLoggedIn', isLoggedIn); console.log('user', user); - return isloggedin ? ( + return isLoggedIn ? ( {/* {can('admin') && } />} */} {children} diff --git a/src/components/collection/CardPortfolio.jsx b/src/components/collection/CardPortfolio.jsx index 3b6cc28..5346c4e 100644 --- a/src/components/collection/CardPortfolio.jsx +++ b/src/components/collection/CardPortfolio.jsx @@ -20,14 +20,14 @@ const CardPortfolio = ({ allCollections }) => { // allCollections, selectedCollection, setSelectedCollection, - fetchAllCollectionsForUser, + // fetchAllCollectionsForUser, addOneToCollection, removeOneFromCollection, } = useCollectionStore(); - useEffect(() => { - fetchAllCollectionsForUser(); - }, []); + // useEffect(() => { + // fetchAllCollectionsForUser(); + // }, []); useEffect(() => { setSelectedCards(selectedCollection?.cards?.slice(0, 30)); diff --git a/src/components/dialogs/LoginDialog.jsx b/src/components/dialogs/LoginDialog.jsx index 4a02438..916944b 100644 --- a/src/components/dialogs/LoginDialog.jsx +++ b/src/components/dialogs/LoginDialog.jsx @@ -26,15 +26,24 @@ function LoginDialog({ open, onClose, onLogin }) { const [name, setName] = useState(''); const [roleData, setRoleData] = useState('admin'); // Adjusted to handle string value const { toggleColorMode, mode } = useMode(); - const [cookies, setCookie, removeCookie] = useCookies(['isloggedin']); + const [cookies, setCookie, removeCookie] = useCookies(['isLoggedIn']); // Flag to track if the component is mounted - const isMounted = useRef(true); + // const isMounted = useRef(true); - useEffect(() => { - return () => { - isMounted.current = false; - }; // Cleanup - }, []); + // useEffect(() => { + // return () => { + // isMounted.current = false; + // }; // Cleanup + // }, []); + + // New function to set cookies and call onLogin + const setLoginState = (isLoggedIn, userId) => { + const expires = new Date(); + expires.setMinutes(expires.getMinutes() + 45); + setCookie('isLoggedIn', isLoggedIn, { expires }); + setCookie('userId', userId, { expires }); + onLogin(isLoggedIn, userId); + }; const handleSubmit = async (e) => { e.preventDefault(); @@ -57,10 +66,10 @@ function LoginDialog({ open, onClose, onLogin }) { // Assuming `userId` is available in the `response`. const expires = new Date(); expires.setMinutes(expires.getMinutes() + 45); - setCookie('isloggedin', true, { expires }); + setCookie('isLoggedIn', true, { expires }); setCookie('userId', response.userId, { expires }); - onLogin(true, response.userId); // isloggedin is set to true + setLoginState(true, response.userId); } } catch (error) { console.error('Login failed:', error); @@ -68,22 +77,21 @@ function LoginDialog({ open, onClose, onLogin }) { }; const handleLogout = () => { - // removeCookie('isloggedin', { path: '/' }); + // removeCookie('isLoggedIn', { path: '/' }); removeCookie('userId', { path: '/' }); // Remove userId cookie authContext.logout(); }; // UseEffect to handle login state change useEffect(() => { - if (authContext.isloggedin && window.location.pathname !== '/profile') { - const isloggedinFromCookie = cookies['isloggedin']; - const userIdFromCookie = cookies['userId']; // Assuming you've set this cookie - - if (isloggedinFromCookie && userIdFromCookie) { - onLogin(isloggedinFromCookie, userIdFromCookie); + if (authContext.isLoggedIn && window.location.pathname !== '/profile') { + const isLoggedInFromCookie = cookies['isLoggedIn']; + const userIdFromCookie = cookies['userId']; + if (isLoggedInFromCookie && userIdFromCookie) { + setLoginState(isLoggedInFromCookie, userIdFromCookie); } } - }, [authContext.isloggedin, onLogin, navigate, cookies]); + }, [authContext.isLoggedIn, onLogin, navigate, cookies]); return ( @@ -95,7 +103,7 @@ function LoginDialog({ open, onClose, onLogin }) { {!mode ? : } - {authContext.isloggedin ? ( + {authContext.isLoggedIn ? ( diff --git a/src/components/dialogs/SelectCollectionDialog.jsx b/src/components/dialogs/SelectCollectionDialog.jsx index f1425e0..86f263e 100644 --- a/src/components/dialogs/SelectCollectionDialog.jsx +++ b/src/components/dialogs/SelectCollectionDialog.jsx @@ -27,8 +27,8 @@ const SelectCollectionDialog = ({ removeCollection, selectedCollection, } = useCollectionStore(); - const [cookies] = useCookies(['userCookie']); - const userId = cookies.userCookie?.id; + const [cookies] = useCookies(['user']); + const userId = cookies.user?.id; const handleSave = () => { const newCollectionInfo = { @@ -38,7 +38,7 @@ const SelectCollectionDialog = ({ }; if (isNew) { - createUserCollection(newCollectionInfo); + createUserCollection(userId, newCollectionInfo); } else if (editedName && editedDescription) { addOneToCollection(newCollectionInfo); } else { diff --git a/src/components/grids/collectionGrids/CardList.jsx b/src/components/grids/collectionGrids/CardList.jsx index 6ac681e..e7c00c3 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -13,11 +13,13 @@ import { TablePagination, TableRow, Button, + TableHead, } from '@mui/material'; import { useCollectionStore } from '../../../context/hooks/collection'; import AssessmentIcon from '@mui/icons-material/Assessment'; import TablePaginationActions from './TablePaginationActions'; import Logger from './Logger'; +import PropTypes from 'prop-types'; // Instantiate logger outside of the component const cardLogger = new Logger([ @@ -37,9 +39,13 @@ const CardList = ({ selectedCards }) => { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(5); const chartContainerRef = useRef(null); - - const emptyRows = - page > 0 ? Math.max(0, (1 + page) * rowsPerPage - selectedCards.length) : 0; + const count = selectedCards?.length || 0; + const emptyRows = useMemo(() => { + // <-- Use useMemo for better performance + return page > 0 + ? Math.max(0, (1 + page) * rowsPerPage - (selectedCards?.length || 0)) + : 0; + }, [page, rowsPerPage, selectedCards]); // <-- Dependencies for useMemo const handleChangePage = (event, newPage) => { cardLogger.logCardAction('Change Page', {}); @@ -100,6 +106,17 @@ const CardList = ({ selectedCards }) => { {/* Include the CronTrigger button */}
+ + + Name + Price + Total Price + Quantity + TCGPlayer Price + Actions + + + {(rowsPerPage > 0 ? // eslint-disable-next-line no-unsafe-optional-chaining @@ -153,6 +170,7 @@ const CardList = ({ selectedCards }) => { ))} + {emptyRows > 0 && ( @@ -164,7 +182,7 @@ const CardList = ({ selectedCards }) => { ({ listItem: { @@ -28,6 +29,12 @@ const useStyles = makeStyles((theme) => ({ textAlign: 'left', marginLeft: theme.spacing(3), }, + loadingContainer: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100vh', + }, editButton: { marginLeft: theme.spacing(2), backgroundColor: theme.palette.primary.main, @@ -47,6 +54,7 @@ const SelectCollectionList = ({ const { allCollections, setSelectedCollection, selectedCollection } = useCollectionStore(); const [cookies] = useCookies(['userCookie']); + const [isLoading, setIsLoading] = useState(false); // const userId = cookies.userCookie?.id; // const [collections, setCollections] = useState([]); @@ -81,32 +89,38 @@ const SelectCollectionList = ({ // ); // The rendering part of the component return ( - - {allCollections - ?.filter((collection) => !!collection?._id) - .map((collection) => ( - - - handleSelect(collection?._id)} - > - - - - - - - ))} - + <> + {isLoading ? ( + + ) : ( + + {allCollections + ?.filter((collection) => !!collection?._id) + .map((collection) => ( + + + handleSelect(collection?._id)} + > + + + + + + + ))} + + )} + ); }; diff --git a/src/components/headings/header/Header.js b/src/components/headings/header/Header.js index 259ede5..c6333fe 100644 --- a/src/components/headings/header/Header.js +++ b/src/components/headings/header/Header.js @@ -9,14 +9,14 @@ import LoginDialog from '../../dialogs/LoginDialog'; const Header = () => { const { isOpen, toggleSidebar, setIsOpen } = useSidebarContext(); - const { isloggedin } = useAuthContext(); + const { isLoggedIn } = useAuthContext(); const [isLoginDialogOpen, setLoginDialogOpen] = useState(false); const theme = useTheme(); const isMobileView = useMediaQuery(theme.breakpoints.down('sm')); - const filteredMenuItems = getMenuItemsData(isloggedin).filter( - (item) => !item.requiresLogin || isloggedin + const filteredMenuItems = getMenuItemsData(isLoggedIn).filter( + (item) => !item.requiresLogin || isLoggedIn ); // Combine useEffect hooks to a single one diff --git a/src/components/headings/header/menuItemsData.jsx b/src/components/headings/header/menuItemsData.jsx index 76a6ca1..67feb62 100644 --- a/src/components/headings/header/menuItemsData.jsx +++ b/src/components/headings/header/menuItemsData.jsx @@ -7,10 +7,10 @@ import { Deck as DeckOfCardsIcon, } from '@mui/icons-material'; -export const getMenuItemsData = (isloggedin) => { - // Error handling: Check if 'isloggedin' is a boolean - if (typeof isloggedin !== 'boolean') { - console.error("Invalid argument: 'isloggedin' should be a boolean"); +export const getMenuItemsData = (isLoggedIn) => { + // Error handling: Check if 'isLoggedIn' is a boolean + if (typeof isLoggedIn !== 'boolean') { + console.error("Invalid argument: 'isLoggedIn' should be a boolean"); return []; } @@ -21,7 +21,7 @@ export const getMenuItemsData = (isloggedin) => { name: 'Store', icon: , to: '/store', - requiresLogin: !isloggedin, + requiresLogin: !isLoggedIn, }, { name: 'Deck Builder', @@ -33,19 +33,19 @@ export const getMenuItemsData = (isloggedin) => { name: 'Cart', icon: , to: '/cart', - requiresLogin: !isloggedin, + requiresLogin: !isLoggedIn, }, { name: 'Collection', icon: , to: '/collection', - requiresLogin: !isloggedin, + requiresLogin: !isLoggedIn, }, ]; // If the user is logged in, set all requiresLogin fields to false - if (isloggedin) { - console.log('isloggedin is true', isloggedin); + if (isLoggedIn) { + console.log('isLoggedIn is true', isLoggedIn); return baseMenuItems.map((item) => ({ ...item, requiresLogin: false, diff --git a/src/components/headings/navigation/MenuItems.js b/src/components/headings/navigation/MenuItems.js index bbc8cc4..f0100c4 100644 --- a/src/components/headings/navigation/MenuItems.js +++ b/src/components/headings/navigation/MenuItems.js @@ -1,146 +1,146 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - MenuItem, - Box, - Typography, - Dialog, - DialogContent, - DialogTitle, - DialogActions, - Button, - List, - Divider, - ListItemIcon, - useTheme, -} from '@mui/material'; -import { - Home as HomeIcon, - Store as StoreIcon, - ShoppingCart as CartIcon, - Logout as LogoutIcon, - Assessment as AssessmentIcon, - Deck as DeckOfCardsIcon, - Person as LoginIcon, -} from '@mui/icons-material'; -import { Link, useLocation, useNavigate } from 'react-router-dom'; -import { styled } from '@mui/system'; -import LoginDialog from '../../dialogs/LoginDialog'; -import { useAuthContext } from '../../../context/hooks/auth'; -import { useCookies } from 'react-cookie'; +// import React, { useEffect, useRef, useState } from 'react'; +// import { +// MenuItem, +// Box, +// Typography, +// Dialog, +// DialogContent, +// DialogTitle, +// DialogActions, +// Button, +// List, +// Divider, +// ListItemIcon, +// useTheme, +// } from '@mui/material'; +// import { +// Home as HomeIcon, +// Store as StoreIcon, +// ShoppingCart as CartIcon, +// Logout as LogoutIcon, +// Assessment as AssessmentIcon, +// Deck as DeckOfCardsIcon, +// Person as LoginIcon, +// } from '@mui/icons-material'; +// import { Link, useLocation, useNavigate } from 'react-router-dom'; +// import { styled } from '@mui/system'; +// import LoginDialog from '../../dialogs/LoginDialog'; +// import { useAuthContext } from '../../../context/hooks/auth'; +// import { useCookies } from 'react-cookie'; -const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ - '& .MuiTypography-root': { - fontSize: '1.2em', - fontWeight: 500, - }, - '& .MuiSvgIcon-root': { - marginRight: theme.spacing(1), - }, -})); +// const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ +// '& .MuiTypography-root': { +// fontSize: '1.2em', +// fontWeight: 500, +// }, +// '& .MuiSvgIcon-root': { +// marginRight: theme.spacing(1), +// }, +// })); -const MenuBox = styled(Box)(({ theme }) => ({ - padding: '1em', - display: 'flex', - flexDirection: 'row', -})); +// const MenuBox = styled(Box)(({ theme }) => ({ +// padding: '1em', +// display: 'flex', +// flexDirection: 'row', +// })); -const menuItemsData = [ - { name: 'Home', icon: , to: '/home', requiresLogin: false }, - { name: 'Store', icon: , to: '/store', requiresLogin: true }, - { - name: 'Deck Builder', - icon: , - to: '/deckbuilder', - requiresLogin: false, - }, - { name: 'Cart', icon: , to: '/cart', requiresLogin: true }, - { - name: 'Collection', - icon: , - to: '/collection', - requiresLogin: true, - }, -]; +// const menuItemsData = [ +// { name: 'Home', icon: , to: '/home', requiresLogin: false }, +// { name: 'Store', icon: , to: '/store', requiresLogin: true }, +// { +// name: 'Deck Builder', +// icon: , +// to: '/deckbuilder', +// requiresLogin: false, +// }, +// { name: 'Cart', icon: , to: '/cart', requiresLogin: true }, +// { +// name: 'Collection', +// icon: , +// to: '/collection', +// requiresLogin: true, +// }, +// ]; -const MenuItems = ({ handleDrawerClose, onLogin }) => { - const { logout } = useAuthContext(); - const [cookies] = useCookies(['isloggedin']); - const isloggedin = cookies.isloggedin === true; // adjust the condition according to how you store the value in the cookie +// const MenuItems = ({ handleDrawerClose, onLogin }) => { +// const { logout } = useAuthContext(); +// const [cookies] = useCookies(['isLoggedIn']); +// const isLoggedIn = cookies.isLoggedIn === true; // adjust the condition according to how you store the value in the cookie - const navigate = useNavigate(); - const isMounted = useRef(true); - const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false); +// const navigate = useNavigate(); +// const isMounted = useRef(true); +// const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false); - useEffect(() => { - isMounted.current = true; - return () => { - isMounted.current = false; - }; - }, []); +// useEffect(() => { +// isMounted.current = true; +// return () => { +// isMounted.current = false; +// }; +// }, []); - const handleLoginDialogClose = () => { - setIsLoginDialogOpen(false); - }; +// const handleLoginDialogClose = () => { +// setIsLoginDialogOpen(false); +// }; - const handleLoginClick = () => { - setIsLoginDialogOpen(true); - }; +// const handleLoginClick = () => { +// setIsLoginDialogOpen(true); +// }; - const handleLoginSuccess = () => { - if (isMounted.current) { - setIsLoginDialogOpen(false); - if (onLogin) { - onLogin(); - } - } - }; +// const handleLoginSuccess = () => { +// if (isMounted.current) { +// setIsLoginDialogOpen(false); +// if (onLogin) { +// onLogin(); +// } +// } +// }; - const handleMenuItemClick = (to) => { - navigate(to); - }; +// const handleMenuItemClick = (to) => { +// navigate(to); +// }; - const filteredMenuItems = menuItemsData.filter( - (item) => !item.requiresLogin || isloggedin - ); - return ( - - {filteredMenuItems?.map((item) => { - const { name, icon, to, requiresLogin } = item; - return isloggedin || !requiresLogin ? ( - handleMenuItemClick(to)} - > - {icon} - {name} - - ) : null; - })} - {isloggedin ? ( - - Logout - - ) : ( - - Login - - )} - - - ); -}; +// const filteredMenuItems = menuItemsData.filter( +// (item) => !item.requiresLogin || isLoggedIn +// ); +// return ( +// +// {filteredMenuItems?.map((item) => { +// const { name, icon, to, requiresLogin } = item; +// return isLoggedIn || !requiresLogin ? ( +// handleMenuItemClick(to)} +// > +// {icon} +// {name} +// +// ) : null; +// })} +// {isLoggedIn ? ( +// +// Logout +// +// ) : ( +// +// Login +// +// )} +// +// +// ); +// }; -export default MenuItems; +// export default MenuItems; diff --git a/src/components/headings/navigation/SideBar.jsx b/src/components/headings/navigation/SideBar.jsx index 7a31169..42510e7 100644 --- a/src/components/headings/navigation/SideBar.jsx +++ b/src/components/headings/navigation/SideBar.jsx @@ -102,8 +102,8 @@ import getMenuItemsData from '../header/menuItemsData'; const SideBar = ({ handleDrawerState, isOpen, handleLoginDialogState }) => { const [selected, setSelected] = useState('Dashboard'); - const { isloggedin } = useAuthContext(); - const menuItemsData = getMenuItemsData(isloggedin); + const { isLoggedIn } = useAuthContext(); + const menuItemsData = getMenuItemsData(isLoggedIn); const handleItemClick = (name) => { setSelected(name); @@ -115,7 +115,7 @@ const SideBar = ({ handleDrawerState, isOpen, handleLoginDialogState }) => { {menuItemsData.map((item) => { const { name, icon, to, requiresLogin } = item; - return isloggedin || !requiresLogin ? ( + return isLoggedIn || !requiresLogin ? ( { })} {/* Additional items based on login status */} - {isloggedin ? ( + {isLoggedIn ? ( }} onClick={handleLoginDialogState} diff --git a/src/components/headings/navigation/TopBar.jsx b/src/components/headings/navigation/TopBar.jsx index 740aad0..9622baf 100644 --- a/src/components/headings/navigation/TopBar.jsx +++ b/src/components/headings/navigation/TopBar.jsx @@ -105,8 +105,8 @@ const TopBar = ({ handleDrawerClose, }) => { const [selected, setSelected] = useState('Dashboard'); - const { isloggedin } = useAuthContext(); - const menuItemsData = getMenuItemsData(isloggedin); + const { isLoggedIn } = useAuthContext(); + const menuItemsData = getMenuItemsData(isLoggedIn); const handleItemClick = (name) => { setSelected(name); @@ -127,7 +127,7 @@ const TopBar = ({ {menuItemsData.map((item) => { const { name, icon, to, requiresLogin } = item; - return isloggedin || !requiresLogin ? ( + return isLoggedIn || !requiresLogin ? ( ) : null; })} - {isloggedin ? ( + {isLoggedIn ? ( }} onClick={handleLoginDialogState} diff --git a/src/context/Auth/authContext.js b/src/context/Auth/authContext.js index 2c5ca49..f130a86 100644 --- a/src/context/Auth/authContext.js +++ b/src/context/Auth/authContext.js @@ -1,3 +1,156 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import axios from 'axios'; +import jwt_decode from 'jwt-decode'; +import { useCookies } from 'react-cookie'; + +const LOGGED_IN_COOKIE = 'loggedIn'; +const AUTH_COOKIE = 'authToken'; +const USER_COOKIE = 'user'; + +// Validator function +const validateData = (data, eventName, functionName) => { + if (!data || Object.keys(data).length === 0) { + console.warn(`Invalid data in ${functionName} for ${eventName}`); + return false; + } + return true; +}; + +// Process the server response based on the action type (Login/Signup) +const processResponseData = (data, type) => { + if (!validateData(data, `${type} Response`, `process${type}Data`)) + return null; + + if (type === 'Login') { + const token = data?.data?.token; + if (!token) return null; + const user = jwt_decode(token); + return { token, user }; + } + + if (type === 'Signup') { + const { success, newUser } = data; + if (success && newUser) return { success, newUser }; + } + + return null; +}; + +// Main AuthContext Provider +export const AuthContext = React.createContext(); + +export default function AuthProvider({ children, serverUrl }) { + const [cookies, setCookie, removeCookie] = useCookies([ + LOGGED_IN_COOKIE, + AUTH_COOKIE, + USER_COOKIE, + ]); + const [isLoading, setIsLoading] = useState(false); + const [isLoggedIn, setisLoggedIn] = useState(false); + const [user, setUser] = useState({}); + const [error, setError] = useState(null); + const [token, setToken] = useState(null); + // const isMounted = useRef(true); + + const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER; + + // Execute authentication actions like login, signup + const executeAuthAction = async (actionType, url, requestData) => { + setIsLoading(true); + try { + const response = await axios.post( + `${REACT_APP_SERVER}/api/users/${url}`, + requestData + ); + const processedData = processResponseData(response.data, actionType); + if (processedData) { + const { token, user, newUser } = processedData; + setCookie(AUTH_COOKIE, token, { path: '/' }); + setCookie(USER_COOKIE, user || newUser, { path: '/' }); + setCookie(LOGGED_IN_COOKIE, true, { path: '/' }); + setisLoggedIn(true); + setToken(token); + setUser(user || newUser); + } + } catch (err) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + // In App.js or inside AuthProvider component + axios.interceptors.request.use( + (config) => { + const token = cookies[AUTH_COOKIE]; + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => Promise.reject(error) + ); + + // Login function + const login = async (username, password) => { + await executeAuthAction('Login', 'signin', { username, password }); + }; + + // Signup function + const signup = async (loginData, basicInfo, otherInfo) => { + await executeAuthAction('Signup', 'signup', { + login_data: loginData, + basic_info: basicInfo, + ...otherInfo, + }); + }; + + // Logout function + const logout = () => { + removeCookie(AUTH_COOKIE); + setisLoggedIn(false); + setToken(null); + setUser({}); + }; + + // Validate token + const validateToken = useCallback(async () => { + // Validation logic here + }, []); + + // Initialization logic to set user and token from cookies + useEffect(() => { + // if (!isMounted.current) return; + + const storedToken = cookies[AUTH_COOKIE]; + const storedUser = cookies[USER_COOKIE]; + + if (storedToken && storedUser) { + setToken(storedToken); + setUser(storedUser); + setisLoggedIn(true); + } + + // isMounted.current = false; + }, [cookies]); + + return ( + + {children} + + ); +} + // import React, { useState, useEffect, useCallback, useRef } from 'react'; // import jwt_decode from 'jwt-decode'; // import axios from 'axios'; @@ -15,7 +168,7 @@ // const { directedResponses, fetchDirectedResponses } = useUtilityContext(); // const [cookies, setCookie, removeCookie] = useCookies(['auth', 'userCookie']); // const [isLoading, setIsLoading] = useState(false); -// const [isloggedin, setIsloggedin] = useState(false); +// const [isLoggedIn, setisLoggedIn] = useState(false); // const [user, setUser] = useState({}); // const [users, setUsers] = useState([]); // const [error, setError] = useState(null); @@ -58,7 +211,7 @@ // const setLoginState = useCallback( // (loggedIn, token, user, error = null) => { // setCookie('auth', token, { secure: true, sameSite: 'strict' }); -// setCookie('isloggedin', String(loggedIn), { +// setCookie('isLoggedIn', String(loggedIn), { // secure: true, // sameSite: 'strict', // }); @@ -70,7 +223,7 @@ // sameSite: 'strict', // }); // } -// setIsloggedin(loggedIn); +// setisLoggedIn(loggedIn); // setToken(token); // setUser(user); // setError(error); @@ -97,7 +250,7 @@ // onLogin(); // Call the passed down function when login is successful // } // } -// setIsloggedin(loginResult?.loggedIn); +// setisLoggedIn(loginResult?.loggedIn); // return loginResult; // } catch (error) { // console.error('Login failed:', error); @@ -140,7 +293,7 @@ // await fetchDirectedResponses(); // // Do not call validateToken here. -// return { loggedIn: isloggedin, token }; +// return { loggedIn: isLoggedIn, token }; // } catch (error) { // console.error(`Error during login: ${error}`); // setError('Login failed'); @@ -223,7 +376,7 @@ // const contextValue = { // isLoading: isLoading, -// isloggedin: isloggedin, +// isLoggedIn: isLoggedIn, // user: users.find((u) => u.id === user.id) || user, // users, // error, @@ -291,320 +444,292 @@ // console.error('Missing essential fields in signup data.'); // } // } -import React, { useState, useEffect, useCallback, useRef } from 'react'; -import jwt_decode from 'jwt-decode'; -import axios from 'axios'; -import { useCookies } from 'react-cookie'; -import { useUtilityContext } from '../UtilityContext/UtilityContext'; - -const ONE_MINUTE = 60000; -const AUTH_COOKIE = 'auth'; -const USER_COOKIE = 'userCookie'; -const LOGGED_IN_COOKIE = 'isloggedin'; -const processLoginData = (data) => { - // Validate the data before processing - if (!validateData(data, 'Login Response', 'processLoginData')) { - console.warn('Invalid login data. Aborting processLoginData.'); - return; - } - - // Extract relevant fields from the received data - const token = data?.data?.token; // Navigate through nested object - - if (token) { - // Save token to local storage - localStorage.setItem('authToken', token); - // Decode user information if it's included in the token - const decodedUser = jwt_decode(token); - // Perform other login success logic here - return { token, user: decodedUser }; - } else { - console.error('Missing essential fields in login data.'); - return null; - } -}; -const isEmpty = (obj) => { - return ( - [Object, Array].includes((obj || {}).constructor) && - !Object.entries(obj || {}).length - ); -}; -const processSignupData = (data) => { - // Validate the data before processing - if (!validateData(data, 'Signup Response', 'processSignupData')) { - console.warn('Invalid signup data. Aborting processSignupData.'); - return; - } - - // Extract relevant fields from the received data - const { success, newUser } = data; - - if (success && newUser) { - // Assume `newUser` contains essential user info - const { id, username } = newUser; - - // Save the new user ID or perform other signup success logic here - // For example, redirect to login or a welcome page - } else { - console.error('Missing essential fields in signup data.'); - } -}; -// Validator function -const validateData = (data, eventName, functionName) => { - const dataType = typeof data; - console.log( - `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` - ); +// import React, { useState, useEffect, useCallback, useRef } from 'react'; +// import jwt_decode from 'jwt-decode'; +// import axios from 'axios'; +// import { useCookies } from 'react-cookie'; +// import { useUtilityContext } from '../UtilityContext/UtilityContext'; - if (data === null || data === undefined) { - console.warn( - `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` - ); - return false; - } +// const LOGGED_IN_COOKIE = 'loggedIn'; +// const AUTH_COOKIE = 'authToken'; +// const USER_COOKIE = 'user'; - if (isEmpty(data)) { - console.error( - `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` - ); - return false; - } +// const processResponseData = (data, type) => { +// if (!validateData(data, `${type} Response`, `process${type}Data`)) { +// console.warn( +// `Invalid ${type.toLowerCase()} data. Aborting process${type}Data.` +// ); +// return; +// } - return true; -}; +// if (type === 'Login') { +// const token = data?.data?.token; +// if (token) { +// localStorage.setItem('authToken', token); +// const decodedUser = jwt_decode(token); +// return { token, user: decodedUser }; +// } +// } else if (type === 'Signup') { +// const { success, newUser } = data; +// if (success && newUser) { +// return { success, newUser }; +// } +// } -export const AuthContext = React.createContext(); +// console.error(`Missing essential fields in ${type.toLowerCase()} data.`); +// return null; +// }; +// const isEmpty = (obj) => { +// return ( +// [Object, Array].includes((obj || {}).constructor) && +// !Object.entries(obj || {}).length +// ); +// }; +// // Validator function +// const validateData = (data, eventName, functionName) => { +// const dataType = typeof data; +// console.log( +// `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` +// ); -export default function AuthProvider({ children, serverUrl }) { - const { directedResponses, fetchDirectedResponses } = useUtilityContext(); - const [cookies, setCookie, removeCookie] = useCookies([ - LOGGED_IN_COOKIE, - AUTH_COOKIE, - USER_COOKIE, - ]); - const [isLoading, setIsLoading] = useState(false); - const [isloggedin, setIsloggedin] = useState(false); - const [user, setUser] = useState({}); - const [users, setUsers] = useState([]); - const [error, setError] = useState(null); - const [token, setToken] = useState(undefined); - const [loginAttempts, setLoginAttempts] = useState(0); - const [lastAttemptTime, setLastAttemptTime] = useState(null); +// if (data === null || data === undefined) { +// console.warn( +// `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` +// ); +// return false; +// } - const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER; - const isMounted = useRef(true); +// if (isEmpty(data)) { +// console.error( +// `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` +// ); +// return false; +// } - useEffect( - () => () => { - isMounted.current = false; - }, - [] - ); +// return true; +// }; - const updateCookies = (names, values) => { - const expires = new Date(); - expires.setMinutes(expires.getMinutes() + 45); - - names.forEach((name, index) => - setCookie(name, values[index], { - expires, - secure: true, - sameSite: 'strict', - }) - ); - }; +// export const AuthContext = React.createContext(); - const setLoginState = useCallback( - (loggedIn, token, user, error = null) => { - updateCookies( - [AUTH_COOKIE, USER_COOKIE, LOGGED_IN_COOKIE], - [token, JSON.stringify(user), String(loggedIn)] - ); - setIsloggedin(loggedIn); - setToken(token); - setUser(user); - setError(error); - }, - [setCookie] - ); +// const setCookies = ( +// setCookieFunc, +// authCookie, +// userCookie, +// loggedInCookie, +// loggedIn, +// token, +// user +// ) => { +// setCookieFunc(authCookie, token); +// setCookieFunc(userCookie, JSON.stringify(user)); +// setCookieFunc(loggedInCookie, String(loggedIn)); +// }; +// export default function AuthProvider({ children, serverUrl }) { +// const { directedResponses, fetchDirectedResponses } = useUtilityContext(); +// const [cookies, setCookie, removeCookie] = useCookies([ +// LOGGED_IN_COOKIE, +// AUTH_COOKIE, +// USER_COOKIE, +// ]); +// const [isLoading, setIsLoading] = useState(false); +// const [isLoggedIn, setisLoggedIn] = useState(false); +// const [user, setUser] = useState({}); +// const [error, setError] = useState(null); +// const [token, setToken] = useState(undefined); +// const isMounted = useRef(true); +// const [loginAttempts, setLoginAttempts] = useState(0); - const resetLoginAttempts = () => - loginAttempts >= 2 && setTimeout(() => setLoginAttempts(0), 60000); - useEffect(resetLoginAttempts, [loginAttempts]); +// const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER; - // useEffect(() => { - // if (loginAttempts >= 2) { - // const timerId = setTimeout(() => { - // setLoginAttempts(0); - // }, 60000); // Reset after 1 minute - // return () => clearTimeout(timerId); - // } - // }, [loginAttempts]); +// useEffect( +// () => () => { +// isMounted.current = false; +// }, +// [] +// ); - // Validate the token - const validateToken = useCallback(async () => { - if (!isMounted.current) return; +// const setLoginState = useCallback( +// (loggedIn, token, user, error = null) => { +// setCookie(AUTH_COOKIE, token); +// setCookie(USER_COOKIE, JSON.stringify(user)); +// setCookie(LOGGED_IN_COOKIE, String(loggedIn)); +// setisLoggedIn(loggedIn); +// setToken(token); +// setUser(user); +// setError(error); +// }, +// [setCookie] +// ); - setIsLoading(true); - try { - const latestSignInResponse = directedResponses.find( - (res) => res.eventType === 'SIGNIN' - ); +// const resetLoginAttempts = () => +// loginAttempts >= 2 && setTimeout(() => setLoginAttempts(0), 60000); +// useEffect(resetLoginAttempts, [loginAttempts]); - if ( - latestSignInResponse && - latestSignInResponse.response.data.token !== token - ) { - const newToken = latestSignInResponse.response.data.token; - const decodedUser = jwt_decode(newToken); - setLoginState(true, newToken, decodedUser); - } else { - throw new Error('Token validation failed'); - } - } catch (error) { - setError('Token validation failed'); - setLoginState(false, null, {}, 'Token validation failed'); - } finally { - setIsLoading(false); - } - }, [directedResponses, setLoginState, token]); +// const updateLoginState = useCallback( +// (loggedIn, token, user, error = null) => { +// setCookies( +// setCookie, +// AUTH_COOKIE, +// USER_COOKIE, +// LOGGED_IN_COOKIE, +// loggedIn, +// token, +// user +// ); +// setisLoggedIn(loggedIn); +// setToken(token); +// setUser(user); +// setError(error); +// }, +// [setCookie] +// ); - // Initialize and Validate Token - const initializeAndValidateToken = useCallback(() => { - if (isMounted.current) { - const queryToken = new URLSearchParams(window.location.search).get( - 'token' - ); - const cookieToken = cookies[AUTH_COOKIE]; - const activeToken = queryToken || cookieToken; +// const validateToken = useCallback(async () => { +// if (!isMounted.current) return; - if (activeToken && activeToken !== token) { - validateToken(activeToken); - } - } - }, [validateToken, cookies[AUTH_COOKIE]]); +// setIsLoading(true); +// try { +// const latestSignInResponse = directedResponses.find( +// (res) => res.eventType === 'SIGNIN' +// ); - useEffect(initializeAndValidateToken, [cookies[AUTH_COOKIE]]); +// if ( +// latestSignInResponse && +// latestSignInResponse.response.data.token !== token +// ) { +// const newToken = latestSignInResponse.response.data.token; +// const decodedUser = jwt_decode(newToken); +// setLoginState(true, newToken, decodedUser); +// } else { +// throw new Error('Token validation failed'); +// } +// } catch (error) { +// setError('Token validation failed'); +// setLoginState(false, null, {}, 'Token validation failed'); +// } finally { +// setIsLoading(false); +// } +// }, [directedResponses, setLoginState, token]); +// useEffect(() => { +// const queryToken = new URLSearchParams(window.location.search).get('token'); +// const cookieToken = cookies[AUTH_COOKIE]; +// const activeToken = queryToken || cookieToken; +// if (activeToken && activeToken !== token) { +// validateToken(activeToken); +// } +// }, [validateToken, cookies[AUTH_COOKIE]]); - // Utility functions - const safeRequest = useCallback(async (apiEndpoint, data, methodName) => { - try { - if (!validateData(data, apiEndpoint, methodName)) { - throw new Error(`Invalid data sent to API endpoint: ${apiEndpoint}`); - } - const response = await axios.post(apiEndpoint, data); - if (!validateData(response, apiEndpoint, methodName)) { - throw new Error( - `Invalid data received from API endpoint: ${apiEndpoint}` - ); - } - return response.data; - } catch (error) { - console.error(`[Error] Failed to send request to: ${apiEndpoint}`, error); - setError({ message: error.message, source: methodName }); - return null; - } - }, []); - const safeResponse = useCallback((data, eventName, handler) => { - try { - if (!validateData(data, eventName, handler.name)) { - throw new Error(`Invalid data received for event: ${eventName}`); - } - return handler(data); - } catch (error) { - console.error(`[Error] Failed to handle event: ${eventName}`, error); - setError({ message: error.message, source: eventName }); - return null; - } - }, []); - const login = async (username, password) => { - const requestData = { username, password }; +// const safeRequest = useCallback(async (apiEndpoint, data, methodName) => { +// try { +// if (!validateData(data, apiEndpoint, methodName)) { +// throw new Error(`Invalid data sent to API endpoint: ${apiEndpoint}`); +// } +// const response = await axios.post(apiEndpoint, data); +// if (!validateData(response, apiEndpoint, methodName)) { +// throw new Error( +// `Invalid data received from API endpoint: ${apiEndpoint}` +// ); +// } +// return response.data; +// } catch (error) { +// console.error(`[Error] Failed to send request to: ${apiEndpoint}`, error); +// setError({ message: error.message, source: methodName }); +// return null; +// } +// }, []); - const response = await safeRequest( - `${REACT_APP_SERVER}/api/users/signin`, - requestData, - 'login' - ); +// const safeResponse = useCallback((data, eventName, handler) => { +// try { +// if (!validateData(data, eventName, handler.name)) { +// throw new Error(`Invalid data received for event: ${eventName}`); +// } +// return handler(data); +// } catch (error) { +// console.error(`[Error] Failed to handle event: ${eventName}`, error); +// setError({ message: error.message, source: eventName }); +// return null; +// } +// }, []); - if (response) { - const processedData = safeResponse(response, 'login', processLoginData); - console.log('PROCESSED DATA:', processedData); - if (processedData) { - setLoginState(true, processedData.token, processedData.user); - } - } - }; - // Action: Handle successful login - const onLogin = (isloggedin, userData) => { - console.log('onLogin method invoked', isloggedin); - setIsloggedin(true); - setUser(userData); - }; +// const executeAuthAction = async (actionType, url, requestData) => { +// const response = await safeRequest( +// `${REACT_APP_SERVER}/api/users/${url}`, +// requestData, +// actionType.toLowerCase() +// ); + +// if (response) { +// const processedData = safeResponse( +// response, +// actionType.toLowerCase(), +// (data) => processResponseData(data, actionType) +// ); +// if (processedData) { +// setLoginState( +// true, +// processedData.token || null, +// processedData.user || processedData.newUser || {} +// ); +// } +// } +// }; - const signup = async (loginData, basicInfo, otherInfo) => { - const requestData = { - login_data: loginData, - basic_info: basicInfo, - ...otherInfo, - }; +// const login = async (username, password) => { +// const requestData = { username, password }; +// await executeAuthAction('Login', 'signin', requestData); +// }; - const response = await safeRequest( - `${REACT_APP_SERVER}/api/signup`, - requestData, - 'signup' - ); +// const signup = async (loginData, basicInfo, otherInfo) => { +// const requestData = { +// login_data: loginData, +// basic_info: basicInfo, +// ...otherInfo, +// }; +// await executeAuthAction('Signup', 'signup', requestData); +// }; - if (response) { - const processedData = safeResponse(response, 'signup', processSignupData); - if (processedData) { - // Logic to set the login state here - setLoginState(true, processedData.token, processedData.user); - } - } - }; +// const logout = () => { +// removeCookie('auth'); +// removeCookie(AUTH_COOKIE); +// setLoginState(false, null, {}); +// console.log('Logout method invoked'); +// }; +// // Add this in your Header component +// useEffect(() => { +// console.log('Value of isLoggedIn from context: ', isLoggedIn); +// }, [isLoggedIn]); - const logout = () => { - removeCookie('auth'); - removeCookie(AUTH_COOKIE); - setLoginState(false, null, {}); - console.log('Logout method invoked'); - }; - // Add this in your Header component - useEffect(() => { - console.log('Value of isloggedin from context: ', isloggedin); - }, [isloggedin]); +// useEffect(() => { +// if (isMounted.current) { +// const queryToken = new URLSearchParams(window.location.search).get( +// 'token' +// ); +// const cookieToken = cookies[AUTH_COOKIE]; +// const activeToken = queryToken || cookieToken; - useEffect(() => { - if (isMounted.current) { - const queryToken = new URLSearchParams(window.location.search).get( - 'token' - ); - const cookieToken = cookies[AUTH_COOKIE]; - const activeToken = queryToken || cookieToken; +// if (activeToken && activeToken !== token) { +// validateToken(activeToken); +// } +// } +// }, [validateToken, cookies[AUTH_COOKIE]]); - if (activeToken && activeToken !== token) { - validateToken(activeToken); - } - } - }, [validateToken, cookies[AUTH_COOKIE]]); - - const contextValue = { - isLoading, - isloggedin, - user, - users, - error, - login, - onLogin, - logout, - signup, - setUser, - setLoginState, - validateToken, - }; +// const contextValue = { +// isLoading, +// isLoggedIn, +// user, +// // users, +// error, +// login, +// // onLogin, +// logout, +// signup, +// setUser, +// setLoginState, +// validateToken, +// }; - return ( - {children} - ); -} +// return ( +// {children} +// ); +// } diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index d1220ea..cbb84b6 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -7,6 +7,7 @@ import React, { useCallback, useMemo, useContext, + useRef, } from 'react'; import { useCookies } from 'react-cookie'; import { @@ -18,11 +19,27 @@ import { getTotalCost, getCardPrice, } from './exampleImport.js'; +import { + filterOutDuplicateYValues, + transformChartData, + convertData, + isEmpty, + validateData, + handleCardAddition, + handleCardRemoval, + getUniqueFilteredXYValues, + calculateTotalFromAllCardPrices, + ensureNumber, + findCollectionIndex, + createApiUrl, + handleApiResponse, + BASE_API_URL, +} from './collectionUtility.jsx'; import { useCombinedContext } from '../CombinedProvider.jsx'; import { useUserContext } from '../UserContext/UserContext.js'; import moment from 'moment'; -// import { useUserContext } from '../UserContext/UserContext.js'; -// 1. Define a default context value +// // import { useUserContext } from '../UserContext/UserContext.js'; +// // 1. Define a default context value const defaultContextValue = { allCollections: [], allCardPrices: [], @@ -45,155 +62,25 @@ const defaultContextValue = { removeOneFromCollection: () => {}, }; -// 2. Replace null with the default value when creating the context +// // 2. Replace null with the default value when creating the context export const CollectionContext = createContext(defaultContextValue); -const filterOutDuplicateYValues = (datasets) => { - // console.log('DATASETS:', datasets); - const seenYValues = new Set(); - return datasets?.filter((data) => { - const yValue = data?.y; - if (seenYValues.has(yValue)) { - return false; - } - seenYValues.add(yValue); - return true; - }); -}; - -const transformChartData = (chartData) => { - let pointsArray = []; - - if (Array.isArray(chartData?.datasets)) { - chartData?.datasets?.forEach((dataset) => { - dataset.data?.forEach((dataEntry) => { - dataEntry.xys?.forEach((xyEntry) => { - const { x, y } = xyEntry.data; - if (x && y !== undefined) { - pointsArray.push({ x, y }); - } - }); - }); - }); - } else { - console.error( - 'Expected chartData.datasets to be an array, but got:', - chartData - ); - } - - return pointsArray; -}; - -function convertData(originalData) { - let finalDataForChart = []; - - const { datasets } = originalData; - - if (Array.isArray(datasets) && datasets.length > 0) { - datasets.forEach((dataset, index) => { - // Loop over all datasets, not just the last one - if (Array.isArray(dataset.data) && dataset.data.length > 0) { - dataset.data.forEach((dataEntry) => { - dataEntry.xys?.forEach((xyEntry) => { - const { label, data } = xyEntry; - // Assume that finalDataForChart has an array for each label - finalDataForChart[label] = finalDataForChart[label] || []; - - data.forEach(({ x, y }) => { - if (x && y !== undefined) { - finalDataForChart[label].push({ x, y }); - } - }); - }); - }); - } - }); - } - - // Convert the data into the format expected by Nivo - const nivoData = Object.keys(finalDataForChart).map((label) => ({ - id: label, - data: finalDataForChart[label], - })); - - return { - ...originalData, - finalDataForChart: nivoData, // Replace this line to return Nivo-formatted data - }; -} - -const isEmpty = (obj) => { - return ( - [Object, Array].includes((obj || {}).constructor) && - !Object.entries(obj || {}).length - ); -}; -const validateData = (data, eventName, functionName) => { - const dataType = typeof data; - console.log( - `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` - ); - if (data === null || data === undefined) { - console.warn( - `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` - ); - return false; - } - if (isEmpty(data)) { - console.error( - `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` - ); - return false; - } - return true; -}; - -const handleCardAddition = (currentCards, cardToAdd) => { - // Check if the card already exists in the currentCards array - const cardToAddId = - typeof cardToAdd.id === 'number' ? String(cardToAdd.id) : cardToAdd.id; - - const matchingCard = currentCards.find((c) => c.id === cardToAddId); - - if (matchingCard) { - matchingCard.quantity++; - return [...currentCards]; - } else { - return [...currentCards, { ...cardToAdd, id: cardToAddId, quantity: 1 }]; - } -}; - -const handleCardRemoval = (currentCards, cardToRemove) => { - // Convert the cardToRemove's id to a string if it's a number - const cardToRemoveId = - typeof cardToRemove.id === 'number' - ? String(cardToRemove.id) - : cardToRemove.id; - - const matchingCard = currentCards.find((c) => c.id === cardToRemoveId); - - if (!matchingCard) { - console.error('Card not found in the collection.'); - return [...currentCards]; - } - - if (matchingCard.quantity > 1) { - matchingCard.quantity--; - return [...currentCards]; - } else { - return currentCards.filter((card) => card.id !== cardToRemoveId); - } -}; - export const CollectionProvider = ({ children }) => { // const { cardPrices } = useCombinedContext(); - const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`; - const [cookies] = useCookies(['userCookie']); + const [cookies] = useCookies(['user']); const { triggerCronJob } = useUserContext(); const [collectionData, setCollectionData] = useState(initialCollectionState); const [allCollections, setAllCollections] = useState([]); + + // const [allCollections, setAllCollections] = useState(() => { + // // Retrieve from local storage when initializing + // const savedCollections = localStorage.getItem('allCollections'); + // return savedCollections ? JSON.parse(savedCollections) : []; + // }); const [allCardPrices, setAllCardPrices] = useState([]); + const updateLocalStorage = (key, data) => { + localStorage.setItem(key, JSON.stringify(data)); + }; const [xyData, setXyData] = useState([ // { // label: '', @@ -210,26 +97,13 @@ export const CollectionProvider = ({ children }) => { useState(false); const chartData = selectedCollection?.chartData || {}; // const datasets = chartData?.datasets || []; - const userId = cookies.userCookie?.id; + const userId = cookies.user?.id; const totalCost = useMemo( () => getTotalCost(selectedCollection), [selectedCollection] ); + const lastFetchedTime = useRef(null); - const calculateTotalFromAllCardPrices = (allCardPrices) => { - if (!Array.isArray(allCardPrices)) return 0; - return allCardPrices.reduce( - (total, price) => total + ensureNumber(price, 0), - 0 - ); - }; - - const ensureNumber = (value, defaultValue = 0) => { - let num = parseFloat(value); - return isNaN(num) ? defaultValue : num; - }; - - // Function to fetch collections const fetchCollections = useCallback(async (userId) => { if (!userId) { console.warn('userId is not set, aborting fetchCollections.'); @@ -237,7 +111,7 @@ export const CollectionProvider = ({ children }) => { } try { - // console.log('Fetching collections...'); + // // console.log('Fetching collections...'); const response = await fetchWrapper( `${BASE_API_URL}/${userId}/collections`, 'GET' @@ -246,12 +120,11 @@ export const CollectionProvider = ({ children }) => { console.log('Fetched collections:', collections); return collections; } catch (error) { - // Your error handling logic here + // // Your error handling logic here return null; } }, []); - // Function to set collections const setCollections = useCallback((collections) => { if (!collections || !Array.isArray(collections)) { console.warn('Invalid collections array:', collections); @@ -267,7 +140,12 @@ export const CollectionProvider = ({ children }) => { ); }); + // Update local state setAllCollections(validCollections); + + // Save to localStorage + // localStorage.setItem('allCollections', JSON.stringify(validCollections)); + setCollectionData( validCollections.length === 0 ? initialCollectionState @@ -278,22 +156,30 @@ export const CollectionProvider = ({ children }) => { ? initialCollectionState : validCollections[0] ); - // Your logic to set collections }, []); - // Your original fetchAndSetCollections function, now simplified const fetchAndSetCollections = useCallback(async () => { + // Get the current time + const currentTime = new Date().getTime(); + + // Check if enough time (1 minute = 60000 ms) has passed since the last fetch + if ( + lastFetchedTime.current && + currentTime - lastFetchedTime.current < 60000 + ) { + console.log('You must wait for 1 minute before fetching again.'); + return; + } + + // Perform the fetch operation const collections = await fetchCollections(userId); if (collections) { setCollections(collections); } - }, [userId, fetchCollections, setCollections]); - const findCollectionIndex = useCallback( - (collections, id) => - collections?.findIndex((collection) => collection?._id === id) ?? -1, - [] - ); + // Update the last fetched time + lastFetchedTime.current = currentTime; + }, [userId, fetchCollections, setCollections]); const updateCollectionData = useCallback( (newData, collectionType) => { @@ -319,26 +205,19 @@ export const CollectionProvider = ({ children }) => { [findCollectionIndex] ); - const createApiUrl = (path) => `${BASE_API_URL}/${path}`; - - const handleApiResponse = (response, method) => { - // Handling POST requests - if (method === 'POST' && response.data?.newCollection) { - return response.data.newCollection; - } - - // Handling PUT requests (updating a collection) - if (method === 'PUT' && response.data?.data?.updatedCollection) { - return response.data.data.updatedCollection; + const createUserCollection = async ( + userId, + newCollectionInfo, + name, + description + ) => { + if (!userId) { + console.error('User ID is undefined.'); + return; } - - throw new Error('Unexpected response format'); - }; - - const createUserCollection = async (userId, collection) => { if ( !validateData( - collection, + newCollectionInfo, 'Create User Collection', 'createUserCollection' ) @@ -347,15 +226,10 @@ export const CollectionProvider = ({ children }) => { return; } - if (!userId) { - console.error('User ID is undefined.'); - return; - } - const payload = { - name: collection.name, - description: collection.description, - userId: collection.userId || userId, + name: name || newCollectionInfo.name, + description: description || newCollectionInfo.description, + userId: userId || newCollectionInfo.userId, totalCost: 0, totalPrice: 0, quantity: 0, @@ -439,20 +313,6 @@ export const CollectionProvider = ({ children }) => { }); }; - const getUniqueFilteredXYValues = (allXYValues) => { - const uniqueXValues = new Set(); - - return allXYValues - .filter((entry) => entry.y !== 0) - .filter((entry) => { - if (!uniqueXValues.has(entry.x)) { - uniqueXValues.add(entry.x); - return true; - } - return false; - }); - }; - const getNewChartData = (activeCollection, updatedPrice, newDataSet) => { const combinedXYValues = [ ...(selectedCollection?.chartData?.datasets?.flatMap( @@ -485,11 +345,11 @@ export const CollectionProvider = ({ children }) => { return; } - // const updatedCards = getUpdatedCards(selectedCollection, card, operation); + // // const updatedCards = getUpdatedCards(selectedCollection, card, operation); let updatedCards; if (operation === 'update') { updatedCards = [...selectedCollection.cards]; - const cardIndex = updatedCards.findIndex((c) => c.id === card.id); + const cardIndex = updatedCards?.findIndex((c) => c.id === card.id); if (cardIndex !== -1) { updatedCards[cardIndex] = { ...updatedCards[cardIndex], @@ -510,7 +370,7 @@ export const CollectionProvider = ({ children }) => { const newDataSet = { data: [ { - xy: [ + xys: [ { label: `Update Number ${ selectedCollection?.chartData?.datasets?.length + 1 || 1 @@ -577,12 +437,20 @@ export const CollectionProvider = ({ children }) => { const updateActiveCollection = useCallback( async (collectionData, existingChartData = {}) => { - // Added existingChartData as an optional parameter const isCreatingNew = !collectionData?._id; - const endpoint = isCreatingNew - ? createApiUrl(`${userId}/collections`) - : createApiUrl(`${userId}/collections/${collectionData._id}`); - const method = isCreatingNew ? 'POST' : 'PUT'; + const isNewCollectionEndpoint = + collectionData?.endpoint === 'newCollection'; + + const endpoint = createApiUrl( + `${userId}/collections${isCreatingNew ? '' : `/${collectionData._id}`}` + ); + + let method = isCreatingNew ? 'POST' : 'PUT'; + console.log(`Debug: Fetching ${method} ${endpoint}`); + + if (!isCreatingNew && isNewCollectionEndpoint) { + method = 'POST'; + } try { const response = await fetchWrapper(endpoint, method, collectionData); @@ -591,60 +459,56 @@ export const CollectionProvider = ({ children }) => { if (!isCreatingNew && !updatedCollection) { throw new Error('Failed to update the existing collection'); } - const newChartData = { - ...updatedCollection.chartData, - xys: [ - ...(existingChartData.xys || []), // Spread existing xys data + + const updateNumber = + updatedCollection?.chartData?.datasets?.length + 1 || 1; + const timestamp = moment().format('YYYY-MM-DD HH:mm'); + const price = updatedCollection.totalPrice; + + const newDataEntry = { + x: timestamp, + y: price, + }; + + const newLabelData = { + label: `Update Number ${updateNumber}`, + data: newDataEntry, + }; + + const newAdditionalPriceData = { + priceChanged: false, // Set this according to your logic + initialPrice: updatedCollection.chartData?.updatedPrice || 0, + updatedPrice: price, + priceDifference: 0, // Set this according to your logic + priceChange: 0, // Set this according to your logic + }; + + const newDataset = { + name: `Dataset ${updateNumber}`, + data: [ { - label: `Update Number ${ - updatedCollection?.chartData?.datasets?.length + 1 || 1 - }`, - data: [ - ...(existingChartData.data || []), // Spread existing data - { - x: moment().format('YYYY-MM-DD HH:mm'), - y: updatedCollection.totalPrice, - }, - ], + xys: [newLabelData], + additionalPriceData: [newAdditionalPriceData], }, ], + }; + + const newChartData = { + ...updatedCollection.chartData, + name: updatedCollection.name, // You may need to set this according to your logic + userId: updatedCollection.userId, // You may need to set this according to your logic + xys: [...(updatedCollection.chartData?.xys || []), newLabelData], datasets: [ ...(updatedCollection.chartData?.datasets || []), - { - data: [ - { - xys: [ - { - label: `Update Number ${ - updatedCollection?.chartData?.datasets?.length + 1 || 1 - }`, - data: [ - { - x: moment().format('YYYY-MM-DD HH:mm'), - y: updatedCollection.totalPrice, - }, - ], - }, - ], - additionalPriceData: { - priceChanged: false, - initialPrice: - updatedCollection.chartData?.updatedPrice || 0, - updatedPrice: updatedCollection.totalPrice, - priceDifference: 0, - priceChange: 0, - }, - }, - ], - }, + newDataset, ], }; - updatedCollection.chartData = newChartData; + updatedCollection.chartData = newChartData; const convertedData = convertData(newChartData); updatedCollection.xys = convertedData; - // setXyData(convertedData.finalDataForChart); - xyData.push(...convertedData.finalDataForChart); // Spread to add to existing array + + xyData.push(...convertedData.finalDataForChart); updateCollectionData(updatedCollection, 'selectedCollection'); updateCollectionData(updatedCollection, 'allCollections'); } catch (error) { @@ -653,6 +517,7 @@ export const CollectionProvider = ({ children }) => { }, [userId, updateCollectionData] ); + // console.log( // '<----------$$$$$$$$$CONVERTED DATA FOR CHART$$$$$$$$$---------->', // xyData @@ -681,7 +546,7 @@ export const CollectionProvider = ({ children }) => { updatedPricesArray.forEach((card) => { const currentCardPrice = selectedCollection?.cards[card?.id]?.price; - // Check if this is the special tagged card + // // Check if this is the special tagged card if (card._tag === 'updated') { console.log('Found the special card:', card); } @@ -710,7 +575,7 @@ export const CollectionProvider = ({ children }) => { const contextValue = useMemo( () => ({ - // DATA + // // DATA allCollections, selectedCollection, collectionData, @@ -729,8 +594,13 @@ export const CollectionProvider = ({ children }) => { calculateTotalPrice: () => getCardPrice(selectedCollection), getTotalCost: () => getTotalCost(selectedCollection), // FUNCTIONS - createUserCollection: (collection, name, description) => - createUserCollection(userId, collection, name, description), + createUserCollection: (userId, newCollectionInfo) => + createUserCollection( + userId, + newCollectionInfo, + newCollectionInfo.name, + newCollectionInfo.description + ), removeCollection: (collection) => removeCollection(collection), fetchAllCollectionsForUser: fetchAndSetCollections, setSelectedCollection: updateActiveCollection, @@ -742,12 +612,16 @@ export const CollectionProvider = ({ children }) => { [allCollections, selectedCollection, totalCost] ); + useEffect(() => { + // Save to local storage whenever it changes + localStorage.setItem('allCollections', JSON.stringify(allCollections)); + }, [allCollections]); + useEffect(() => { console.log('COLLECTION CONTEXT: ', { contextValue, }); }, [contextValue, updatedPricesFromCombinedContext]); - // Assuming updatedPrices is passed as a prop or state useEffect(() => { if (selectedCollection && totalCost) { @@ -771,9 +645,9 @@ export const CollectionProvider = ({ children }) => { ); }; -// useCollectionStore.js -// import { useContext } from 'react'; -// import { CollectionContext } from '../CollectionContext/CollectionContext'; +// // useCollectionStore.js +// // import { useContext } from 'react'; +// // import { CollectionContext } from '../CollectionContext/CollectionContext'; export const useCollectionStore = () => { const context = useContext(CollectionContext); diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx new file mode 100644 index 0000000..7cc3702 --- /dev/null +++ b/src/context/CollectionContext/collectionUtility.jsx @@ -0,0 +1,259 @@ +const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`; + +/** + * Filters out duplicate Y values from an array of datasets. + * @param {Array} datasets - An array of dataset objects. + * @returns {Array} Filtered datasets. + */ +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; + }); +}; + +/** + * Transforms the given chartData into an array of x-y points. + * @param {Object} chartData - Chart data object. + * @returns {Array} Transformed data. + */ +const transformChartData = (chartData) => { + const pointsArray = []; + + if (Array.isArray(chartData?.datasets)) { + chartData?.datasets?.forEach((dataset) => { + dataset.data?.forEach((dataEntry) => { + dataEntry.xys?.forEach((xyEntry) => { + const { x, y } = xyEntry.data; + if (x && y !== undefined) { + pointsArray.push({ x, y }); + } + }); + }); + }); + } + + return pointsArray; +}; + +/** + * Converts the given original data into a Nivo-compatible format. + * @param {Object} originalData - Original data object. + * @returns {Object} Converted data. + */ +const convertData = (originalData) => { + const finalDataForChart = []; + const { datasets } = originalData; + + if (Array.isArray(datasets) && datasets.length > 0) { + datasets.forEach((dataset, index) => { + if (Array.isArray(dataset.data) && dataset.data.length > 0) { + dataset.data.forEach((dataEntry) => { + dataEntry.xys?.forEach((xyEntry) => { + const { label, data } = xyEntry; + finalDataForChart[label] = finalDataForChart[label] || []; + + data.forEach(({ x, y }) => { + if (x && y !== undefined) { + finalDataForChart[label].push({ x, y }); + } + }); + }); + }); + } + }); + } + + const nivoData = Object.keys(finalDataForChart).map((label) => ({ + id: label, + data: finalDataForChart[label], + })); + + return { + ...originalData, + finalDataForChart: nivoData, + }; +}; + +/** + * Checks if the given object is empty. + * @param {Object} obj - Object to check. + * @returns {Boolean} True if the object is empty, false otherwise. + */ +const isEmpty = (obj) => { + return ( + [Object, Array].includes((obj || {}).constructor) && + !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. + */ +const validateData = (data, eventName, functionName) => { + if (typeof data === 'object') { + if (isEmpty(data)) { + 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; +}; + +/** + * 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. + */ +const handleCardAddition = (currentCards, cardToAdd) => { + // Initialize currentCards to an empty array if it's not defined + currentCards = currentCards || []; + + const cardToAddId = + typeof cardToAdd.id === 'number' ? String(cardToAdd.id) : cardToAdd.id; + const matchingCard = currentCards.find((c) => c.id === cardToAddId); + + if (matchingCard) { + matchingCard.quantity++; + return [...currentCards]; + } + + return [...currentCards, { ...cardToAdd, id: cardToAddId, quantity: 1 }]; +}; + +/** + * 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. + */ +const handleCardRemoval = (currentCards, cardToRemove) => { + // Initialize currentCards to an empty array if it's not defined + currentCards = currentCards || []; + + const cardToRemoveId = + typeof cardToRemove.id === 'number' + ? String(cardToRemove.id) + : cardToRemove.id; + const matchingCard = currentCards.find((c) => c.id === cardToRemoveId); + + if (!matchingCard) { + console.error('Card not found in the collection.'); + return [...currentCards]; + } + + if (matchingCard.quantity > 1) { + matchingCard.quantity--; + return [...currentCards]; + } else { + return currentCards.filter((card) => card.id !== cardToRemoveId); + } +}; + +/** + * 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. + */ +const getUniqueFilteredXYValues = (allXYValues) => { + const uniqueXValues = new Set(); + return allXYValues + .filter((entry) => entry.y !== 0) + .filter((entry) => { + if (!uniqueXValues.has(entry.x)) { + uniqueXValues.add(entry.x); + return true; + } + return false; + }); +}; + +/** + * Calculates the total price from an array of card prices. + * @param {Array} allCardPrices - Array of card prices. + * @returns {Number} Total price. + */ +const calculateTotalFromAllCardPrices = (allCardPrices) => { + if (!Array.isArray(allCardPrices)) return 0; + return allCardPrices.reduce( + (total, price) => total + ensureNumber(price, 0), + 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. + */ +const ensureNumber = (value, defaultValue = 0) => { + const num = parseFloat(value); + return isNaN(num) ? defaultValue : num; +}; + +/** + * Finds the index of a collection by its ID. + * @param {Array} collections - Array of collections. + * @param {String} id - ID of the collection to find. + * @returns {Number} Index of the collection, or -1 if not found. + */ +const findCollectionIndex = (collections, id) => + collections?.findIndex((collection) => collection?._id === id) ?? -1; + +/** + * Creates a full API URL from a given path. + * @param {String} path - API path. + * @returns {String} Full API URL. + */ +const createApiUrl = (path) => `${BASE_API_URL}/${path}`; + +/** + * Handles API responses based on the HTTP method used. + * @param {Object} response - API response. + * @param {String} method - HTTP method used (e.g., 'POST', 'PUT'). + * @returns {Object} Processed API response. + */ +const handleApiResponse = (response, method) => { + if (method === 'POST' && response?.data?.newCollection) { + return response?.data?.newCollection; + } + if (method === 'PUT' && response.data?.data?.updatedCollection) { + return response?.data?.data?.updatedCollection; + } + throw new Error('Unexpected response format'); +}; + +// Export the utility functions if needed +module.exports = { + filterOutDuplicateYValues, + transformChartData, + convertData, + isEmpty, + validateData, + handleCardAddition, + handleCardRemoval, + getUniqueFilteredXYValues, + calculateTotalFromAllCardPrices, + ensureNumber, + findCollectionIndex, + createApiUrl, + handleApiResponse, + BASE_API_URL, +}; diff --git a/src/context/CollectionContext/copy.js b/src/context/CollectionContext/copy.js new file mode 100644 index 0000000..cb6ff73 --- /dev/null +++ b/src/context/CollectionContext/copy.js @@ -0,0 +1,926 @@ +// import React, { +// createContext, +// useState, +// useEffect, +// useCallback, +// useMemo, +// useContext, +// } from 'react'; +// import { useCookies } from 'react-cookie'; +// import { +// initialCollectionState, +// fetchWrapper, +// removeDuplicateCollections, +// calculateAndUpdateTotalPrice, +// calculateTotalPrice, +// getTotalCost, +// getCardPrice, +// } from './exampleImport.js'; +// import { useCombinedContext } from '../CombinedProvider.jsx'; +// import { useUserContext } from '../UserContext/UserContext.js'; +// import moment from 'moment'; +// // import { useUserContext } from '../UserContext/UserContext.js'; +// // 1. Define a default context value +// const defaultContextValue = { +// allCollections: [], +// allCardPrices: [], +// xy: [], +// selectedCollection: {}, +// collectionData: initialCollectionState, +// totalCost: 0, +// openChooseCollectionDialog: false, +// updatedPricesFromCombinedContext: {}, +// setUpdatedPricesFromCombinedContext: () => {}, +// setOpenChooseCollectionDialog: () => {}, +// calculateTotalPrice: () => {}, +// getTotalCost: () => {}, +// createUserCollection: () => {}, +// removeCollection: () => {}, +// fetchAllCollectionsForUser: () => {}, +// setSelectedCollection: () => {}, +// setAllCollections: () => {}, +// addOneToCollection: () => {}, +// removeOneFromCollection: () => {}, +// }; + +// // 2. Replace null with the default value when creating the context +// export const CollectionContext = createContext(defaultContextValue); +// const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`; + +// const filterOutDuplicateYValues = (datasets) => { +// // console.log('DATASETS:', datasets); +// const seenYValues = new Set(); +// return datasets?.filter((data) => { +// const yValue = data?.y; +// if (seenYValues.has(yValue)) { +// return false; +// } +// seenYValues.add(yValue); +// return true; +// }); +// }; + +// const transformChartData = (chartData) => { +// let pointsArray = []; + +// if (Array.isArray(chartData?.datasets)) { +// chartData?.datasets?.forEach((dataset) => { +// dataset.data?.forEach((dataEntry) => { +// dataEntry.xys?.forEach((xyEntry) => { +// const { x, y } = xyEntry.data; +// if (x && y !== undefined) { +// pointsArray.push({ x, y }); +// } +// }); +// }); +// }); +// } else { +// console.error( +// 'Expected chartData.datasets to be an array, but got:', +// chartData +// ); +// } + +// return pointsArray; +// }; + +// function convertData(originalData) { +// let finalDataForChart = []; + +// const { datasets } = originalData; + +// if (Array.isArray(datasets) && datasets.length > 0) { +// datasets.forEach((dataset, index) => { +// // Loop over all datasets, not just the last one +// if (Array.isArray(dataset.data) && dataset.data.length > 0) { +// dataset.data.forEach((dataEntry) => { +// dataEntry.xys?.forEach((xyEntry) => { +// const { label, data } = xyEntry; +// // Assume that finalDataForChart has an array for each label +// finalDataForChart[label] = finalDataForChart[label] || []; + +// data.forEach(({ x, y }) => { +// if (x && y !== undefined) { +// finalDataForChart[label].push({ x, y }); +// } +// }); +// }); +// }); +// } +// }); +// } + +// // Convert the data into the format expected by Nivo +// const nivoData = Object.keys(finalDataForChart).map((label) => ({ +// id: label, +// data: finalDataForChart[label], +// })); + +// return { +// ...originalData, +// finalDataForChart: nivoData, // Replace this line to return Nivo-formatted data +// }; +// } + +// const isEmpty = (obj) => { +// return ( +// [Object, Array].includes((obj || {}).constructor) && +// !Object.entries(obj || {}).length +// ); +// }; +// const validateData = (data, eventName, functionName) => { +// const dataType = typeof data; +// console.log( +// `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` +// ); +// if (data === null || data === undefined) { +// console.warn( +// `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` +// ); +// return false; +// } +// if (isEmpty(data)) { +// console.error( +// `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` +// ); +// return false; +// } +// return true; +// }; + +// const handleCardAddition = (currentCards, cardToAdd) => { +// // Check if the card already exists in the currentCards array +// const cardToAddId = +// typeof cardToAdd.id === 'number' ? String(cardToAdd.id) : cardToAdd.id; + +// const matchingCard = currentCards.find((c) => c.id === cardToAddId); + +// if (matchingCard) { +// matchingCard.quantity++; +// return [...currentCards]; +// } else { +// return [...currentCards, { ...cardToAdd, id: cardToAddId, quantity: 1 }]; +// } +// }; + +// const handleCardRemoval = (currentCards, cardToRemove) => { +// // Convert the cardToRemove's id to a string if it's a number +// const cardToRemoveId = +// typeof cardToRemove.id === 'number' +// ? String(cardToRemove.id) +// : cardToRemove.id; + +// const matchingCard = currentCards.find((c) => c.id === cardToRemoveId); + +// if (!matchingCard) { +// console.error('Card not found in the collection.'); +// return [...currentCards]; +// } + +// if (matchingCard.quantity > 1) { +// matchingCard.quantity--; +// return [...currentCards]; +// } else { +// return currentCards.filter((card) => card.id !== cardToRemoveId); +// } +// }; +// const calculateTotalFromAllCardPrices = (allCardPrices) => { +// if (!Array.isArray(allCardPrices)) return 0; +// return allCardPrices.reduce( +// (total, price) => total + ensureNumber(price, 0), +// 0 +// ); +// }; +// const ensureNumber = (value, defaultValue = 0) => { +// let num = parseFloat(value); +// return isNaN(num) ? defaultValue : num; +// }; +// const HTTP_METHODS = { +// POST: 'POST', +// PUT: 'PUT', +// GET: 'GET', +// DELETE: 'DELETE', +// }; +// const handleError = (error) => { +// console.error(`An error occurred: ${error.message}`); +// }; +// const findCollectionIndex = useCallback( +// (collections, id) => +// collections?.findIndex((collection) => collection?._id === id) ?? -1, +// [] +// ); +// const apiRequest = async (userId, endpoint, method, data) => { +// try { +// const url = createApiUrl(endpoint); +// const response = await fetchWrapper(url, method, data); + +// if (!response || response.error) { +// throw new Error( +// response ? response.error.message : 'Failed to connect to the server.' +// ); +// } + +// return handleApiResponse(response, method); +// } catch (error) { +// console.error(`API Request failed: ${error.message}`); +// return null; +// } +// }; +// const createApiUrl = (path) => `${BASE_API_URL}/${path}`; +// const handleApiResponse = (response, method) => { +// // Handling POST requests +// if (method === 'POST' && response?.data?.newCollection) { +// return response.data.newCollection; +// } + +// // Handling PUT requests (updating a collection) +// if (method === 'PUT' && response.data?.data?.updatedCollection) { +// return response.data.data.updatedCollection; +// } + +// throw new Error('Unexpected response format'); +// }; +// const formatDate = () => moment().format('YYYY-MM-DD HH:mm'); +// // Utility function to filter unique X values +// const filterUniqueXValues = (values) => { +// const uniqueXValues = new Set(); +// return values.filter((entry) => { +// if (!uniqueXValues.has(entry.x)) { +// uniqueXValues.add(entry.x); +// return true; +// } +// return false; +// }); +// }; +// const getUniqueFilteredXYValues = (allXYValues) => { +// const uniqueXValues = new Set(); + +// return allXYValues +// .filter((entry) => entry.y !== 0) +// .filter((entry) => { +// if (!uniqueXValues.has(entry.x)) { +// uniqueXValues.add(entry.x); +// return true; +// } +// return false; +// }); +// }; +// const safeFetch = async (url, method, payload) => { +// try { +// const response = await fetchWrapper(url, method, payload); +// return handleApiResponse(response, method); +// } catch (error) { +// handleError(error); +// return null; +// } +// }; +// const fetchAndUpdateCollectionData = async ( +// endpoint, +// method, +// payload, +// setCollectionFn, +// setErrorFn +// ) => { +// try { +// const response = await fetchWrapper(endpoint, method, payload); +// const data = handleApiResponse(response, method); + +// if (data) { +// setCollectionFn(data); +// } else { +// throw new Error('Data is null'); +// } +// } catch (error) { +// setErrorFn(`Failed to process the request: ${error.message}`); +// } +// }; + +// const apiRequestWrapper = (userId, endpoint, method, data) => { +// return apiRequest(userId, `${userId}/collections${endpoint}`, method, data); +// }; + +// const updateCollectionData = (newData, updaterFn) => { +// updaterFn((prevData) => { +// const index = findCollectionIndex(prevData, newData._id); +// return index === -1 +// ? [...prevData, newData] +// : Object.assign([...prevData], { [index]: newData }); +// }); +// }; + +// const CollectionHandler = () => { +// // const { cardPrices } = useCombinedContext(); +// const [cookies] = useCookies(['user']); +// const { triggerCronJob } = useUserContext(); +// const [collectionData, setCollectionData] = useState(initialCollectionState); +// const [allCollections, setAllCollections] = useState([]); +// const [allCardPrices, setAllCardPrices] = useState([]); +// const [xyData, setXyData] = useState([ +// // { +// // label: '', +// // data: [], +// // }, +// ]); // New state to hold xy data +// // const [updatedPrices, setUpdatedPrices] = useState([]); +// const [ +// updatedPricesFromCombinedContext, +// setUpdatedPricesFromCombinedContext, +// ] = useState({}); +// const [selectedCollection, setSelectedCollection] = useState({}); +// const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = +// useState(false); +// const chartData = selectedCollection?.chartData || {}; +// // const datasets = chartData?.datasets || []; +// const userId = cookies.user?.id; +// const totalCost = useMemo( +// () => getTotalCost(selectedCollection), +// [selectedCollection] +// ); +// // [COLLECTION RETRIEVAL] +// const fetchCollections = useCallback( +// async (userId) => { +// fetchAndUpdateCollectionData( +// `${BASE_API_URL}/${userId}/collections`, +// HTTP_METHODS.GET, +// null, +// setAllCollections, +// handleError +// ); +// }, +// [setAllCollections, handleError] +// ); +// const useCollection = (userId) => { +// const [collections, setCollections] = useState(null); + +// const fetchAndSetCollections = useCallback(async () => { +// const data = await safeFetch( +// `${BASE_API_URL}/${userId}/collections`, +// HTTP_METHODS.GET +// ); +// if (data) setCollections(data.allCollections); +// }, [userId]); + +// return [collections, fetchAndSetCollections]; +// }; +// // ... Other Collection related methods + +// // [COLLECTION CREATION & DELETION] +// const useCollectionCrud = (userId) => { +// const [selectedCollection, setSelectedCollection] = useState(null); +// const [allCollections, setAllCollections] = useState([]); + +// const createUserCollection = useCallback( +// async (newCollectionInfo) => { +// const payload = { ...newCollectionInfo, userId, totalCost: 0, totalPrice: 0 }; +// const response = await safeFetch( +// createApiUrl(`${userId}/collections/newCollection/${userId}`), +// 'POST', +// payload +// ); +// if (response) performCollectionUpdate(response, 'create'); +// }, +// [userId] +// ); + +// const removeCollection = useCallback( +// async (collectionId) => { +// await safeFetch( +// createApiUrl(`${userId}/collections/${collectionId}`), +// 'DELETE' +// ); +// setAllCollections((prev) => prev.filter((item) => item._id !== collectionId)); +// }, +// [userId] +// ); +// const handleCollection = useCallback( +// async (method, payload, collectionId = '') => { +// const endpoint = collectionId +// ? `${userId}/collections/${collectionId}` +// : `${userId}/collections`; +// fetchAndUpdateCollectionData( +// endpoint, +// method, +// payload, +// setAllCollections, +// handleError +// ); +// }, +// [userId, setAllCollections, handleError] +// ); +// return [createUserCollection, removeCollection]; +// }; +// // [CARD ADDITION & REMOVAL] +// const addOrRemoveCard = useCallback( +// async (card, cardInfo, operation) => { +// // Your previous logic here. + +// const collectionId = selectedCollection._id || allCollections[0]._id; +// if (!collectionId) { +// // Handle error +// return; +// } + +// const updateInfo = getUpdateInfo(card, cardInfo, operation); // Assume this function is implemented +// const method = operation === 'create' ? 'POST' : 'PUT'; + +// fetchAndUpdateCollectionData( +// `${userId}/collections/${collectionId}`, +// method, +// updateInfo, +// setSelectedCollection, +// handleError +// ); +// }, +// [userId, setSelectedCollection, handleError] +// ); +// const useCardManager = (userId, selectedCollection, allCollections) => { +// const addOrRemoveCard = useCallback( +// async (card, operation) => { +// // ... (your existing logic here, try to use helper methods) +// await performCollectionUpdate(updatedCollection, 'update'); +// }, +// [userId, selectedCollection, allCollections] +// ); + +// return [addOrRemoveCard]; +// }; + +// // [COLLECTION DATA UPDATE] +// const performCollectionUpdate = useCallback( +// async (updateInfo, operation) => { +// const method = operation === 'create' ? 'POST' : 'PUT'; +// const endpoint = updateInfo._id ? `/${updateInfo._id}` : ''; +// const updatedCollection = await apiRequestWrapper( +// userId, +// endpoint, +// method, +// updateInfo +// ); +// if (updatedCollection) { +// updateCollectionData(updatedCollection, setAllCollections); +// updateCollectionData(updatedCollection, setSelectedCollection); +// } +// }, +// [userId, setAllCollections, setSelectedCollection] +// ); +// return ( +// // Your JSX components here +// ); +// }; +// // [COLLECTION RETRIEVAL] +// // const fetchCollections = useCallback( +// // async (userId) => { +// // fetchAndUpdateCollectionData( +// // `${BASE_API_URL}/${userId}/collections`, +// // HTTP_METHODS.GET, +// // null, +// // setAllCollections, +// // handleError +// // ); +// // }, +// // [setAllCollections, handleError] +// // ); +// // const setCollections = useCallback((collections) => { +// // try { +// // if (!collections || !Array.isArray(collections)) +// // throw new Error('Invalid collections array'); + +// // // Other logic for setting collections +// // } catch (error) { +// // handleError(error); +// // } +// // }, []); +// // const fetchAndSetCollections = useCallback(async () => { +// // try { +// // const collections = await fetchCollections(userId); +// // if (collections) setCollections(collections); +// // } catch (error) { +// // handleError(error); +// // } +// // }, [userId, fetchCollections, setCollections]); +// // // [COLLECTION CREATION & DELETION] +// // const createUserCollection = async ( +// // userId, +// // newCollectionInfo, +// // name, +// // description +// // ) => { +// // if (!userId) { +// // console.error('User ID is undefined.'); +// // return; +// // } +// // if ( +// // !validateData( +// // newCollectionInfo, +// // 'Create User Collection', +// // 'createUserCollection' +// // ) +// // ) { +// // console.error('Validation failed for collection data.'); +// // return; +// // } + +// // const payload = { +// // name: name || newCollectionInfo.name, +// // description: description || newCollectionInfo.description, +// // userId: userId || newCollectionInfo.userId, +// // totalCost: 0, +// // totalPrice: 0, +// // quantity: 0, +// // totalQuantity: 0, +// // xys: xyData || [], +// // allCardPrices: [], +// // cards: [], +// // chartData: {}, +// // }; + +// // try { +// // const url = createApiUrl(`${userId}/collections/newCollection/${userId}`); +// // const response = await fetchWrapper(url, 'POST', payload); + +// // if (!response) { +// // console.error('Failed to connect to the server.'); +// // return; +// // } + +// // if (response.error) { +// // console.error( +// // `Failed to create a new collection: ${response.error.message}` +// // ); +// // return; +// // } + +// // const savedCollection = handleApiResponse(response, 'POST'); +// // await performCollectionUpdate(payload, 'create'); + +// // // updateCollectionData(savedCollection, 'allCollections'); +// // // updateCollectionData(savedCollection, 'collectionData'); +// // } catch (error) { +// // console.error(`Failed to create a new collection: ${error.message}`); +// // } +// // }; +// // const removeCollection = async (collection) => { +// // if (!collection._id) { +// // console.error('Collection ID is undefined.'); +// // return; +// // } + +// // try { +// // const url = createApiUrl(`${userId}/collections/${collection._id}`); +// // const response = await fetchWrapper(url, 'DELETE'); + +// // if (response.error) { +// // console.error('Failed to delete the collection:', response.error); +// // return; +// // } + +// // setAllCollections((prev) => +// // prev.filter((item) => item._id !== collection._id) +// // ); + +// // if (selectedCollection._id === collection._id) { +// // setSelectedCollection(initialCollectionState); +// // setCollectionData(initialCollectionState); +// // } +// // } catch (error) { +// // console.error(`Failed to delete the collection: ${error.message}`); +// // } +// // }; +// // // const handleCollection = useCallback( +// // // async (method, payload, collectionId = '') => { +// // // const endpoint = collectionId +// // // ? `${userId}/collections/${collectionId}` +// // // : `${userId}/collections`; +// // // fetchAndUpdateCollectionData( +// // // endpoint, +// // // method, +// // // payload, +// // // setAllCollections, +// // // handleError +// // // ); +// // // }, +// // // [userId, setAllCollections, handleError] +// // // ); +// // // [CARD ADDITION & REMOVAL] +// // const getUpdatedCards = (activeCollection, card, operation) => { +// // const handler = +// // operation === 'add' ? handleCardAddition : handleCardRemoval; +// // const updatedCards = handler(activeCollection?.cards, card); + +// // return updatedCards.map((card) => { +// // const cardPrice = card.card_prices?.[0]?.tcgplayer_price; +// // const computedPrice = cardPrice * card.quantity; + +// // const newDataset = { x: formatDate(), y: computedPrice }; +// // card.chart_datasets = filterUniqueXValues([ +// // ...(card?.chart_datasets || []), +// // newDataset, +// // ]); + +// // card.price = cardPrice; +// // card.totalPrice = computedPrice; +// // return card; +// // }); +// // }; +// // const getNewChartData = (activeCollection, updatedPrice, newDataSet) => { +// // const combinedXYValues = [ +// // ...(selectedCollection?.chartData?.datasets?.flatMap( +// // (dataset) => dataset.data +// // ) || []), +// // newDataSet.data[0].xy, +// // ]; + +// // const filteredXYValues = getUniqueFilteredXYValues(combinedXYValues); + +// // return { +// // name: `Chart for Collection: ${activeCollection?.name}`, +// // userId: userId, +// // updatedPrice: updatedPrice, +// // xys: xyData || [], +// // datasets: [ +// // ...(selectedCollection?.chartData?.datasets || []), +// // newDataSet, +// // ], +// // allXYValues: filteredXYValues, +// // }; +// // }; +// // // const addOrRemoveCard = useCallback( +// // // async (card, cardInfo, operation) => { +// // // // Your previous logic here. + +// // // const collectionId = selectedCollection._id || allCollections[0]._id; +// // // if (!collectionId) { +// // // // Handle error +// // // return; +// // // } + +// // // const updateInfo = getUpdateInfo(card, cardInfo, operation); // Assume this function is implemented +// // // const method = operation === 'create' ? 'POST' : 'PUT'; + +// // // fetchAndUpdateCollectionData( +// // // `${userId}/collections/${collectionId}`, +// // // method, +// // // updateInfo, +// // // setSelectedCollection, +// // // handleError +// // // ); +// // // }, +// // // [userId, setSelectedCollection, handleError] +// // // ); +// // // // [COLLECTION DATA UPDATE] +// // // const performCollectionUpdate = useCallback( +// // // async (updateInfo, operation) => { +// // // const method = operation === 'create' ? 'POST' : 'PUT'; +// // // const endpoint = updateInfo._id ? `/${updateInfo._id}` : ''; +// // // const updatedCollection = await apiRequestWrapper( +// // // userId, +// // // endpoint, +// // // method, +// // // updateInfo +// // // ); +// // // if (updatedCollection) { +// // // updateCollectionData(updatedCollection, setAllCollections); +// // // updateCollectionData(updatedCollection, setSelectedCollection); +// // // } +// // // }, +// // // [userId, setAllCollections, setSelectedCollection] +// // // ); +// // const updateCollection = (newData, collectionType, updaterFn) => { +// // switch (collectionType) { +// // case 'allCollections': +// // return updaterFn((prevCollections = []) => { +// // const existingIndex = findCollectionIndex( +// // prevCollections, +// // newData?._id +// // ); +// // return existingIndex === -1 +// // ? [...prevCollections, newData] +// // : Object.assign([...prevCollections], { [existingIndex]: newData }); +// // }); +// // case 'selectedCollection': +// // case 'collectionData': +// // return updaterFn(newData); +// // default: +// // break; +// // } +// // }; +// // const updateActiveCollection = useCallback( +// // async (collectionData, existingChartData = {}) => { +// // const isCreatingNew = !collectionData?._id; +// // const isNewCollectionEndpoint = +// // collectionData?.endpoint === 'newCollection'; // Add this line to check if it's the newcollection endpoint +// // const endpoint = isCreatingNew +// // ? createApiUrl(`${userId}/collections`) +// // : createApiUrl(`${userId}/collections/${collectionData._id}`); +// // let method = isCreatingNew ? 'POST' : 'PUT'; // Default setting +// // console.log(`Debug: Fetching ${method} ${endpoint}`); // Debugging log + +// // // Restrict to only POST for the 'newcollection' endpoint +// // if (!isCreatingNew && isNewCollectionEndpoint) { +// // method = 'POST'; +// // } +// // try { +// // const response = await fetchWrapper(endpoint, method, collectionData); +// // const updatedCollection = handleApiResponse(response, method); + +// // if (!isCreatingNew && !updatedCollection) { +// // throw new Error('Failed to update the existing collection'); +// // } +// // const newChartData = { +// // ...updatedCollection.chartData, +// // xys: [ +// // ...(existingChartData.xys || []), // Spread existing xys data +// // { +// // label: `Update Number ${ +// // updatedCollection?.chartData?.datasets?.length + 1 || 1 +// // }`, +// // data: [ +// // ...(existingChartData.data || []), // Spread existing data +// // { +// // x: moment().format('YYYY-MM-DD HH:mm'), +// // y: updatedCollection.totalPrice, +// // }, +// // ], +// // }, +// // ], +// // datasets: [ +// // ...(updatedCollection.chartData?.datasets || []), +// // { +// // data: [ +// // { +// // xys: [ +// // { +// // label: `Update Number ${ +// // updatedCollection?.chartData?.datasets?.length + 1 || 1 +// // }`, +// // data: [ +// // { +// // x: moment().format('YYYY-MM-DD HH:mm'), +// // y: updatedCollection.totalPrice, +// // }, +// // ], +// // }, +// // ], +// // additionalPriceData: { +// // priceChanged: false, +// // initialPrice: +// // updatedCollection.chartData?.updatedPrice || 0, +// // updatedPrice: updatedCollection.totalPrice, +// // priceDifference: 0, +// // priceChange: 0, +// // }, +// // }, +// // ], +// // }, +// // ], +// // }; +// // updatedCollection.chartData = newChartData; + +// // const convertedData = convertData(newChartData); +// // updatedCollection.xys = convertedData; +// // // setXyData(convertedData.finalDataForChart); +// // xyData.push(...convertedData.finalDataForChart); // Spread to add to existing array +// // updateCollectionData(updatedCollection, 'selectedCollection'); +// // updateCollectionData(updatedCollection, 'allCollections'); +// // } catch (error) { +// // console.error(`Failed to update the collection: ${error.message}`); +// // } +// // }, +// // [userId, updateCollectionData] +// // ); +// // console.log( +// // '<----------$$$$$$$$$CONVERTED DATA FOR CHART$$$$$$$$$---------->', +// // xyData +// // ); +// // useEffect(() => { +// // // Check if the prices are updated or new cards are added +// // const updatedPricesArray = +// // updatedPricesFromCombinedContext?.updatedPrices || []; + +// // if (!Array.isArray(updatedPricesArray)) { +// // return; // Exit the useEffect early if not an array +// // } + +// useEffect(() => { +// const updatedPricesArray = Object.keys( +// updatedPricesFromCombinedContext || {} +// ).map((cardId) => updatedPricesFromCombinedContext[cardId]); + +// console.log( +// '[1][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][UPDATED PRICES]==========>', +// updatedPricesArray +// ); + +// const updatedCardPrices = []; + +// updatedPricesArray.forEach((card) => { +// const currentCardPrice = selectedCollection?.cards[card?.id]?.price; + +// // Check if this is the special tagged card +// if (card._tag === 'updated') { +// console.log('Found the special card:', card); +// } + +// if (card?.updatedPrice !== currentCardPrice) { +// updatedCardPrices.push(card); +// console.log( +// '[2][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][CARD]==========>', +// card +// ); +// console.log( +// 'CARD FROM SELECTED COLLECTIONS:', +// selectedCollection.cards[card.id] +// ); +// } else { +// console.log('Price has not been updated for card with ID:', card.id); +// } +// }); + +// if (updatedCardPrices.length > 0) { +// updatedCardPrices.forEach((card) => { +// addOrRemoveCard(card, { updated: true }, 'update'); +// }); +// } +// }, [updatedPricesFromCombinedContext]); + +// const contextValue = useMemo( +// () => ({ +// // DATA +// allCollections, +// selectedCollection, +// collectionData, +// totalCost, + +// allCardPrices: selectedCollection?.allCardPrices || [], +// xys: xyData || [], +// openChooseCollectionDialog, +// updatedPricesFromCombinedContext, +// setUpdatedPricesFromCombinedContext: (updatedPrices) => { +// // This is the function that will be passed to the combined context to update the prices +// setUpdatedPricesFromCombinedContext(updatedPrices); +// }, +// setOpenChooseCollectionDialog, +// // FUNCTIONS +// calculateTotalPrice: () => getCardPrice(selectedCollection), +// getTotalCost: () => getTotalCost(selectedCollection), +// // FUNCTIONS +// createUserCollection: (userId, newCollectionInfo) => +// createUserCollection( +// userId, +// newCollectionInfo, +// newCollectionInfo.name, +// newCollectionInfo.description +// ), +// removeCollection: (collection) => removeCollection(collection), +// fetchAllCollectionsForUser: fetchAndSetCollections, +// setSelectedCollection: updateActiveCollection, +// setAllCollections: (collections) => setAllCollections(collections), +// addOneToCollection: (card, cardInfo) => +// addOrRemoveCard(card, cardInfo, 'add'), +// removeOneFromCollection: (card) => addOrRemoveCard(card, null, 'remove'), +// }), +// [allCollections, selectedCollection, totalCost] +// ); + +// useEffect(() => { +// console.log('COLLECTION CONTEXT: ', { +// contextValue, +// }); +// }, [contextValue, updatedPricesFromCombinedContext]); +// // Assuming updatedPrices is passed as a prop or state + +// useEffect(() => { +// if (selectedCollection && totalCost) { +// // Trigger the cron job whenever the selectedCollection changes +// triggerCronJob(); +// } +// }, [selectedCollection, triggerCronJob, totalCost]); + +// useEffect(() => { +// console.log('Total Cost has been updated:', totalCost); +// }, [totalCost]); + +// useEffect(() => { +// if (userId) fetchAndSetCollections(); +// }, [userId]); + +// return ( +// +// {children} +// +// ); +// }; + +// // useCollectionStore.js +// // import { useContext } from 'react'; +// // import { CollectionContext } from '../CollectionContext/CollectionContext'; + +// export const useCollectionStore = () => { +// const context = useContext(CollectionContext); +// if (!context) { +// throw new Error( +// 'useCollectionStore must be used within a CollectionProvider' +// ); +// } +// return context; +// }; diff --git a/src/context/DeckContext/DeckContext.js b/src/context/DeckContext/DeckContext.js index 2409cb0..8507498 100644 --- a/src/context/DeckContext/DeckContext.js +++ b/src/context/DeckContext/DeckContext.js @@ -41,11 +41,11 @@ const removeDuplicateDecks = (decks) => { }; export const DeckProvider = ({ children }) => { const { getCardData } = useCardStore(); - const [cookies, setCookie] = useCookies(['userCookie']); + const [cookies, setCookie] = useCookies(['user']); const [deckData, setDeckData] = useState({}); const [allDecks, setAllDecks] = useState([]); const [selectedDeck, setSelectedDeck] = useState({}); - const userId = cookies.userCookie?.id; + const userId = cookies.user?.id; const calculateAndUpdateTotalPrice = (deck) => { let totalPrice = 0; diff --git a/src/context/SideBarProvider.jsx b/src/context/SideBarProvider.jsx index 64ddff9..dc310d2 100644 --- a/src/context/SideBarProvider.jsx +++ b/src/context/SideBarProvider.jsx @@ -4,7 +4,7 @@ const SidebarContext = createContext(); export const SidebarProvider = ({ children }) => { const [isOpen, setIsOpen] = useState(false); - const [isLoggedIn, setIsLoggedIn] = useState(false); + const [isLoggedIn, setisLoggedIn] = useState(false); const [sidebarBackgroundColor, setSidebarBackgroundColor] = useState('#FFFFFF'); const [sidebarImage, setSidebarImage] = useState(null); @@ -14,11 +14,11 @@ export const SidebarProvider = ({ children }) => { }; const login = () => { - setIsLoggedIn(true); + setisLoggedIn(true); }; const logout = () => { - setIsLoggedIn(false); + setisLoggedIn(false); }; return ( diff --git a/src/context/UserContext/UserContext.js b/src/context/UserContext/UserContext.js index 9d93e8d..9ebada0 100644 --- a/src/context/UserContext/UserContext.js +++ b/src/context/UserContext/UserContext.js @@ -1,6 +1,13 @@ -import React, { createContext, useState, useEffect, useContext } from 'react'; +import React, { + createContext, + useState, + useEffect, + useContext, + useCallback, +} from 'react'; import { useCookies } from 'react-cookie'; import { useAuthContext } from '../hooks/auth'; +import { useCollectionStore } from '../CollectionContext/CollectionContext'; const UserContext = createContext(); @@ -8,8 +15,10 @@ export const UserProvider = ({ children }) => { const [cookies, setCookie] = useCookies(['userCookie']); // const [user, setUser] = useState(null); const [isCronJobTriggered, setIsCronJobTriggered] = useState(false); + const [allCollections, setAllCollections] = useState([]); const { user, setUser } = useAuthContext(); // Use the useAuthContext hook + const { fetchCollections, fetchAllCollectionsForUser } = useCollectionStore(); const triggerCronJob = async () => { // Add your code here @@ -32,11 +41,20 @@ export const UserProvider = ({ children }) => { console.log('User Data Sent to Server and Cookie Updated:', userData); }; + const fetchAndSetCollections = useCallback(async () => { + const collections = fetchAllCollectionsForUser(user.id); + if (collections) { + setAllCollections(collections); // This is saved in the context + } + }, [user.id, fetchCollections]); + return ( { // }; const fetchDirectedResponses = async () => { - let isMounted = true; // Added this flag + // let isMounted = true; // Added this flag try { setIsContextLoading(true); const response = await axios.get(`${BASE_API_URL}/directedResponses`); const data = response.data; - if (isMounted) { - // Check if component is still mounted - Array.isArray(data) - ? setDirectedResponses(data) - : setDirectedResponses([]); - } + // if (isMounted) { + // Check if component is still mounted + Array.isArray(data) + ? setDirectedResponses(data) + : setDirectedResponses([]); + // } } catch (error) { - if (isMounted) { - // Check if component is still mounted - console.error('Error:', error); - setDirectedResponses([]); - } + // if (isMounted) { + // Check if component is still mounted + console.error('Error:', error); + setDirectedResponses([]); + // } } finally { - if (isMounted) { - // Check if component is still mounted - setIsContextLoading(false); - } + // if (isMounted) { + // Check if component is still mounted + setIsContextLoading(false); + // } } }; - useEffect(() => { - let isMounted = true; // Added this flag + // useEffect(() => { + // let isMounted = true; // Added this flag - if (isMounted && isContextLoading) { - // console.log('Loading...'); - } else if (isMounted && !isContextLoading) { - // console.log('Finished Loading'); - } + // if (isMounted && isContextLoading) { + // // console.log('Loading...'); + // } else if (isMounted && !isContextLoading) { + // // console.log('Finished Loading'); + // } - return () => { - isMounted = false; // Cleanup - }; - }, [isContextLoading]); + // return () => { + // isMounted = false; // Cleanup + // }; + // }, [isContextLoading]); const contextValue = { isLoading: isContextLoading, diff --git a/src/pages/CollectionPage.js b/src/pages/CollectionPage.js index 8bfb325..1993fd3 100644 --- a/src/pages/CollectionPage.js +++ b/src/pages/CollectionPage.js @@ -13,7 +13,6 @@ const CollectionPage = () => { // const [defaultCollection, setDefaultCollection] = useState([]); const { userCookie } = useCookies(['userCookie'])[0]; const { - fetchAllCollectionsForUser, allCollections = [], setAllCollections, selectedCollection, @@ -24,16 +23,6 @@ const CollectionPage = () => { } = useCollectionStore(); const userId = userCookie?.id; - useEffect(() => { - fetchAllCollectionsForUser() - .then(() => { - console.log('Fetched collections successfully'); - }) - .catch((err) => { - console.error('Failed to get all collections:', err); - }); - }, [fetchAllCollectionsForUser]); - if (loading) return ; if (error) return ; diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js index caddf7b..72103ca 100644 --- a/src/pages/HomePage.js +++ b/src/pages/HomePage.js @@ -67,9 +67,9 @@ const CarouselImage = ({ image, caption }) => { }; const CarouselContainer = ({ isMounted }) => { - if (!isMounted.current) { - return; - } + // if (!isMounted.current) { + // return; + // } const classes = useStyles(); return ( { const theme = useTheme(); const isMounted = useRef(true); - useEffect(() => { - return () => { - isMounted.current = false; - }; - }, []); + // useEffect(() => { + // return () => { + // isMounted.current = false; + // }; + // }, []); return ( From 309d518b04200ecb389200784512af2924ce1c8f Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Sat, 4 Nov 2023 11:47:14 -0700 Subject: [PATCH 05/10] add --- src/App.js | 13 +- src/assets/icons/{ => tech}/css.png | Bin src/assets/icons/{ => tech}/express.png | Bin src/assets/icons/{ => tech}/figma.png | Bin src/assets/icons/{ => tech}/git.png | Bin src/assets/icons/{ => tech}/html.png | Bin src/assets/icons/{ => tech}/java.png | Bin src/assets/icons/{ => tech}/javascript.png | Bin src/assets/icons/{ => tech}/mongodb.png | Bin src/assets/icons/{ => tech}/nodejs.png | Bin src/assets/icons/{ => tech}/reactjs.png | Bin src/assets/icons/{ => tech}/redux.png | Bin src/assets/icons/{ => tech}/tailwind.png | Bin src/assets/icons/{ => tech}/threejs.svg | 0 .../grids/deckBuilderGrids/DeckButtonList.js | 12 +- .../CollectionContext/CollectionContext.jsx | 447 +++++++++++------- .../CollectionContext/collectionUtility.jsx | 150 +++++- .../CollectionContext/exampleImport.js | 220 ++++----- src/context/DeckContext/DeckContext.js | 9 +- src/context/UserContext/UserContext.js | 14 +- 20 files changed, 544 insertions(+), 321 deletions(-) rename src/assets/icons/{ => tech}/css.png (100%) rename src/assets/icons/{ => tech}/express.png (100%) rename src/assets/icons/{ => tech}/figma.png (100%) rename src/assets/icons/{ => tech}/git.png (100%) rename src/assets/icons/{ => tech}/html.png (100%) rename src/assets/icons/{ => tech}/java.png (100%) rename src/assets/icons/{ => tech}/javascript.png (100%) rename src/assets/icons/{ => tech}/mongodb.png (100%) rename src/assets/icons/{ => tech}/nodejs.png (100%) rename src/assets/icons/{ => tech}/reactjs.png (100%) rename src/assets/icons/{ => tech}/redux.png (100%) rename src/assets/icons/{ => tech}/tailwind.png (100%) rename src/assets/icons/{ => tech}/threejs.svg (100%) diff --git a/src/App.js b/src/App.js index 17d079e..a030e5d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ // External Imports -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { BrowserRouter as Router, Route, @@ -103,9 +103,9 @@ const App = () => { const { fetchAllCollectionsForUser, allCollections } = useCollectionStore(); const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); const { isLoading, setIsContextLoading } = useUtilityContext(); - const { user } = useUserContext(); // Assuming 'user' exists and is non-null if the user is logged in - // const [currentPage, setCurrentPage] = useState(null); // Add this line - // const location = useLocation(); // Add this line to get the current location + const { user } = useUserContext(); + + const hasFetchedCollectionsRef = useRef(false); // This ref is used to indicate whether collections have been fetched. useEffect(() => { if (user) { @@ -134,15 +134,16 @@ const App = () => { // Assuming currentPage is a piece of state that changes when the user changes pages useEffect(() => { - if (user && (!allCollections || allCollections.length === 0)) { + if (user && !hasFetchedCollectionsRef.current) { try { fetchAllCollectionsForUser(); + hasFetchedCollectionsRef.current = true; // Set the ref to true after fetching console.log('Fetched collections because none were present.'); } catch (err) { console.error('Failed to fetch collections:', err); } } - }, [user, allCollections]); // Add location.pathname to the dependency list + }, [user, fetchAllCollectionsForUser]); // Removed allCollections from dependency list to prevent re-fetching when they update return ( <> diff --git a/src/assets/icons/css.png b/src/assets/icons/tech/css.png similarity index 100% rename from src/assets/icons/css.png rename to src/assets/icons/tech/css.png diff --git a/src/assets/icons/express.png b/src/assets/icons/tech/express.png similarity index 100% rename from src/assets/icons/express.png rename to src/assets/icons/tech/express.png diff --git a/src/assets/icons/figma.png b/src/assets/icons/tech/figma.png similarity index 100% rename from src/assets/icons/figma.png rename to src/assets/icons/tech/figma.png diff --git a/src/assets/icons/git.png b/src/assets/icons/tech/git.png similarity index 100% rename from src/assets/icons/git.png rename to src/assets/icons/tech/git.png diff --git a/src/assets/icons/html.png b/src/assets/icons/tech/html.png similarity index 100% rename from src/assets/icons/html.png rename to src/assets/icons/tech/html.png diff --git a/src/assets/icons/java.png b/src/assets/icons/tech/java.png similarity index 100% rename from src/assets/icons/java.png rename to src/assets/icons/tech/java.png diff --git a/src/assets/icons/javascript.png b/src/assets/icons/tech/javascript.png similarity index 100% rename from src/assets/icons/javascript.png rename to src/assets/icons/tech/javascript.png diff --git a/src/assets/icons/mongodb.png b/src/assets/icons/tech/mongodb.png similarity index 100% rename from src/assets/icons/mongodb.png rename to src/assets/icons/tech/mongodb.png diff --git a/src/assets/icons/nodejs.png b/src/assets/icons/tech/nodejs.png similarity index 100% rename from src/assets/icons/nodejs.png rename to src/assets/icons/tech/nodejs.png diff --git a/src/assets/icons/reactjs.png b/src/assets/icons/tech/reactjs.png similarity index 100% rename from src/assets/icons/reactjs.png rename to src/assets/icons/tech/reactjs.png diff --git a/src/assets/icons/redux.png b/src/assets/icons/tech/redux.png similarity index 100% rename from src/assets/icons/redux.png rename to src/assets/icons/tech/redux.png diff --git a/src/assets/icons/tailwind.png b/src/assets/icons/tech/tailwind.png similarity index 100% rename from src/assets/icons/tailwind.png rename to src/assets/icons/tech/tailwind.png diff --git a/src/assets/icons/threejs.svg b/src/assets/icons/tech/threejs.svg similarity index 100% rename from src/assets/icons/threejs.svg rename to src/assets/icons/tech/threejs.svg diff --git a/src/components/grids/deckBuilderGrids/DeckButtonList.js b/src/components/grids/deckBuilderGrids/DeckButtonList.js index 0a87a9b..e1e5bc8 100644 --- a/src/components/grids/deckBuilderGrids/DeckButtonList.js +++ b/src/components/grids/deckBuilderGrids/DeckButtonList.js @@ -17,14 +17,14 @@ const useDeckButtonListStyles = makeStyles((theme) => ({ backgroundColor: 'rgba(0, 0, 0, 0.04)', }, margin: theme?.spacing(1), - [theme?.breakpoints.down('xs')]: { + [theme?.breakpoints?.down('xs')]: { fontSize: '0.7rem', padding: '5px 8px', }, - [theme?.breakpoints.up('sm')]: { + [theme?.breakpoints?.up('sm')]: { fontSize: '0.8rem', }, - [theme?.breakpoints.up('md')]: { + [theme?.breakpoints?.up('md')]: { fontSize: '1rem', }, }, @@ -38,13 +38,13 @@ const useDeckButtonListStyles = makeStyles((theme) => ({ })); const useStyles = makeStyles((theme) => ({ root: { - [theme?.breakpoints.up('md')]: { + [theme?.breakpoints?.up('md')]: { backgroundColor: 'blue', }, - [theme?.breakpoints.down('sm')]: { + [theme?.breakpoints?.down('sm')]: { backgroundColor: 'red', }, - [theme?.breakpoints.between('sm', 'md')]: { + [theme?.breakpoints?.between('sm', 'md')]: { backgroundColor: 'green', }, }, diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index cbb84b6..5013295 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -10,15 +10,6 @@ import React, { useRef, } from 'react'; import { useCookies } from 'react-cookie'; -import { - initialCollectionState, - fetchWrapper, - removeDuplicateCollections, - calculateAndUpdateTotalPrice, - calculateTotalPrice, - getTotalCost, - getCardPrice, -} from './exampleImport.js'; import { filterOutDuplicateYValues, transformChartData, @@ -34,6 +25,12 @@ import { createApiUrl, handleApiResponse, BASE_API_URL, + fetchWrapper, + removeDuplicateCollections, + getTotalCost, + initialCollectionState, + determineHttpMethod, + getCardPrice, } from './collectionUtility.jsx'; import { useCombinedContext } from '../CombinedProvider.jsx'; import { useUserContext } from '../UserContext/UserContext.js'; @@ -65,29 +62,66 @@ const defaultContextValue = { // // 2. Replace null with the default value when creating the context export const CollectionContext = createContext(defaultContextValue); +const logError = (source, action, error) => { + console.error( + `[${source.toUpperCase()}] Failed to ${action}: ${error.message}` + ); +}; + +// Reusable validation and error logging +const validateUserIdAndData = (userId, data, actionDescription) => { + if (!userId) { + logError( + 'validateUserIdAndData', + actionDescription, + new Error('User ID is undefined.') + ); + return false; + } + if (!validateData(data, actionDescription, actionDescription)) { + logError( + 'validateUserIdAndData', + actionDescription, + new Error('Validation failed for collection data.') + ); + return false; + } + return true; +}; + +// Abstracted payload creation to reduce repetition +const createPayload = (info, data, defaultXyData) => ({ + userId: info.userId || data.userId, // Assuming this is an ObjectId string + name: info.name || data.name, + description: info.description || data.description, + totalCost: '', // Initialize as empty string if not provided + totalPrice: 0, // Initialize as 0 if not provided + quantity: 0, // Initialize as 0 if not provided + totalQuantity: 0, // Initialize as 0 if not provided + previousDayTotalPrice: 0, // Initialize as 0 if not provided + dailyPriceChange: 0, // Initialize as 0 if not provided + priceDifference: 0, // Initialize as 0 if not provided + priceChange: 0, // Initialize as 0 if not provided + allCardPrices: [], // Initialize as empty array if not provided + cards: [], // Initialize as empty array if not provided + currentChartDatasets: [], // Initialize as empty array if not provided + xys: defaultXyData || [], // Use defaultXyData or initialize as empty if not provided + chartData: { + name: '', // Initialize as empty string if not provided + userId: info.userId || data.userId, // Assuming this is an ObjectId string + datasets: [], // Initialize as empty array if not provided + xys: [], // Initialize as empty array if not provided + allXYValues: [], // Initialize as empty array if not provided + }, +}); + export const CollectionProvider = ({ children }) => { // const { cardPrices } = useCombinedContext(); const [cookies] = useCookies(['user']); const { triggerCronJob } = useUserContext(); const [collectionData, setCollectionData] = useState(initialCollectionState); const [allCollections, setAllCollections] = useState([]); - - // const [allCollections, setAllCollections] = useState(() => { - // // Retrieve from local storage when initializing - // const savedCollections = localStorage.getItem('allCollections'); - // return savedCollections ? JSON.parse(savedCollections) : []; - // }); - const [allCardPrices, setAllCardPrices] = useState([]); - const updateLocalStorage = (key, data) => { - localStorage.setItem(key, JSON.stringify(data)); - }; - const [xyData, setXyData] = useState([ - // { - // label: '', - // data: [], - // }, - ]); // New state to hold xy data - // const [updatedPrices, setUpdatedPrices] = useState([]); + const [xyData, setXyData] = useState([]); const [ updatedPricesFromCombinedContext, setUpdatedPricesFromCombinedContext, @@ -95,8 +129,6 @@ export const CollectionProvider = ({ children }) => { const [selectedCollection, setSelectedCollection] = useState({}); const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = useState(false); - const chartData = selectedCollection?.chartData || {}; - // const datasets = chartData?.datasets || []; const userId = cookies.user?.id; const totalCost = useMemo( () => getTotalCost(selectedCollection), @@ -111,7 +143,6 @@ export const CollectionProvider = ({ children }) => { } try { - // // console.log('Fetching collections...'); const response = await fetchWrapper( `${BASE_API_URL}/${userId}/collections`, 'GET' @@ -120,7 +151,7 @@ export const CollectionProvider = ({ children }) => { console.log('Fetched collections:', collections); return collections; } catch (error) { - // // Your error handling logic here + console.error('Error fetching collections:', error); return null; } }, []); @@ -143,7 +174,7 @@ export const CollectionProvider = ({ children }) => { // Update local state setAllCollections(validCollections); - // Save to localStorage + // Optionally save to localStorage // localStorage.setItem('allCollections', JSON.stringify(validCollections)); setCollectionData( @@ -159,51 +190,42 @@ export const CollectionProvider = ({ children }) => { }, []); const fetchAndSetCollections = useCallback(async () => { - // Get the current time - const currentTime = new Date().getTime(); + const currentTime = Date.now(); + const fetchDelay = 60000; // 1 minute in milliseconds - // Check if enough time (1 minute = 60000 ms) has passed since the last fetch if ( lastFetchedTime.current && - currentTime - lastFetchedTime.current < 60000 + currentTime - lastFetchedTime.current < fetchDelay ) { - console.log('You must wait for 1 minute before fetching again.'); + console.log( + `You must wait for ${fetchDelay / 1000} seconds before fetching again.` + ); return; } - // Perform the fetch operation - const collections = await fetchCollections(userId); - if (collections) { - setCollections(collections); - } - - // Update the last fetched time lastFetchedTime.current = currentTime; + const collections = await fetchCollections(userId); + if (collections) setCollections(collections); }, [userId, fetchCollections, setCollections]); - const updateCollectionData = useCallback( - (newData, collectionType) => { - if (collectionType === 'allCollections') { - setAllCollections((prevCollections = []) => { - const existingIndex = findCollectionIndex( - prevCollections, - newData?._id - ); - if (existingIndex === -1) return [...prevCollections, newData]; - const updatedCollections = [...prevCollections]; - updatedCollections[existingIndex] = newData; - return updatedCollections; - }); - } else if (collectionType === 'selectedCollection') { - setSelectedCollection(newData); - - // updateActiveCollection(newData); - } else if (collectionType === 'collectionData') { - setCollectionData(newData); - } - }, - [findCollectionIndex] - ); + const updateCollectionData = useCallback((newData, collectionType) => { + if (collectionType === 'allCollections') { + setAllCollections((prevCollections = []) => { + const existingIndex = findCollectionIndex( + prevCollections, + newData?._id + ); + if (existingIndex === -1) return [...prevCollections, newData]; + const updatedCollections = [...prevCollections]; + updatedCollections[existingIndex] = newData; + return updatedCollections; + }); + } else if (collectionType === 'selectedCollection') { + setSelectedCollection(newData); + } else if (collectionType === 'collectionData') { + setCollectionData(newData); + } + }, []); const createUserCollection = async ( userId, @@ -211,56 +233,33 @@ export const CollectionProvider = ({ children }) => { name, description ) => { - if (!userId) { - console.error('User ID is undefined.'); - return; + if (newCollectionInfo?._id) { + console.warn( + 'Collection already has an ID, should not create a new one.' + ); + return; // early exit if _id is present } - if ( - !validateData( - newCollectionInfo, - 'Create User Collection', - 'createUserCollection' - ) - ) { - console.error('Validation failed for collection data.'); + const actionDescription = 'create a new collection'; + if (!validateUserIdAndData(userId, newCollectionInfo, actionDescription)) return; - } - const payload = { - name: name || newCollectionInfo.name, - description: description || newCollectionInfo.description, - userId: userId || newCollectionInfo.userId, - totalCost: 0, - totalPrice: 0, - quantity: 0, - totalQuantity: 0, - xys: xyData || [], - allCardPrices: [], - cards: [], - chartData: {}, - }; + const payload = createPayload( + { name, description, userId }, + newCollectionInfo + ); try { - const url = createApiUrl(`${userId}/collections/newCollection/${userId}`); + const url = createApiUrl(`${userId}/collections`); const response = await fetchWrapper(url, 'POST', payload); + if (!response) throw new Error('Failed to connect to the server.'); - if (!response) { - console.error('Failed to connect to the server.'); - return; - } + if (response.error) throw new Error(response.error.message); - if (response.error) { - console.error( - `Failed to create a new collection: ${response.error.message}` - ); - return; - } - - const savedCollection = handleApiResponse(response, 'POST'); + const savedCollection = await handleApiResponse(response, 'POST'); updateCollectionData(savedCollection, 'allCollections'); updateCollectionData(savedCollection, 'collectionData'); } catch (error) { - console.error(`Failed to create a new collection: ${error.message}`); + logError('createUserCollection', actionDescription, error); } }; @@ -435,89 +434,191 @@ export const CollectionProvider = ({ children }) => { ] ); + // const updateActiveCollection = useCallback( + // async (collectionData, existingChartData = {}) => { + // const isCreatingNew = !collectionData?._id; + // const isNewCollectionEndpoint = + // collectionData?.endpoint === 'newCollection'; + + // const endpoint = createApiUrl( + // `${userId}/collections${isCreatingNew ? '' : `/${collectionData._id}`}` + // ); + + // let method = isCreatingNew ? 'POST' : 'PUT'; + // console.log(`Debug: Fetching ${method} ${endpoint}`); + + // if (!isCreatingNew && isNewCollectionEndpoint) { + // method = 'POST'; + // } + + // try { + // const response = await fetchWrapper(endpoint, method, collectionData); + // const updatedCollection = handleApiResponse(response, method); + + // if (!isCreatingNew && !updatedCollection) { + // throw new Error('Failed to update the existing collection'); + // } + + // const updateNumber = + // updatedCollection?.chartData?.datasets?.length + 1 || 1; + // const timestamp = moment().format('YYYY-MM-DD HH:mm'); + // const price = updatedCollection.totalPrice; + + // const newDataEntry = { + // x: timestamp, + // y: price, + // }; + + // const newLabelData = { + // label: `Update Number ${updateNumber}`, + // data: newDataEntry, + // }; + + // const newAdditionalPriceData = { + // priceChanged: false, // Set this according to your logic + // initialPrice: updatedCollection.chartData?.updatedPrice || 0, + // updatedPrice: price, + // priceDifference: 0, // Set this according to your logic + // priceChange: 0, // Set this according to your logic + // }; + + // const newDataset = { + // name: `Dataset ${updateNumber}`, + // data: [ + // { + // xys: [newLabelData], + // additionalPriceData: [newAdditionalPriceData], + // }, + // ], + // }; + + // const newChartData = { + // ...updatedCollection.chartData, + // name: updatedCollection.name, // You may need to set this according to your logic + // userId: updatedCollection.userId, // You may need to set this according to your logic + // xys: [...(updatedCollection.chartData?.xys || []), newLabelData], + // datasets: [ + // ...(updatedCollection.chartData?.datasets || []), + // newDataset, + // ], + // }; + + // updatedCollection.chartData = newChartData; + // const convertedData = convertData(newChartData); + // updatedCollection.xys = convertedData; + + // xyData.push(...convertedData.finalDataForChart); + // updateCollectionData(updatedCollection, 'selectedCollection'); + // updateCollectionData(updatedCollection, 'allCollections'); + // } catch (error) { + // console.error(`Failed to update the collection: ${error.message}`); + // } + // }, + // [userId, updateCollectionData] + // ); const updateActiveCollection = useCallback( async (collectionData, existingChartData = {}) => { - const isCreatingNew = !collectionData?._id; - const isNewCollectionEndpoint = - collectionData?.endpoint === 'newCollection'; + if (!collectionData) throw new Error('No collection data provided.'); - const endpoint = createApiUrl( - `${userId}/collections${isCreatingNew ? '' : `/${collectionData._id}`}` + const isCreatingNew = !collectionData?._id; + const actionDescription = isCreatingNew + ? 'create a new collection' + : 'update the existing collection'; + const method = determineHttpMethod( + isCreatingNew, + collectionData?.endpoint ); - let method = isCreatingNew ? 'POST' : 'PUT'; - console.log(`Debug: Fetching ${method} ${endpoint}`); + try { + if (isCreatingNew) { + // Skip the fetch call for new collection creation and just update the local state if needed + console.log( + `Skipping fetch call to ${method} since it's a new collection` + ); - if (!isCreatingNew && isNewCollectionEndpoint) { - method = 'POST'; - } + // You could still call 'updateChartDataForCollection' or any other local state updates here if needed + // updateChartDataForCollection(collectionData, existingChartData); + } else { + const endpoint = createApiUrl( + `${userId}/collections/${collectionData._id}` + ); - try { - const response = await fetchWrapper(endpoint, method, collectionData); - const updatedCollection = handleApiResponse(response, method); + // Include the 'existingChartData' in the request if necessary + const payload = { ...collectionData, existingChartData }; + console.log(`Debug: ${method} ${endpoint}`); - if (!isCreatingNew && !updatedCollection) { - throw new Error('Failed to update the existing collection'); - } + const response = await fetchWrapper(endpoint, method, payload); + const updatedCollection = await handleApiResponse(response, method); - const updateNumber = - updatedCollection?.chartData?.datasets?.length + 1 || 1; - const timestamp = moment().format('YYYY-MM-DD HH:mm'); - const price = updatedCollection.totalPrice; - - const newDataEntry = { - x: timestamp, - y: price, - }; - - const newLabelData = { - label: `Update Number ${updateNumber}`, - data: newDataEntry, - }; - - const newAdditionalPriceData = { - priceChanged: false, // Set this according to your logic - initialPrice: updatedCollection.chartData?.updatedPrice || 0, - updatedPrice: price, - priceDifference: 0, // Set this according to your logic - priceChange: 0, // Set this according to your logic - }; - - const newDataset = { - name: `Dataset ${updateNumber}`, - data: [ - { - xys: [newLabelData], - additionalPriceData: [newAdditionalPriceData], - }, - ], - }; - - const newChartData = { - ...updatedCollection.chartData, - name: updatedCollection.name, // You may need to set this according to your logic - userId: updatedCollection.userId, // You may need to set this according to your logic - xys: [...(updatedCollection.chartData?.xys || []), newLabelData], - datasets: [ - ...(updatedCollection.chartData?.datasets || []), - newDataset, - ], - }; - - updatedCollection.chartData = newChartData; - const convertedData = convertData(newChartData); - updatedCollection.xys = convertedData; - - xyData.push(...convertedData.finalDataForChart); - updateCollectionData(updatedCollection, 'selectedCollection'); - updateCollectionData(updatedCollection, 'allCollections'); + if (!updatedCollection) + throw new Error('No collection returned from server.'); + + // Function call to handle the chart data update logic + updateChartDataForCollection(updatedCollection, existingChartData); + + updateCollectionData(updatedCollection, 'selectedCollection'); + updateCollectionData(updatedCollection, 'allCollections'); + } } catch (error) { - console.error(`Failed to update the collection: ${error.message}`); + logError('updateActiveCollection', actionDescription, error); + console.error(`Failed to ${actionDescription}: ${error.message}`); } }, [userId, updateCollectionData] ); + function determineHttpMethod(isCreatingNew, isNewCollectionEndpoint) { + if (isCreatingNew) return 'POST'; + return isNewCollectionEndpoint ? 'POST' : 'PUT'; + } + function updateChartDataForCollection(collection) { + // Check if there's existing chart data to update; if not, create a new structure + const chartData = collection.chartData || { datasets: [] }; + + // Determine the update number and the current timestamp + const updateNumber = chartData.datasets.length + 1; + const timestamp = new Date().toISOString(); + + // Calculate the new price and possibly other metrics for the chart data + const newPrice = collection.totalPrice; + const previousPrice = chartData.datasets.slice(-1)[0]?.y || 0; + const priceDifference = newPrice - previousPrice; + + // Create a new data entry for the update + const newDataEntry = { + x: timestamp, // x-axis typically holds the timestamp + y: newPrice, // y-axis typically holds the value such as price + updateNumber: updateNumber, // Additional data can be included as needed + }; + + // Update the datasets with the new entry + chartData.datasets.push(newDataEntry); + + // Additional logic to handle price changes and other metrics can be added here + // For instance, if there's a significant change, we might want to highlight it + const priceChange = previousPrice + ? (priceDifference / previousPrice) * 100 + : 0; + + // Add any additional data you wish to store with the chart data + const additionalPriceData = { + initialPrice: previousPrice, + updatedPrice: newPrice, + priceDifference: priceDifference, + priceChangePercentage: priceChange.toFixed(2), // toFixed(2) for percentage with 2 decimal places + }; + + // Attach the new additional data to the last dataset entry + chartData.datasets[chartData.datasets.length - 1].additionalPriceData = + additionalPriceData; + + // Assign the updated chart data back to the collection object + collection.chartData = chartData; + + // Return the updated collection object + return collection; + } + // console.log( // '<----------$$$$$$$$$CONVERTED DATA FOR CHART$$$$$$$$$---------->', // xyData diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx index 7cc3702..2d5026e 100644 --- a/src/context/CollectionContext/collectionUtility.jsx +++ b/src/context/CollectionContext/collectionUtility.jsx @@ -1,5 +1,10 @@ const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`; - +const initialCollectionState = { + _id: '', + cards: [], + quantity: 0, + totalPrice: 0, +}; /** * Filters out duplicate Y values from an array of datasets. * @param {Array} datasets - An array of dataset objects. @@ -172,11 +177,29 @@ const handleCardRemoval = (currentCards, cardToRemove) => { * @returns {Array} Filtered array of XY value objects. */ 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'); + return []; + } + const uniqueXValues = new Set(); return allXYValues - .filter((entry) => entry.y !== 0) .filter((entry) => { - if (!uniqueXValues.has(entry.x)) { + // 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; } @@ -184,6 +207,11 @@ const getUniqueFilteredXYValues = (allXYValues) => { }); }; +// Example usage: +// console.log(getUniqueFilteredXYValues(null)); // Should return [] +// console.log(getUniqueFilteredXYValues(undefined)); // Should return [] +// console.log(getUniqueFilteredXYValues([{ x: 1, y: 0 }, { x: 2, y: 3 }])); // Should return [{ x: 2, y: 3 }] + /** * Calculates the total price from an array of card prices. * @param {Array} allCardPrices - Array of card prices. @@ -224,23 +252,110 @@ const findCollectionIndex = (collections, id) => */ const createApiUrl = (path) => `${BASE_API_URL}/${path}`; +// To prevent making the same type of request within 10 seconds +const lastRequestTime = { + POST: 0, + PUT: 0, + DELETE: 0, + GET: 0, + // Add other methods if needed +}; + /** - * Handles API responses based on the HTTP method used. - * @param {Object} response - API response. - * @param {String} method - HTTP method used (e.g., 'POST', 'PUT'). - * @returns {Object} Processed API response. + * Checks whether a new request can be made based on the last request's timestamp. + * @param {String} method - The HTTP method for the request. */ -const handleApiResponse = (response, method) => { - if (method === 'POST' && response?.data?.newCollection) { - return response?.data?.newCollection; +const canMakeRequest = (method) => { + const currentTime = Date.now(); + // The comment mentioned 10 seconds, but the code was checking for 5 seconds. Adjust as necessary. + return currentTime - lastRequestTime[method] > 1000; // Now it's 10 seconds +}; + +/** + * Updates the last request timestamp for a given method. + * @param {String} method - The HTTP method for the request. + */ +const updateLastRequestTime = (method) => { + lastRequestTime[method] = Date.now(); +}; +/** + * Wraps fetch API calls and implements a rate limit for each HTTP method type. + * @param {String} url - The API URL to make the request to. + * @param {String} method - The HTTP method for the request. + * @param {Object} [body=null] - The request payload if needed. + * @returns {Promise} - The response from the API call. + */ +const fetchWrapper = async (url, method, body = null) => { + // if (!canMakeRequest(method)) { + // throw new Error( + // `A ${method} request was made recently. Please wait before trying again.` + // ); + // } + + const options = { + method, + headers: { 'Content-Type': 'application/json' }, + ...(body && { body: JSON.stringify(body) }), + }; + + try { + const response = await fetch(url, options); + if (!response.ok) { + // We handle non-ok responses immediately + throw new Error(`API request failed with status ${response.status}`); + } + updateLastRequestTime(method); + // Assuming handleApiResponse is expecting a Response object + return handleApiResponse(response); + } catch (error) { + console.error(`Fetch failed: ${error}`); + // It's useful to log the stack trace in development + console.trace(); + throw error; // Re-throwing the error for upstream catch blocks to handle + } +}; + +const handleApiResponse = async (response) => { + if (!(response instanceof Response)) { + const error = new Error( + "The response object is not an instance of the Fetch API's Response class." + ); + console.error(error.message, response); + throw error; } - if (method === 'PUT' && response.data?.data?.updatedCollection) { - return response?.data?.data?.updatedCollection; + + try { + const jsonResponse = await response.json(); + return jsonResponse; + } catch (e) { + const error = new Error(`Failed to parse JSON from the response: ${e}`); + console.error(error.message); + throw error; } - throw new Error('Unexpected response format'); }; -// Export the utility functions if needed +const getTotalCost = (selectedCollection) => { + if (!selectedCollection || !Array.isArray(selectedCollection.cards)) return 0; + + return selectedCollection.cards.reduce((total, card) => { + const cardPrice = + (card.card_prices && card.card_prices[0]?.tcgplayer_price) || 0; + return total + cardPrice * card.quantity; + }, 0); +}; + +const removeDuplicateCollections = (collections) => { + const uniqueCollections = {}; + collections?.forEach((collection) => { + uniqueCollections[collection._id] = collection; + }); + return Object.values(uniqueCollections); +}; + +const getCardPrice = (collection) => + console.log('CARD:', collection) || + parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); + module.exports = { filterOutDuplicateYValues, transformChartData, @@ -254,6 +369,11 @@ module.exports = { ensureNumber, findCollectionIndex, createApiUrl, - handleApiResponse, BASE_API_URL, + handleApiResponse, + fetchWrapper, + getTotalCost, + getCardPrice, + removeDuplicateCollections, + initialCollectionState, }; diff --git a/src/context/CollectionContext/exampleImport.js b/src/context/CollectionContext/exampleImport.js index 63aea53..68aebb7 100644 --- a/src/context/CollectionContext/exampleImport.js +++ b/src/context/CollectionContext/exampleImport.js @@ -1,123 +1,123 @@ -// Initial collection state -export const initialCollectionState = { - _id: '', - cards: [], - quantity: 0, - totalPrice: 0, -}; +// // Initial collection state +// export const initialCollectionState = { +// _id: '', +// cards: [], +// quantity: 0, +// totalPrice: 0, +// }; -// Fetch wrapper function -export const fetchWrapper = async (url, method, body = null) => { - try { - const options = { - method, - headers: { 'Content-Type': 'application/json' }, - ...(body && { body: JSON.stringify(body) }), - }; - const response = await fetch(url, options); - // console.log('RESPONSE:', response); - if (!response.ok) { - const message = `HTTP error! status: ${response.status}`; - console.error(message); - throw new Error(message); - } - return await response.json(); - } catch (error) { - console.error('Fetch Error:', error); - throw error; - // if (!response.ok) { - // throw new Error(`HTTP error! status: ${response.status}`); - // } - // return await response.json(); - } -}; +// // Fetch wrapper function +// // export const fetchWrapper = async (url, method, body = null) => { +// // try { +// // const options = { +// // method, +// // headers: { 'Content-Type': 'application/json' }, +// // ...(body && { body: JSON.stringify(body) }), +// // }; +// // const response = await fetch(url, options); +// // // console.log('RESPONSE:', response); +// // if (!response.ok) { +// // const message = `HTTP error! status: ${response.status}`; +// // console.error(message); +// // throw new Error(message); +// // } +// // return await response.json(); +// // } catch (error) { +// // console.error('Fetch Error:', error); +// // throw error; +// // // if (!response.ok) { +// // // throw new Error(`HTTP error! status: ${response.status}`); +// // // } +// // // return await response.json(); +// // } +// // }; -// Remove duplicate collections -export const removeDuplicateCollections = (collections) => { - const uniqueCollections = {}; - collections?.forEach((collection) => { - uniqueCollections[collection._id] = collection; - }); - return Object.values(uniqueCollections); -}; +// // Remove duplicate collections +// // export const removeDuplicateCollections = (collections) => { +// // const uniqueCollections = {}; +// // collections?.forEach((collection) => { +// // uniqueCollections[collection._id] = collection; +// // }); +// // return Object.values(uniqueCollections); +// // }; -// Calculate and update total price -export const calculateAndUpdateTotalPrice = (collection) => { - if (!collection || !collection.cards) return 0; - return collection.cards.reduce( - (total, card) => total + card.price * card.quantity, - 0 - ); -}; +// // Calculate and update total price +// export const calculateAndUpdateTotalPrice = (collection) => { +// if (!collection || !collection.cards) return 0; +// return collection.cards.reduce( +// (total, card) => total + card.price * card.quantity, +// 0 +// ); +// }; -// Format card data -export const formatCardData = (card) => ({ - id: card.id, - ...Object.fromEntries( - [ - 'name', - 'type', - 'frameType', - 'description', - 'card_images', - 'archetype', - 'atk', - 'def', - 'level', - 'race', - 'attribute', - 'quantity', - ].map((key) => [key, card[key] || null]) - ), -}); +// // Format card data +// export const formatCardData = (card) => ({ +// id: card.id, +// ...Object.fromEntries( +// [ +// 'name', +// 'type', +// 'frameType', +// 'description', +// 'card_images', +// 'archetype', +// 'atk', +// 'def', +// 'level', +// 'race', +// 'attribute', +// 'quantity', +// ].map((key) => [key, card[key] || null]) +// ), +// }); -// Get card quantity from a collection -export const getCardQuantity = (collectionId, allCollections) => { - const collection = allCollections?.find((item) => item._id === collectionId); - if (!collection) return 0; - return collection.cards.reduce((acc, card) => acc + card.quantity, 0); -}; +// // Get card quantity from a collection +// export const getCardQuantity = (collectionId, allCollections) => { +// const collection = allCollections?.find((item) => item._id === collectionId); +// if (!collection) return 0; +// return collection.cards.reduce((acc, card) => acc + card.quantity, 0); +// }; -// Calculate total cost of the selected collection +// // Calculate total cost of the selected collection +// // export const getTotalCost = (selectedCollection) => { +// // if (!selectedCollection?.cards) return 0; +// // return selectedCollection.cards.reduce((total, card) => { +// // const price = card.card_prices?.[0]?.tcgplayer_price; +// // return price ? total + parseFloat(price) : total; +// // }, 0); +// // }; // export const getTotalCost = (selectedCollection) => { -// if (!selectedCollection?.cards) return 0; +// if (!selectedCollection || !Array.isArray(selectedCollection.cards)) return 0; + // return selectedCollection.cards.reduce((total, card) => { -// const price = card.card_prices?.[0]?.tcgplayer_price; -// return price ? total + parseFloat(price) : total; +// const cardPrice = +// (card.card_prices && card.card_prices[0]?.tcgplayer_price) || 0; +// return total + cardPrice * card.quantity; // }, 0); // }; -export const getTotalCost = (selectedCollection) => { - if (!selectedCollection || !Array.isArray(selectedCollection.cards)) return 0; - - return selectedCollection.cards.reduce((total, card) => { - const cardPrice = - (card.card_prices && card.card_prices[0]?.tcgplayer_price) || 0; - return total + cardPrice * card.quantity; - }, 0); -}; -export const getCardPrice = (collection) => - console.log('CARD:', collection) || - parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); +// export const getCardPrice = (collection) => +// console.log('CARD:', collection) || +// parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); -// Function to calculate total price of a collection -export const calculateTotalPrice = (collection) => { - // Assuming collection is an object where each key-value pair is cardId-price - return Object.allCardPrices(collection).reduce; -}; -// const getCollectionQuantity = useCallback(() => { -// return ( -// selectedCollection?.cards?.reduce( -// (acc, card) => acc + card.quantity, -// 0 -// ) || 0 -// ); -// }, [selectedCollection]); +// // Function to calculate total price of a collection +// export const calculateTotalPrice = (collection) => { +// // Assuming collection is an object where each key-value pair is cardId-price +// return Object.allCardPrices(collection).reduce; +// }; +// // const getCollectionQuantity = useCallback(() => { +// // return ( +// // selectedCollection?.cards?.reduce( +// // (acc, card) => acc + card.quantity, +// // 0 +// // ) || 0 +// // ); +// // }, [selectedCollection]); -// getCardPrice(selectedCollection); -// console.log( -// 'SELECTED COLLECTION CARD PRICE:', -// getCardPrice( -// selectedCollection?.cards?.find((card) => card.id === collectionData.id) -// ) -// ); +// // getCardPrice(selectedCollection); +// // console.log( +// // 'SELECTED COLLECTION CARD PRICE:', +// // getCardPrice( +// // selectedCollection?.cards?.find((card) => card.id === collectionData.id) +// // ) +// // ); diff --git a/src/context/DeckContext/DeckContext.js b/src/context/DeckContext/DeckContext.js index 8507498..1731712 100644 --- a/src/context/DeckContext/DeckContext.js +++ b/src/context/DeckContext/DeckContext.js @@ -21,7 +21,7 @@ export const DeckContext = createContext({ updateAndSyncDeck: () => {}, fetchAllDecksForUser: () => {}, }); -const apiBase = `${process.env.REACT_APP_SERVER}/api`; +const apiBase = `${process.env.REACT_APP_SERVER}/api/users`; const fetchWrapper = async (url, method, body = null) => { const options = { @@ -59,7 +59,7 @@ export const DeckProvider = ({ children }) => { const fetchDecksForUser = useCallback(async () => { try { - const url = `${apiBase}/users/${userId}/decks`; + const url = `${apiBase}/${userId}/decks`; return await fetchWrapper(url, 'GET'); } catch (error) { console.error(`Failed to fetch decks for user: ${error.message}`); @@ -133,7 +133,7 @@ export const DeckProvider = ({ children }) => { }); try { - const url = `${apiBase}/users/${userId}/decks/${selectedDeck._id}`; // Removed deckId from the URL + const url = `${apiBase}/${userId}/decks/${selectedDeck._id}`; // Removed deckId from the URL const bodyData = { ...newDeckData, deckId: newDeckData._id, // Included deckId in the body @@ -146,7 +146,7 @@ export const DeckProvider = ({ children }) => { const createUserDeck = async (userId, newDeckInfo) => { try { - const url = `${apiBase}/newDeck/${userId}`; + const url = `${apiBase}/${userId}/decks`; const cards = newDeckInfo.cards ? [formatCardData(newDeckInfo.cards)] : []; @@ -158,6 +158,7 @@ export const DeckProvider = ({ children }) => { description: newDeckInfo.description || '', name: newDeckInfo.name || '', }); + console.log('NEW DECK DATA:', data); setDeckData(data); } catch (error) { console.error(`Failed to create a new deck: ${error.message}`); diff --git a/src/context/UserContext/UserContext.js b/src/context/UserContext/UserContext.js index 9ebada0..458788e 100644 --- a/src/context/UserContext/UserContext.js +++ b/src/context/UserContext/UserContext.js @@ -41,12 +41,12 @@ export const UserProvider = ({ children }) => { console.log('User Data Sent to Server and Cookie Updated:', userData); }; - const fetchAndSetCollections = useCallback(async () => { - const collections = fetchAllCollectionsForUser(user.id); - if (collections) { - setAllCollections(collections); // This is saved in the context - } - }, [user.id, fetchCollections]); + // const fetchAndSetCollections = useCallback(async () => { + // const collections = fetchAllCollectionsForUser(user.id); + // if (collections) { + // setAllCollections(collections); // This is saved in the context + // } + // }, [user.id, fetchCollections]); return ( { user, setUser, allCollections, - fetchAndSetCollections, + // fetchAndSetCollections, setCookie, updateUser, triggerCronJob, From c21f0de5a55fb3fa7561fb1b6458f76f840be70d Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Wed, 15 Nov 2023 16:07:57 -0800 Subject: [PATCH 06/10] adding before big update --- src/App.js | 125 +- .../actionButtons/CardActionButtons.jsx | 118 +- .../actionButtons/ChooseCollectionDialog.jsx | 2 +- .../actionButtons/GenericActionButtons.jsx | 69 +- src/components/buttons/buttonStyles.jsx | 30 + src/components/cards/CardDetailsContainer.jsx | 26 + src/components/cards/GenericCard.jsx | 300 +-- src/components/cards/cardStyles.js | 148 -- src/components/cards/cardStyles.jsx | 192 ++ src/components/collection/CardPortfolio.jsx | 2 +- .../collection/SelectCollection.jsx | 2 +- src/components/content/PortfolioContent.jsx | 43 +- .../dialogs/CollectionSelectorDialog.jsx | 77 + .../dialogs/SelectCollectionDialog.jsx | 2 +- .../grids/collectionGrids/CardList.jsx | 2 +- .../collectionGrids/SelectCollectionList.jsx | 2 +- src/components/grids/gridStyles.jsx | 20 + .../searchResultGrids/DeckSearchCardGrid.jsx | 230 +- src/components/headings/PortfolioHeader.jsx | 2 +- .../headings/header/menuItemsData.jsx | 2 +- src/components/media/CardMedia.jsx | 56 +- src/components/media/CardMediaAndDetails.jsx | 28 + src/components/media/CardMediaSection.js | 179 +- src/components/media/mediaStyles.jsx | 13 + src/components/modals/GenericCardModal.jsx | 297 +-- src/components/modals/modalStyles.jsx | 44 + .../other/CalculateCollectionStatistics.jsx | 57 + src/components/other/ChartErrorBoundary.jsx | 30 + src/components/other/ChartTooltip.jsx | 42 + .../other/CollectionStatisticsSelector.jsx | 95 + .../other/CollectionValueTracker.jsx | 67 + src/components/other/LinearChart.js | 408 ++-- src/components/other/PortfolioChart.jsx | 417 +++- src/components/other/TimeRangeSelector.jsx | 36 +- src/components/other/UpdateStatusBox.jsx | 100 + src/components/other/UserStats.jsx | 2 +- src/components/other/chartUtils.jsx | 104 + src/containers/PortfolioChartContainer.jsx | 131 +- src/containers/PortfolioListContainer.jsx | 2 +- src/context/AppContextProvider.jsx | 24 + src/context/CardContext/CardStore.js | 5 + src/context/CartContext/CartContext.js | 4 + src/context/ChartContext/ChartContext.jsx | 26 +- .../CollectionContext/CollectionContext.jsx | 1980 ++++++++++++----- src/context/CollectionContext/cardHelpers.jsx | 80 + .../CollectionContext/collectionUtility.jsx | 149 +- src/context/CollectionContext/fml.jsx | 1389 ++++++++++++ src/context/CollectionContext/fmljsx | 1 + .../CollectionContext/validateCollection.jsx | 68 + src/context/CombinedProvider.jsx | 645 +++--- src/context/CutstomLogger.jsx | 23 + src/context/DeckContext/DeckContext.js | 9 +- src/context/SocketProvider.jsx | 14 +- src/context/UserContext/UserContext.js | 2 +- src/context/hooks/collection.jsx | 24 +- src/context/hooks/useAppContext.jsx | 16 + src/context/hooks/useDialog.jsx | 32 + src/context/hooks/useSnackBar.jsx | 28 + src/index.js | 16 +- src/pages/CollectionPage.js | 98 +- src/pages/HomePage.js | 44 +- src/pages/ProfilePage.js | 88 +- 62 files changed, 5987 insertions(+), 2280 deletions(-) create mode 100644 src/components/buttons/buttonStyles.jsx create mode 100644 src/components/cards/CardDetailsContainer.jsx delete mode 100644 src/components/cards/cardStyles.js create mode 100644 src/components/cards/cardStyles.jsx create mode 100644 src/components/dialogs/CollectionSelectorDialog.jsx create mode 100644 src/components/grids/gridStyles.jsx create mode 100644 src/components/media/CardMediaAndDetails.jsx create mode 100644 src/components/media/mediaStyles.jsx create mode 100644 src/components/modals/modalStyles.jsx create mode 100644 src/components/other/CalculateCollectionStatistics.jsx create mode 100644 src/components/other/ChartErrorBoundary.jsx create mode 100644 src/components/other/ChartTooltip.jsx create mode 100644 src/components/other/CollectionStatisticsSelector.jsx create mode 100644 src/components/other/CollectionValueTracker.jsx create mode 100644 src/components/other/UpdateStatusBox.jsx create mode 100644 src/components/other/chartUtils.jsx create mode 100644 src/context/AppContextProvider.jsx create mode 100644 src/context/CollectionContext/cardHelpers.jsx create mode 100644 src/context/CollectionContext/fml.jsx create mode 100644 src/context/CollectionContext/fmljsx create mode 100644 src/context/CollectionContext/validateCollection.jsx create mode 100644 src/context/CutstomLogger.jsx create mode 100644 src/context/hooks/useAppContext.jsx create mode 100644 src/context/hooks/useDialog.jsx create mode 100644 src/context/hooks/useSnackBar.jsx diff --git a/src/App.js b/src/App.js index a030e5d..92e069a 100644 --- a/src/App.js +++ b/src/App.js @@ -28,7 +28,7 @@ import CardDeckAnimation from './pages/CardDeckAnimation'; // Context Hooks Imports import { useCombinedContext } from './context/CombinedProvider'; import { useUserContext } from './context/UserContext/UserContext'; -import { useCollectionStore } from './context/hooks/collection'; +import { useCollectionStore } from './context/CollectionContext/CollectionContext'; import { useUtilityContext } from './context/UtilityContext/UtilityContext'; // Styled Components @@ -48,37 +48,76 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { cronData, handleSendAllCardsInCollections, listOfMonitoredCards, - handleRetreiveListOfMonitoredCards, - retrievedListOfMonitoredCards, + // handleRetreiveListOfMonitoredCards, + // retrievedListOfMonitoredCards, allCollectionsUpdated, } = useCombinedContext(); - // const { fetchAllCollectionsForUser } = useCollectionStore(); + const { allCollections } = useCollectionStore(); + const [priceHistory, setPriceHistory] = useState([]); + + const { handlePricesActivateCron, cardsWithChangedPrice } = + useCombinedContext(); const { user } = useUserContext(); + const [priceFlux, setPriceFlux] = useState(null); const userId = user?.userID; - useEffect(() => { setLastCronJobTriggerTime(new Date().getTime()); }, [setLastCronJobTriggerTime]); + // --------------------------------------------------------------- + // useEffect(() => { + // handlePricesActivateCron( + // userId, + // listOfMonitoredCards, + // allCollections, + // cardsWithChangedPrice + // ); + // }, [ + // userId, + // listOfMonitoredCards, + // allCollections, + // cardsWithChangedPrice, + // priceFlux, + // ]); useEffect(() => { const handleTriggerCronJob = () => { const currentTime = new Date().getTime(); + const timeDifference = currentTime - lastCronJobTriggerTime; + const previousTotalPrice = allCollections?.totalPrice; + if (!priceHistory.includes(previousTotalPrice)) { + priceHistory.push(previousTotalPrice); + } + const minutes = Math.floor(timeDifference / 60000); // 1 minute = 60000 milliseconds + const seconds = Math.floor((timeDifference % 60000) / 1000); // remainder in milliseconds converted to seconds + + // Output the remaining time in minutes and seconds + console.log( + `REMAINING TIME: ${minutes} minute(s) and ${seconds} second(s)` + ); + + for (const collection of allCollections) { + if ( + collection?.cards && + collection?.cards?.length > 0 && + collection.totalPrice !== previousTotalPrice // Implement your logic here + ) { + setPriceFlux(new Date().getTime()); // Trigger a re-render + console.log('PRICE FLUX:', priceFlux); + } + } + // if (collection.totalPrice !== previousTotalPrice) { + // // Update dependencies of useEffect + // setPriceFlux(new Date().getTime()); // Trigger a re-render + // } if (currentTime - lastCronJobTriggerTime >= 60000) { setLastCronJobTriggerTime(currentTime); - if (userId && listOfMonitoredCards && retrievedListOfMonitoredCards) { - handleSendAllCardsInCollections( - userId, - listOfMonitoredCards, - retrievedListOfMonitoredCards - ); - console.log('SENDING ALL CARDS IN COLLECTIONS'); - } else if (userId && listOfMonitoredCards) { - console.log('RETRIEVING LIST OF MONITORED CARDS'); - handleSendAllCardsInCollections( - userId, - listOfMonitoredCards, - handleRetreiveListOfMonitoredCards() - ); + if (userId && listOfMonitoredCards) { + console.log('RETRIEVING LIST OF MONITORED CARDS (paused)'); + // handleSendAllCardsInCollections( + // userId, + // listOfMonitoredCards + // // handleRetrieveListOfMonitoredCards() + // ); console.log('Triggered the cron job.'); } } @@ -90,22 +129,21 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { cronData, lastCronJobTriggerTime, allCollectionsUpdated, - handleRetreiveListOfMonitoredCards, + // handleRetrieveListOfMonitoredCards, handleSendAllCardsInCollections, listOfMonitoredCards, - retrievedListOfMonitoredCards, userId, ]); }; // Main Component const App = () => { - const { fetchAllCollectionsForUser, allCollections } = useCollectionStore(); - const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); - const { isLoading, setIsContextLoading } = useUtilityContext(); const { user } = useUserContext(); + const { isLoading, setIsContextLoading } = useUtilityContext(); + const { fetchAllCollectionsForUser } = useCollectionStore(); + const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); - const hasFetchedCollectionsRef = useRef(false); // This ref is used to indicate whether collections have been fetched. + useCronJob(lastCronJobTriggerTime, setLastCronJobTriggerTime); useEffect(() => { if (user) { @@ -115,7 +153,7 @@ const App = () => { return () => { console.log('Private routes no longer available'); }; - }, [user]); // useEffect will re-run whenever 'user' changes + }, [user]); useEffect(() => { if (!isLoading) { @@ -130,20 +168,31 @@ const App = () => { return () => clearTimeout(timer); }, [setIsContextLoading]); - useCronJob(lastCronJobTriggerTime, setLastCronJobTriggerTime); - - // Assuming currentPage is a piece of state that changes when the user changes pages useEffect(() => { - if (user && !hasFetchedCollectionsRef.current) { - try { - fetchAllCollectionsForUser(); - hasFetchedCollectionsRef.current = true; // Set the ref to true after fetching - console.log('Fetched collections because none were present.'); - } catch (err) { - console.error('Failed to fetch collections:', err); + let isMounted = true; // Add a flag to track if the component is mounted + const fetchCollections = async () => { + if (user && isMounted) { + try { + // const response = fet + if (isMounted) { + console.log('Fetched collections because none were present.'); + // Update state only if the component is still mounted + console.log('RESPONSE:', isMounted); + // setOfficialCollectionDatasets(response.data); + } + } catch (err) { + console.error('Failed to fetch collections:', err); + } } - } - }, [user, fetchAllCollectionsForUser]); // Removed allCollections from dependency list to prevent re-fetching when they update + }; + + fetchCollections(); + + // Cleanup function to cancel any ongoing tasks when the component unmounts + return () => { + isMounted = false; + }; + }, [user, fetchAllCollectionsForUser]); return ( <> diff --git a/src/components/buttons/actionButtons/CardActionButtons.jsx b/src/components/buttons/actionButtons/CardActionButtons.jsx index 7a4c705..833c3bd 100644 --- a/src/components/buttons/actionButtons/CardActionButtons.jsx +++ b/src/components/buttons/actionButtons/CardActionButtons.jsx @@ -1,74 +1,81 @@ import React, { useCallback } from 'react'; import { Button, Grid } from '@mui/material'; -import { makeStyles } from '@mui/styles'; -import { useModal } from '../../../context/hooks/modal'; +import { useStyles } from '../buttonStyles'; +import useAppContext from '../../../context/hooks/useAppContext'; -const useStyles = makeStyles((theme) => ({ - root: { - height: '100%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - alignItems: 'center', - flexGrow: 1, - background: '#f1f1f1', - borderRadius: '8px', - }, - buttonGrid: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - marginTop: '8px', - }, - fullWidthButton: { - width: '100%', - height: '100%', - borderRadius: '4px', - }, -})); - -const CardActionButtons = ({ - card, - quantity, - context, - contextProps, - handleOpenDialog, -}) => { +const CardActionButtons = ({ card, quantity, context, closeModal }) => { const classes = useStyles(); + const [contextProps, isContextAvailable] = useAppContext(context); + + if (!isContextAvailable) { + console.error(`The component isn't wrapped with the ${context}Provider`); + return null; // Consider rendering an error boundary or a user-friendly error message instead. + } + + // console.log('CARD TO ADD', card); + // Action types + const ADD = 'add'; + const REMOVE_ONE = 'removeOne'; + const REMOVE_ALL = 'removeAll'; + + // Action handler + const performAction = useCallback( + (action, payload) => { + console.log(`action --> ${action}`, `payload --> ${payload}`); + try { + switch (action) { + case ADD: + contextProps[context][`addOneTo${context}`](payload); + break; + case REMOVE_ONE: + contextProps[context].removeOne(payload); + break; + case REMOVE_ALL: + contextProps[context].removeAll(payload); + break; + default: + console.error(`Unhandled action type: ${action}`); + } + } catch (error) { + console.error( + `Error performing action '${action}' with payload`, + payload, + error + ); + } + }, + [context, contextProps] + ); + + const handleAddClick = () => { + performAction(ADD, card); + closeModal(); + }; - const handleAddToContext = useCallback(() => { - console.log(`Context is: ${context}`); - const addMethod = contextProps[`addOneTo${context}`]; - if (typeof addMethod === 'function') { - addMethod(card); - } else if (context === 'Collection') { - console.log( - "Opening ChooseCollectionDialog because the context is 'Collection'" - ); - handleOpenDialog(); - } else { - console.error(`Method addOneTo${context} not found in contextProps`); + const handleClickOutside = () => { + if (quantity === 0) { + closeModal(); } - }, [context, contextProps, card, handleOpenDialog]); + }; return ( -
- {quantity > 0 ? ( +
+ {card.quantity > 0 ? ( <> - {`In ${context}: `} {quantity} + {`In ${context}: `} {card.quantity} - - + + @@ -77,8 +84,11 @@ const CardActionButtons = ({ diff --git a/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx b/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx index 77eb817..5ea3488 100644 --- a/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx +++ b/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx @@ -11,7 +11,7 @@ import { Snackbar, } from '@mui/material'; import { makeStyles } from '@mui/styles'; -import { useCollectionStore } from '../../../context/hooks/collection'; +import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; const useStyles = makeStyles((theme) => ({ listItem: { diff --git a/src/components/buttons/actionButtons/GenericActionButtons.jsx b/src/components/buttons/actionButtons/GenericActionButtons.jsx index 4f56698..ff7a5dc 100644 --- a/src/components/buttons/actionButtons/GenericActionButtons.jsx +++ b/src/components/buttons/actionButtons/GenericActionButtons.jsx @@ -1,12 +1,8 @@ -// Dependencies -import React, { useState, useEffect, useContext } from 'react'; +import React, { useEffect } from 'react'; import { makeStyles } from '@mui/styles'; -import { CollectionContext } from '../../../context/CollectionContext/CollectionContext'; -import { DeckContext } from '../../../context/DeckContext/DeckContext'; -import { CartContext } from '../../../context/CartContext/CartContext'; import GenericCardModal from '../../modals/GenericCardModal'; import CardActionButtons from './CardActionButtons'; -// import ChooseCollectionDialog from './ChooseCollectionDialog'; +import useAppContext from '../../../context/hooks/useAppContext'; const useStyles = makeStyles({ root: { @@ -21,59 +17,42 @@ const GenericActionButtons = ({ card, context, open, - component, + openModal, closeModal, - // handleOpenDialog, + isModalOpen, + setModalOpen, }) => { const classes = useStyles(); + const [contextProps, isContextAvailable] = useAppContext(context); - const collectionContext = useContext(CollectionContext); - - if (!collectionContext) { - console.error("The component isn't wrapped with CollectionProvider"); - return null; + if (!isContextAvailable) { + console.error(`The component isn't wrapped with the ${context}Provider`); + return null; // Consider rendering an error boundary or user-friendly error message instead. } - const contexts = { - Deck: useContext(DeckContext), - Cart: useContext(CartContext), - Store: useContext(CartContext), - Collection: collectionContext, - }; - - const [openDialog, setOpenDialog] = useState(false); - - const contextProps = contexts[context] || {}; - - // useEffect(() => { - // console.log(`openChooseCollectionDialog is: ${openChooseCollectionDialog}`); - // }, [openChooseCollectionDialog]); - - const toggleDialog = (setState) => () => { - setState((prevState) => !prevState); - }; + // Ensure contextProps is an object with the expected methods before using them + if ( + typeof contextProps !== 'object' || + typeof contextProps[context] !== 'object' + ) { + console.error(`Invalid contextProps provided for the context: ${context}`); + return null; // Consider rendering an error boundary or user-friendly error message instead. + } return (
setModalOpen(false)} + open={open} /> - {context in contexts && ( - - )} - {/* */} diff --git a/src/components/buttons/buttonStyles.jsx b/src/components/buttons/buttonStyles.jsx new file mode 100644 index 0000000..838e89a --- /dev/null +++ b/src/components/buttons/buttonStyles.jsx @@ -0,0 +1,30 @@ +import { makeStyles } from '@mui/styles'; +export const useStyles = makeStyles((theme) => ({ + root: { + height: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'center', + flexGrow: 1, + background: '#f1f1f1', + borderRadius: '8px', + }, + buttonGrid: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + marginTop: '8px', + }, + fullWidthButton: { + width: '100%', + height: '100%', + borderRadius: '4px', + }, + fitWidthButton: { + width: 'fit-content', + height: '100%', + // transform: scale(0.9), + borderRadius: '4px', + }, +})); diff --git a/src/components/cards/CardDetailsContainer.jsx b/src/components/cards/CardDetailsContainer.jsx new file mode 100644 index 0000000..3b47c40 --- /dev/null +++ b/src/components/cards/CardDetailsContainer.jsx @@ -0,0 +1,26 @@ +// Import necessary modules from MUI or other libraries +import React from 'react'; +import { Typography, Grid } from '@mui/material'; +import { useStyles } from './cardStyles'; + +const CardDetailsContainer = ({ card }) => { + const classes = useStyles(); + + // Here you can add more details about the card using the 'card' object + // For now, just displaying the card name and description if available + return ( + + + + {card?.name} + + + {card?.desc || 'No description available.'} + + {/* You can continue adding more details here */} + + + ); +}; + +export default CardDetailsContainer; diff --git a/src/components/cards/GenericCard.jsx b/src/components/cards/GenericCard.jsx index a563a1b..480c7b8 100644 --- a/src/components/cards/GenericCard.jsx +++ b/src/components/cards/GenericCard.jsx @@ -1,221 +1,109 @@ -import React, { useState, useEffect, useContext, useRef } from 'react'; -import { - Card, - CardContent, - CardActions, - CardMedia, - Typography, - Button, - Popover, - Grid, - ButtonGroup, -} from '@mui/material'; +import React, { useEffect } from 'react'; +import { Card, CardContent, CardActions, Typography } from '@mui/material'; import CardMediaSection from '../media/CardMediaSection'; import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons'; import placeholderImage from '../../assets/images/placeholder.jpeg'; -import { DeckContext } from '../../context/DeckContext/DeckContext'; -import { CartContext } from '../../context/CartContext/CartContext'; -import { CollectionContext } from '../../context/CollectionContext/CollectionContext'; -import { makeStyles } from '@mui/styles'; +import { useStyles } from './cardStyles'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -const useStyles = makeStyles((theme) => ({ - card: { - display: 'flex', - flexDirection: 'column', - height: '100%', - maxHeight: '300px', // or any desired max height - minHeight: '300px', // make sure it matches max height - overflow: 'hidden', // ensures content doesn't spill out - borderRadius: theme.spacing(1), // Add border radius for cards - boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.2)', // Add shadow for cards - transition: 'transform 0.2s', - '&:hover': { - transform: 'scale(1.03)', // Add a slight scale effect on hover - }, - }, - image: { - maxHeight: '200px', - width: '100%', - objectFit: 'cover', // Ensure the image covers the entire space - }, - text: { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - button: { - // maxWidth: '200px', - minHeight: '40px', - maxHeight: '60px', - width: '100%', - }, - content: { - transform: 'scale(0.9)', // scales down to 90% of the original size - padding: 0, - }, - cardActions: { - marginTop: 'auto', // pushes the actions to the bottom - display: 'flex', - justifyContent: 'center', // centers the buttons - width: '100%', - }, -})); - -const GenericCard = ({ - card, - context, - cardInfo, - cardRef, - setHoveredCard, - hoveredCard, - setIsPopoverOpen, - isPopoverOpen, - // anchorEl, - // handlePopoverOpen, - // handlePopoverClose, - // open, -}) => { - const deckContext = useContext(DeckContext); - const cartContext = useContext(CartContext); - const collectionContext = useContext(CollectionContext); - const contextProps = - { - Deck: deckContext, - Cart: cartContext, - Collection: collectionContext, - }[context] || {}; - // const tooltipRef = useRef(null); - // const cardRef = useRef(null); +const GenericCard = React.forwardRef((props, ref) => { + const { + card, + context, + isModalOpen, + setModalOpen, + hoveredCard, + setHoveredCard, + setIsPopoverOpen, + setClickedCard, + } = props; + const classes = useStyles(); + const { selectedCollection } = useCollectionStore(); + const requiresDoubleButtons = context === 'Deck' || context === 'Collection'; // Added this line + const checkCardInCollection = () => { + if (selectedCollection) { + const cardIds = selectedCollection.cards.map((card) => card.id); + return cardIds.includes(card.id); + } + return false; + }; + // Function to handle click on the card, opening the modal and updating state + // Function to handle click on the card, opening the modal and updating state + const handleClick = () => { + const cardIsInCollection = checkCardInCollection(); + console.log('Modal opened with card:', card); + console.log('Card is in collection:', cardIsInCollection); - const [buttonVariant, setButtonVariant] = useState('contained'); - const [isModalOpen, setModalOpen] = useState(false); - // const [anchorEl, setAnchorEl] = useState(null); - const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; - const open = Boolean(hoveredCard === card); + setModalOpen(true); + setIsPopoverOpen(false); + }; - const openModal = () => setModalOpen(true); - const closeModal = () => setModalOpen(false); - const openProductModal = openModal; - // const handlePopoverOpen = () => { - // setHoveredCard(card); - // }; + // Function to handle hover interactions with the card + const handleInteraction = (hoverState) => { + if (!isModalOpen) { + setHoveredCard((prev) => (hoverState ? card : null)); + setIsPopoverOpen(hoverState); + } + }; - // const handlePopoverClose = () => { - // setHoveredCard(null); - // }; + // Effect to close popover when modal is open or reactivate when modal closes + useEffect(() => { + setIsPopoverOpen(isModalOpen ? false : hoveredCard === card); + if (isModalOpen) { + setHoveredCard(null); + } + }, [isModalOpen, hoveredCard, card, setIsPopoverOpen, setHoveredCard]); - const classes = useStyles(); - const handleCardHover = (cardData) => { - setHoveredCard(cardData); - setIsPopoverOpen(true); // or based on other logic you might have - }; + // Get the card image URL, or use placeholder if not available + const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; + const price = `Price: ${card?.card_prices?.[0]?.tcgplayer_price || 'N/A'}`; + console.log(typeof handleInteraction); // Should log 'function' - const CardContentByContext = ({ context }) => { - const price = `Price: ${card?.card_prices?.[0]?.tcgplayer_price}`; - return ( - <> - - {price} - + return ( + + + + {card?.name} - {/* Quantity: {contextProps?.deckCardQuantity?.quantityOfSameId || `Not in ${context?.toLowerCase()}`} */} + {price} - - ); - }; - // console.log('cardRef:', cardRef.current); - // console.log('anchorEl:', anchorEl); - // console.log('open:', open); - // console.log('handlePopoverClose:', handlePopoverClose); - // console.log('handlePopoverOpen:', handlePopoverOpen); - return ( - - {context === 'Deck' ? ( - - ) : ( - - )} - - - {card?.name} - - - - {context === 'Store' || context === 'Cart' ? ( -
- - -
- ) : ( - - {/* */} - - - )} -
-
+
+ + {/* Conditionally render action buttons based on context */} + {requiresDoubleButtons ? ( + <> + {/* */} + + + ) : ( + + )} +
); -}; +}); + +GenericCard.displayName = 'GenericCard'; export default GenericCard; diff --git a/src/components/cards/cardStyles.js b/src/components/cards/cardStyles.js deleted file mode 100644 index a818c1a..0000000 --- a/src/components/cards/cardStyles.js +++ /dev/null @@ -1,148 +0,0 @@ -import { makeStyles } from '@mui/styles'; - -export const commonStyles = makeStyles((theme) => ({ - card: { - display: 'flex', - flexDirection: 'column', - margin: '16px', - // Enhanced responsiveness: - // Use percentage widths and media queries to better adjust card size on various screens - width: '100%', - [theme?.breakpoints?.up('sm')]: { - width: '80%', - }, - [theme?.breakpoints?.up('md')]: { - width: '60%', - }, - }, - media: { - flex: 1, - objectFit: 'cover', - }, - content: { - flex: 1, - overflow: 'auto', - }, - button: { - margin: '4px', - }, - actionButtons: { - backgroundColor: '#f5f5f5', - padding: '12px', - margin: '8px 0', - borderRadius: '8px', - }, - tooltip: { - display: 'none', - position: 'absolute', - top: 0, - left: '100%', - zIndex: 999, - backgroundColor: 'rgba(0, 0, 0, 0.8)', - color: 'white', - padding: '10px', - borderRadius: '5px', - width: '40vw', - height: 'auto', - maxHeight: '40vh', - overflowY: 'auto', - '&.show': { - display: 'block', - }, - span: { - display: 'block', - marginBottom: '4px', - lineHeight: 1.2, - '&:last-child': { - marginBottom: 0, - }, - }, - strong: { - fontWeight: 'bold', - }, - tooltipTitle: { - fontWeight: 'bold', - fontSize: '14px', - marginBottom: '8px', - textAlign: 'center', - }, - }, -})); - -export const deckCardStyles = makeStyles({ - card: { - position: 'relative', - maxHeight: 100, - height: '100%', - width: '100%', - }, - content: { - overflow: 'hidden', - }, - media: { - width: '100%', - objectFit: 'contain', - }, - text: { - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - }, -}); - -export const productCardStyles = makeStyles({ - card: { - maxWidth: '100%', - minHeight: 300, - padding: '16px', - }, - largerButton: { - margin: '0 8px', - padding: '10px', - fontSize: '20px', - '@media(max-width: 600px)': { - fontSize: '16px', - }, - }, - actionButtons: { - padding: '10px', - margin: '10px 0', - borderRadius: '4px', - }, -}); - -// Function to merge common and specific styles -export const mergeStyles = (common, specific) => { - const mergedStyles = { - ...common, - ...specific, - }; - - // Convert the merged styles into valid class names - const classNames = { - card: { - ...common.card, - ...specific.card, - }, - media: { - ...common.media, - ...specific.media, - }, - content: { - ...common.content, - ...specific.content, - }, - button: { - ...common.button, - ...specific.button, - }, - actionButtons: { - ...common.actionButtons, - ...specific.actionButtons, - }, - }; - for (const key in mergedStyles) { - classNames[key] = Object.keys(mergedStyles[key]).join(' '); - } - return classNames; -}; diff --git a/src/components/cards/cardStyles.jsx b/src/components/cards/cardStyles.jsx new file mode 100644 index 0000000..fb39acc --- /dev/null +++ b/src/components/cards/cardStyles.jsx @@ -0,0 +1,192 @@ +import { makeStyles } from '@mui/styles'; + +export const useStyles = makeStyles((theme) => ({ + card: { + display: 'flex', + flexDirection: 'column', + height: '100%', + maxHeight: '300px', // or any desired max height + minHeight: '300px', // make sure it matches max height + overflow: 'hidden', // ensures content doesn't spill out + borderRadius: theme.spacing(1), // Add border radius for cards + boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.2)', // Add shadow for cards + transition: 'transform 0.2s', + '&:hover': { + transform: 'scale(1.03)', // Add a slight scale effect on hover + }, + }, + image: { + maxHeight: '200px', + width: '100%', + objectFit: 'cover', // Ensure the image covers the entire space + }, + text: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + button: { + // maxWidth: '200px', + minHeight: '40px', + maxHeight: '60px', + width: '100%', + }, + content: { + transform: 'scale(0.9)', // scales down to 90% of the original size + padding: 0, + }, + cardActions: { + marginTop: 'auto', // pushes the actions to the bottom + display: 'flex', + justifyContent: 'center', // centers the buttons + width: '100%', + }, +})); +// import { makeStyles } from '@mui/styles'; + +// export const commonStyles = makeStyles((theme) => ({ +// card: { +// display: 'flex', +// flexDirection: 'column', +// margin: '16px', +// // Enhanced responsiveness: +// // Use percentage widths and media queries to better adjust card size on various screens +// width: '100%', +// [theme?.breakpoints?.up('sm')]: { +// width: '80%', +// }, +// [theme?.breakpoints?.up('md')]: { +// width: '60%', +// }, +// }, +// media: { +// flex: 1, +// objectFit: 'cover', +// }, +// content: { +// flex: 1, +// overflow: 'auto', +// }, +// button: { +// margin: '4px', +// }, +// actionButtons: { +// backgroundColor: '#f5f5f5', +// padding: '12px', +// margin: '8px 0', +// borderRadius: '8px', +// }, +// tooltip: { +// display: 'none', +// position: 'absolute', +// top: 0, +// left: '100%', +// zIndex: 999, +// backgroundColor: 'rgba(0, 0, 0, 0.8)', +// color: 'white', +// padding: '10px', +// borderRadius: '5px', +// width: '40vw', +// height: 'auto', +// maxHeight: '40vh', +// overflowY: 'auto', +// '&.show': { +// display: 'block', +// }, +// span: { +// display: 'block', +// marginBottom: '4px', +// lineHeight: 1.2, +// '&:last-child': { +// marginBottom: 0, +// }, +// }, +// strong: { +// fontWeight: 'bold', +// }, +// tooltipTitle: { +// fontWeight: 'bold', +// fontSize: '14px', +// marginBottom: '8px', +// textAlign: 'center', +// }, +// }, +// })); + +// export const deckCardStyles = makeStyles({ +// card: { +// position: 'relative', +// maxHeight: 100, +// height: '100%', +// width: '100%', +// }, +// content: { +// overflow: 'hidden', +// }, +// media: { +// width: '100%', +// objectFit: 'contain', +// }, +// text: { +// textOverflow: 'ellipsis', +// overflow: 'hidden', +// whiteSpace: 'nowrap', +// }, +// }); + +// export const productCardStyles = makeStyles({ +// card: { +// maxWidth: '100%', +// minHeight: 300, +// padding: '16px', +// }, +// largerButton: { +// margin: '0 8px', +// padding: '10px', +// fontSize: '20px', +// '@media(max-width: 600px)': { +// fontSize: '16px', +// }, +// }, +// actionButtons: { +// padding: '10px', +// margin: '10px 0', +// borderRadius: '4px', +// }, +// }); + +// // Function to merge common and specific styles +// export const mergeStyles = (common, specific) => { +// const mergedStyles = { +// ...common, +// ...specific, +// }; + +// // Convert the merged styles into valid class names +// const classNames = { +// card: { +// ...common.card, +// ...specific.card, +// }, +// media: { +// ...common.media, +// ...specific.media, +// }, +// content: { +// ...common.content, +// ...specific.content, +// }, +// button: { +// ...common.button, +// ...specific.button, +// }, +// actionButtons: { +// ...common.actionButtons, +// ...specific.actionButtons, +// }, +// }; +// for (const key in mergedStyles) { +// classNames[key] = Object.keys(mergedStyles[key]).join(' '); +// } +// return classNames; +// }; diff --git a/src/components/collection/CardPortfolio.jsx b/src/components/collection/CardPortfolio.jsx index 5346c4e..ee1966a 100644 --- a/src/components/collection/CardPortfolio.jsx +++ b/src/components/collection/CardPortfolio.jsx @@ -3,7 +3,7 @@ import SelectCollection from './SelectCollection'; import PortfolioContent from '../content/PortfolioContent'; import { Box, Typography } from '@mui/material'; import CollectionContainer from '../../containers/collectionPageContainers/CollectionContainer'; -import { useCollectionStore } from '../../context/hooks/collection'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; // import UpdateChartData from './UpdateChartData'; const CardPortfolio = ({ allCollections }) => { diff --git a/src/components/collection/SelectCollection.jsx b/src/components/collection/SelectCollection.jsx index 395cdb1..919a6ef 100644 --- a/src/components/collection/SelectCollection.jsx +++ b/src/components/collection/SelectCollection.jsx @@ -5,8 +5,8 @@ import { useCookies } from 'react-cookie'; import PropTypes from 'prop-types'; import SimpleReusableButton from '../reusable/SimpleReusableButton'; import SelectCollectionList from '../grids/collectionGrids/SelectCollectionList'; -import { useCollectionStore } from '../../context/hooks/collection'; import SelectCollectionDialog from '../dialogs/SelectCollectionDialog'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; const useStyles = makeStyles((theme) => ({ root: { diff --git a/src/components/content/PortfolioContent.jsx b/src/components/content/PortfolioContent.jsx index d7a70fb..8591778 100644 --- a/src/components/content/PortfolioContent.jsx +++ b/src/components/content/PortfolioContent.jsx @@ -1,22 +1,16 @@ import React from 'react'; -import { Box } from '@mui/system'; +import { Box, Paper } from '@mui/material'; import PortfolioHeader from '../headings/PortfolioHeader'; import PortfolioListContainer from '../../containers/PortfolioListContainer'; -import { useCollectionStore } from '../../context/hooks/collection'; import PortfolioChartContainer from '../../containers/PortfolioChartContainer'; const PortfolioContent = ({ error, selectedCards, removeCard }) => { - const { selectedCollection } = useCollectionStore(); - return ( { display: 'flex', flexDirection: { xs: 'column', md: 'row' }, justifyContent: 'space-between', - alignItems: 'stretch', // Make child elements stretch to maximum available height + alignItems: 'start', // Align items to the start of the container width: '100%', - mt: 2, - height: '100%', + gap: 2, // Adds space between the components }} > - - - + - + ); diff --git a/src/components/dialogs/CollectionSelectorDialog.jsx b/src/components/dialogs/CollectionSelectorDialog.jsx new file mode 100644 index 0000000..045a313 --- /dev/null +++ b/src/components/dialogs/CollectionSelectorDialog.jsx @@ -0,0 +1,77 @@ +// CollectionSelectorDialog.js +import React from 'react'; +import { + Dialog, + DialogTitle, + List, + ListItem, + ButtonBase, + ListItemText, + Divider, +} from '@mui/material'; +import { makeStyles } from '@mui/styles'; + +const useStyles = makeStyles((theme) => ({ + listItem: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(2), + backgroundColor: '#ffffff', + borderRadius: '8px', + marginBottom: theme.spacing(2), + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', + }, + listItemText: { + flex: 1, + textAlign: 'left', + marginLeft: theme.spacing(3), + }, +})); + +const CollectionSelectorDialog = ({ + open, + allCollections, + onClose, + onCollectionSelect, +}) => { + const classes = useStyles(); + + if (!Array.isArray(allCollections)) { + console.error('collection is not an array', allCollections); + return null; // or return a default empty array or some placeholder content + } + + const handleSelectCollection = (collection) => { + onCollectionSelect( + `Collection "${collection.name}" selected successfully!` + ); + onClose(); + }; + + return ( + + Select a Collection + + {allCollections?.map((collection) => ( + + + handleSelectCollection(collection)} + > + + + + + + ))} + + + ); +}; + +export default CollectionSelectorDialog; diff --git a/src/components/dialogs/SelectCollectionDialog.jsx b/src/components/dialogs/SelectCollectionDialog.jsx index 86f263e..93a7698 100644 --- a/src/components/dialogs/SelectCollectionDialog.jsx +++ b/src/components/dialogs/SelectCollectionDialog.jsx @@ -7,8 +7,8 @@ import { TextField, Typography, } from '@mui/material'; -import { useCollectionStore } from '../../context/hooks/collection'; import { useCookies } from 'react-cookie'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; const SelectCollectionDialog = ({ isDialogOpen, diff --git a/src/components/grids/collectionGrids/CardList.jsx b/src/components/grids/collectionGrids/CardList.jsx index e7c00c3..8ece1cd 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -15,11 +15,11 @@ import { Button, TableHead, } from '@mui/material'; -import { useCollectionStore } from '../../../context/hooks/collection'; import AssessmentIcon from '@mui/icons-material/Assessment'; import TablePaginationActions from './TablePaginationActions'; import Logger from './Logger'; import PropTypes from 'prop-types'; +import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; // Instantiate logger outside of the component const cardLogger = new Logger([ diff --git a/src/components/grids/collectionGrids/SelectCollectionList.jsx b/src/components/grids/collectionGrids/SelectCollectionList.jsx index 0423c50..dec753a 100644 --- a/src/components/grids/collectionGrids/SelectCollectionList.jsx +++ b/src/components/grids/collectionGrids/SelectCollectionList.jsx @@ -10,8 +10,8 @@ import { import { makeStyles } from '@mui/styles'; import { useCookies } from 'react-cookie'; import PropTypes from 'prop-types'; -import { useCollectionStore } from '../../../context/hooks/collection'; import LoadingIndicator from '../../indicators/LoadingIndicator'; +import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; const useStyles = makeStyles((theme) => ({ listItem: { diff --git a/src/components/grids/gridStyles.jsx b/src/components/grids/gridStyles.jsx new file mode 100644 index 0000000..dc7227f --- /dev/null +++ b/src/components/grids/gridStyles.jsx @@ -0,0 +1,20 @@ +import { makeStyles } from '@mui/styles'; +export const useStyles = makeStyles((theme) => ({ + cardContainer: { + display: 'flex', + justifyContent: 'center', + maxHeight: '300px', // or any desired max height + minHeight: '300px', // make sure it matches max height + overflow: 'hidden', // ensures content doesn't spill out + }, + card: { + width: '100%', + // transform: 'scale(0.9)', // scales down to 90% of the original size + }, + loading: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100vh', + }, +})); diff --git a/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx b/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx index 316ece7..b933005 100644 --- a/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx +++ b/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx @@ -1,162 +1,114 @@ -import React, { useEffect, useRef, useState } from 'react'; +// DeckSearchCardGrid.jsx +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Box, Grid, useMediaQuery, useTheme } from '@mui/material'; -import GenericCard from '../../cards/GenericCard'; -import { makeStyles } from '@mui/styles'; -import CustomPopover from '../../cards/CustomPopover'; +import { useStyles } from '../gridStyles'; import LoadingIndicator from '../../indicators/LoadingIndicator'; - -const useStyles = makeStyles((theme) => ({ - cardContainer: { - display: 'flex', - justifyContent: 'center', - maxHeight: '300px', // or any desired max height - minHeight: '300px', // make sure it matches max height - overflow: 'hidden', // ensures content doesn't spill out - }, - card: { - width: '100%', - // transform: 'scale(0.9)', // scales down to 90% of the original size - }, - loading: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100vh', - }, -})); +import GenericCard from '../../cards/GenericCard'; +import GenericCardModal from '../../modals/GenericCardModal'; const DeckSearchCardGrid = ({ cards, userDecks }) => { const classes = useStyles(); const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme?.breakpoints?.down('sm')); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const [hoveredCard, setHoveredCard] = useState(null); + const [clickedCard, setClickedCard] = useState(null); const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [isModalOpen, setModalOpen] = useState(false); const [isLoading, setIsLoading] = useState(true); - const cardRef = useRef(null); useEffect(() => { - // Mock fetching data or some asynchronous action - // Remove this if you have actual data fetching logic - setTimeout(() => { - setIsLoading(false); - }, 1000); + // Simulated data fetching delay + const timer = setTimeout(() => setIsLoading(false), 1000); + return () => clearTimeout(timer); }, []); + // Using useMemo to prevent unnecessary recalculations of uniqueCards + const uniqueCards = useMemo(() => { + const cardMap = new Map(cards.map((card) => [card.id, card])); + return Array.from(cardMap.values()); + }, [cards]); + + // If data is loading, show loading indicator if (isLoading) { - return ( - - - - ); + return ; } + // closeModal function to pass to GenericCardModal and action buttons + const closeModal = () => setModalOpen(false); + return ( - - - {cards?.map((card, i) => ( - -
- -
-
- ))} -
-
+ <> + + + ); }; export default DeckSearchCardGrid; -// const DeckSearchCardGrid = ({ cards, userDecks }) => { -// const theme = useTheme(); -// const isSmallScreen = useMediaQuery(theme?.breakpoints?.down('sm')); -// const classes = useStyles(); -// // const [anchorEl, setAnchorEl] = useState(null); -// const [isHovering, setHovering] = useState(false); -// const cardRef = useRef(null); -// const [hoveredCard, setHoveredCard] = useState(null); -// const [anchorEl, setAnchorEl] = React.useState(null); - -// const handlePopoverOpen = (event) => { -// setAnchorEl(event.currentTarget); -// }; - -// const handlePopoverClose = () => { -// setAnchorEl(null); -// }; -// // const handlePopoverOpen = (card) => { -// // setHoveredCard(card); -// // }; - -// // const handlePopoverClose = () => { -// // setHoveredCard(null); -// // }; - -// const open = Boolean(anchorEl); - -// return ( -// -// {cards?.map((card, i) => ( -// -// {/* handlePopoverOpen(card)} -// // handleMouseLeave={handlePopoverClose} */} -// {/* > */} -//
-// handlePopoverOpen(card)} -// handlePopoverOpen={handlePopoverOpen} -// isHovering={hoveredCard === card} -// setHovering={setHoveredCard} -// /> -//
-// {/*
*/} -//
-// ))} -//
-// ); -// }; - -// export default DeckSearchCardGrid; +// Separating Grid logic for better readability and reusability +const GridContainer = ({ + classes, + cards, + userDecks, + hoveredCard, + setHoveredCard, + isPopoverOpen, + setIsPopoverOpen, + cardRef, + isModalOpen, + setModalOpen, + clickedCard, + setClickedCard, + context, +}) => ( + + + {cards?.map((card, index) => ( + +
+ +
+
+ ))} +
+
+); diff --git a/src/components/headings/PortfolioHeader.jsx b/src/components/headings/PortfolioHeader.jsx index cdbdc5a..e68f267 100644 --- a/src/components/headings/PortfolioHeader.jsx +++ b/src/components/headings/PortfolioHeader.jsx @@ -3,8 +3,8 @@ import React from 'react'; import { AlertTitle, Box, Typography } from '@mui/material'; import Alert from '@mui/material/Alert'; -import { useCollectionStore } from '../../context/hooks/collection'; import HeaderTitle from '../reusable/HeaderTitle'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; export const PortfolioHeader = ({ error }) => { const { allCollections, selectedCollection } = useCollectionStore(); diff --git a/src/components/headings/header/menuItemsData.jsx b/src/components/headings/header/menuItemsData.jsx index 67feb62..77ce330 100644 --- a/src/components/headings/header/menuItemsData.jsx +++ b/src/components/headings/header/menuItemsData.jsx @@ -45,7 +45,7 @@ export const getMenuItemsData = (isLoggedIn) => { // If the user is logged in, set all requiresLogin fields to false if (isLoggedIn) { - console.log('isLoggedIn is true', isLoggedIn); + // console.log('isLoggedIn is true', isLoggedIn); return baseMenuItems.map((item) => ({ ...item, requiresLogin: false, diff --git a/src/components/media/CardMedia.jsx b/src/components/media/CardMedia.jsx index 9233a7b..4030aac 100644 --- a/src/components/media/CardMedia.jsx +++ b/src/components/media/CardMedia.jsx @@ -1,27 +1,49 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { CardMedia } from '@mui/material'; import { makeStyles } from '@mui/styles'; const useStyles = makeStyles({ media: { - width: '100%', // Make it responsive - // minHeight: '50%', // Set minimum height - // '@media (min-width:600px)': { - // minHeight: '400px', - // }, + width: '100%', }, }); -const ReusableCardMedia = ({ imgUrl }) => { - const classes = useStyles(); - return ( - - ); -}; +const ReusableCardMedia = React.forwardRef( + ( + { + imgUrl, + onMouseEnter, + onMouseLeave, + onClick, + isModalOpen, + setIsPopoverOpen, + }, + ref + ) => { + const classes = useStyles(); + const altText = `Image for ${imgUrl || 'the card'}`; + + useEffect(() => { + if (isModalOpen) { + setIsPopoverOpen(false); + } + }, [isModalOpen, setIsPopoverOpen]); + + return ( + + ); + } +); + +ReusableCardMedia.displayName = 'ReusableCardMedia'; export default ReusableCardMedia; diff --git a/src/components/media/CardMediaAndDetails.jsx b/src/components/media/CardMediaAndDetails.jsx new file mode 100644 index 0000000..e584941 --- /dev/null +++ b/src/components/media/CardMediaAndDetails.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Grid } from '@mui/material'; +import CardMediaSection from './CardMediaSection'; // Ensure correct import +import CardDetailsContainer from '../cards/CardDetailsContainer'; +// import { useStyles } from './detailsStyles'; // Assuming there are styles, uncomment if needed + +// This component doesn't need to maintain state or use hooks, hence it's a simple functional component +const CardMediaAndDetails = ({ card }) => { + // const classes = useStyles(); // Uncomment if styles are used + + return ( + + + + + + + + + ); +}; + +// Using React.memo to avoid unnecessary re-renders +export default React.memo(CardMediaAndDetails); diff --git a/src/components/media/CardMediaSection.js b/src/components/media/CardMediaSection.js index dd19fc5..a37f483 100644 --- a/src/components/media/CardMediaSection.js +++ b/src/components/media/CardMediaSection.js @@ -1,106 +1,111 @@ -import React, { useState, useEffect } from 'react'; +// CardMediaSection.js +import React from 'react'; import ReusableCardMedia from './CardMedia'; -import placeholderImage from '../../assets/images/placeholder.jpeg'; +import CardToolTip from '../cards/CardToolTip'; import { makeStyles } from '@mui/styles'; import { Popover } from '@mui/material'; -import CardToolTip from '../cards/CardToolTip'; +import PropTypes from 'prop-types'; const useStyles = makeStyles({ mediaContainer: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - cursor: 'pointer', - '&:hover': { - opacity: 0.7, - }, + position: 'relative', }, }); -const CardMediaSection = ({ - imgUrl = placeholderImage, - card, - // open, - openModal, - cardRef, - modalIsOpen, - onCardHover, - cardData, - setIsPopoverOpen, - isPopoverOpen, -}) => { - const classes = useStyles(); - const [hasLoggedCard, setHasLoggedCard] = useState(false); - - useEffect(() => { - if (!hasLoggedCard) { - // console.log('CARD:', card); - setHasLoggedCard(true); - } - }, [hasLoggedCard, card]); - - // useEffect(() => { - // if (!modalIsOpen) { - // setIsPopoverOpen(false); - // } - // }, [modalIsOpen]); - - const open = Boolean(cardData === card) && !modalIsOpen; +const anchorOrigin = { + vertical: 'bottom', + horizontal: 'left', +}; - const handleMouseEnter = () => { - if (!modalIsOpen && typeof onCardHover === 'function') { - onCardHover(card); - } - }; +const transformOrigin = { + vertical: 'top', + horizontal: 'left', +}; - const handleMouseLeave = () => { - if (!modalIsOpen && typeof onCardHover === 'function') { - onCardHover(null); - // setIsPopoverOpen(false); - } - }; +const CardMediaSection = React.forwardRef( + ( + { + imgUrl, + card, + isHovered, + handleInteraction, + handleClick, + setClickedCard, + isRequired, + }, + ref + ) => { + const classes = useStyles(); - // const handleClick = () => { - // // Close the popover and open the modal - // setIsPopoverOpen(false); - // openModal(); - // }; + // Event handlers are now only assigned if isRequired is true + const eventHandlers = isRequired + ? { + onMouseEnter: () => { + if (typeof handleInteraction === 'function') { + handleInteraction(true); + } + }, + onMouseLeave: () => { + if (typeof handleInteraction === 'function') { + handleInteraction(false); + } + }, + onClick: () => { + if (typeof handleClick === 'function') { + handleClick(); + } + if (typeof setClickedCard === 'function') { + setClickedCard(card); + } + }, + } + : {}; - return ( -
-
{ - openModal(); - setIsPopoverOpen(false); - }} - > - + return ( +
+ + {isRequired && isHovered && ref.current && ( + { + if (typeof handleInteraction === 'function') { + handleInteraction(false); + } + }} + disableRestoreFocus + > + + + )}
+ ); + } +); - onCardHover(null)} - disableRestoreFocus - > - - -
- ); -}; +CardMediaSection.displayName = 'CardMediaSection'; -export default CardMediaSection; +CardMediaSection.propTypes = { + imgUrl: PropTypes.string.isRequired, + card: PropTypes.object.isRequired, + isHovered: PropTypes.bool, + handleInteraction: PropTypes.func, + handleClick: PropTypes.func, + setClickedCard: PropTypes.func, + isRequired: PropTypes.bool, +}; CardMediaSection.defaultProps = { - // eslint-disable-next-line @typescript-eslint/no-empty-function - onCardHover: () => {}, // provide a no-op function as default + isHovered: false, + handleInteraction: null, + handleClick: null, + setClickedCard: null, + isRequired: true, }; + +export default CardMediaSection; diff --git a/src/components/media/mediaStyles.jsx b/src/components/media/mediaStyles.jsx new file mode 100644 index 0000000..a2ccd44 --- /dev/null +++ b/src/components/media/mediaStyles.jsx @@ -0,0 +1,13 @@ +import { makeStyles } from '@mui/styles'; + +export const useStyles = makeStyles({ + mediaContainer: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + cursor: 'pointer', + '&:hover': { + opacity: 0.7, + }, + }, +}); diff --git a/src/components/modals/GenericCardModal.jsx b/src/components/modals/GenericCardModal.jsx index c4e201a..bd44026 100644 --- a/src/components/modals/GenericCardModal.jsx +++ b/src/components/modals/GenericCardModal.jsx @@ -1,220 +1,135 @@ -import React, { useContext, useEffect, useState, useCallback } from 'react'; +import React, { useEffect, useState } from 'react'; import { Dialog, - DialogContent, DialogTitle, - Grid, + DialogContent, Snackbar, - List, - ListItem, - ButtonBase, - ListItemText, - Divider, - Typography, + Alert, } from '@mui/material'; -import CardMediaSection from '../media/CardMediaSection'; -import CardDetailsContainer from './cardModal/CardDetailsContainer'; +import { useStyles } from './modalStyles'; +import useAppContext from '../../context/hooks/useAppContext'; +import useSnackbar from '../../context/hooks/useSnackBar'; +import CardMediaAndDetails from '../media/CardMediaAndDetails'; import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons'; -import { DeckContext } from '../../context/DeckContext/DeckContext'; -import { CartContext } from '../../context/CartContext/CartContext'; -import { CollectionContext } from '../../context/CollectionContext/CollectionContext'; -import { makeStyles } from '@mui/styles'; -import { useCollectionStore } from '../../context/hooks/collection'; - -const useStyles = makeStyles((theme) => ({ - actionButtons: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - gap: '1.5rem', - }, - media: { - objectFit: 'cover', - borderRadius: '4px', - boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', - }, - details: { - display: 'flex', - alignItems: 'center', - gap: '1.5rem', - marginBottom: '1.5rem', - }, - dialogTitle: { - fontSize: '1.5rem', - fontWeight: 600, - color: theme.palette.primary.dark, - }, - dialogContent: { - padding: '2rem', - }, - listItem: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: theme.spacing(2), - backgroundColor: '#ffffff', - borderRadius: '8px', - marginBottom: theme.spacing(2), - boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', - }, - listItemText: { - flex: 1, - textAlign: 'left', - marginLeft: theme.spacing(3), - }, -})); -const GenericCardModal = ({ open, onClose, card, cardInfo, context }) => { +const GenericCardModal = ({ + open, + card, + context, + closeModal, + setModalOpen, +}) => { const classes = useStyles(); - const deckContext = useContext(DeckContext); - const cartContext = useContext(CartContext); - const collectionContext = useContext(CollectionContext); - const [openSnackbar, setOpenSnackbar] = useState(false); - const [snackbarMessage, setSnackbarMessage] = useState(''); + const [contextProps, isContextAvailable] = useAppContext(context); + const [snackbar, handleSnackbar, handleCloseSnackbar] = useSnackbar(); + // const [isOpen, setIsOpen] = useState(false); + const [hasLoggedCard, setHasLoggedCard] = useState(false); - if (!collectionContext) { - console.error("The component isn't wrapped with CollectionProvider"); - return null; + if (!isContextAvailable) { + handleSnackbar( + `The component isn't wrapped with the ${context}Provider`, + 'error' + ); + console.error(`The component isn't wrapped with the ${context}Provider`); + return null; // Consider rendering an error boundary or user-friendly error message instead. } - const contextProps = - { - Deck: deckContext, - Cart: cartContext, - Store: cartContext, - Collection: collectionContext, - }[context] || {}; + const requiresDoubleButtons = context === 'Deck' || context === 'Collection'; - const { - openChooseCollectionDialog, - setOpenChooseCollectionDialog, - allCollections, - fetchAllCollectionsForUser, - setSelectedCollection, - } = contextProps; - const handleSelectCollection = useCallback( - (collectionId) => { - const foundCollection = allCollections.find( - (collection) => collection._id === collectionId - ); + // useEffect(() => { + // setIsOpen(open); + // }, [open]); - if (foundCollection) { - setSelectedCollection(foundCollection); - setOpenChooseCollectionDialog(false); - setSnackbarMessage('Collection selected successfully!'); - setOpenSnackbar(true); - } else { - setSnackbarMessage('Collection not found!'); - setOpenSnackbar(true); - } - }, - [allCollections, setSelectedCollection] - ); + useEffect(() => { + if (open && card && !hasLoggedCard) { + console.log('Modal opened with card:', card); + handleSnackbar('Card details loaded successfully.', 'success'); + setHasLoggedCard(true); + } + }, [open, card, hasLoggedCard, handleSnackbar]); - const handleClose = (event, reason) => { - if (reason === 'backdropClick' || reason === 'escapeKeyDown') { - setSnackbarMessage(`${context} successfully updated`); - setOpenSnackbar(true); - onClose(); + // useEffect(() => { + // if (!open && hasLoggedCard) { + // setHasLoggedCard(false); + // } + // }, [open, hasLoggedCard]); + useEffect(() => { + if (!open) { + setHasLoggedCard(false); // Reset hasLoggedCard when modal closes + } + }, [open]); // Removed hasLoggedCard from dependency array + + useEffect(() => { + if (context) { + // console.log('CONTEXT:', context); + } + }, [context]); // Removed hasLoggedCard from dependency array + useEffect(() => { + if (contextProps) { + // console.log('CONTEXT_PROPS:', contextProps); } + }, [contextProps]); // Removed hasLoggedCard from dependency array + + // Example function to be called when an action is successful + const handleActionSuccess = () => { + handleSnackbar('Action was successful!', 'success'); }; - // console.log('openChooseCollectionDialog', openChooseCollectionDialog); + // Example function to be called when an action fails + const handleActionFailure = (error) => { + console.error('Action failed:', error); + handleSnackbar('Action failed. Please try again.', 'error'); + }; - // useEffect(() => { - // if (openChooseCollectionDialog === true) { - // // console.log('Fetching collections...', openChooseCollectionDialog); - // fetchAllCollectionsForUser(); - // } - // }, [openChooseCollectionDialog]); - // console.log('open --------> ', open); return ( - + { + closeModal(); // Call closeModal directly + }} + // open={isOpen} + // onClose={() => { + // setIsOpen(false); + // if (closeModal) closeModal(); + // }} + fullWidth + maxWidth="md" + > {card?.name} - - - + {requiresDoubleButtons && ( + <> + + - - - - - + + )} - - {['Deck', 'Cart', 'Store', 'Collection'].includes(context) && ( - <> - - {context === 'Deck' && ( - <> - - - - )} - - )} - {openChooseCollectionDialog && ( - setOpenChooseCollectionDialog(false)} - > - Select a Collection - - - {allCollections.map((collection) => ( - - - handleSelectCollection(collection._id)} - > - - - - - - ))} - - - setOpenSnackbar(false)} - message={snackbarMessage} - autoHideDuration={3000} - /> - - )} setOpenSnackbar(false)} - message={snackbarMessage} - /> + onClose={handleCloseSnackbar} + > + + {snackbar.message} + + ); }; diff --git a/src/components/modals/modalStyles.jsx b/src/components/modals/modalStyles.jsx new file mode 100644 index 0000000..1badb2e --- /dev/null +++ b/src/components/modals/modalStyles.jsx @@ -0,0 +1,44 @@ +import { makeStyles } from '@mui/styles'; + +export const useStyles = makeStyles((theme) => ({ + actionButtons: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '1.5rem', + }, + media: { + objectFit: 'cover', + borderRadius: '4px', + boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', + }, + details: { + display: 'flex', + alignItems: 'center', + gap: '1.5rem', + marginBottom: '1.5rem', + }, + dialogTitle: { + fontSize: '1.5rem', + fontWeight: 600, + color: theme.palette.primary.dark, + }, + dialogContent: { + padding: '2rem', + }, + listItem: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(2), + backgroundColor: '#ffffff', + borderRadius: '8px', + marginBottom: theme.spacing(2), + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', + }, + listItemText: { + flex: 1, + textAlign: 'left', + marginLeft: theme.spacing(3), + }, +})); diff --git a/src/components/other/CalculateCollectionStatistics.jsx b/src/components/other/CalculateCollectionStatistics.jsx new file mode 100644 index 0000000..83f4eaf --- /dev/null +++ b/src/components/other/CalculateCollectionStatistics.jsx @@ -0,0 +1,57 @@ +import React, { useMemo } from 'react'; +import { MenuItem, Select, Typography } from '@mui/material'; + +// Helper function to calculate statistics +const calculateStatistics = (data, timeRange) => { + // Filter the data according to the timeRange + const filteredData = data.filter( + (item) => new Date(item.x).getTime() >= Date.now() - timeRange + ); + + const prices = filteredData.map((d) => d.y); + const high = Math.max(...prices); + const low = Math.min(...prices); + const percentChange = + ((prices[prices.length - 1] - prices[0]) / prices[0]) * 100; + const average = prices.reduce((a, b) => a + b, 0) / prices.length; + + return { high, low, percentChange, average }; +}; + +const StatisticsSelector = ({ data, timeRange }) => { + const [selectedStat, setSelectedStat] = React.useState(''); + + // Compute the statistics when the data or timeRange changes + const stats = useMemo( + () => calculateStatistics(data, timeRange), + [data, timeRange] + ); + + // Handle selection change + const handleChange = (event) => { + setSelectedStat(event.target.value); + }; + + return ( + <> + + + {selectedStat === 'high' && `High: $${stats.high}`} + {selectedStat === 'low' && `Low: $${stats.low}`} + {selectedStat === 'percentChange' && + `Change: ${stats.percentChange.toFixed(2)}%`} + {selectedStat === 'average' && `24hr Avg: $${stats.average.toFixed(2)}`} + + + ); +}; + +export default StatisticsSelector; diff --git a/src/components/other/ChartErrorBoundary.jsx b/src/components/other/ChartErrorBoundary.jsx new file mode 100644 index 0000000..7ec649a --- /dev/null +++ b/src/components/other/ChartErrorBoundary.jsx @@ -0,0 +1,30 @@ +import { Typography } from '@mui/material'; +import React from 'react'; + +class ChartErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + return { hasError: true, error }; + } + + componentDidCatch(error, errorInfo) { + // You can log the error to an error reporting service + console.error('Error in chart component:', error, errorInfo); + } + + render() { + if (this.state.hasError) { + // You can render any custom fallback UI + return Unable to display chart; + } + + return this.props.children; + } +} + +export default ChartErrorBoundary; diff --git a/src/components/other/ChartTooltip.jsx b/src/components/other/ChartTooltip.jsx new file mode 100644 index 0000000..c5dee1b --- /dev/null +++ b/src/components/other/ChartTooltip.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { makeStyles, Tooltip, Typography } from '@mui/material'; + +// Define your styles here +const useStyles = makeStyles((theme) => ({ + // ... your other styles + tooltipTarget: { + cursor: 'pointer', // Change the cursor to indicate it's hoverable + }, +})); + +const ChartTooltip = ({ point, lastData, hoveredData, latestData }) => { + const classes = useStyles(); + + if (!point) return null; + + // Formatting the date just once to be used in multiple places + const formattedTime = hoveredData + ? new Date(hoveredData.x).toLocaleString() + : new Date((latestData || lastData).x).toLocaleString(); + + const formattedValue = `$${ + hoveredData ? hoveredData.y : (latestData || lastData)?.y + }`; + + return ( + <> + + + {formattedTime} + + + + + {formattedValue} + + + + ); +}; + +export default ChartTooltip; diff --git a/src/components/other/CollectionStatisticsSelector.jsx b/src/components/other/CollectionStatisticsSelector.jsx new file mode 100644 index 0000000..ce90125 --- /dev/null +++ b/src/components/other/CollectionStatisticsSelector.jsx @@ -0,0 +1,95 @@ +import React, { useState, useMemo } from 'react'; +import { MenuItem, Select, Typography, Box } from '@mui/material'; + +function calculateStatistics(data, timeRange) { + if (!data || data.length === 0) { + return {}; // Return an empty object if data is not available + } + + const filteredData = data.filter( + (item) => new Date(item.x).getTime() >= Date.now() - timeRange + ); + + if (filteredData.length === 0) { + return {}; // Return an empty object if filtered data is not available + } + + const sortedData = [...filteredData].sort((a, b) => a.y - b.y); + const highPoint = sortedData.at(-1)?.y || 0; + const lowPoint = sortedData[0]?.y || 0; + const percentChange = + sortedData.length > 1 ? ((highPoint - lowPoint) / lowPoint) * 100 : 0; + const sum = filteredData.reduce((acc, curr) => acc + curr.y, 0); + const average = sum / filteredData.length || 0; + const volume = filteredData.length; + + const mean = sum / volume; + const squaredDiffs = filteredData.map((item) => { + const diff = item.y - mean; + return diff * diff; + }); + const volatility = Math.sqrt( + squaredDiffs.reduce((a, b) => a + b, 0) / volume + ); + + return { + highPoint: highPoint.toFixed(2), + lowPoint: lowPoint.toFixed(2), + percentChange: percentChange.toFixed(2), + average: average.toFixed(2), + volume, + volatility: volatility.toFixed(2), + }; +} + +const CollectionStatisticsSelector = ({ data, timeRange }) => { + const [selectedStat, setSelectedStat] = useState(''); + + const stats = useMemo( + () => calculateStatistics(data, timeRange), + [data, timeRange] + ); + + const handleChange = (event) => { + setSelectedStat(event.target.value); + }; + + return ( + + + + + {selectedStat && + `${selectedStat.replace(/([A-Z])/g, ' $1').trim()}: $${ + stats[selectedStat] + }`} + + + ); +}; + +export default CollectionStatisticsSelector; diff --git a/src/components/other/CollectionValueTracker.jsx b/src/components/other/CollectionValueTracker.jsx new file mode 100644 index 0000000..27e4f32 --- /dev/null +++ b/src/components/other/CollectionValueTracker.jsx @@ -0,0 +1,67 @@ +import React, { useState, useEffect } from 'react'; +import { Typography, Box, useTheme } from '@mui/material'; +import { useCombinedContext } from '../../context/CombinedProvider'; + +const CollectionValueTracker = ({ data }) => { + const theme = useTheme(); + const { allCardPrices } = useCombinedContext(); + const [totalValue, setTotalValue] = useState(0); + const [changeOverTime, setChangeOverTime] = useState(0); + console.log('allCardPrices', data.allCardPrices); + useEffect(() => { + // const allPrices = data?.cards?.map((card) => card?.price); + // console.log('[1]allPrices', allPrices); + // const newValue = data?cards?.reduce((acc, card) => acc + (card?.price, 0)); + // console.log('[2]newValue', newValue); + // const change = data?cards?.reduce( + // (acc, card) => acc + (card?.latestPrice - card?.lastSavedPrice), + // 0 + // ); + // setTotalValue(newValue); + // setChangeOverTime(change); + }, [data]); + + useEffect(() => { + // Update total value based on allCardPrices + if (Array.isArray(allCardPrices) && allCardPrices?.length > 0) { + const total = allCardPrices + .map((price) => parseFloat(price)) // Convert each price to a number + .filter((price) => !isNaN(price)) // Filter out non-numeric values + .reduce((acc, price) => acc + price, 0); // Sum up all prices + console.log('total', total); + setTotalValue(total); + } + }, [allCardPrices]); + + const trend = changeOverTime > 0 ? 'increased' : 'decreased'; + const trendColor = changeOverTime > 0 ? 'success.main' : 'error.main'; + + console.log('totalValue', totalValue); + return ( + + + Total Collection Value: ${totalValue?.toFixed(2)} + + + Value {trend} in the last 24h: ${Math.abs(changeOverTime)?.toFixed(2)} + + + ); +}; + +export default CollectionValueTracker; diff --git a/src/components/other/LinearChart.js b/src/components/other/LinearChart.js index 35cb65b..15371b0 100644 --- a/src/components/other/LinearChart.js +++ b/src/components/other/LinearChart.js @@ -4,6 +4,8 @@ import { makeStyles, useTheme } from '@mui/styles'; import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; import Tooltip from '@mui/material/Tooltip'; +import ChartErrorBoundary from './ChartErrorBoundary'; +import CustomLogger from '../../context/CutstomLogger'; const useStyles = makeStyles((theme) => ({ chartContainer: { @@ -25,6 +27,8 @@ const useStyles = makeStyles((theme) => ({ textAlign: 'center', marginTop: theme.spacing(2), fontSize: '1rem', + fontWeight: 'bold', + marginBottom: theme.spacing(4), color: theme.palette.text.primary, }, yAxisLabel: { @@ -35,6 +39,8 @@ const useStyles = makeStyles((theme) => ({ textAlign: 'center', marginTop: theme.spacing(2), fontSize: '1rem', + fontWeight: 'bold', // Make the label text bold + marginLeft: theme.spacing(4), // Increase spacing from the chart color: theme.palette.text.primary, }, customTooltip: { @@ -43,8 +49,32 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: theme.palette.background.paper, boxShadow: theme.shadows[3], }, + customGridLine: { + stroke: theme.palette.text.secondary, + strokeWidth: 2, + strokeDasharray: 'none', + }, })); +// function logReadableChartInfo( +// // chartDimensions, +// dataForChart, +// datesTimesValues, +// filteredChartData, +// latestData +// ) { +// console.log('[7][DATA FOR CHART]:', JSON.stringify(dataForChart, null, 2)); +// console.log( +// '[8][DATES TIMES VALUES]:', +// JSON.stringify(datesTimesValues, null, 2) +// ); +// console.log( +// '[4][FILTERED CHART DATA]:', +// JSON.stringify(filteredChartData, null, 2) +// ); +// console.log('[5][LATEST DATA]:', JSON.stringify(latestData, null, 2)); +// } + const CustomTooltip = ({ point }) => { const theme = useTheme(); const { serieId, data } = point; @@ -90,15 +120,54 @@ const roundToNearestTenth = (value) => { const getFilteredData = (data, timeRange) => { const cutOffTime = new Date().getTime() - timeRange; return data - .filter((d) => new Date(d.x).getTime() >= cutOffTime) + .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) })); }; +const getAveragedData = (data) => { + // Use a regular function instead of a hook + if (!Array.isArray(data)) { + return []; + } + return data.map((row, index, total) => { + const start = Math.max(0, index - 6); + const end = index; + const subset = total.slice(start, end + 1); + const sum = subset.reduce((a, b) => a + b.y, 0); + return { + x: row.x, + y: sum / subset.length || 0, + }; + }); +}; + +const getTickValues = (timeRange) => { + const mapping = { + '15m': 'every 15 minutes', + '2h': 'every 2 hours', + '1d': 'every day', + '1w': 'every week', + }; + return mapping[timeRange] || 'every week'; // Default to 'every week' if no match +}; + const useMovingAverage = (data, numberOfPricePoints) => { return useMemo(() => { if (!Array.isArray(data)) { return []; } + console.log('[1][Data]----------> [', data + ']'); + console.log( + '[2][NUMBER OF POINTS]----------> [', + numberOfPricePoints + ']' + ); return data.map((row, index, total) => { const start = Math.max(0, index - numberOfPricePoints); @@ -106,6 +175,7 @@ const useMovingAverage = (data, numberOfPricePoints) => { const subset = total.slice(start, end + 1); const sum = subset.reduce((a, b) => a + b.y, 0); return { + // x: String(row.x), x: row.x, y: sum / subset.length || 0, }; @@ -113,13 +183,6 @@ const useMovingAverage = (data, numberOfPricePoints) => { }, [data, numberOfPricePoints]); }; -const getAveragedData = (filteredData) => { - const averaged = useMovingAverage(filteredData, 6); - return useMemo(() => { - return averaged; - }, [averaged]); -}; - const useEventHandlers = () => { const [hoveredData, setHoveredData] = useState(null); @@ -139,180 +202,197 @@ const useEventHandlers = () => { return { hoveredData, handleMouseMove, handleMouseLeave }; }; -const getTickValues = (timeRange, timeRanges) => { - switch (timeRange) { - case timeRanges[0].value: - return 'every 15 minutes'; - case timeRanges[1].value: - return 'every 2 hours'; - case timeRanges[2].value: - return 'every day'; - default: - return 'every week'; +const formatDateToString = (date) => { + if (!(date instanceof Date) || isNaN(date.getTime())) { + console.error('Invalid date:', date); + return ''; } + return `${date.getFullYear()}-${(date.getMonth() + 1) + .toString() + .padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date + .getHours() + .toString() + .padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; }; const LinearChart = ({ - data, + filteredChartData, + datesTimesValues, latestData, dimensions, timeRanges, timeRange, }) => { - const classes = useStyles(); const theme = useTheme(); const [isZoomed, setIsZoomed] = useState(false); const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); - const isValidDataPoint = (d) => d && 'x' in d && 'y' in d; - if (!Array.isArray(data) || !data.every(isValidDataPoint)) { - return No valid data available; - } - const filteredData = useMemo( - () => getFilteredData(data, timeRange), - [data, timeRange] - ); - const averagedData = getAveragedData(filteredData); - // console.log('averagedData', averagedData); - const tickValues = useMemo( - () => getTickValues(timeRange, timeRanges), - [timeRange] + () => getFilteredData(filteredChartData, timeRange), + [filteredChartData, timeRange] ); - const lastData = useMemo(() => { - if (data && data.length) { - return { - x: data[data.length - 1].x, - y: data[data.length - 1].y, - }; - } - return {}; - }, [data]); + const dataForChart = useMemo(() => { + return datesTimesValues.dates.map((date, index) => ({ + x: formatDateToString( + new Date(`${date} ${datesTimesValues.times[index]}`) + ), + y: datesTimesValues.values[index], + })); + }, [datesTimesValues]); + CustomLogger('LinearChart', 'info', { + filteredChartData, + datesTimesValues, + }); + const tickValues = useMemo(() => getTickValues(timeRange), [timeRange]); - return ( -
- !d.x || !d.y) + ) { + return ( + + No valid data available + + ); + } + // const classes = useStyles(); + // const theme = useTheme(); - tickSize: 5, - tickPadding: 5, - tickRotation: 0, - legendOffset: -12, - legend: 'Time', - format: '%H:%M', - }} - axisLeft={{ - orient: 'left', - legend: 'Value ($)', - legendOffset: 12, - legendPosition: 'middle', - legendTextColor: theme.palette.text.primary, - legendTextSize: 14, - format: (value) => `$${value}`, - }} - enablePointLabel - pointLabel="y" - pointLabelYOffset={-12} - pointSize={6} - pointBorderWidth={1} - // pointBorderColor={{ from: 'color', modifiers: [['darker', 0.7]] }} - pointColor={theme.palette.primary.main} - // pointBorderColor={theme.palette.secondary.main} - colors={[theme.palette.primary.light]} - theme={{ - points: { - dot: { - ...classes.customPoint, // Added for improved style - border: '1px solid ' + theme.palette.secondary.main, - }, - tooltip: { - container: { - borderRadius: theme.shape.borderRadius, - }, - }, - }, - grid: { - line: { - stroke: theme.palette.divider, - strokeWidth: 1, - strokeDasharray: '4 4', - }, - }, - }} - lineWidth={3} - curve="monotoneX" - useMesh={true} - data={[ - { - id: 'Dataset', - data: averagedData?.map((d) => ({ - x: new Date(d.x), - y: parseFloat(d.y), - })), - }, - ]} - // data={[ - // { - // id: 'Dataset', - // data: averagedData.map((d) => ({ - // x: new Date(d.x).getTime(), - // y: parseFloat(d.y), - // })), - // }, - // ]} - onMouseMove={handleMouseMove} - onMouseLeave={handleMouseLeave} - onClick={() => setIsZoomed(!isZoomed)} - tooltip={({ point }) => } - sliceTooltip={({ slice }) => { - const point = slice.points.find( - (p) => p.id === 'Dataset' && p.data.x === lastData.x - ); - if (point) { - return ; - } - return null; - }} - /> - - - {hoveredData - ? new Date(hoveredData.x).toLocaleString() - : latestData?.x} - - - - - {`$${hoveredData ? hoveredData.y : latestData?.y}`} - - -
+ // // Hooks should be at the top level of your component + // const [isZoomed, setIsZoomed] = useState(false); + // const filteredData = useMemo( + // () => getFilteredData(filteredChartData, timeRange), + // [filteredChartData, timeRange] + // ); + // // const averagedData = useMemo( + // // () => getAveragedData(filteredData), + // // [filteredData] + // // ); + // const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); + + // if (!Array.isArray(filteredChartData)) { + // return No valid data available; + // } + + // if ( + // !datesTimesValues || + // !datesTimesValues.dates || + // !datesTimesValues.times || + // !datesTimesValues.values + // ) { + // console.error('Invalid averaged chart data:', datesTimesValues); + // return Invalid data for the chart; + // } + + // const dataForChart = useMemo(() => { + // return [ + // { + // id: 'Averaged Data', + // data: datesTimesValues.dates.map((date, index) => ({ + // x: formatDateToString( + // new Date(`${date} ${datesTimesValues.times[index]}`) + // ), + // y: datesTimesValues.values[index], + // })), + // }, + // ]; + // }, [datesTimesValues]); + + // if (dataForChart[0].data.length === 0) { + // return No valid data available; + // } + // const tickValues = useMemo(() => getTickValues(timeRange), [timeRange]); + + // if ( + // !Array.isArray(filteredChartData) || + // filteredChartData.some((d) => !d.x || !d.y) + // ) { + // return No valid data available; + // } + // // logReadableChartInfo( + // // dataForChart, + // // datesTimesValues, + // // filteredChartData, + // // latestData + // // ); + // try { + // const filteredData = useMemo( + // () => getFilteredData(filteredChartData, timeRange), + // [filteredChartData, timeRange] + // ); + // getAveragedData(filteredData); + // } catch (error) { + // console.error('Error processing data for chart:', error); + // } // console.log('averagedData', averagedData); + + // const lastData = useMemo(() => { + // if (filteredChartData && filteredChartData.length) { + // return { + // x: filteredChartData[filteredChartData.length - 1].x, + // y: filteredChartData[filteredChartData.length - 1].y, + // }; + // } + // return {}; + // }, [filteredChartData]); + + const chartProps = { + margin: { top: 50, right: 110, bottom: 50, left: 60 }, + data: [{ id: 'Data', data: dataForChart }], + animate: true, + motionStiffness: 90, + motionDamping: 15, + xScale: { + type: 'time', + format: '%Y-%m-%d %H:%M', + useUTC: false, + precision: 'minute', + }, + yScale: { type: 'linear', min: 'auto', max: 'auto' }, + axisBottom: { + tickRotation: 0, + legendOffset: -12, + legend: 'Time', + tickPadding: 10, + tickSize: 10, + format: '%b %d', + tickValues: tickValues, + }, + axisLeft: { + orient: 'left', + legend: 'Value ($)', + legendOffset: 12, + legendPosition: 'middle', + format: (value) => `$${value}`, + tickPadding: 10, + tickSize: 10, + }, + pointSize: 6, + pointBorderWidth: 1, + pointColor: theme.palette.primary.main, + colors: theme.palette.chartColors, + theme: theme.chart, + lineWidth: 3, + curve: 'monotoneX', + useMesh: true, + onMouseMove: handleMouseMove, + onMouseLeave: handleMouseLeave, + onClick: () => setIsZoomed(!isZoomed), + tooltip: CustomTooltip, + sliceTooltip: ({ slice }) => { + const point = slice.points.find( + (p) => p.id === 'Data' && p.data.x === latestData.x + ); + return point ? : null; + }, + }; + + return ( + +
+ +
+
); }; diff --git a/src/components/other/PortfolioChart.jsx b/src/components/other/PortfolioChart.jsx index a116633..72928b1 100644 --- a/src/components/other/PortfolioChart.jsx +++ b/src/components/other/PortfolioChart.jsx @@ -1,112 +1,112 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { + Box, Container, Grid, MenuItem, Paper, Select, - debounce, + styled, + useTheme, + // debounce, } from '@mui/material'; -import { styled } from '@mui/system'; import LinearChart from './LinearChart'; -import { useCollectionStore } from '../../context/hooks/collection'; -import TimeRangeSelector, { timeRanges } from './TimeRangeSelector'; +import TimeRangeSelector from './TimeRangeSelector'; import { useChartContext } from '../../context/ChartContext/ChartContext'; import ErrorBoundary from '../../context/ErrorBoundary'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import CollectionStatisticsSelector from './CollectionStatisticsSelector'; +import UpdateStatusBox from './UpdateStatusBox'; +import { useCombinedContext } from '../../context/CombinedProvider'; +import debounce from 'lodash/debounce'; +import { + convertDataForNivo, + getUniqueFilteredXYValues, + groupAndAverageData, +} from './chartUtils'; -const ChartPaper = styled('div')(({ theme }) => ({ - borderRadius: '12px', - // width: '100%', - height: '100%', - minWidth: '500px', - maxWidth: '800px', - boxShadow: '0 3px 5px 2px rgba(0, 0, 0, .3)', - backgroundColor: '#ffffff', - color: '#333', - position: 'relative', - [theme.breakpoints.down('sm')]: { - minWidth: '300px', - }, - [theme.breakpoints.up('md')]: { - minWidth: '500px', - }, - [theme.breakpoints.up('lg')]: { - minWidth: '700px', - }, +// const ChartPaper = styled(Paper)(({ theme }) => ({ +// borderRadius: theme.shape.borderRadius, +// boxShadow: theme.shadows[5], +// backgroundColor: theme.palette.background.default, +// color: theme.palette.text.secondary, +// padding: theme.spacing(3), +// [theme.breakpoints.down('sm')]: { minWidth: '300px' }, +// [theme.breakpoints.up('md')]: { minWidth: '500px' }, +// [theme.breakpoints.up('lg')]: { minWidth: '700px' }, +// })); +const ChartPaper = styled(Paper)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + padding: theme.spacing(3), + minHeight: '400px', + width: '100%', + display: 'flex', + marginLeft: 'auto', + marginRight: 'auto', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + overflow: 'hidden', })); - -const groupAndAverageData = (data, threshold = 1) => { - if (!data || data.length === 0) return []; - - // 1. Create the clusters - const clusters = []; - let currentCluster = [data[0]]; - - for (let i = 1; i < data.length; i++) { - if (data[i].x - data[i - 1].x <= threshold) { - currentCluster.push(data[i]); - } else { - clusters.push(currentCluster); - currentCluster = [data[i]]; - } - } - clusters.push(currentCluster); // Push the last cluster - - // 2. For each cluster, find the middlemost x-value and average y-values - const averagedData = clusters.map((cluster) => { - const middleIndex = Math.floor(cluster.length / 2); - const avgY = - cluster.reduce((sum, point) => sum + point.y, 0) / cluster.length; - - return { - x: cluster[middleIndex].x, - y: avgY, - }; - }); - - return averagedData; -}; - const PortfolioChart = () => { - const { latestData, setLatestData, timeRange, setTimeRange } = + const theme = useTheme(); + const { latestData, setLatestData, timeRange, timeRanges } = useChartContext(); const [lastUpdateTime, setLastUpdateTime] = useState(null); const chartContainerRef = useRef(null); + const [chartDimensions, setChartDimensions] = useState({ + width: 0, + height: 0, + }); const { selectedCollection } = useCollectionStore(); - const datasets = selectedCollection?.chartData?.allXYValues || []; - const datasets2 = useMemo(() => groupAndAverageData(datasets), [datasets]); - - // Debounced function for setting the last update time - const debouncedSetLastUpdateTime = useMemo( - () => - debounce(() => { - const currentTime = new Date().getTime(); - - if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { - setLastUpdateTime(currentTime); - - const lastDatasetIndex = datasets.length - 1; - const lastDataset = datasets[lastDatasetIndex]; - lastDataset && setLatestData(lastDataset); - } - }, 100), - [datasets, lastUpdateTime] + const { socket } = useCombinedContext(); + + const filteredChartData = useMemo(() => { + const allXYValues = selectedCollection?.chartData?.allXYValues; + return allXYValues ? getUniqueFilteredXYValues(allXYValues) : []; + }, [selectedCollection]); + + const threshold = useMemo(() => timeRange * 0.1, [timeRange]); + const rawData = useMemo( + () => groupAndAverageData(filteredChartData, threshold), + [filteredChartData, threshold] ); + const nivoReadyData = useMemo(() => convertDataForNivo(rawData), [rawData]); + + // Now use this threshold when calling your data grouping function + + // Now use this threshold when calling your data grouping function + const updateLastTime = () => { + const currentTime = new Date().getTime(); + if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { + setLastUpdateTime(currentTime); + const lastDataset = filteredChartData[filteredChartData.length - 1]; + lastDataset && setLatestData(lastDataset); + } + }; useEffect(() => { - if (!datasets) return; - debouncedSetLastUpdateTime(); - }, [datasets, debouncedSetLastUpdateTime]); - - const chartDimensions = useMemo( - () => - chartContainerRef.current?.getBoundingClientRect() || { - width: 400, - height: 600, - }, - [chartContainerRef.current] - ); + const handleResize = debounce(() => { + if (chartContainerRef.current) { + setChartDimensions({ + width: chartContainerRef.current.offsetWidth, + height: chartContainerRef.current.offsetHeight, + }); + } + }, 100); + window.addEventListener('resize', handleResize); + + handleResize(); + + return () => { + window.removeEventListener('resize', handleResize); + handleResize.cancel(); + }; + }, []); return ( { display: 'flex', flexDirection: 'column', alignItems: 'center', - // width: '100%', - // height: '100%', + padding: theme.spacing(2), + gap: theme.spacing(2), + backgroundColor: theme.palette.background.paper, // Dark background for the container + color: theme.palette.text.primary, // Text color for better contrast }} > - {/* */} - setTimeRange(e.target.value)} - /> - + {/* - - {datasets2.length > 0 ? ( - - ) : ( -
No data available
- )} -
+ + + +
*/} + + + {filteredChartData.length > 0 ? ( + + ) : ( +
No data available
+ )} +
+
- {/*
*/}
); }; export default PortfolioChart; + +// import React, { useEffect, useMemo, useRef, useState } from 'react'; +// import { +// Box, +// Container, +// Grid, +// MenuItem, +// Paper, +// Select, +// styled, +// useTheme, +// // debounce, +// } from '@mui/material'; +// import LinearChart from './LinearChart'; +// import TimeRangeSelector from './TimeRangeSelector'; +// import { useChartContext } from '../../context/ChartContext/ChartContext'; +// import ErrorBoundary from '../../context/ErrorBoundary'; +// import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +// import CollectionStatisticsSelector from './CollectionStatisticsSelector'; +// import UpdateStatusBox from './UpdateStatusBox'; +// import { useCombinedContext } from '../../context/CombinedProvider'; +// import debounce from 'lodash/debounce'; +// import { +// convertDataForNivo, +// getUniqueFilteredXYValues, +// groupAndAverageData, +// } from './chartUtils'; + +// const ChartPaper = styled(Paper)(({ theme }) => ({ +// borderRadius: theme.shape.borderRadius, +// boxShadow: theme.shadows[5], +// backgroundColor: theme.palette.background.default, +// color: theme.palette.text.secondary, +// padding: theme.spacing(3), +// [theme.breakpoints.down('sm')]: { minWidth: '300px' }, +// [theme.breakpoints.up('md')]: { minWidth: '500px' }, +// [theme.breakpoints.up('lg')]: { minWidth: '700px' }, +// })); + +// const PortfolioChart = () => { +// const theme = useTheme(); +// const { selectedCollection } = useCollectionStore(); +// const { timeRange } = useChartContext(); +// const chartContainerRef = useRef(null); +// const [chartDimensions, setChartDimensions] = useState({ +// width: 400, +// height: 400, +// }); +// // const { latestData, setLatestData, timeRange, timeRanges } = +// useChartContext(); +// const [lastUpdateTime, setLastUpdateTime] = useState(null); +// const { socket } = useCombinedContext(); + +// const filteredChartData = useMemo(() => { +// const allXYValues = selectedCollection?.chartData?.allXYValues; +// return allXYValues ? getUniqueFilteredXYValues(allXYValues) : []; +// }, [selectedCollection]); + +// // const threshold = useMemo(() => timeRange * 0.1, [timeRange]); +// // const rawData = useMemo( +// // () => groupAndAverageData(filteredChartData, threshold), +// // [filteredChartData, threshold] +// // ); +// // const nivoReadyData = useMemo(() => convertDataForNivo(rawData), [rawData]); + +// // Now use this threshold when calling your data grouping function + +// // Now use this threshold when calling your data grouping function +// const updateLastTime = () => { +// const currentTime = new Date().getTime(); +// if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { +// setLastUpdateTime(currentTime); +// const lastDataset = filteredChartData[filteredChartData.length - 1]; +// lastDataset && setLatestData(lastDataset); +// } +// }; +// const filteredChartData = useMemo(() => { +// // Process data for the chart here using selectedCollection +// return selectedCollection?.chartData?.allXYValues || []; +// }, [selectedCollection]); + +// useEffect(() => { +// const handleResize = debounce(() => { +// if (chartContainerRef.current) { +// setChartDimensions({ +// width: chartContainerRef.current.offsetWidth, +// height: chartContainerRef.current.offsetHeight, +// }); +// } +// }, 100); + +// const resizeObserver = new ResizeObserver(handleResize); +// if (chartContainerRef.current) { +// resizeObserver.observe(chartContainerRef.current); +// } + +// return () => { +// resizeObserver.disconnect(); +// handleResize.cancel(); +// }; +// }, []); +// return ( +// +// +// {/* +// +// +// +// */} +// +// +// {filteredChartData.length > 0 ? ( +// +// ) : ( +//
No data available
+// )} +//
+//
+//
+//
+// ); +// }; + +// export default PortfolioChart; diff --git a/src/components/other/TimeRangeSelector.jsx b/src/components/other/TimeRangeSelector.jsx index 330f24a..7edcaab 100644 --- a/src/components/other/TimeRangeSelector.jsx +++ b/src/components/other/TimeRangeSelector.jsx @@ -1,21 +1,27 @@ import React from 'react'; import { MenuItem, Select } from '@mui/material'; +import { useChartContext } from '../../context/ChartContext/ChartContext'; -export const timeRanges = [ - { label: '2 hours', value: 2 * 60 * 60 * 1000 }, - { label: '24 hours', value: 24 * 60 * 60 * 1000 }, - { label: '7 days', value: 7 * 24 * 60 * 60 * 1000 }, - { label: '1 month', value: 30 * 24 * 60 * 60 * 1000 }, -]; +// Remove setTimeRange(value); from here +const TimeRangeSelector = ({ onChange }) => { + const { timeRanges, timeRange, setTimeRange, handleChange, currentValue } = + useChartContext(); + const isInRange = timeRanges.some((option) => option.value === timeRange); + const safeTimeRange = isInRange ? timeRange : timeRanges[0].value; -const TimeRangeSelector = ({ value, onChange }) => ( - -); + return ( + + ); +}; export default TimeRangeSelector; diff --git a/src/components/other/UpdateStatusBox.jsx b/src/components/other/UpdateStatusBox.jsx new file mode 100644 index 0000000..7398fff --- /dev/null +++ b/src/components/other/UpdateStatusBox.jsx @@ -0,0 +1,100 @@ +import React, { useState, useEffect } from 'react'; +import { 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(['user']); + + 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: listOfSimulatedCards, + }); + } + }; + + // 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/UserStats.jsx b/src/components/other/UserStats.jsx index 466e111..65c3b8c 100644 --- a/src/components/other/UserStats.jsx +++ b/src/components/other/UserStats.jsx @@ -1,8 +1,8 @@ import React from 'react'; import { Box, Typography } from '@mui/material'; import { useDeckStore } from '../../context/DeckContext/DeckContext'; -import { useCollectionStore } from '../../context/hooks/collection'; import { useCartStore } from '../../context/CartContext/CartContext'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; const UserStats = () => { const { allDecks } = useDeckStore(); diff --git a/src/components/other/chartUtils.jsx b/src/components/other/chartUtils.jsx new file mode 100644 index 0000000..c8b489f --- /dev/null +++ b/src/components/other/chartUtils.jsx @@ -0,0 +1,104 @@ +export const getUniqueFilteredXYValues = (allXYValues) => { + if (!Array.isArray(allXYValues)) { + console.error('Invalid input: allXYValues should be an array'); + return []; + } + + const uniqueXValues = new Set(); + return allXYValues + .filter((entry) => { + return ( + entry && + typeof entry === 'object' && + typeof entry.y === 'number' && + entry.y !== 0 + ); + }) + .filter((entry) => { + 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; + }); +}; + +export const groupAndAverageData = (data, threshold = 1) => { + if (!data || data.length === 0) return { dates: [], times: [], values: [] }; + + const clusters = []; + let currentCluster = [data[0]]; + + for (let i = 1; i < data.length; i++) { + const prevTime = new Date(data[i - 1].x).getTime(); + const currentTime = new Date(data[i].x).getTime(); + if (currentTime - prevTime <= threshold) { + currentCluster.push(data[i]); + } else { + clusters.push(currentCluster); + currentCluster = [data[i]]; + } + } + clusters.push(currentCluster); // Push the last cluster + + // For each cluster, find the middlemost x-value and average y-values + let dates = []; + let times = []; + let values = []; + + clusters.forEach((cluster) => { + const middleIndex = Math.floor(cluster.length / 2); + const middleDatum = cluster[middleIndex]; + const date = new Date(middleDatum.x); + const avgY = + cluster.reduce((sum, point) => sum + point.y, 0) / cluster.length; + + const hours = date.getHours(); + const minutes = date.getMinutes(); + const AMPM = hours >= 12 ? 'PM' : 'AM'; + const adjustedHours = hours % 12 || 12; // Converts "0" to "12" + + dates.push( + `${date.getFullYear()}-${String(date.getMonth() + 1).padStart( + 2, + '0' + )}-${String(date.getDate()).padStart(2, '0')}` + ); + times.push( + `${String(adjustedHours).padStart(2, '0')}:${String(minutes).padStart( + 2, + '0' + )} ${AMPM}` + ); + values.push(avgY); + }); + + return { dates, times, values }; +}; + +export const convertDataForNivo = ({ dates, times, values }) => { + if (!dates.length || !times.length || !values.length) { + console.error('Invalid data arrays provided'); + return []; + } + + // Assuming that dates, times, and values arrays are of equal length + const nivoData = dates.map((date, index) => { + const dateTime = new Date(`${date} ${times[index]}`); + // Nivo chart requires the date to be either a Date object or an ISO date string + return { + x: dateTime.toISOString(), + y: values[index], + }; + }); + + // Wrapping the data for a single series, you can add more series similarly + return [ + { + id: 'Averaged Data', + data: nivoData, + }, + ]; +}; diff --git a/src/containers/PortfolioChartContainer.jsx b/src/containers/PortfolioChartContainer.jsx index 93332ac..25b45d3 100644 --- a/src/containers/PortfolioChartContainer.jsx +++ b/src/containers/PortfolioChartContainer.jsx @@ -1,25 +1,120 @@ import React from 'react'; -import { Grid, Paper } from '@mui/material'; -import PortfolioChart from '../components/other/PortfolioChart'; // Assuming the import path based on your previous code +import { Box, Grid, Paper } from '@mui/material'; +import PortfolioChart from '../components/other/PortfolioChart'; +import TimeRangeSelector from '../components/other/TimeRangeSelector'; +import CollectionStatisticsSelector from '../components/other/CollectionStatisticsSelector'; +import UpdateStatusBox from '../components/other/UpdateStatusBox'; +import { useTheme } from '@mui/material/styles'; +import { useSocketContext } from '../context/SocketProvider'; +import { useChartContext } from '../context/ChartContext/ChartContext'; +import { useCollectionStore } from '../context/CollectionContext/CollectionContext'; +import CollectionValueTracker from '../components/other/CollectionValueTracker'; +const paperStyle = { + elevation: 3, + borderRadius: 2, + p: 2, + height: '25vh', // Set the container height to 25vh + display: 'flex', + flexDirection: 'row', // Change to 'row' to fit selectors horizontally + justifyContent: 'space-between', // Distribute space evenly between selectors + alignItems: 'center', // Align items vertically in the center + gap: 2, // Set a gap between the selectors +}; +const paperChartStyle = { + elevation: 3, + borderRadius: 2, + p: 2, + // height: '25vh', // Set the container height to 25vh + maxWidth: '100vw', + display: 'flex', + flexDirection: 'row', // Change to 'row' to fit selectors horizontally + justifyContent: 'space-between', // Distribute space evenly between selectors + alignItems: 'center', // Align items vertically in the center + gap: 2, // Set a gap between the selectors +}; +const selectorStyle = { + flex: 1, // Allow the selector to grow + display: 'flex', + flexDirection: 'column', + height: '100%', // Set the selector height to 100% +}; const PortfolioChartContainer = ({ selectedCards, removeCard }) => { + const theme = useTheme(); + const selectorStyle = { + // width: '100%', + // maxWidth: '100vw', + // maxWidth: '250px', // Match the width of the update box if needed + mb: theme.spacing(2), + }; + + const { socket } = useSocketContext(); + const { timeRange } = useChartContext(); + const { allCollections, selectedCollection } = useCollectionStore(); + const data = allCollections.map((collection) => { + return { + name: collection.name, + data: collection.chartData.allXYValues, + }; + }); return ( - - - - - + + {/* Updaters Row */} + + + {/* */} + + + {/* */} + + + + + + + {/* Main Grid Container */} + + {/* Portfolio Chart Row */} + + + + + + + {/* Selectors Row */} + + + + + + + + + {' '} + + + + ); }; diff --git a/src/containers/PortfolioListContainer.jsx b/src/containers/PortfolioListContainer.jsx index 4865b33..5bffa50 100644 --- a/src/containers/PortfolioListContainer.jsx +++ b/src/containers/PortfolioListContainer.jsx @@ -4,7 +4,7 @@ import CardList from '../components/grids/collectionGrids/CardList'; const PortfolioListContainer = ({ selectedCards, removeCard }) => { return ( - + { + const Deck = useContext(DeckContext); + const Cart = useContext(CartContext); + const Collection = useContext(CollectionContext); + + // Combine the context values into one object + const appContextValues = { Deck, Cart, Collection }; + + return ( + + {children} + + ); +}; diff --git a/src/context/CardContext/CardStore.js b/src/context/CardContext/CardStore.js index f6566c0..404a196 100644 --- a/src/context/CardContext/CardStore.js +++ b/src/context/CardContext/CardStore.js @@ -104,6 +104,11 @@ export const CardProvider = ({ children }) => { initialStore, cookies, currentCart, + // getCardQuantity: (cardId) => { + // if + // const card = cartData?.cart?.find((c) => c?.id === cardId); + // return card?.quantity || 0; + // }, setSearchData, setDeckSearchData, setSavedDeckData, diff --git a/src/context/CartContext/CartContext.js b/src/context/CartContext/CartContext.js index a96e45e..7653cc8 100644 --- a/src/context/CartContext/CartContext.js +++ b/src/context/CartContext/CartContext.js @@ -229,6 +229,10 @@ export const CartProvider = ({ children }) => { const value = { cartData, getCardQuantity, + // getCardQuantity: (cardId) => { + // const card = cartData?.cart?.find((c) => c?.id === cardId); + // return card?.quantity || 0; + // }, cartCardQuantity: cartData.cart?.reduce( (acc, card) => acc + card.quantity, 0 diff --git a/src/context/ChartContext/ChartContext.jsx b/src/context/ChartContext/ChartContext.jsx index fb978e0..f28f87a 100644 --- a/src/context/ChartContext/ChartContext.jsx +++ b/src/context/ChartContext/ChartContext.jsx @@ -12,11 +12,33 @@ export const useChartContext = () => { export const ChartProvider = ({ children }) => { const [latestData, setLatestData] = useState(null); - const [timeRange, setTimeRange] = useState(24 * 60 * 60 * 1000); // Default to 24 hours + const [timeRange, setTimeRange] = useState(86400000 || 24 * 60 * 60 * 1000); // Default to 24 hours + + // Correctly initialize timeRanges with the useState hook + const [timeRanges] = useState([ + { label: '2 hours', value: 720000 || 2 * 60 * 60 * 1000 }, + { label: '24 hours', value: 86400000 || 24 * 60 * 60 * 1000 }, + { label: '7 days', value: 604800000 || 7 * 24 * 60 * 60 * 1000 }, + { label: '1 month', value: 2592000000 || 30 * 24 * 60 * 60 * 1000 }, + ]); + + const currentValue = timeRanges.find((option) => option.value === timeRange); + + const handleChange = (e) => { + setTimeRange(e.target.value); + }; return ( {children} diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index 5013295..3b7a2be 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -12,10 +12,6 @@ import React, { import { useCookies } from 'react-cookie'; import { filterOutDuplicateYValues, - transformChartData, - convertData, - isEmpty, - validateData, handleCardAddition, handleCardRemoval, getUniqueFilteredXYValues, @@ -29,104 +25,206 @@ import { removeDuplicateCollections, getTotalCost, initialCollectionState, - determineHttpMethod, getCardPrice, + defaultContextValue, + validateUserIdAndData, + createPayload, + logError, + determineHttpMethod, } from './collectionUtility.jsx'; import { useCombinedContext } from '../CombinedProvider.jsx'; import { useUserContext } from '../UserContext/UserContext.js'; import moment from 'moment'; -// // import { useUserContext } from '../UserContext/UserContext.js'; -// // 1. Define a default context value -const defaultContextValue = { - allCollections: [], - allCardPrices: [], - xy: [], - selectedCollection: {}, - collectionData: initialCollectionState, - totalCost: 0, - openChooseCollectionDialog: false, - updatedPricesFromCombinedContext: {}, - setUpdatedPricesFromCombinedContext: () => {}, - setOpenChooseCollectionDialog: () => {}, - calculateTotalPrice: () => {}, - getTotalCost: () => {}, - createUserCollection: () => {}, - removeCollection: () => {}, - fetchAllCollectionsForUser: () => {}, - setSelectedCollection: () => {}, - setAllCollections: () => {}, - addOneToCollection: () => {}, - removeOneFromCollection: () => {}, -}; +import CustomLogger from '../CutstomLogger.jsx'; +import { + createNewDataSet, + getCollectionId, + // updateCardInCollection, +} from './cardHelpers.jsx'; +import { set } from 'date-fns'; -// // 2. Replace null with the default value when creating the context export const CollectionContext = createContext(defaultContextValue); -const logError = (source, action, error) => { - console.error( - `[${source.toUpperCase()}] Failed to ${action}: ${error.message}` - ); +const constructPayloadWithDifferences = ( + existingData, + newData, + debug = false +) => { + const payload = {}; + let logContent = '[constructPayloadWithDifferences] Differences in data:\n'; + let typeMismatchContent = ''; // String to store type mismatch messages + let nonMatchingKeys = []; // List to store non-matching keys + + Object.keys(newData).forEach((key) => { + const isTypeDifferent = typeof newData[key] !== typeof existingData[key]; + const isValueDifferent = newData[key] !== existingData[key]; + + if (isValueDifferent || isTypeDifferent) { + payload[key] = newData[key]; + nonMatchingKeys.push(key); // Add the non-matching key to the list + + if (debug) { + if (isTypeDifferent) { + typeMismatchContent += ` - Field "${key}": Expected Type: ${typeof existingData[ + key + ]}, Received Type: ${typeof newData[key]}\n`; + } + if (isValueDifferent) { + logContent += ` - Field "${key}": Old Value: ${JSON.stringify( + existingData[key] + )}, New Value: ${JSON.stringify(newData[key])}\n`; + } + } + } + }); + + if (debug) { + console.log('1. Constructing payload with differences:', logContent); + if (typeMismatchContent) { + console.log('2. Type mismatches found:', typeMismatchContent); + } + } + return { payload, nonMatchingKeys, typeMismatchContent }; // Return both the payload, the list of non-matching keys, and type mismatch messages }; -// Reusable validation and error logging -const validateUserIdAndData = (userId, data, actionDescription) => { - if (!userId) { - logError( - 'validateUserIdAndData', - actionDescription, - new Error('User ID is undefined.') - ); - return false; +function getCurrentChartDatasets(chartData) { + if (!chartData || !chartData.datasets) { + console.error('Invalid or missing chart data'); + return []; } - if (!validateData(data, actionDescription, actionDescription)) { - logError( - 'validateUserIdAndData', - actionDescription, - new Error('Validation failed for collection data.') + + const currentChartDatasets = []; + + // Iterate over each dataset + chartData.datasets.forEach((dataset) => { + // Check if dataset has 'xys' array + if (dataset.data && dataset.data.length > 0) { + dataset.data.forEach((dataEntry) => { + if (dataEntry.xys && dataEntry.xys.length > 0) { + // Add all 'xys' entries to the currentChartDatasets array + currentChartDatasets.push(...dataEntry.xys.map((xy) => xy.data)); + } + }); + } + }); + + return currentChartDatasets; +} + +const getPriceChange = (collectionPriceHistory) => { + if ( + !Array.isArray(collectionPriceHistory) || + collectionPriceHistory.length === 0 + ) { + console.warn('Invalid or empty price history', collectionPriceHistory); + return 'n/a'; + } + + const mostRecentPrice = + collectionPriceHistory[collectionPriceHistory.length - 1]?.num; + const currentDate = new Date(); + + // Get the first price from the last 24 hours + const firstPriceFromLastDay = collectionPriceHistory + .slice() + .reverse() + .find((priceHistory) => { + const historyDate = new Date(priceHistory.timestamp); + return currentDate - historyDate <= 24 * 60 * 60 * 1000; // less than 24 hours + })?.num; + + if (mostRecentPrice && firstPriceFromLastDay) { + const priceChange = + ((mostRecentPrice - firstPriceFromLastDay) / firstPriceFromLastDay) * 100; + console.log( + `Price change over the last 24 hours is: ${priceChange.toFixed(2)}%` ); - return false; + return priceChange.toFixed(2); + } else { + console.error('Could not calculate price change due to missing data'); + return null; } - return true; }; -// Abstracted payload creation to reduce repetition -const createPayload = (info, data, defaultXyData) => ({ - userId: info.userId || data.userId, // Assuming this is an ObjectId string - name: info.name || data.name, - description: info.description || data.description, - totalCost: '', // Initialize as empty string if not provided - totalPrice: 0, // Initialize as 0 if not provided - quantity: 0, // Initialize as 0 if not provided - totalQuantity: 0, // Initialize as 0 if not provided - previousDayTotalPrice: 0, // Initialize as 0 if not provided - dailyPriceChange: 0, // Initialize as 0 if not provided - priceDifference: 0, // Initialize as 0 if not provided - priceChange: 0, // Initialize as 0 if not provided - allCardPrices: [], // Initialize as empty array if not provided - cards: [], // Initialize as empty array if not provided - currentChartDatasets: [], // Initialize as empty array if not provided - xys: defaultXyData || [], // Use defaultXyData or initialize as empty if not provided - chartData: { - name: '', // Initialize as empty string if not provided - userId: info.userId || data.userId, // Assuming this is an ObjectId string - datasets: [], // Initialize as empty array if not provided - xys: [], // Initialize as empty array if not provided - allXYValues: [], // Initialize as empty array if not provided - }, -}); +const getUpdatedChartData = (collection, newPrice) => { + const newXYValue = { + label: `Update - ${new Date().toISOString()}`, + x: new Date().toISOString(), + y: newPrice, + }; + console.log('Updating chart data with:', collection.chartData.allXYValues); + const allXYValues = collection.chartData?.allXYValues || []; + console.log('ALL XY VALUES:', allXYValues); + return { + ...collection, + chartData: { + ...collection.chartData, + allXYValues: [...(collection.chartData?.allXYValues ?? []), newXYValue], + }, + totalPrice: newPrice, + }; +}; + +const mergeCards = (existingCards, updatedCards) => { + const updatedCardMap = new Map(updatedCards.map((card) => [card.id, card])); + return existingCards.map((card) => { + if (updatedCardMap.has(card.id)) { + return { + ...card, + ...updatedCardMap.get(card.id), + price: updatedCardMap.get(card.id).price || card.price, + quantity: updatedCardMap.get(card.id).quantity || card.quantity, + }; + } + return card; + }); +}; + +const updateCardInCollection = (cards, cardToUpdate) => { + // Validate that cards is an array + if (!Array.isArray(cards)) { + throw new TypeError('The first argument must be an array of cards.'); + } + + // Validate that cardToUpdate is an object + if (typeof cardToUpdate !== 'object' || cardToUpdate === null) { + throw new TypeError('The card to update must be an object.'); + } + + // Validate that cardToUpdate has an id property + if (!('id' in cardToUpdate)) { + throw new Error('The card to update must have an "id" property.'); + } + + try { + // Attempt to update the card within the collection + const updatedCards = cards.map( + (card) => + card.id === cardToUpdate.id ? { ...card, ...cardToUpdate } : card // Update the card if the id matches + ); + console.log('3. Updated cards in collection:', updatedCards); + return updatedCards; + } catch (error) { + console.error('3. Failed to update card in collection:', error); + throw error; + } +}; export const CollectionProvider = ({ children }) => { - // const { cardPrices } = useCombinedContext(); const [cookies] = useCookies(['user']); - const { triggerCronJob } = useUserContext(); + const [selectedCollection, setSelectedCollection] = useState( + initialCollectionState + ); const [collectionData, setCollectionData] = useState(initialCollectionState); const [allCollections, setAllCollections] = useState([]); - const [xyData, setXyData] = useState([]); + const [totalPrice, setTotalPrice] = useState(0); + const [allCardPrices, setAllCardPrices] = useState([]); const [ updatedPricesFromCombinedContext, setUpdatedPricesFromCombinedContext, - ] = useState({}); - const [selectedCollection, setSelectedCollection] = useState({}); + ] = useState([]); + + const [xyData, setXyData] = useState([]); const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = useState(false); const userId = cookies.user?.id; @@ -134,98 +232,101 @@ export const CollectionProvider = ({ children }) => { () => getTotalCost(selectedCollection), [selectedCollection] ); - const lastFetchedTime = useRef(null); - - const fetchCollections = useCallback(async (userId) => { - if (!userId) { - console.warn('userId is not set, aborting fetchCollections.'); - return null; - } - - try { - const response = await fetchWrapper( - `${BASE_API_URL}/${userId}/collections`, - 'GET' - ); - const collections = response?.data?.allCollections; - console.log('Fetched collections:', collections); - return collections; - } catch (error) { - console.error('Error fetching collections:', error); - return null; - } - }, []); - const setCollections = useCallback((collections) => { - if (!collections || !Array.isArray(collections)) { - console.warn('Invalid collections array:', collections); - return; - } - - const uniqueCollections = removeDuplicateCollections(collections); - const validCollections = uniqueCollections.filter(Boolean); - - validCollections.forEach((collection) => { - collection.totalPrice = calculateTotalFromAllCardPrices( - collection.allCardPrices - ); - }); + const lastFetchedTime = useRef(null); + const calculateTotalPrice = (collection) => + collection.cards.reduce( + (total, card) => total + (card.price || 0) * (card.quantity || 0), + 0 + ); - // Update local state - setAllCollections(validCollections); + // Consolidated state setters for collection + const setCollectionState = (newState) => { + setSelectedCollection(newState); + setAllCollections((prev) => + prev.map((c) => (c._id === newState._id ? newState : c)) + ); + }; - // Optionally save to localStorage - // localStorage.setItem('allCollections', JSON.stringify(validCollections)); + const createUpdateInfo = ( + updatedCards, + updatedPrice, + userId, + selectedCollection, + collectionId + ) => { + const updateInfo = { + userId: userId, + name: selectedCollection.name, + description: selectedCollection.description, + cards: updatedCards, + totalCost: updatedPrice, + totalPrice: updatedPrice, + _id: collectionId, + allCardPrices: updatedCards.flatMap((card) => + Array(card.quantity).fill(card.card_prices?.[0]?.tcgplayer_price) + ), + quantity: updatedCards.length, + totalQuantity: updatedCards.reduce((acc, card) => acc + card.quantity, 0), + }; - setCollectionData( - validCollections.length === 0 - ? initialCollectionState - : validCollections[0] - ); - setSelectedCollection( - validCollections.length === 0 - ? initialCollectionState - : validCollections[0] - ); - }, []); + console.log('4. Created update information:', updateInfo); + return updateInfo; + }; const fetchAndSetCollections = useCallback(async () => { + // Throttle the fetch calls const currentTime = Date.now(); - const fetchDelay = 60000; // 1 minute in milliseconds - + const fetchDelay = 60000; // 1 minute if ( lastFetchedTime.current && currentTime - lastFetchedTime.current < fetchDelay - ) { - console.log( - `You must wait for ${fetchDelay / 1000} seconds before fetching again.` - ); + ) return; - } - lastFetchedTime.current = currentTime; - const collections = await fetchCollections(userId); - if (collections) setCollections(collections); - }, [userId, fetchCollections, setCollections]); - - const updateCollectionData = useCallback((newData, collectionType) => { - if (collectionType === 'allCollections') { - setAllCollections((prevCollections = []) => { - const existingIndex = findCollectionIndex( - prevCollections, - newData?._id - ); - if (existingIndex === -1) return [...prevCollections, newData]; - const updatedCollections = [...prevCollections]; - updatedCollections[existingIndex] = newData; - return updatedCollections; - }); - } else if (collectionType === 'selectedCollection') { - setSelectedCollection(newData); - } else if (collectionType === 'collectionData') { - setCollectionData(newData); + + try { + const response = await fetchWrapper( + createApiUrl(`${userId}/collections`), + 'GET' + ); + + console.log('FETCHED COLLECTIONS:', response); + console.log('5. Fetched and set collections:', response); + + setAllCollections(response.data || []); + setCollectionData(response.data?.[0] || initialCollectionState); + setSelectedCollection(response.data?.[0] || initialCollectionState); + } catch (error) { + console.error(`Failed to fetch collections: ${error}`); } - }, []); + }, [userId]); + + const updateCollectionData = useCallback( + (newData, collectionType) => { + switch (collectionType) { + case 'allCollections': + setAllCollections((prev) => + prev.findIndex((c) => c._id === newData._id) === -1 + ? [...prev, newData] + : prev.map((c) => (c._id === newData._id ? newData : c)) + ); + break; + case 'selectedCollection': + setSelectedCollection(newData); + break; + case 'collectionData': + setCollectionData(newData); + break; + default: + console.warn( + '6. Unknown collection type for update:', + collectionType + ); + } + }, + [setAllCollections, setSelectedCollection, setCollectionData] + ); const createUserCollection = async ( userId, @@ -233,34 +334,36 @@ export const CollectionProvider = ({ children }) => { name, description ) => { - if (newCollectionInfo?._id) { - console.warn( - 'Collection already has an ID, should not create a new one.' - ); - return; // early exit if _id is present - } - const actionDescription = 'create a new collection'; - if (!validateUserIdAndData(userId, newCollectionInfo, actionDescription)) + if ( + !userId || + !name || + !validateUserIdAndData( + userId, + newCollectionInfo, + 'create a new collection' + ) + ) { + console.warn('Invalid inputs for creating user collection.'); return; + } const payload = createPayload( { name, description, userId }, newCollectionInfo ); + const url = createApiUrl(`${userId}/collections`); + console.log('Creating user collection with data:', { + userId, + newCollectionInfo, + name, + description, + }); + console.log('Payload for user collection:', payload); - try { - const url = createApiUrl(`${userId}/collections`); - const response = await fetchWrapper(url, 'POST', payload); - if (!response) throw new Error('Failed to connect to the server.'); - - if (response.error) throw new Error(response.error.message); - - const savedCollection = await handleApiResponse(response, 'POST'); - updateCollectionData(savedCollection, 'allCollections'); - updateCollectionData(savedCollection, 'collectionData'); - } catch (error) { - logError('createUserCollection', actionDescription, error); - } + const savedCollection = await fetchWrapper(url, 'POST', payload); + console.log('6. Saved collection:', savedCollection); + updateCollectionData(savedCollection, 'allCollections'); + updateCollectionData(savedCollection, 'collectionData'); }; const removeCollection = async (collection) => { @@ -269,471 +372,304 @@ export const CollectionProvider = ({ children }) => { return; } - try { - const url = createApiUrl(`${userId}/collections/${collection._id}`); - const response = await fetchWrapper(url, 'DELETE'); - - if (response.error) { - console.error('Failed to delete the collection:', response.error); - return; - } - - setAllCollections((prev) => - prev.filter((item) => item._id !== collection._id) - ); + const url = createApiUrl(`${userId}/collections/${collection._id}`); + await fetchWrapper(url, 'DELETE'); + setAllCollections((prev) => + prev.filter((item) => item._id !== collection._id) + ); - if (selectedCollection._id === collection._id) { - setSelectedCollection(initialCollectionState); - setCollectionData(initialCollectionState); - } - } catch (error) { - console.error(`Failed to delete the collection: ${error.message}`); + if (selectedCollection._id === collection._id) { + setSelectedCollection(initialCollectionState); + setCollectionData(initialCollectionState); } }; - const getUpdatedCards = (activeCollection, card, operation) => { - const cardsToUpdate = - operation === 'add' - ? handleCardAddition(activeCollection?.cards, card) - : handleCardRemoval(activeCollection?.cards, card); - - return cardsToUpdate.map((card) => { - const cardPrice = card.card_prices?.[0]?.tcgplayer_price; - const computedPrice = cardPrice * card.quantity; - console.log('COMPUTED PRICE:', computedPrice); - const allDatasets = [ - ...(card?.chart_datasets || []), - { x: moment().format('YYYY-MM-DD HH:mm'), y: computedPrice }, - ]; - card.chart_datasets = filterOutDuplicateYValues(allDatasets); - card.price = cardPrice; - card.totalPrice = computedPrice; - return card; - }); - }; - - const getNewChartData = (activeCollection, updatedPrice, newDataSet) => { - const combinedXYValues = [ - ...(selectedCollection?.chartData?.datasets?.flatMap( - (dataset) => dataset.data - ) || []), - newDataSet.data[0].xy, - ]; - - const filteredXYValues = getUniqueFilteredXYValues(combinedXYValues); + const getUpdatedCards = (activeCollection, cardUpdate, operation) => { + console.log('CARD UPDATE:', cardUpdate); + console.log('OPERATION', operation); + + let cardsToUpdate; + + switch (operation) { + case 'add': + cardsToUpdate = handleCardAddition(activeCollection?.cards, cardUpdate); + break; + case 'remove': + cardsToUpdate = handleCardRemoval(activeCollection?.cards, cardUpdate); + break; + case 'update': + // Find the card by some unique identifier, e.g., id + // eslint-disable-next-line no-case-declarations + const cardIndex = activeCollection.cards.findIndex( + (c) => c.id === cardUpdate.id + ); + if (cardIndex === -1) { + console.error('Card not found in the collection.'); + return activeCollection.cards; // Return the unchanged cards array + } - return { - name: `Chart for Collection: ${activeCollection?.name}`, - userId: userId, - updatedPrice: updatedPrice, - xys: xyData || [], - datasets: [ - ...(selectedCollection?.chartData?.datasets || []), - newDataSet, - ], - allXYValues: filteredXYValues, - }; - }; + // eslint-disable-next-line no-case-declarations + const existingCard = activeCollection.cards[cardIndex]; + + // eslint-disable-next-line no-case-declarations + const updatedCard = { + ...existingCard, + latestPrice: cardUpdate.latestPrice, // assuming latestPrice is an object { num, timestamp, _id } + lastSavedPrice: cardUpdate.lastSavedPrice, // assuming lastSavedPrice is an object { num, timestamp, _id } + name: cardUpdate.name, + quantity: cardUpdate.quantity, + tag: cardUpdate.tag, + // Update priceHistory, ensure it's an array and append the new price + priceHistory: Array.isArray(existingCard.priceHistory) + ? [...existingCard.priceHistory, cardUpdate.priceHistory[0]] + : [cardUpdate.priceHistory[0]], + }; + console.log('UPDATED CARD:', updatedCard); + + // Replace the old card with the updated card + cardsToUpdate = [ + ...activeCollection.cards.slice(0, cardIndex), + updatedCard, + ...activeCollection.cards.slice(cardIndex + 1), + ]; + console.log('UPDATED CARD:', updatedCard); + return cardsToUpdate; // Directly return the updated array of cards + + default: + console.error('Unsupported operation:', operation); + cardsToUpdate = activeCollection.cards; // Return the unchanged cards array + } - const addOrRemoveCard = useCallback( - async (card, cardInfo, operation) => { - const collectionId = selectedCollection?._id || allCollections[0]?._id; - if (!collectionId) { - console.error('No valid collection selected.'); - setOpenChooseCollectionDialog(true); - return; - } + console.log('CARDS TO UPDATE:', cardsToUpdate); + return cardsToUpdate.map((existingCard) => { + console.log('EXISTING CARD BEFORE UPDATE:', existingCard); - // // const updatedCards = getUpdatedCards(selectedCollection, card, operation); - let updatedCards; - if (operation === 'update') { - updatedCards = [...selectedCollection.cards]; - const cardIndex = updatedCards?.findIndex((c) => c.id === card.id); - if (cardIndex !== -1) { - updatedCards[cardIndex] = { - ...updatedCards[cardIndex], - ...card, // update card's details from the given card. - quantity: updatedCards[cardIndex].quantity, // ensure quantity doesn't change. - }; - } + // Calculate new card values + let cardPrice = null; + if (existingCard.price) { + cardPrice = existingCard.price; } else { - updatedCards = getUpdatedCards(selectedCollection, card, operation); + cardPrice = existingCard.card_prices[0].tcgplayer_price; } - const allCardPrices = updatedCards.flatMap((card) => - Array(card.quantity).fill(card.card_prices?.[0]?.tcgplayer_price) - ); - const initialPrice = selectedCollection?.totalPrice; - const updatedPrice = calculateTotalFromAllCardPrices(allCardPrices); - const priceDifference = - updatedPrice - (selectedCollection.chartData?.updatedPrice || 0); - const newDataSet = { - data: [ - { - xys: [ - { - label: `Update Number ${ - selectedCollection?.chartData?.datasets?.length + 1 || 1 - }`, - x: moment().format('YYYY-MM-DD HH:mm'), - y: updatedPrice, - }, - ], - additionalPriceData: { - priceChanged: priceDifference !== 0, - initialPrice: initialPrice, - updatedPrice: updatedPrice, - priceDifference: priceDifference, - priceChange: - Math.round((priceDifference / (initialPrice || 1)) * 100) / 100, - }, - }, - ], - }; - console.log('XYDATASET-----1:', xyData); - const updateInfo = { - ...cardInfo, - name: selectedCollection?.name, - description: selectedCollection?.description, - cards: updatedCards, - userId: userId, - totalCost: updatedPrice, - totalPrice: updatedPrice, - xys: xyData, - quantity: updatedCards.length, - totalQuantity: updatedCards.reduce( - (acc, card) => acc + card.quantity, - 0 - ), - chartData: getNewChartData( - selectedCollection, - updatedPrice, - newDataSet - ), - allCardPrices: allCardPrices, - _id: collectionId, - }; - const updatedCollection = { ...selectedCollection, ...updateInfo }; - console.log( - 'UPDATED COLLECTION DATA PRIOR TO SERVER UPDATE:', - updatedCollection - ); - await updateActiveCollection(updatedCollection); - updateCollectionData(updatedCollection, 'selectedCollection'); - updateCollectionData(updatedCollection, 'allCollections'); - }, - [ - selectedCollection, - allCollections, - userId, - openChooseCollectionDialog, - handleCardAddition, - handleCardRemoval, - updateCollectionData, - setOpenChooseCollectionDialog, - ] - ); + console.log('EXISTING CARD PRICE:', cardPrice); + const computedPrice = + cardPrice * (cardUpdate.quantity || existingCard.quantity); + console.log('EXISTING CARD TOTALPRICE:', computedPrice); - // const updateActiveCollection = useCallback( - // async (collectionData, existingChartData = {}) => { - // const isCreatingNew = !collectionData?._id; - // const isNewCollectionEndpoint = - // collectionData?.endpoint === 'newCollection'; - - // const endpoint = createApiUrl( - // `${userId}/collections${isCreatingNew ? '' : `/${collectionData._id}`}` - // ); - - // let method = isCreatingNew ? 'POST' : 'PUT'; - // console.log(`Debug: Fetching ${method} ${endpoint}`); - - // if (!isCreatingNew && isNewCollectionEndpoint) { - // method = 'POST'; - // } - - // try { - // const response = await fetchWrapper(endpoint, method, collectionData); - // const updatedCollection = handleApiResponse(response, method); - - // if (!isCreatingNew && !updatedCollection) { - // throw new Error('Failed to update the existing collection'); - // } - - // const updateNumber = - // updatedCollection?.chartData?.datasets?.length + 1 || 1; - // const timestamp = moment().format('YYYY-MM-DD HH:mm'); - // const price = updatedCollection.totalPrice; - - // const newDataEntry = { - // x: timestamp, - // y: price, - // }; - - // const newLabelData = { - // label: `Update Number ${updateNumber}`, - // data: newDataEntry, - // }; - - // const newAdditionalPriceData = { - // priceChanged: false, // Set this according to your logic - // initialPrice: updatedCollection.chartData?.updatedPrice || 0, - // updatedPrice: price, - // priceDifference: 0, // Set this according to your logic - // priceChange: 0, // Set this according to your logic - // }; - - // const newDataset = { - // name: `Dataset ${updateNumber}`, - // data: [ - // { - // xys: [newLabelData], - // additionalPriceData: [newAdditionalPriceData], - // }, - // ], - // }; - - // const newChartData = { - // ...updatedCollection.chartData, - // name: updatedCollection.name, // You may need to set this according to your logic - // userId: updatedCollection.userId, // You may need to set this according to your logic - // xys: [...(updatedCollection.chartData?.xys || []), newLabelData], - // datasets: [ - // ...(updatedCollection.chartData?.datasets || []), - // newDataset, - // ], - // }; - - // updatedCollection.chartData = newChartData; - // const convertedData = convertData(newChartData); - // updatedCollection.xys = convertedData; - - // xyData.push(...convertedData.finalDataForChart); - // updateCollectionData(updatedCollection, 'selectedCollection'); - // updateCollectionData(updatedCollection, 'allCollections'); - // } catch (error) { - // console.error(`Failed to update the collection: ${error.message}`); - // } - // }, - // [userId, updateCollectionData] - // ); - const updateActiveCollection = useCallback( - async (collectionData, existingChartData = {}) => { - if (!collectionData) throw new Error('No collection data provided.'); - - const isCreatingNew = !collectionData?._id; - const actionDescription = isCreatingNew - ? 'create a new collection' - : 'update the existing collection'; - const method = determineHttpMethod( - isCreatingNew, - collectionData?.endpoint - ); - - try { - if (isCreatingNew) { - // Skip the fetch call for new collection creation and just update the local state if needed - console.log( - `Skipping fetch call to ${method} since it's a new collection` - ); - - // You could still call 'updateChartDataForCollection' or any other local state updates here if needed - // updateChartDataForCollection(collectionData, existingChartData); - } else { - const endpoint = createApiUrl( - `${userId}/collections/${collectionData._id}` - ); - - // Include the 'existingChartData' in the request if necessary - const payload = { ...collectionData, existingChartData }; - console.log(`Debug: ${method} ${endpoint}`); + // Generate chart data and price history + const newChartDataEntry = { + x: moment().format('YYYY-MM-DD HH:mm'), + y: computedPrice, + }; + const newPriceHistoryEntry = createPriceHistoryObject(computedPrice); + const valueOfMostRecentEntry = computedPrice; + // existingCard.chart_datasets?.[existingCard.chart_datasets?.length - 1] + // ?.data?.y - + // existingCard.chart_datasets?.[existingCard.chart_datasets?.length - 2] + // ?.data?.y; + console.log('VALUE OF MOST RECENT ENTRY:', computedPrice); + + const updatedCard = { + ...existingCard, + price: cardPrice, + totalPrice: computedPrice, + tag: 'monitored', + chart_datasets: [ + ...(existingCard.chart_datasets || []), + newChartDataEntry, + ], + priceHistory: + existingCard.priceHistory && + existingCard.priceHistory[existingCard.priceHistory.length - 1] !== + valueOfMostRecentEntry // Check if the new price history entry is different from the last one + ? [...existingCard.priceHistory, newPriceHistoryEntry] + : [newPriceHistoryEntry], + }; - const response = await fetchWrapper(endpoint, method, payload); - const updatedCollection = await handleApiResponse(response, method); + console.log('UPDATED CARD:', updatedCard); + return updatedCard; + }); + }; - if (!updatedCollection) - throw new Error('No collection returned from server.'); + const createPriceHistoryObject = (price) => ({ + timestamp: new Date().toISOString(), + num: price, + }); - // Function call to handle the chart data update logic - updateChartDataForCollection(updatedCollection, existingChartData); + const getUpdatedCollection = async ( + collectionWithCards, + cardUpdate, + operation + ) => { + console.log('CARD UPDATE:', cardUpdate); + console.log('OPERATION', operation); - updateCollectionData(updatedCollection, 'selectedCollection'); - updateCollectionData(updatedCollection, 'allCollections'); - } - } catch (error) { - logError('updateActiveCollection', actionDescription, error); - console.error(`Failed to ${actionDescription}: ${error.message}`); - } - }, - [userId, updateCollectionData] - ); + console.log('COLLECTION WITH CARDS:', collectionWithCards); - function determineHttpMethod(isCreatingNew, isNewCollectionEndpoint) { - if (isCreatingNew) return 'POST'; - return isNewCollectionEndpoint ? 'POST' : 'PUT'; - } - function updateChartDataForCollection(collection) { - // Check if there's existing chart data to update; if not, create a new structure - const chartData = collection.chartData || { datasets: [] }; - - // Determine the update number and the current timestamp - const updateNumber = chartData.datasets.length + 1; - const timestamp = new Date().toISOString(); - - // Calculate the new price and possibly other metrics for the chart data - const newPrice = collection.totalPrice; - const previousPrice = chartData.datasets.slice(-1)[0]?.y || 0; - const priceDifference = newPrice - previousPrice; - - // Create a new data entry for the update - const newDataEntry = { - x: timestamp, // x-axis typically holds the timestamp - y: newPrice, // y-axis typically holds the value such as price - updateNumber: updateNumber, // Additional data can be included as needed - }; + const updatedCards = collectionWithCards.cards; + console.log('UPDATED CARDS:', updatedCards); - // Update the datasets with the new entry - chartData.datasets.push(newDataEntry); - - // Additional logic to handle price changes and other metrics can be added here - // For instance, if there's a significant change, we might want to highlight it - const priceChange = previousPrice - ? (priceDifference / previousPrice) * 100 - : 0; - - // Add any additional data you wish to store with the chart data - const additionalPriceData = { - initialPrice: previousPrice, - updatedPrice: newPrice, - priceDifference: priceDifference, - priceChangePercentage: priceChange.toFixed(2), // toFixed(2) for percentage with 2 decimal places + // Calculate new total price for the collection + const updatedTotalPrice = updatedCards.reduce( + (total, card) => total + card.totalPrice, + 0 + ); + console.log('UPDATED TOTAL PRICE:', updatedTotalPrice); + + // Construct a new collection price history object + const newCollectionPriceHistoryObject = + createPriceHistoryObject(updatedTotalPrice); + + // Update collection chart data + const updatedChartData = { + ...selectedCollection.chartData, + allXYValues: [ + ...(selectedCollection.chartData?.allXYValues || []), + { + label: `Update - ${new Date().toISOString()}`, + x: new Date().toISOString(), + y: updatedTotalPrice, + }, + ], + datasets: [ + ...(selectedCollection.chartData?.datasets || []), + createNewDataSet(updatedTotalPrice, selectedCollection), + ], }; - // Attach the new additional data to the last dataset entry - chartData.datasets[chartData.datasets.length - 1].additionalPriceData = - additionalPriceData; - - // Assign the updated chart data back to the collection object - collection.chartData = chartData; - - // Return the updated collection object - return collection; - } + const testData = getUpdatedChartData(selectedCollection, updatedTotalPrice); + console.log('TEST DATA:', testData); + // Ensure _id is included if updating an existing collection + const collectionId = selectedCollection._id || null; + if (collectionId) { + console.log('COLLECTION ID:', collectionId); + } - // console.log( - // '<----------$$$$$$$$$CONVERTED DATA FOR CHART$$$$$$$$$---------->', - // xyData - // ); - // useEffect(() => { - // // Check if the prices are updated or new cards are added - // const updatedPricesArray = - // updatedPricesFromCombinedContext?.updatedPrices || []; + // Construct the updated collection object + const updatedCollection = { + ...selectedCollection, + allCardPrices: updatedCards.flatMap((card) => + Array(card.quantity).fill(card.price) + ), + description: selectedCollection.description, + name: selectedCollection.name, + userId: userId, + totalPrice: updatedTotalPrice, + totalCost: updatedTotalPrice.toString(), + totalQuantity: updatedCards.reduce((acc, card) => acc + card.quantity, 0), + quantity: updatedCards.length, + _id: collectionId, + cards: updatedCards, + chartData: updatedChartData, + dailyPriceChange: getPriceChange( + selectedCollection.collectionPriceHistory + ), + collectionPriceHistory: [ + ...selectedCollection.collectionPriceHistory, + newCollectionPriceHistoryObject, + ], + }; + console.log('UPDATED COLLECTION:', updatedCollection); - // if (!Array.isArray(updatedPricesArray)) { - // return; // Exit the useEffect early if not an array - // } + // Check if creating a new collection or updating an existing one + const isCreating = !collectionId; + const endpoint = createApiUrl( + `${userId}/collections/${collectionId || ''}` + ); + const method = isCreating ? 'POST' : 'PUT'; - useEffect(() => { - const updatedPricesArray = Object.keys( - updatedPricesFromCombinedContext || {} - ).map((cardId) => updatedPricesFromCombinedContext[cardId]); + // Here we only log what would be sent to the API, without making an actual call + const { nonMatchingKeys, payload } = constructPayloadWithDifferences( + selectedCollection, + updatedCollection, + true + ); // Assume constructPayload does the necessary processing console.log( - '[1][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][UPDATED PRICES]==========>', - updatedPricesArray + `Sending ${method} request to ${endpoint} with payload:`, + payload ); + console.log('PAYLOAD:', payload); + console.log('NON-MATCHING KEYS:', nonMatchingKeys); + const response = await fetchWrapper(endpoint, method, payload); + console.log('RESPONSE', response); + const updatedCollectionPostServer = response.data; + console.log('UPDATED COLLECTION POST SERVER:', updatedCollectionPostServer); + + updateCollectionData(updatedCollectionPostServer, 'selectedCollection'); + updateCollectionData(updatedCollectionPostServer, 'collectionData'); + updateCollectionData(updatedCollectionPostServer, 'allCollections'); + + return updatedCollection; + }; - const updatedCardPrices = []; - - updatedPricesArray.forEach((card) => { - const currentCardPrice = selectedCollection?.cards[card?.id]?.price; - - // // Check if this is the special tagged card - if (card._tag === 'updated') { - console.log('Found the special card:', card); - } + const handleCardOperation = async (card, operation) => { + if (!card) { + console.error('Card is undefined.', card); + return; + } - if (card?.updatedPrice !== currentCardPrice) { - updatedCardPrices.push(card); - console.log( - '[2][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][CARD]==========>', - card - ); - console.log( - 'CARD FROM SELECTED COLLECTIONS:', - selectedCollection.cards[card.id] - ); - } else { - console.log('Price has not been updated for card with ID:', card.id); - } - }); + const updatedCards = getUpdatedCards(selectedCollection, card, operation); + console.log('UPDATED CARDS:', updatedCards); + const collectionWithCards = { ...selectedCollection, cards: updatedCards }; + console.log('COLLECTION WITH CARDS:', collectionWithCards); + const updatedCollection = await getUpdatedCollection( + collectionWithCards, + card, + operation + ); + console.log('UPDATED COLLECTION POST OP HANDLING:', updatedCollection); - if (updatedCardPrices.length > 0) { - updatedCardPrices.forEach((card) => { - addOrRemoveCard(card, { updated: true }, 'update'); - }); - } - }, [updatedPricesFromCombinedContext]); + return updatedCollection; + }; const contextValue = useMemo( () => ({ - // // DATA allCollections, selectedCollection, collectionData, totalCost, - + totalPrice, allCardPrices: selectedCollection?.allCardPrices || [], xys: xyData || [], openChooseCollectionDialog, - updatedPricesFromCombinedContext, - setUpdatedPricesFromCombinedContext: (updatedPrices) => { - // This is the function that will be passed to the combined context to update the prices - setUpdatedPricesFromCombinedContext(updatedPrices); - }, + setAllCardPrices, + setXyData, + setUpdatedPricesFromCombinedContext, setOpenChooseCollectionDialog, - // FUNCTIONS calculateTotalPrice: () => getCardPrice(selectedCollection), getTotalCost: () => getTotalCost(selectedCollection), - // FUNCTIONS - createUserCollection: (userId, newCollectionInfo) => - createUserCollection( - userId, - newCollectionInfo, - newCollectionInfo.name, - newCollectionInfo.description - ), - removeCollection: (collection) => removeCollection(collection), + getCardQuantity: (cardId) => + selectedCollection?.cards?.find((c) => c?.id === cardId)?.quantity || 0, + createUserCollection, + removeCollection, + addOneToCollection: (card) => handleCardOperation(card, 'add'), + removeOneFromCollection: (card) => handleCardOperation(card, 'remove'), + updateOneFromCollection: (card) => handleCardOperation(card, 'update'), fetchAllCollectionsForUser: fetchAndSetCollections, - setSelectedCollection: updateActiveCollection, - setAllCollections: (collections) => setAllCollections(collections), - addOneToCollection: (card, cardInfo) => - addOrRemoveCard(card, cardInfo, 'add'), - removeOneFromCollection: (card) => addOrRemoveCard(card, null, 'remove'), + fetchAllCollections: fetchAndSetCollections, + setSelectedCollection, + setAllCollections, }), - [allCollections, selectedCollection, totalCost] + [allCollections, selectedCollection, totalCost, totalPrice, xyData] ); useEffect(() => { - // Save to local storage whenever it changes - localStorage.setItem('allCollections', JSON.stringify(allCollections)); - }, [allCollections]); - - useEffect(() => { - console.log('COLLECTION CONTEXT: ', { - contextValue, + console.log('CONTEXT STATE:', { + totalCost, + totalPrice, + selectedCollection, + allCollections, + collectionData, + updatedPricesFromCombinedContext, + xyData, }); - }, [contextValue, updatedPricesFromCombinedContext]); + }, [allCollections, selectedCollection, totalCost, totalPrice, xyData]); - useEffect(() => { - if (selectedCollection && totalCost) { - // Trigger the cron job whenever the selectedCollection changes - triggerCronJob(); - } - }, [selectedCollection, triggerCronJob, totalCost]); - - useEffect(() => { - console.log('Total Cost has been updated:', totalCost); - }, [totalCost]); + // useEffect(() => { + // console.log('UPDATED COLLECTION DATA POST SERVER:', collectionData); + // }, [collectionData]); useEffect(() => { if (userId) fetchAndSetCollections(); @@ -746,10 +682,6 @@ export const CollectionProvider = ({ children }) => { ); }; -// // useCollectionStore.js -// // import { useContext } from 'react'; -// // import { CollectionContext } from '../CollectionContext/CollectionContext'; - export const useCollectionStore = () => { const context = useContext(CollectionContext); if (!context) { @@ -759,3 +691,851 @@ export const useCollectionStore = () => { } return context; }; +// const addOrRemoveCard = useCallback( +// async (card, cardInfo, operation) => { +// const collectionId = getCollectionId(selectedCollection, allCollections); +// if (!collectionId) { +// console.error('No valid collection selected.'); +// setOpenChooseCollectionDialog(true); +// return; +// } + +// let updatedCards = +// operation === 'update' +// ? updateCardInCollection(selectedCollection.cards, card) +// : getUpdatedCards(selectedCollection, card, operation); + +// const updatedPrice = calculateTotalFromAllCardPrices(updatedCards); +// const accumulatedTotal = selectedCollection.totalPrice + updatedPrice; + +// const newXYValue = { +// label: `Update - ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: accumulatedTotal, +// }; + +// // Update the chart data with the new accumulated total +// const updatedChartData = { +// ...selectedCollection.chartData, +// allXYValues: [...selectedCollection.chartData.allXYValues, newXYValue], +// }; + +// const updateInfo = createUpdateInfo( +// updatedCards, +// accumulatedTotal, +// cardInfo, +// userId, +// selectedCollection, +// collectionId, +// updatedChartData, +// xyData +// ); +// console.log('[UPDATE INFO]:', updateInfo); +// console.log('[ACCUMULATED]:', accumulatedTotal); +// const updatedCollection = { +// ...selectedCollection, +// ...updateInfo, +// chartData: updatedChartData, +// // totalPrice: accumulatedTotal, +// collectionPriceHistory: [ +// ...(selectedCollection.collectionPriceHistory || []), +// createPriceHistoryObject(accumulatedTotal), +// ], +// }; + +// console.log('[COLLECTION DATA][B4UPDATE]:', updatedCollection); +// await updateActiveCollection(updatedCollection, updateInfo.chartData); +// updateCollectionData(updatedCollection, 'selectedCollection'); +// updateCollectionData(updatedCollection, 'collectionData'); +// }, +// [ +// selectedCollection, +// allCollections, +// userId, +// openChooseCollectionDialog, +// handleCardAddition, +// handleCardRemoval, +// updateCollectionData, +// setOpenChooseCollectionDialog, +// ] +// ); + +// Main functions +// const addOrUpdateCardInCollection = (collection, card, newPrice) => { +// // Find the card in the collection or create a new one +// const existingCardIndex = collection.cards.findIndex( +// (c) => c.id === card.id +// ); +// const updatedCard = updateCardPriceDetails(card, newPrice); + +// let updatedCards = [...collection.cards]; +// if (existingCardIndex >= 0) { +// updatedCards[existingCardIndex] = updatedCard; // Update existing card +// } else { +// updatedCards = [...updatedCards, updatedCard]; // Add new card +// } + +// const updatedTotalPrice = updatedCards.reduce( +// (acc, card) => acc + card.totalPrice, +// 0 +// ); +// const updatedAllCardPrices = updatedCards.map((card) => card.price); + +// return { +// ...collection, +// cards: updatedCards, +// totalPrice: updatedTotalPrice, +// allCardPrices: updatedAllCardPrices, +// }; +// }; + +// Function to save the collection to the backend +// const saveCollectionToApi = async (collection, method) => { +// try { +// const url = `/api/users/collections/${ +// method === 'POST' ? '' : collection._id +// }`; +// const response = await fetchWrapper(url, method, collection); +// console.log( +// `Collection ${method === 'POST' ? 'created' : 'updated'}:`, +// response.data +// ); +// } catch (error) { +// console.error( +// `Failed to ${method === 'POST' ? 'create' : 'update'} collection:`, +// error +// ); +// } +// }; + +// const updateActiveCollection = useCallback( +// async (collectionData) => { +// if (!collectionData) throw new Error('No collection data provided.'); + +// CustomLogger({ 'COLLECTION DATA': collectionData }); +// const isCreatingNew = !collectionData?._id; +// const actionDescription = isCreatingNew +// ? 'create a new collection' +// : 'update the existing collection'; +// const method = determineHttpMethod( +// isCreatingNew, +// collectionData?.endpoint +// ); +// CustomLogger({ 'METHOD:': method }); +// try { +// if (isCreatingNew) { +// console.log( +// `Skipping fetch call to ${method} since it's a new collection` +// ); +// } else { +// const endpoint = createApiUrl( +// `${userId}/collections/${collectionData?._id}` +// ); +// console.log('ENDPOINT:', endpoint); + +// const payload = constructPayloadWithDifferences( +// initialCollectionState, +// collectionData +// ); +// console.log('PAYLOAD:', collectionData); +// console.log(`Debug: ${method} ${endpoint}`); +// const response = await fetchWrapper(endpoint, method, payload); +// console.log('[UPDATE RESPONSE][RAW]:', response); +// const updatedCollection = response?.data; +// console.log('[UPDATE RESPONSE][DES]:', updatedCollection); + +// if (!updatedCollection) +// throw new Error('No collection returned from server.'); +// updateCollectionChartData(updatedCollection, existingChartData); +// // updateChartDataForCollection(updatedCollection, existingChartData); +// updateCollectionData(updatedCollection, 'selectedCollection'); +// updateCollectionData(updatedCollection, 'collectionData'); +// updateCollectionData(updatedCollection, 'allCollections'); +// } +// } catch (error) { +// console.error( +// `Failed to update collection for user ${userId} with collectionId ${collectionData._id}:`, +// error +// ); +// logError('updateActiveCollection', actionDescription, error); +// console.error(`Failed to ${actionDescription}: ${error.message}`); +// } +// }, +// [userId, updateCollectionData] +// ); +// Adds or updates a card in the collection + +// const handleUpdatePrices = useCallback( +// (priceUpdates) => { +// const timestamp = new Date().toISOString(); + +// setSelectedCollection((prevCollection) => { +// const updatedCards = prevCollection.cards.map((card) => { +// const update = priceUpdates.find((u) => u.id === card.id); +// if (update) { +// return { +// ...card, +// price: update.newPrice, +// latestPrice: { num: update.newPrice, timestamp }, +// lastSavedPrice: card.latestPrice, +// priceHistory: +// card.latestPrice.num !== update.newPrice +// ? [...card.priceHistory, { num: update.newPrice, timestamp }] +// : card.priceHistory, +// chart_datasets: updateChartDatasets( +// card, +// update.newPrice * card.quantity, +// timestamp +// ), +// }; +// } +// return card; +// }); + +// const newTotalPrice = calculateTotalFromCardPrices(updatedCards); + +// // Save the updated collection to the backend +// updateCardPrices({ +// ...prevCollection, +// cards: updatedCards, +// totalPrice: newTotalPrice, +// }); + +// return { +// ...prevCollection, +// cards: updatedCards, +// totalPrice: newTotalPrice, +// }; +// }); +// }, +// [updateCardPrices] +// ); + +// Transform the card data to fit the collection item structure +// const transformCardToCollectionItem = (card) => { +// return { +// id: card.id, +// name: card.name, +// price: card.price, +// quantity: 1, // Default to 1 for a new card, adjust as needed +// ... any other card properties needed +// }; +// }; +// const updateCardPriceHistory = (card, newPrice) => { +// if (card.priceHistory.slice(-1)[0]?.num !== newPrice) { +// return [...card.priceHistory, createPriceHistoryEntry(newPrice)]; +// } +// return card.priceHistory; +// }; +// // Refactored getUpdatedCards function +// const getUpdatedCards = (activeCollection, card, operation) => { +// const handleOperation = +// operation === 'add' ? handleCardAddition : handleCardRemoval; +// const cardsToUpdate = handleOperation(activeCollection?.cards, card); + +// return cardsToUpdate.map(updateCardDetails); +// }; + +// // // Function to handle the update of card price and price history +// // const updateCardDetails = (card) => { +// // const cardPrice = card.card_prices?.[0]?.tcgplayer_price; +// // const computedPrice = cardPrice * card.quantity; + +// // card.chart_datasets = updateChartDatasets(card, computedPrice); +// // card.price = cardPrice; +// // card.totalPrice = computedPrice; + +// // const lastEntry = card.priceHistory?.slice(-1)?.[0]; +// // if (!lastEntry || lastEntry.num !== cardPrice) { +// // card.priceHistory = [ +// // ...(card.priceHistory || []), +// // createPriceHistoryObject(cardPrice), +// // ]; +// // } + +// // return card; +// // }; + +// // Update the allCardPrices array based on the new card's price +// const updateAllCardPrices = (card) => { +// return [...selectedCollection.allCardPrices, card.price]; +// }; +// // Update the collection's cards array with the new card +// const updatePriceHistory = (card, newPrice) => { +// const lastPriceEntry = card.priceHistory?.slice(-1)?.[0]; +// return lastPriceEntry && lastPriceEntry.num !== newPrice +// ? [ +// ...card.priceHistory, +// { num: newPrice, timestamp: new Date().toISOString() }, +// ] +// : card.priceHistory; +// }; + +// Only add a new entry if the price has changed +// Refactored addOrRemoveCard function +// const addOrRemoveCard = useCallback( +// async (card, cardInfo, operation) => { +// const collectionId = selectedCollection._id; +// if (!collectionId) { +// console.error('No valid collection selected.'); +// setOpenChooseCollectionDialog(true); +// return; +// } + +// const updatedCards = +// operation === 'update' +// ? updateCardInCollection(selectedCollection.cards, card) +// : getUpdatedCards(selectedCollection, card, operation); + +// const updateInfo = createUpdateInfo(selectedCollection, updatedCards); +// const updatedCollection = await updateActiveCollection( +// selectedCollection._id, +// updateInfo +// ); + +// updateCollectionData(updatedCollection, 'selectedCollection'); +// }, +// [selectedCollection, updateCollectionData, setOpenChooseCollectionDialog] +// ); + +// Function to update the active collection on the backend +// const updateActiveCollection = async (collectionId, updateInfo) => { +// const url = createApiUrl(`${userId}/collections/${collectionId._id}`); + +// try { +// const response = await fetchWrapper(url, 'PUT', updateInfo); +// if (!response.data) +// throw new Error('No collection returned from server.'); +// return response.data; +// } catch (error) { +// console.error(`Failed to update collection: ${error}`); +// throw error; +// } +// }; + +// const getUpdatedCards = (activeCollection, card, operation) => { +// const cardsToUpdate = +// operation === 'add' +// ? handleCardAddition(activeCollection?.cards, card) +// : handleCardRemoval(activeCollection?.cards, card); + +// return cardsToUpdate.map((card) => { +// const cardPrice = card.card_prices?.[0]?.tcgplayer_price; +// const computedPrice = cardPrice * card.quantity; +// console.log('COMPUTED PRICE:', computedPrice); + +// // Update chart_datasets if necessary +// const allDatasets = [ +// ...(card?.chart_datasets || []), +// { x: moment().format('YYYY-MM-DD HH:mm'), y: computedPrice }, +// ]; +// card.chart_datasets = filterOutDuplicateYValues(allDatasets); + +// // Set card's price and total price +// card.price = cardPrice; +// card.totalPrice = computedPrice; + +// // Create a new price history object +// const newPriceHistoryObject = createPriceHistoryObject(cardPrice); + +// // Update priceHistory only if the last entry's num is different from the new price +// if ( +// !card.priceHistory || +// card.priceHistory.length === 0 || +// card.priceHistory[card.priceHistory.length - 1].num !== cardPrice +// ) { +// card.priceHistory = [ +// ...(card.priceHistory || []), +// newPriceHistoryObject, +// ]; +// } +// card.tag = card.tag || 'monitored'; +// return card; +// }); +// }; + +// const createPriceHistoryObject = (price) => ({ +// timestamp: new Date().toISOString(), +// num: price, +// }); + +// // The main function for adding or removing a card +// const addOrRemoveCard = useCallback( +// async (card, cardInfo, operation) => { +// const collectionId = getCollectionId(selectedCollection, allCollections); +// if (!collectionId) { +// console.error('No valid collection selected.'); +// setOpenChooseCollectionDialog(true); +// return; +// } + +// let updatedCards = +// operation === 'update' +// ? updateCardInCollection(selectedCollection.cards, card) +// : getUpdatedCards(selectedCollection, card, operation); + +// const updatedPrice = calculateTotalFromAllCardPrices(updatedCards); +// const accumulatedTotal = selectedCollection.totalPrice + updatedPrice; + +// const newXYValue = { +// label: `Update - ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: accumulatedTotal, +// }; + +// // Update the chart data with the new accumulated total +// const updatedChartData = { +// ...selectedCollection.chartData, +// allXYValues: [...selectedCollection.chartData.allXYValues, newXYValue], +// }; + +// const updateInfo = createUpdateInfo( +// updatedCards, +// accumulatedTotal, +// cardInfo, +// userId, +// selectedCollection, +// collectionId, +// updatedChartData, +// xyData +// ); +// console.log('[UPDATE INFO]:', updateInfo); +// console.log('[ACCUMULATED]:', accumulatedTotal); +// const updatedCollection = { +// ...selectedCollection, +// ...updateInfo, +// chartData: updatedChartData, +// // totalPrice: accumulatedTotal, +// collectionPriceHistory: [ +// ...(selectedCollection.collectionPriceHistory || []), +// createPriceHistoryObject(accumulatedTotal), +// ], +// }; + +// console.log('[COLLECTION DATA][B4UPDATE]:', updatedCollection); +// await updateActiveCollection(updatedCollection, updateInfo.chartData); +// updateCollectionData(updatedCollection, 'selectedCollection'); +// updateCollectionData(updatedCollection, 'collectionData'); +// }, +// [ +// selectedCollection, +// allCollections, +// userId, +// openChooseCollectionDialog, +// handleCardAddition, +// handleCardRemoval, +// updateCollectionData, +// setOpenChooseCollectionDialog, +// ] +// ); + +// const updateCardCollection = async ( +// selectedCollection, +// priceData, +// operation +// ) => { +// if (operation !== 'update') { +// console.warn('Invalid operation:', operation); +// return; +// } + +// const updatedCards = priceData.data.data; +// let accumulatedTotal = selectedCollection.totalPrice || 0; +// let newXYValues = [...selectedCollection.chartData.allXYValues]; + +// updatedCards.forEach((card) => { +// // Only add to priceHistory if the num value is different from the last entry +// const lastPriceHistoryNum = card.priceHistory?.slice(-1)[0]?.num; +// if (card.latestPrice?.num !== lastPriceHistoryNum) { +// const newPriceHistoryEntry = { +// num: card.latestPrice?.num || 0, +// timestamp: new Date().toISOString(), +// }; +// card.priceHistory = [ +// ...(card.priceHistory || []), +// newPriceHistoryEntry, +// ]; +// } + +// card.lastSavedPrice = { +// num: card.lastSavedPrice?.num || card.latestPrice?.num || 0, +// timestamp: card.lastSavedPrice?.timestamp || new Date().toISOString(), +// }; + +// card.latestPrice = { +// num: card.latestPrice?.num || 0, +// timestamp: new Date().toISOString(), +// }; + +// card.tag = card.tag || 'monitored'; + +// // Calculate the new total price +// accumulatedTotal += card.latestPrice.num * card.quantity; + +// // Update the chart data +// newXYValues.push({ +// label: `Update - ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: accumulatedTotal, +// }); +// }); + +// // Update the chart data with accumulated values +// const updatedChartData = { +// ...selectedCollection.chartData, +// allXYValues: newXYValues, +// }; + +// const updatedCollectionWithChartData = { +// ...selectedCollection, +// cards: updatedCards, +// totalPrice: accumulatedTotal, +// chartData: updatedChartData, +// }; + +// await updateActiveCollection(updatedCollectionWithChartData); +// return updatedCollectionWithChartData; +// }; + +// const updateCollectionChartData = ( +// collection, +// updatedPrice, +// updatedChartData +// ) => { +// const chartData = collection.chartData || { +// name: `Chart for Collection: ${collection.name}`, +// userId: collection.userId, +// datasets: [], +// allXYValues: [], +// xys: [], +// }; + +// const newXYValue = { +// label: `Update - ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: updatedPrice, +// }; + +// // Update the allXYValues array with the new data point +// const allXYValues = [...collection.chartData.allXYValues, newXYValue]; + +// const uniqueFilteredXYValues = getUniqueFilteredXYValues(allXYValues); + +// return { +// ...collection, +// chartData: { +// ...collection.chartData, +// allXYValues: uniqueFilteredXYValues, +// }, +// totalPrice: updatedPrice, +// }; +// }; + +// Create a new XY value for the current update +// const newXYValue = { +// label: `Update ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: updatedPrice, +// additionalPriceData: { +// priceChanged: updatedPrice !== collection.totalPrice, +// initialPrice: collection.totalPrice, +// updatedPrice: updatedPrice, +// priceDifference: updatedPrice - collection.totalPrice, +// priceChange: parseFloat( +// ((updatedPrice - collection.totalPrice) / collection.totalPrice) * 100 +// ).toFixed(2), +// }, +// }; + +// Update datasets if newDataSet is provided +// if (newDataSet && Array.isArray(newDataSet.data)) { +// newDataSet.data.forEach((dataset) => { +// if (dataset.xys && Array.isArray(dataset.xys)) { +// chartData.datasets.push(dataset); +// } +// }); +// } +// if (newDataSet && Array.isArray(newDataSet.data)) { +// newDataSet.data.forEach((dataset) => { +// if (dataset.xys && Array.isArray(dataset.xys)) { +// chartData.datasets.push(...dataset.xys); +// } +// }); +// } +// Return the updated collection with the new chartData +// const updateCardCollection = async ( +// selectedCollection, +// priceData, +// operation +// ) => { +// const priceArray = priceData.data.data; +// let updatedCards = selectedCollection.cards.map((card) => { +// const matchingNewCard = priceArray.find( +// (newCard) => newCard.id === card.id +// ); +// if (matchingNewCard) { +// const { latestPrice, priceHistory, name, quantity, tag, _id } = +// matchingNewCard; +// return { ...card, latestPrice, priceHistory, name, quantity, tag, _id }; +// } +// return card; +// }); + +// let newTotalPrice = updatedCards.reduce( +// (total, card) => total + (card.latestPrice?.num * card.quantity || 0), +// 0 +// ); + +// let newAllCardPrices = updatedCards.flatMap((card) => +// Array(card.quantity).fill(card.latestPrice?.num) +// ); + +// const newDataSet = { +// data: [ +// { +// xys: [ +// { +// label: `Update ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: newTotalPrice, +// }, +// ], +// }, +// ], +// }; + +// const updatedCollectionWithChartData = updateCollectionChartData( +// selectedCollection, +// newTotalPrice, +// newDataSet +// ); + +// await updateActiveCollection(updatedCollectionWithChartData); + +// return updatedCollectionWithChartData; +// }; + +// function updateChartDataForCollection(collection, chartData) { +// if (!collection.chartData) { +// collection.chartData = { +// name: `Chart for Collection: ${collection.name}`, +// userId: collection.userId, +// datasets: [], +// allXYValues: [], +// }; +// } + +// if (chartData) { +// collection.chartData = { ...collection.chartData, ...chartData }; +// } + +// const newXYSData = getUniqueFilteredXYValues( +// collection.chartData.allXYValues +// ); +// const timestamp = new Date(); +// const newPrice = collection.totalPrice; +// const previousPrice = collection.previousDayTotalPrice || newPrice; +// const priceDifference = newPrice - previousPrice; +// const priceChange = previousPrice +// ? (priceDifference / previousPrice) * 100 +// : 0; + +// collection.chartData.allXYValues.push({ +// label: `Update ${timestamp.toISOString()}`, +// x: timestamp, +// y: newPrice, +// }); + +// const newDatasetEntry = { +// name: collection.name, +// data: newXYSData.map((xy) => ({ +// ...xy, +// additionalPriceData: { +// priceChanged: priceDifference !== 0, +// initialPrice: previousPrice, +// updatedPrice: newPrice, +// priceDifference: priceDifference, +// priceChange: parseFloat(priceChange.toFixed(2)), +// }, +// })), +// }; + +// collection.chartData.datasets.push(newDatasetEntry); + +// return collection; +// } +// useEffect(() => { +// if (!selectedCollection || typeof totalPrice !== 'number') return; + +// const updateNumber = +// selectedCollection.chartData?.datasets?.length + 1 || 1; +// const newDataSet = createNewDataSet(updateNumber, totalPrice); + +// const updatedChartData = updateCollectionChartData( +// selectedCollection, +// totalPrice, +// newDataSet +// ); + +// const updatedCollection = { +// ...selectedCollection, +// chartData: updatedChartData, +// totalPrice, +// allCardPrices, +// }; + +// updateActiveCollection(updatedCollection); +// }, [totalPrice, selectedCollection, allCardPrices]); + +// const createNewDataSet = (updateNumber, totalPrice) => ({ +// data: [ +// { +// xys: [ +// { +// label: `Update Number ${updateNumber}`, +// data: { x: new Date().toISOString(), y: totalPrice }, +// }, +// ], +// additionalPriceData: {}, // Additional data can be added here +// }, +// ], +// }); + +// Additional functions and context setup as necessary + +// const updateActiveCollection = useCallback( +// async (collectionData, existingChartData = {}) => { +// if (!collectionData) throw new Error('No collection data provided.'); + +// const isCreatingNew = !collectionData?._id; +// const actionDescription = isCreatingNew +// ? 'create a new collection' +// : 'update the existing collection'; +// const method = determineHttpMethod( +// isCreatingNew, +// collectionData?.endpoint +// ); + +// try { +// if (isCreatingNew) { +// console.log( +// `Skipping fetch call to ${method} since it's a new collection` +// ); +// updateChartDataForCollection(collectionData, existingChartData); +// } else { +// const endpoint = createApiUrl( +// `${userId}/collections/${collectionData._id}` +// ); +// console.log('ENDPOINT:', endpoint); + +// // Calculate the difference between the existing chart data and the new collection data +// const payload = constructPayloadWithDifferences( +// existingChartData, +// collectionData +// ); + +// console.log(`Debug: ${method} ${endpoint}`); +// const response = await fetchWrapper(endpoint, method, payload); +// console.log('RESPONSE:', response); +// const updatedCollection = response?.data; +// console.log('[COLLECTION DATA][A4UPDATE]:', updatedCollection); + +// if (!updatedCollection) +// throw new Error('No collection returned from server.'); + +// updateChartDataForCollection(updatedCollection, existingChartData); +// updateCollectionData(updatedCollection, 'selectedCollection'); +// updateCollectionData(updatedCollection, 'collectionData'); +// updateCollectionData(updatedCollection, 'allCollections'); +// } +// } catch (error) { +// console.error( +// `Failed to update collection for user ${userId} with collectionId ${collectionData._id}:`, +// error +// ); +// logError('updateActiveCollection', actionDescription, error); +// console.error(`Failed to ${actionDescription}: ${error.message}`); +// } +// }, +// [userId, updateCollectionData] +// ); + +// // Helper function to construct payload with only the differences +// function constructPayloadWithDifferences(existingData, newData) { +// const payload = {}; +// Object.keys(newData).forEach((key) => { +// if (newData[key] !== existingData[key]) { +// payload[key] = newData[key]; +// } +// }); +// return payload; +// } + +// function updateChartDataForCollection(collection, chartData) { +// if (!collection.chartData) { +// collection.chartData = { +// // Initialize with defaults if not present +// name: `Chart for Collection: ${collection.name}`, +// userId: collection.userId, +// datasets: [], +// allXYValues: [], +// }; +// } + +// // Merge in any additional chartData that was passed in +// if (chartData) { +// collection.chartData = { ...collection.chartData, ...chartData }; +// } + +// // Get new x and y values +// const newXYSData = getUniqueFilteredXYValues( +// collection.chartData.allXYValues +// ); + +// // Calculate new and previous price +// const timestamp = new Date(); +// const newPrice = collection.totalPrice; // Fixed the code to get the correct value +// const previousPrice = collection.previousDayTotalPrice || newPrice; // Assuming previousDayTotalPrice is a property +// const priceDifference = newPrice - previousPrice; +// const priceChange = previousPrice +// ? (priceDifference / previousPrice) * 100 +// : 0; + +// // Update the allXYValues field with the new data point +// collection.chartData.allXYValues.push({ +// label: `Update ${new Date().toISOString()}`, +// x: new Date(), +// y: newPrice, +// }); + +// const newDatasetEntry = { +// name: collection.name, // or some other way to name the dataset +// data: [ +// { +// xys: [ +// // newXYSData.map((xy) => ({ +// { +// label: `Update ${timestamp.toISOString()}`, +// data: { x: timestamp, y: newPrice }, +// }, +// // })), +// ], +// additionalPriceData: [ +// { +// priceChanged: priceDifference !== 0, +// initialPrice: previousPrice, +// updatedPrice: newPrice, +// priceDifference: priceDifference, +// priceChange: parseFloat(priceChange.toFixed(2)), +// }, +// ], +// }, +// ], +// }; + +// // Push the new Dataset to the datasets array +// collection.chartData.datasets.push(newDatasetEntry); + +// // Return the updated collection object +// return collection; +// } diff --git a/src/context/CollectionContext/cardHelpers.jsx b/src/context/CollectionContext/cardHelpers.jsx new file mode 100644 index 0000000..c1ba28b --- /dev/null +++ b/src/context/CollectionContext/cardHelpers.jsx @@ -0,0 +1,80 @@ +import moment from 'moment'; +import { useCollectionStore } from './CollectionContext'; + +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 createUpdateInfo = ( +// updatedCards, +// updatedPrice, +// cardInfo, +// userId, +// selectedCollection, +// collectionId, +// newDataSet, +// xyData +// ) => { +// // const { updateCollectionChartData } = useCollectionStore(); +// return { +// ...cardInfo, +// name: selectedCollection?.name, +// description: selectedCollection?.description, +// cards: updatedCards, +// userId: userId, +// totalCost: updatedPrice, +// totalPrice: updatedPrice, +// xys: xyData, +// quantity: updatedCards.length, +// totalQuantity: updatedCards.reduce((acc, card) => acc + card.quantity, 0), +// chartData: updateCollectionChartData( +// updatedPrice, +// selectedCollection, +// newDataSet +// ), +// allCardPrices: updatedCards.flatMap((card) => +// Array(card.quantity).fill(card.card_prices?.[0]?.tcgplayer_price) +// ), +// _id: collectionId, +// }; +// }; diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx index 2d5026e..52f6490 100644 --- a/src/context/CollectionContext/collectionUtility.jsx +++ b/src/context/CollectionContext/collectionUtility.jsx @@ -1,9 +1,28 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`; const initialCollectionState = { - _id: '', - cards: [], - quantity: 0, - totalPrice: 0, + userId: '', // Assuming this is an ObjectId string + name: '', // Initialize as empty string if not provided + description: '', // Initialize as empty string if not provided + totalCost: '', // Initialize as empty string if not provided + totalPrice: 0, // Initialize as 0 if not provided + quantity: 0, // Initialize as 0 if not provided + totalQuantity: 0, // Initialize as 0 if not provided + previousDayTotalPrice: 0, // Initialize as 0 if not provided + dailyPriceChange: 0, // Initialize as 0 if not provided + priceDifference: 0, // Initialize as 0 if not provided + priceChange: 0, // Initialize as 0 if not provided + allCardPrices: [], // Initialize as empty array if not provided + cards: [], // Initialize as empty array if not provided + currentChartDatasets: [], // Initialize as empty array if not provided + xys: [], // Use defaultXyData or initialize as empty if not provided + chartData: { + name: '', // Initialize as empty string if not provided + userId: '', // Assuming this is an ObjectId string + datasets: [], // Initialize as empty array if not provided + xys: [], // Initialize as empty array if not provided + allXYValues: [], // Initialize as empty array if not provided + }, }; /** * Filters out duplicate Y values from an array of datasets. @@ -130,6 +149,8 @@ const handleCardAddition = (currentCards, cardToAdd) => { // Initialize currentCards to an empty array if it's not defined currentCards = currentCards || []; + console.log('CURRENT CARDS:', currentCards); + console.log('CARD TO ADD:', cardToAdd); const cardToAddId = typeof cardToAdd.id === 'number' ? String(cardToAdd.id) : cardToAdd.id; const matchingCard = currentCards.find((c) => c.id === cardToAddId); @@ -152,21 +173,30 @@ const handleCardRemoval = (currentCards, cardToRemove) => { // Initialize currentCards to an empty array if it's not defined currentCards = currentCards || []; + console.log('CURRENT CARDS:', currentCards); + console.log('CARD TO REMOVE:', cardToRemove); + const cardToRemoveId = typeof cardToRemove.id === 'number' ? String(cardToRemove.id) : cardToRemove.id; - const matchingCard = currentCards.find((c) => c.id === cardToRemoveId); - if (!matchingCard) { + // Find the card to remove in the current cards array + const cardIndex = currentCards.findIndex((c) => c.id === cardToRemoveId); + + if (cardIndex === -1) { console.error('Card not found in the collection.'); return [...currentCards]; } + const matchingCard = currentCards[cardIndex]; + + // If the card has a quantity greater than 1, decrement it if (matchingCard.quantity > 1) { matchingCard.quantity--; return [...currentCards]; } else { + // Remove the card from the collection if quantity is 1 or less return currentCards.filter((card) => card.id !== cardToRemoveId); } }; @@ -286,12 +316,6 @@ const updateLastRequestTime = (method) => { * @returns {Promise} - The response from the API call. */ const fetchWrapper = async (url, method, body = null) => { - // if (!canMakeRequest(method)) { - // throw new Error( - // `A ${method} request was made recently. Please wait before trying again.` - // ); - // } - const options = { method, headers: { 'Content-Type': 'application/json' }, @@ -304,12 +328,10 @@ const fetchWrapper = async (url, method, body = null) => { // We handle non-ok responses immediately throw new Error(`API request failed with status ${response.status}`); } - updateLastRequestTime(method); - // Assuming handleApiResponse is expecting a Response object - return handleApiResponse(response); + updateLastRequestTime(method); // Assumed to be a function that updates some kind of state + return await response.json(); // Directly returning the JSON response } catch (error) { console.error(`Fetch failed: ${error}`); - // It's useful to log the stack trace in development console.trace(); throw error; // Re-throwing the error for upstream catch blocks to handle } @@ -324,6 +346,10 @@ const handleApiResponse = async (response) => { throw error; } + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + try { const jsonResponse = await response.json(); return jsonResponse; @@ -343,6 +369,92 @@ const getTotalCost = (selectedCollection) => { return total + cardPrice * card.quantity; }, 0); }; +// const getCardQuantity = (cardId) => { +// const card = selectedCollection?.cards?.find((c) => c.id === cardId); +// return card?.quantity || 0; +// } + +const defaultContextValue = { + allCollections: [], + allCardPrices: [], + xy: [], + selectedCollection: {}, + collectionData: initialCollectionState, + officialCollectionDatasets: [], + totalCost: 0, + openChooseCollectionDialog: false, + updatedPricesFromCombinedContext: {}, + setUpdatedPricesFromCombinedContext: () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + setOpenChooseCollectionDialog: () => {}, + calculateTotalPrice: () => {}, + getTotalCost: () => {}, + createUserCollection: () => {}, + removeCollection: () => {}, + fetchAllCollectionsForUser: () => {}, + setSelectedCollection: () => {}, + setAllCollections: () => {}, + addOneToCollection: () => {}, + removeOneFromCollection: () => {}, +}; + +const logError = (source, action, error) => { + console.error( + `[${source.toUpperCase()}] Failed to ${action}: ${error.message}` + ); +}; + +// Reusable validation and error logging +const validateUserIdAndData = (userId, data, actionDescription) => { + if (!userId) { + logError( + 'validateUserIdAndData', + actionDescription, + new Error('User ID is undefined.') + ); + return false; + } + if (!validateData(data, actionDescription, actionDescription)) { + logError( + 'validateUserIdAndData', + actionDescription, + new Error('Validation failed for collection data.') + ); + return false; + } + return true; +}; + +const determineHttpMethod = (isCreatingNew, endpoint) => { + return isCreatingNew ? 'POST' : 'PUT'; +}; + +// Abstracted payload creation to reduce repetition +const createPayload = (info, data, defaultXyData) => ({ + userId: info.userId || data.userId, // Assuming this is an ObjectId string + name: info.name || data.name, + description: info.description || data.description, + totalCost: '', // Initialize as empty string if not provided + totalPrice: 0, // Initialize as 0 if not provided + quantity: 0, // Initialize as 0 if not provided + totalQuantity: 0, // Initialize as 0 if not provided + previousDayTotalPrice: 0, // Initialize as 0 if not provided + dailyPriceChange: 0, // Initialize as 0 if not provided + priceDifference: 0, // Initialize as 0 if not provided + priceChange: 0, // Initialize as 0 if not provided + collectionPriceHistory: [], // Initialize as empty array if not provided + allCardPrices: [], // Initialize as empty array if not provided + cards: [], // Initialize as empty array if not provided + currentChartDatasets: [], // Initialize as empty array if not provided + xys: defaultXyData || [], // Use defaultXyData or initialize as empty if not provided + chartData: { + name: '', // Initialize as empty string if not provided + userId: info.userId || data.userId, // Assuming this is an ObjectId string + datasets: [], // Initialize as empty array if not provided + xys: [], // Initialize as empty array if not provided + allXYValues: [], // Initialize as empty array if not provided + }, +}); const removeDuplicateCollections = (collections) => { const uniqueCollections = {}; @@ -376,4 +488,9 @@ module.exports = { getCardPrice, removeDuplicateCollections, initialCollectionState, + defaultContextValue, + validateUserIdAndData, + determineHttpMethod, + createPayload, + logError, }; diff --git a/src/context/CollectionContext/fml.jsx b/src/context/CollectionContext/fml.jsx new file mode 100644 index 0000000..439993d --- /dev/null +++ b/src/context/CollectionContext/fml.jsx @@ -0,0 +1,1389 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +import React, { + createContext, + useState, + useEffect, + useCallback, + useMemo, + useContext, + useRef, +} from 'react'; +import { useCookies } from 'react-cookie'; +import { + filterOutDuplicateYValues, + handleCardAddition, + handleCardRemoval, + getUniqueFilteredXYValues, + calculateTotalFromAllCardPrices, + ensureNumber, + findCollectionIndex, + createApiUrl, + handleApiResponse, + BASE_API_URL, + fetchWrapper, + removeDuplicateCollections, + getTotalCost, + initialCollectionState, + getCardPrice, + defaultContextValue, + validateUserIdAndData, + createPayload, + logError, + determineHttpMethod, +} from './collectionUtility.jsx'; +import { useCombinedContext } from '../CombinedProvider.jsx'; +import { useUserContext } from '../UserContext/UserContext.js'; +import moment from 'moment'; +import CustomLogger from '../CutstomLogger.jsx'; +import { + createNewDataSet, + getCollectionId, + // updateCardInCollection, +} from './cardHelpers.jsx'; + +export const CollectionContext = createContext(defaultContextValue); + +const constructPayloadWithDifferences = (existingData, newData) => { + const payload = {}; + let logContent = + '[constructPayloadWithDifferences] -----> Differences found in collection data:\n'; + + Object.keys(newData).forEach((key) => { + if (newData[key] !== existingData[key]) { + payload[key] = newData[key]; + + logContent += ` - Field "${key}":\n`; + logContent += ` Old Value: ${JSON.stringify(existingData[key])}\n`; + logContent += ` New Value: ${JSON.stringify(newData[key])}\n`; + } + }); + + if (Object.keys(payload).length > 0) { + console.log('Payload with differences:', payload); + } else { + console.log('No differences found between existing and new data.'); + } + + return payload; +}; + +const updateCardInCollection = (cards, cardToUpdate) => { + // Validate that cards is an array + if (!Array.isArray(cards)) { + throw new TypeError('The first argument must be an array of cards.'); + } + + // Validate that cardToUpdate is an object + if (typeof cardToUpdate !== 'object' || cardToUpdate === null) { + throw new TypeError('The card to update must be an object.'); + } + + // Validate that cardToUpdate has an id property + if (!('id' in cardToUpdate)) { + throw new Error('The card to update must have an "id" property.'); + } + + try { + // Attempt to update the card within the collection + const updatedCards = cards.map((card) => + card.id === cardToUpdate.id ? { ...card, ...cardToUpdate } : card + ); + return updatedCards; + } catch (error) { + // Handle any other errors that occur during the map operation + throw new Error(`Failed to update card in collection: ${error.message}`); + } +}; + +export const CollectionProvider = ({ children }) => { + // const { cardPrices } = useCombinedContext(); + const [cookies] = useCookies(['user']); + const [collectionData, setCollectionData] = useState(initialCollectionState); + const [collection, setCollection] = useState({ + cards: [], + allCardPrices: [], + }); + + const [officialCollectionDatasets, setOfficialCollectionDatasets] = useState([ + {}, + ]); + const [allCollections, setAllCollections] = useState([]); + const [allCardPrices, setAllCardPrices] = useState([]); + const [totalPrice, setTotalPrice] = useState(0); + const [xyData, setXyData] = useState([]); + const [ + updatedPricesFromCombinedContext, + setUpdatedPricesFromCombinedContext, + ] = useState({}); + // const [selectedCollection, setSelectedCollection] = useState({}); + const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = + useState(false); + const userId = cookies.user?.id; + const totalCost = useMemo( + () => getTotalCost(selectedCollection), + [selectedCollection] + ); + const [selectedCollection, setSelectedCollection] = useState( + initialCollectionState + ); + const lastFetchedTime = useRef(null); + const calculateTotalPrice = (collection) => + collection.cards.reduce( + (total, card) => total + (card.price || 0) * (card.quantity || 0), + 0 + ); + + // Consolidated state setters for collection + const setCollectionState = (newState) => { + setSelectedCollection(newState); + setAllCollections((prev) => + prev.map((c) => (c._id === newState._id ? newState : c)) + ); + }; + + const createUpdateInfo = ( + updatedCards, + updatedPrice, + cardInfo, + userId, + selectedCollection, + collectionId, + newDataSet, + xyData + ) => { + // const { updateCollectionChartData } = useCollectionStore(); + return { + ...cardInfo, + name: selectedCollection?.name, + description: selectedCollection?.description, + cards: updatedCards, + userId: userId, + totalCost: updatedPrice, + totalPrice: updatedPrice, + xys: xyData, + quantity: updatedCards.length, + totalQuantity: updatedCards, + // chartData: updateCollectionChartData( + // updatedPrice, + // selectedCollection, + // newDataSet + // ), + allCardPrices: updatedCards?.flatMap((card) => + Array(card.quantity).fill(card.card_prices?.[0]?.tcgplayer_price) + ), + _id: collectionId, + }; + }; + + const saveCollectionToApi = async (collection, isUpdate = false) => { + const method = isUpdate ? 'PUT' : 'POST'; + const endpoint = isUpdate ? `/${collection._id}` : ''; + const url = `/api/users/collections${endpoint}`; + + try { + const response = await fetchWrapper(url, method, collection); + console.log( + `Collection ${isUpdate ? 'updated' : 'created'}:`, + response.data + ); + return response.data; // Return the updated data from the server + } catch (error) { + console.error( + `Failed to ${isUpdate ? 'update' : 'create'} collection:`, + error + ); + } + }; + + const createUserCollection = async ( + userId, + newCollectionInfo, + name, + description + ) => { + if (!userId) { + console.warn('Invalid userId to createUserCollection.'); + return; + } + if (!name) { + console.warn('Invalid name to createUserCollection.'); + return; + } + const actionDescription = 'create a new collection'; + if (!validateUserIdAndData(userId, newCollectionInfo, actionDescription)) + return; + + const payload = createPayload( + { name, description, userId }, + newCollectionInfo + ); + // const url = createApiUrl(`${userId}/collections`); + // console.log('URL:', url); + try { + // const response = await fetchWrapper(url, 'POST', payload); + const response = await saveCollectionToApi(payload, false); // Pass false to indicate it's not an update + + if (response?.error) { + console.error('Failed to create the collection:', response.error); + return; + } + if (response?.data) { + console.log('RESPONSE:', response); + setAllCollections((prev) => [...prev, response.data]); + setSelectedCollection(response.data); + setCollectionData(response.data); + } + return response; + } catch (error) { + logError('createUserCollection', { actionDescription, error }); + } + }; + + const removeCollection = async (collection) => { + if (!collection._id) { + console.error('Collection ID is undefined.'); + return; + } + + try { + const url = createApiUrl(`${userId}/collections`); + const response = await fetchWrapper(url, 'DELETE'); + + if (response.error) { + console.error('Failed to delete the collection:', response.error); + return; + } + + setAllCollections((prev) => + prev.filter((item) => item._id !== collection._id) + ); + + if (selectedCollection._id === collection._id) { + setSelectedCollection(initialCollectionState); + setCollectionData(initialCollectionState); + } + } catch (error) { + console.error(`Failed to delete the collection: ${error.message}`); + } + }; + + const fetchCollections = useCallback(async (userId) => { + if (!userId) { + console.warn('userId is not set, aborting fetchCollections.'); + return null; + } + const url = createApiUrl(`${userId}/collections`); + console.log('URL:', url); + try { + console.log(`Debug: ${'GET'} ${url}`); + const response = await fetchWrapper(url, 'GET'); + console.log('[GET RESPONSE]:', response); + const collections = response?.data; + console.log('Fetched collections:', collections); + return collections; + } catch (error) { + console.error('Error fetching collections:', error); + logError('fetchCollections', 'fetch collections', error); + return null; + } + }, []); + + const setCollections = useCallback((collections) => { + if (!collections || !Array.isArray(collections)) { + console.warn('Invalid collections array:', collections); + return; + } + + const uniqueCollections = removeDuplicateCollections(collections); + const validCollections = uniqueCollections.filter(Boolean); + + validCollections.forEach((collection) => { + collection.totalPrice = calculateTotalFromAllCardPrices( + collection.allCardPrices + ); + }); + + setAllCollections(validCollections); + + setCollectionData( + validCollections.length === 0 + ? initialCollectionState + : validCollections[0] + ); + setSelectedCollection( + validCollections.length === 0 + ? initialCollectionState + : validCollections[0] + ); + }, []); + + const fetchAndSetCollections = useCallback(async () => { + // Throttle the fetch calls + const currentTime = Date.now(); + const fetchDelay = 60000; // 1 minute + if ( + lastFetchedTime.current && + currentTime - lastFetchedTime.current < fetchDelay + ) + return; + lastFetchedTime.current = currentTime; + + try { + const response = await fetchWrapper( + createApiUrl(`collections/${userId}`), + 'GET' + ); + + console.log('FETCHED COLLECTIONS:', response); + setAllCollections(response.data || []); + } catch (error) { + console.error(`Failed to fetch collections: ${error}`); + } + }, [userId]); + // Function to update chart datasets and avoid duplicate entries + + // Function to calculate the total price from all card prices + const calculateTotalFromCardPrices = (cards) => + cards.reduce((total, card) => total + card.price * card.quantity, 0); + + // Function to update the card details + const updateCardDetails = (card, newPrice) => { + const priceHasChanged = + !card.priceHistory.length || + card.priceHistory.slice(-1)[0].num !== newPrice; + return { + ...card, + price: newPrice, + totalPrice: newPrice * card.quantity, + latestPrice: createPriceHistoryEntry(newPrice), + lastSavedPrice: card.latestPrice || createPriceHistoryEntry(newPrice), + chart_datasets: updateChartDatasets( + card, + newPrice * card.quantity, + new Date().toISOString() + ), + quantity: card.quantity, + priceHistory: priceHasChanged + ? [...card.priceHistory, createPriceHistoryEntry(newPrice)] + : card.priceHistory, + }; + }; + + // Function to add a card to the selected collection + // Add card to the selected collection + const createPriceHistoryEntry = (price) => ({ + num: price, + timestamp: new Date().toISOString(), + }); + + const updateChartDatasets = (card, computedPrice) => { + const newDataset = { x: new Date().toISOString(), y: computedPrice }; + const datasets = card.chart_datasets || []; + // Assuming a utility function is in place to filter out duplicates + return [...datasets, newDataset]; // For simplicity, this example does not filter out duplicates + }; + + const updateCardPriceDetails = (card, newPrice) => { + const priceChanged = + !card.priceHistory.length || + card.priceHistory.slice(-1)[0].num !== newPrice; + return { + ...card, + price: newPrice, + totalPrice: newPrice * (card.quantity || 1), // Default to 1 if quantity is not set + latestPrice: createPriceHistoryEntry(newPrice), + lastSavedPrice: priceChanged ? card.latestPrice : card.lastSavedPrice, // Update lastSavedPrice only if price changed + priceHistory: priceChanged + ? [...card.priceHistory, createPriceHistoryEntry(newPrice)] + : card.priceHistory, + chart_datasets: updateChartDatasets( + card, + newPrice * (card.quantity || 1) + ), + }; + }; + // const addOrRemoveCard = useCallback( +// async (card, cardInfo, operation) => { +// const collectionId = getCollectionId(selectedCollection, allCollections); +// if (!collectionId) { +// console.error('No valid collection selected.'); +// setOpenChooseCollectionDialog(true); +// return; +// } + +// let updatedCards = +// operation === 'update' +// ? updateCardInCollection(selectedCollection.cards, card) +// : getUpdatedCards(selectedCollection, card, operation); + +// const updatedPrice = calculateTotalFromAllCardPrices(updatedCards); +// const accumulatedTotal = selectedCollection.totalPrice + updatedPrice; + +// const newXYValue = { +// label: `Update - ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: accumulatedTotal, +// }; + +// // Update the chart data with the new accumulated total +// const updatedChartData = { +// ...selectedCollection.chartData, +// allXYValues: [...selectedCollection.chartData.allXYValues, newXYValue], +// }; + +// const updateInfo = createUpdateInfo( +// updatedCards, +// accumulatedTotal, +// cardInfo, +// userId, +// selectedCollection, +// collectionId, +// updatedChartData, +// xyData +// ); +// console.log('[UPDATE INFO]:', updateInfo); +// console.log('[ACCUMULATED]:', accumulatedTotal); +// const updatedCollection = { +// ...selectedCollection, +// ...updateInfo, +// chartData: updatedChartData, +// // totalPrice: accumulatedTotal, +// collectionPriceHistory: [ +// ...(selectedCollection.collectionPriceHistory || []), +// createPriceHistoryObject(accumulatedTotal), +// ], +// }; + +// console.log('[COLLECTION DATA][B4UPDATE]:', updatedCollection); +// await updateActiveCollection(updatedCollection, updateInfo.chartData); +// updateCollectionData(updatedCollection, 'selectedCollection'); +// updateCollectionData(updatedCollection, 'collectionData'); +// }, +// [ +// selectedCollection, +// allCollections, +// userId, +// openChooseCollectionDialog, +// handleCardAddition, +// handleCardRemoval, +// updateCollectionData, +// setOpenChooseCollectionDialog, +// ] +// ); + + // Main functions + const addOrUpdateCardInCollection = (collection, card, newPrice) => { + // Find the card in the collection or create a new one + const existingCardIndex = collection.cards.findIndex( + (c) => c.id === card.id + ); + const updatedCard = updateCardPriceDetails(card, newPrice); + + let updatedCards = [...collection.cards]; + if (existingCardIndex >= 0) { + updatedCards[existingCardIndex] = updatedCard; // Update existing card + } else { + updatedCards = [...updatedCards, updatedCard]; // Add new card + } + + const updatedTotalPrice = updatedCards.reduce( + (acc, card) => acc + card.totalPrice, + 0 + ); + const updatedAllCardPrices = updatedCards.map((card) => card.price); + + return { + ...collection, + cards: updatedCards, + totalPrice: updatedTotalPrice, + allCardPrices: updatedAllCardPrices, + }; + }; + + // Function to save the collection to the backend + // const saveCollectionToApi = async (collection, method) => { + // try { + // const url = `/api/users/collections/${ + // method === 'POST' ? '' : collection._id + // }`; + // const response = await fetchWrapper(url, method, collection); + // console.log( + // `Collection ${method === 'POST' ? 'created' : 'updated'}:`, + // response.data + // ); + // } catch (error) { + // console.error( + // `Failed to ${method === 'POST' ? 'create' : 'update'} collection:`, + // error + // ); + // } + // }; + + const contextValue = useMemo( + () => ({ + allCollections, + selectedCollection, + collectionData, + officialCollectionDatasets, + totalCost, + totalPrice, + // processAndUpdateCardPrices: (cardPrices, newCardPricesData) => + // processAndUpdateCardPrices(cardPrices, newCardPricesData), + allCardPrices: selectedCollection?.allCardPrices || [], + xys: xyData || [], + openChooseCollectionDialog, + // collectionData, + // createUserCollection, + // removeCollection, + updatedPricesFromCombinedContext, + // addCard: (card) => addOrUpdateCard(card, 'add'), + // updateCardPrices: (cardUpdates) => + // cardUpdates.forEach((card) => addOrUpdateCard(card, 'update')), + setAllCardPrices: (cardPrices) => setAllCardPrices(cardPrices), + // updateCardPrices: (priceUpdates) => updateCardPrices(priceUpdates), + addCardToCollection: (card) => addOrUpdateCardInCollection(card), + setXyData: (newXyData) => setXyData(newXyData), + // updateCollection: (collection) => updateCollection(collection), + updateCardCollection: (collection, cardData, operation) => + updateCardInCollection(collection, cardData, operation), + setOfficialCollectionDatasets, + setUpdatedPricesFromCombinedContext: (updatedPrices) => { + setUpdatedPricesFromCombinedContext(updatedPrices); + }, + setOpenChooseCollectionDialog, + calculateTotalPrice: () => getCardPrice(selectedCollection), + getTotalCost: () => getTotalCost(selectedCollection), + getCardQuantity: (cardId) => { + const card = selectedCollection?.cards?.find((c) => c?.id === cardId); + return card?.quantity || 0; + }, + createUserCollection: (userId, newCollectionInfo) => + createUserCollection( + userId, + newCollectionInfo, + newCollectionInfo.name, + newCollectionInfo.description + ), + + removeCollection: (collection) => removeCollection(collection), + fetchAllCollectionsForUser: fetchAndSetCollections, + // addCardToCollection: (card) => addCardToCollection(card), + setSelectedCollection: (collectionId) => + setSelectedCollection(collectionId), + setAllCollections: (collections) => setAllCollections(collections), + addOneToCollection: (card) => addOrUpdateCardInCollection(card), + // removeOneFromCollection: (card) => addOrRemoveCard(card, null, 'remove'), + }), + [allCollections, selectedCollection, totalCost, totalPrice, xyData] + ); + + useEffect(() => { + // Log context values + console.log('CONTEXT:', contextValue); + console.log('TOTAL COST:', totalCost); + console.log('TOTAL PRICE:', totalPrice); + console.log('SELECTED COLLECTION:', selectedCollection); + console.log('ALL COLLECTIONS:', allCollections); + console.log('COLLECTION DATA:', collectionData); + console.log('OFFICIAL COLLECTION DATASETS:', officialCollectionDatasets); + console.log( + 'UPDATED PRICES FROM CONTEXT:', + updatedPricesFromCombinedContext + ); + console.log('ALL CARD PRICES:', selectedCollection?.allCardPrices); + console.log('XY DATA:', xyData); + }, [ + contextValue, + totalPrice, + selectedCollection, + updatedPricesFromCombinedContext, + xyData, + ]); + useEffect(() => { + console.log('UPDATED COLLECTION DATA POST SERVER:', collectionData); + }, [collectionData]); + + useEffect(() => { + if (userId) { + fetchAndSetCollections(); + } + }, [userId]); + + return ( + + {children} + + ); +}; + +export const useCollectionStore = () => { + const context = useContext(CollectionContext); + if (!context) { + throw new Error( + 'useCollectionStore must be used within a CollectionProvider' + ); + } + return context; +}; +// const updateActiveCollection = useCallback( +// async (collectionData) => { +// if (!collectionData) throw new Error('No collection data provided.'); + +// CustomLogger({ 'COLLECTION DATA': collectionData }); +// const isCreatingNew = !collectionData?._id; +// const actionDescription = isCreatingNew +// ? 'create a new collection' +// : 'update the existing collection'; +// const method = determineHttpMethod( +// isCreatingNew, +// collectionData?.endpoint +// ); +// CustomLogger({ 'METHOD:': method }); +// try { +// if (isCreatingNew) { +// console.log( +// `Skipping fetch call to ${method} since it's a new collection` +// ); +// } else { +// const endpoint = createApiUrl( +// `${userId}/collections/${collectionData?._id}` +// ); +// console.log('ENDPOINT:', endpoint); + +// const payload = constructPayloadWithDifferences( +// initialCollectionState, +// collectionData +// ); +// console.log('PAYLOAD:', collectionData); +// console.log(`Debug: ${method} ${endpoint}`); +// const response = await fetchWrapper(endpoint, method, payload); +// console.log('[UPDATE RESPONSE][RAW]:', response); +// const updatedCollection = response?.data; +// console.log('[UPDATE RESPONSE][DES]:', updatedCollection); + +// if (!updatedCollection) +// throw new Error('No collection returned from server.'); +// updateCollectionChartData(updatedCollection, existingChartData); +// // updateChartDataForCollection(updatedCollection, existingChartData); +// updateCollectionData(updatedCollection, 'selectedCollection'); +// updateCollectionData(updatedCollection, 'collectionData'); +// updateCollectionData(updatedCollection, 'allCollections'); +// } +// } catch (error) { +// console.error( +// `Failed to update collection for user ${userId} with collectionId ${collectionData._id}:`, +// error +// ); +// logError('updateActiveCollection', actionDescription, error); +// console.error(`Failed to ${actionDescription}: ${error.message}`); +// } +// }, +// [userId, updateCollectionData] +// ); +// Adds or updates a card in the collection + +// const handleUpdatePrices = useCallback( +// (priceUpdates) => { +// const timestamp = new Date().toISOString(); + +// setSelectedCollection((prevCollection) => { +// const updatedCards = prevCollection.cards.map((card) => { +// const update = priceUpdates.find((u) => u.id === card.id); +// if (update) { +// return { +// ...card, +// price: update.newPrice, +// latestPrice: { num: update.newPrice, timestamp }, +// lastSavedPrice: card.latestPrice, +// priceHistory: +// card.latestPrice.num !== update.newPrice +// ? [...card.priceHistory, { num: update.newPrice, timestamp }] +// : card.priceHistory, +// chart_datasets: updateChartDatasets( +// card, +// update.newPrice * card.quantity, +// timestamp +// ), +// }; +// } +// return card; +// }); + +// const newTotalPrice = calculateTotalFromCardPrices(updatedCards); + +// // Save the updated collection to the backend +// updateCardPrices({ +// ...prevCollection, +// cards: updatedCards, +// totalPrice: newTotalPrice, +// }); + +// return { +// ...prevCollection, +// cards: updatedCards, +// totalPrice: newTotalPrice, +// }; +// }); +// }, +// [updateCardPrices] +// ); + +// Transform the card data to fit the collection item structure +// const transformCardToCollectionItem = (card) => { +// return { +// id: card.id, +// name: card.name, +// price: card.price, +// quantity: 1, // Default to 1 for a new card, adjust as needed +// ... any other card properties needed +// }; +// }; +// const updateCardPriceHistory = (card, newPrice) => { +// if (card.priceHistory.slice(-1)[0]?.num !== newPrice) { +// return [...card.priceHistory, createPriceHistoryEntry(newPrice)]; +// } +// return card.priceHistory; +// }; +// // Refactored getUpdatedCards function +// const getUpdatedCards = (activeCollection, card, operation) => { +// const handleOperation = +// operation === 'add' ? handleCardAddition : handleCardRemoval; +// const cardsToUpdate = handleOperation(activeCollection?.cards, card); + +// return cardsToUpdate.map(updateCardDetails); +// }; + +// const updateCollectionData = useCallback( +// (newData, collectionType) => { +// switch (collectionType) { +// case 'allCollections': +// setAllCollections((prevCollections) => { +// const indexToUpdate = prevCollections.findIndex( +// (collection) => collection._id === newData._id +// ); +// if (indexToUpdate === -1) { +// return [...prevCollections, newData]; +// } else { +// const updatedCollections = [...prevCollections]; +// updatedCollections[indexToUpdate] = newData; +// return updatedCollections; +// } +// }); +// break; +// case 'selectedCollection': +// setSelectedCollection(newData); +// break; +// case 'collectionData': +// setCollectionData(newData); +// break; +// default: +// console.warn('Unknown collection type for update:', collectionType); +// } +// }, +// [setAllCollections, setSelectedCollection, setCollectionData] +// ); + +// // // Function to handle the update of card price and price history +// // const updateCardDetails = (card) => { +// // const cardPrice = card.card_prices?.[0]?.tcgplayer_price; +// // const computedPrice = cardPrice * card.quantity; + +// // card.chart_datasets = updateChartDatasets(card, computedPrice); +// // card.price = cardPrice; +// // card.totalPrice = computedPrice; + +// // const lastEntry = card.priceHistory?.slice(-1)?.[0]; +// // if (!lastEntry || lastEntry.num !== cardPrice) { +// // card.priceHistory = [ +// // ...(card.priceHistory || []), +// // createPriceHistoryObject(cardPrice), +// // ]; +// // } + +// // return card; +// // }; + +// // Update the allCardPrices array based on the new card's price +// const updateAllCardPrices = (card) => { +// return [...selectedCollection.allCardPrices, card.price]; +// }; +// // Update the collection's cards array with the new card +// const updatePriceHistory = (card, newPrice) => { +// const lastPriceEntry = card.priceHistory?.slice(-1)?.[0]; +// return lastPriceEntry && lastPriceEntry.num !== newPrice +// ? [ +// ...card.priceHistory, +// { num: newPrice, timestamp: new Date().toISOString() }, +// ] +// : card.priceHistory; +// }; + +// Only add a new entry if the price has changed +// Refactored addOrRemoveCard function +// const addOrRemoveCard = useCallback( +// async (card, cardInfo, operation) => { +// const collectionId = selectedCollection._id; +// if (!collectionId) { +// console.error('No valid collection selected.'); +// setOpenChooseCollectionDialog(true); +// return; +// } + +// const updatedCards = +// operation === 'update' +// ? updateCardInCollection(selectedCollection.cards, card) +// : getUpdatedCards(selectedCollection, card, operation); + +// const updateInfo = createUpdateInfo(selectedCollection, updatedCards); +// const updatedCollection = await updateActiveCollection( +// selectedCollection._id, +// updateInfo +// ); + +// updateCollectionData(updatedCollection, 'selectedCollection'); +// }, +// [selectedCollection, updateCollectionData, setOpenChooseCollectionDialog] +// ); + +// Function to update the active collection on the backend +// const updateActiveCollection = async (collectionId, updateInfo) => { +// const url = createApiUrl(`${userId}/collections/${collectionId._id}`); + +// try { +// const response = await fetchWrapper(url, 'PUT', updateInfo); +// if (!response.data) +// throw new Error('No collection returned from server.'); +// return response.data; +// } catch (error) { +// console.error(`Failed to update collection: ${error}`); +// throw error; +// } +// }; + +// const getUpdatedCards = (activeCollection, card, operation) => { +// const cardsToUpdate = +// operation === 'add' +// ? handleCardAddition(activeCollection?.cards, card) +// : handleCardRemoval(activeCollection?.cards, card); + +// return cardsToUpdate.map((card) => { +// const cardPrice = card.card_prices?.[0]?.tcgplayer_price; +// const computedPrice = cardPrice * card.quantity; +// console.log('COMPUTED PRICE:', computedPrice); + +// // Update chart_datasets if necessary +// const allDatasets = [ +// ...(card?.chart_datasets || []), +// { x: moment().format('YYYY-MM-DD HH:mm'), y: computedPrice }, +// ]; +// card.chart_datasets = filterOutDuplicateYValues(allDatasets); + +// // Set card's price and total price +// card.price = cardPrice; +// card.totalPrice = computedPrice; + +// // Create a new price history object +// const newPriceHistoryObject = createPriceHistoryObject(cardPrice); + +// // Update priceHistory only if the last entry's num is different from the new price +// if ( +// !card.priceHistory || +// card.priceHistory.length === 0 || +// card.priceHistory[card.priceHistory.length - 1].num !== cardPrice +// ) { +// card.priceHistory = [ +// ...(card.priceHistory || []), +// newPriceHistoryObject, +// ]; +// } +// card.tag = card.tag || 'monitored'; +// return card; +// }); +// }; + +// const createPriceHistoryObject = (price) => ({ +// timestamp: new Date().toISOString(), +// num: price, +// }); + +// // The main function for adding or removing a card +// const addOrRemoveCard = useCallback( +// async (card, cardInfo, operation) => { +// const collectionId = getCollectionId(selectedCollection, allCollections); +// if (!collectionId) { +// console.error('No valid collection selected.'); +// setOpenChooseCollectionDialog(true); +// return; +// } + +// let updatedCards = +// operation === 'update' +// ? updateCardInCollection(selectedCollection.cards, card) +// : getUpdatedCards(selectedCollection, card, operation); + +// const updatedPrice = calculateTotalFromAllCardPrices(updatedCards); +// const accumulatedTotal = selectedCollection.totalPrice + updatedPrice; + +// const newXYValue = { +// label: `Update - ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: accumulatedTotal, +// }; + +// // Update the chart data with the new accumulated total +// const updatedChartData = { +// ...selectedCollection.chartData, +// allXYValues: [...selectedCollection.chartData.allXYValues, newXYValue], +// }; + +// const updateInfo = createUpdateInfo( +// updatedCards, +// accumulatedTotal, +// cardInfo, +// userId, +// selectedCollection, +// collectionId, +// updatedChartData, +// xyData +// ); +// console.log('[UPDATE INFO]:', updateInfo); +// console.log('[ACCUMULATED]:', accumulatedTotal); +// const updatedCollection = { +// ...selectedCollection, +// ...updateInfo, +// chartData: updatedChartData, +// // totalPrice: accumulatedTotal, +// collectionPriceHistory: [ +// ...(selectedCollection.collectionPriceHistory || []), +// createPriceHistoryObject(accumulatedTotal), +// ], +// }; + +// console.log('[COLLECTION DATA][B4UPDATE]:', updatedCollection); +// await updateActiveCollection(updatedCollection, updateInfo.chartData); +// updateCollectionData(updatedCollection, 'selectedCollection'); +// updateCollectionData(updatedCollection, 'collectionData'); +// }, +// [ +// selectedCollection, +// allCollections, +// userId, +// openChooseCollectionDialog, +// handleCardAddition, +// handleCardRemoval, +// updateCollectionData, +// setOpenChooseCollectionDialog, +// ] +// ); + +// const updateCardCollection = async ( +// selectedCollection, +// priceData, +// operation +// ) => { +// if (operation !== 'update') { +// console.warn('Invalid operation:', operation); +// return; +// } + +// const updatedCards = priceData.data.data; +// let accumulatedTotal = selectedCollection.totalPrice || 0; +// let newXYValues = [...selectedCollection.chartData.allXYValues]; + +// updatedCards.forEach((card) => { +// // Only add to priceHistory if the num value is different from the last entry +// const lastPriceHistoryNum = card.priceHistory?.slice(-1)[0]?.num; +// if (card.latestPrice?.num !== lastPriceHistoryNum) { +// const newPriceHistoryEntry = { +// num: card.latestPrice?.num || 0, +// timestamp: new Date().toISOString(), +// }; +// card.priceHistory = [ +// ...(card.priceHistory || []), +// newPriceHistoryEntry, +// ]; +// } + +// card.lastSavedPrice = { +// num: card.lastSavedPrice?.num || card.latestPrice?.num || 0, +// timestamp: card.lastSavedPrice?.timestamp || new Date().toISOString(), +// }; + +// card.latestPrice = { +// num: card.latestPrice?.num || 0, +// timestamp: new Date().toISOString(), +// }; + +// card.tag = card.tag || 'monitored'; + +// // Calculate the new total price +// accumulatedTotal += card.latestPrice.num * card.quantity; + +// // Update the chart data +// newXYValues.push({ +// label: `Update - ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: accumulatedTotal, +// }); +// }); + +// // Update the chart data with accumulated values +// const updatedChartData = { +// ...selectedCollection.chartData, +// allXYValues: newXYValues, +// }; + +// const updatedCollectionWithChartData = { +// ...selectedCollection, +// cards: updatedCards, +// totalPrice: accumulatedTotal, +// chartData: updatedChartData, +// }; + +// await updateActiveCollection(updatedCollectionWithChartData); +// return updatedCollectionWithChartData; +// }; + +// const updateCollectionChartData = ( +// collection, +// updatedPrice, +// updatedChartData +// ) => { +// const chartData = collection.chartData || { +// name: `Chart for Collection: ${collection.name}`, +// userId: collection.userId, +// datasets: [], +// allXYValues: [], +// xys: [], +// }; + +// const newXYValue = { +// label: `Update - ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: updatedPrice, +// }; + +// // Update the allXYValues array with the new data point +// const allXYValues = [...collection.chartData.allXYValues, newXYValue]; + +// const uniqueFilteredXYValues = getUniqueFilteredXYValues(allXYValues); + +// return { +// ...collection, +// chartData: { +// ...collection.chartData, +// allXYValues: uniqueFilteredXYValues, +// }, +// totalPrice: updatedPrice, +// }; +// }; + +// Create a new XY value for the current update +// const newXYValue = { +// label: `Update ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: updatedPrice, +// additionalPriceData: { +// priceChanged: updatedPrice !== collection.totalPrice, +// initialPrice: collection.totalPrice, +// updatedPrice: updatedPrice, +// priceDifference: updatedPrice - collection.totalPrice, +// priceChange: parseFloat( +// ((updatedPrice - collection.totalPrice) / collection.totalPrice) * 100 +// ).toFixed(2), +// }, +// }; + +// Update datasets if newDataSet is provided +// if (newDataSet && Array.isArray(newDataSet.data)) { +// newDataSet.data.forEach((dataset) => { +// if (dataset.xys && Array.isArray(dataset.xys)) { +// chartData.datasets.push(dataset); +// } +// }); +// } +// if (newDataSet && Array.isArray(newDataSet.data)) { +// newDataSet.data.forEach((dataset) => { +// if (dataset.xys && Array.isArray(dataset.xys)) { +// chartData.datasets.push(...dataset.xys); +// } +// }); +// } +// Return the updated collection with the new chartData +// const updateCardCollection = async ( +// selectedCollection, +// priceData, +// operation +// ) => { +// const priceArray = priceData.data.data; +// let updatedCards = selectedCollection.cards.map((card) => { +// const matchingNewCard = priceArray.find( +// (newCard) => newCard.id === card.id +// ); +// if (matchingNewCard) { +// const { latestPrice, priceHistory, name, quantity, tag, _id } = +// matchingNewCard; +// return { ...card, latestPrice, priceHistory, name, quantity, tag, _id }; +// } +// return card; +// }); + +// let newTotalPrice = updatedCards.reduce( +// (total, card) => total + (card.latestPrice?.num * card.quantity || 0), +// 0 +// ); + +// let newAllCardPrices = updatedCards.flatMap((card) => +// Array(card.quantity).fill(card.latestPrice?.num) +// ); + +// const newDataSet = { +// data: [ +// { +// xys: [ +// { +// label: `Update ${new Date().toISOString()}`, +// x: new Date().toISOString(), +// y: newTotalPrice, +// }, +// ], +// }, +// ], +// }; + +// const updatedCollectionWithChartData = updateCollectionChartData( +// selectedCollection, +// newTotalPrice, +// newDataSet +// ); + +// await updateActiveCollection(updatedCollectionWithChartData); + +// return updatedCollectionWithChartData; +// }; + +// function updateChartDataForCollection(collection, chartData) { +// if (!collection.chartData) { +// collection.chartData = { +// name: `Chart for Collection: ${collection.name}`, +// userId: collection.userId, +// datasets: [], +// allXYValues: [], +// }; +// } + +// if (chartData) { +// collection.chartData = { ...collection.chartData, ...chartData }; +// } + +// const newXYSData = getUniqueFilteredXYValues( +// collection.chartData.allXYValues +// ); +// const timestamp = new Date(); +// const newPrice = collection.totalPrice; +// const previousPrice = collection.previousDayTotalPrice || newPrice; +// const priceDifference = newPrice - previousPrice; +// const priceChange = previousPrice +// ? (priceDifference / previousPrice) * 100 +// : 0; + +// collection.chartData.allXYValues.push({ +// label: `Update ${timestamp.toISOString()}`, +// x: timestamp, +// y: newPrice, +// }); + +// const newDatasetEntry = { +// name: collection.name, +// data: newXYSData.map((xy) => ({ +// ...xy, +// additionalPriceData: { +// priceChanged: priceDifference !== 0, +// initialPrice: previousPrice, +// updatedPrice: newPrice, +// priceDifference: priceDifference, +// priceChange: parseFloat(priceChange.toFixed(2)), +// }, +// })), +// }; + +// collection.chartData.datasets.push(newDatasetEntry); + +// return collection; +// } +// useEffect(() => { +// if (!selectedCollection || typeof totalPrice !== 'number') return; + +// const updateNumber = +// selectedCollection.chartData?.datasets?.length + 1 || 1; +// const newDataSet = createNewDataSet(updateNumber, totalPrice); + +// const updatedChartData = updateCollectionChartData( +// selectedCollection, +// totalPrice, +// newDataSet +// ); + +// const updatedCollection = { +// ...selectedCollection, +// chartData: updatedChartData, +// totalPrice, +// allCardPrices, +// }; + +// updateActiveCollection(updatedCollection); +// }, [totalPrice, selectedCollection, allCardPrices]); + +// const createNewDataSet = (updateNumber, totalPrice) => ({ +// data: [ +// { +// xys: [ +// { +// label: `Update Number ${updateNumber}`, +// data: { x: new Date().toISOString(), y: totalPrice }, +// }, +// ], +// additionalPriceData: {}, // Additional data can be added here +// }, +// ], +// }); + +// Additional functions and context setup as necessary + +// const updateActiveCollection = useCallback( +// async (collectionData, existingChartData = {}) => { +// if (!collectionData) throw new Error('No collection data provided.'); + +// const isCreatingNew = !collectionData?._id; +// const actionDescription = isCreatingNew +// ? 'create a new collection' +// : 'update the existing collection'; +// const method = determineHttpMethod( +// isCreatingNew, +// collectionData?.endpoint +// ); + +// try { +// if (isCreatingNew) { +// console.log( +// `Skipping fetch call to ${method} since it's a new collection` +// ); +// updateChartDataForCollection(collectionData, existingChartData); +// } else { +// const endpoint = createApiUrl( +// `${userId}/collections/${collectionData._id}` +// ); +// console.log('ENDPOINT:', endpoint); + +// // Calculate the difference between the existing chart data and the new collection data +// const payload = constructPayloadWithDifferences( +// existingChartData, +// collectionData +// ); + +// console.log(`Debug: ${method} ${endpoint}`); +// const response = await fetchWrapper(endpoint, method, payload); +// console.log('RESPONSE:', response); +// const updatedCollection = response?.data; +// console.log('[COLLECTION DATA][A4UPDATE]:', updatedCollection); + +// if (!updatedCollection) +// throw new Error('No collection returned from server.'); + +// updateChartDataForCollection(updatedCollection, existingChartData); +// updateCollectionData(updatedCollection, 'selectedCollection'); +// updateCollectionData(updatedCollection, 'collectionData'); +// updateCollectionData(updatedCollection, 'allCollections'); +// } +// } catch (error) { +// console.error( +// `Failed to update collection for user ${userId} with collectionId ${collectionData._id}:`, +// error +// ); +// logError('updateActiveCollection', actionDescription, error); +// console.error(`Failed to ${actionDescription}: ${error.message}`); +// } +// }, +// [userId, updateCollectionData] +// ); + +// // Helper function to construct payload with only the differences +// function constructPayloadWithDifferences(existingData, newData) { +// const payload = {}; +// Object.keys(newData).forEach((key) => { +// if (newData[key] !== existingData[key]) { +// payload[key] = newData[key]; +// } +// }); +// return payload; +// } + +// function updateChartDataForCollection(collection, chartData) { +// if (!collection.chartData) { +// collection.chartData = { +// // Initialize with defaults if not present +// name: `Chart for Collection: ${collection.name}`, +// userId: collection.userId, +// datasets: [], +// allXYValues: [], +// }; +// } + +// // Merge in any additional chartData that was passed in +// if (chartData) { +// collection.chartData = { ...collection.chartData, ...chartData }; +// } + +// // Get new x and y values +// const newXYSData = getUniqueFilteredXYValues( +// collection.chartData.allXYValues +// ); + +// // Calculate new and previous price +// const timestamp = new Date(); +// const newPrice = collection.totalPrice; // Fixed the code to get the correct value +// const previousPrice = collection.previousDayTotalPrice || newPrice; // Assuming previousDayTotalPrice is a property +// const priceDifference = newPrice - previousPrice; +// const priceChange = previousPrice +// ? (priceDifference / previousPrice) * 100 +// : 0; + +// // Update the allXYValues field with the new data point +// collection.chartData.allXYValues.push({ +// label: `Update ${new Date().toISOString()}`, +// x: new Date(), +// y: newPrice, +// }); + +// const newDatasetEntry = { +// name: collection.name, // or some other way to name the dataset +// data: [ +// { +// xys: [ +// // newXYSData.map((xy) => ({ +// { +// label: `Update ${timestamp.toISOString()}`, +// data: { x: timestamp, y: newPrice }, +// }, +// // })), +// ], +// additionalPriceData: [ +// { +// priceChanged: priceDifference !== 0, +// initialPrice: previousPrice, +// updatedPrice: newPrice, +// priceDifference: priceDifference, +// priceChange: parseFloat(priceChange.toFixed(2)), +// }, +// ], +// }, +// ], +// }; + +// // Push the new Dataset to the datasets array +// collection.chartData.datasets.push(newDatasetEntry); + +// // Return the updated collection object +// return collection; +// } diff --git a/src/context/CollectionContext/fmljsx b/src/context/CollectionContext/fmljsx new file mode 100644 index 0000000..1587fa4 --- /dev/null +++ b/src/context/CollectionContext/fmljsx @@ -0,0 +1 @@ +isMountedisMountedisMountedisMountedisMounted \ No newline at end of file diff --git a/src/context/CollectionContext/validateCollection.jsx b/src/context/CollectionContext/validateCollection.jsx new file mode 100644 index 0000000..4e1b10c --- /dev/null +++ b/src/context/CollectionContext/validateCollection.jsx @@ -0,0 +1,68 @@ +function validateCollection(collection) { + const warnings = []; + + // Utility to check if a value matches the type + function checkType(value, type, fieldName) { + if (typeof value !== type) { + warnings.push( + `Warning: Field "${fieldName}" should be of type "${type}".` + ); + } + } + + if ( + !collection.userId || + !mongoose.Types.ObjectId.isValid(collection.userId) + ) { + warnings.push('Warning: "userId" is missing or invalid.'); + } + if (typeof collection.name !== 'string') { + warnings.push('Warning: "name" is missing or not a string.'); + } + // ... continue with other fields + + // For nested objects like priceEntrySchema, you would have to check each field + if (collection.latestPrice) { + if (typeof collection.latestPrice.num !== 'number') { + warnings.push('Warning: "latestPrice.num" should be a number.'); + } + if (!(collection.latestPrice.timestamp instanceof Date)) { + warnings.push('Warning: "latestPrice.timestamp" should be a Date.'); + } + } + + // For arrays, you would check each element + if (Array.isArray(collection.priceHistory)) { + collection.priceHistory.forEach((entry, index) => { + if (typeof entry.num !== 'number') { + warnings.push( + `Warning: "priceHistory[${index}].num" should be a number.` + ); + } + if (!(entry.timestamp instanceof Date)) { + warnings.push( + `Warning: "priceHistory[${index}].timestamp" should be a Date.` + ); + } + }); + } + + // ... repeat for each field and nested object/array as needed + + if (warnings.length > 0) { + console.warn('Validation warnings:', warnings.join('\n')); + } else { + console.log('No validation warnings. The object is valid.'); + } + + return warnings.length === 0; +} + +// Example usage: +const collectionToValidate = { + userId: '507f191e810c19729de860ea', // This should be a valid ObjectId + name: 'My Collection', + // ... other fields +}; + +validateCollection(collectionToValidate); diff --git a/src/context/CombinedProvider.jsx b/src/context/CombinedProvider.jsx index fd74f69..bc0d6ba 100644 --- a/src/context/CombinedProvider.jsx +++ b/src/context/CombinedProvider.jsx @@ -7,8 +7,9 @@ import React, { useState, } from 'react'; import { useCookies } from 'react-cookie'; -import { useSocket } from './SocketProvider'; import { CollectionContext } from './CollectionContext/CollectionContext'; +import { useSocketContext } from './SocketProvider'; +import CustomLogger from './CutstomLogger'; export const CombinedContext = createContext(); @@ -21,14 +22,21 @@ const initialState = { collectionData: {}, currentChartData: {}, allCollectionsUpdated: {}, + simData: {}, allCollectionData: {}, cronData: {}, finalUpdateData: {}, cardPrices: {}, + eventsTriggered: null, + cardsWithChangedPrice: {}, previousDayTotalPrice: 0, dailyPriceChange: 0, priceDifference: 0, + allCardPrices: {}, + handleCardPricesUpdated: {}, retrievedListOfMonitoredCards: {}, + listOfMonitoredCards: {}, + listOfSimulatedCards: {}, isLoading: false, cronTriggerTimestamps: [], emittedResponses: [], @@ -74,6 +82,38 @@ const filterDuplicatePrices = (data) => { }); }; +function processCardPrices(cardPrices) { + if (!cardPrices || !cardPrices.data || !Array.isArray(cardPrices.data.data)) { + console.error('Invalid cardPrices data structure.'); + return; + } + + const priceArray = []; + let totalPrice = 0; + + cardPrices.data.data.forEach((card) => { + const { latestPrice, quantity } = card; + + if (!latestPrice || !quantity) { + console.error(`Missing price or quantity for card ID: ${card.id}`); + return; + } + + for (let i = 0; i < quantity; i++) { + priceArray.push(latestPrice.num); + totalPrice += latestPrice.num; + } + }); + + console.log('Price Array:', priceArray); + console.log('Total Price:', totalPrice.toFixed(2)); + + // Save priceArray and totalPrice as needed + // For example, you might want to set them to your application's state + + return { priceArray, totalPrice: totalPrice.toFixed(2) }; +} + const isEmpty = (obj) => { return ( [Object, Array].includes((obj || {}).constructor) && @@ -84,7 +124,7 @@ const isEmpty = (obj) => { const validateData = (data, eventName, functionName) => { const dataType = typeof data; console.log( - `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` + `[SUCCESS] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` ); if (data === null || data === undefined) { console.warn( @@ -100,38 +140,53 @@ const validateData = (data, eventName, functionName) => { } return true; }; + export const CombinedProvider = ({ children }) => { - const { userCookie } = useCookies(['userCookie']); + const { user } = useCookies(['user']); const [state, setState] = useState(initialState); - const userId = userCookie?.userID; + const userId = user?.userId; const { selectedCollection, allCollections, + updateOneFromCollection, + processAndUpdateCardPrices, + setAllCardPrices, setAllCollections, updatedPricesFromCombinedContext, + officialCollectionDatasets, + setOfficialCollectionDatasets, setUpdatedPricesFromCombinedContext, } = useContext(CollectionContext); - const socket = useSocket(); - // const createStateUpdaterFunction = (key) => - // useCallback( - // (data) => - // setState((prev) => ({ ...prev, [key]: { ...prev[key], ...data } })), - // [] - // ); - const createStateUpdaterFunction = (key) => - useCallback((data) => setState((prev) => ({ ...prev, [key]: data })), []); + const socket = useSocketContext(); - // ----------- STATE UPDATER FUNCTIONS ----------- + const createStateUpdaterFunction = (key) => + useCallback( + (data) => { + setState((prev) => { + let newData; + if (Array.isArray(data)) { + newData = [...data]; + } else if (typeof data === 'object' && data !== null) { + newData = { ...data }; + } else { + newData = data; + } + return { ...prev, [key]: newData }; + }); + }, + [setState] + ); - // State updater functions generated using the higher-order function const setDataFunctions = { data: createStateUpdaterFunction('chartData'), messageTest: createStateUpdaterFunction('messageTest'), finalUpdateData: createStateUpdaterFunction('finalUpdateData'), chartData: createStateUpdaterFunction('chartData'), existingChartData: createStateUpdaterFunction('existingChartData'), + listOfSimulatedCards: createStateUpdaterFunction('listOfSimulatedCards'), // cardStats: createStateUpdaterFunction('cardStats'), cardPrices: createStateUpdaterFunction('cardPrices'), + allCardPrices: createStateUpdaterFunction('allCardPrices'), retrievedListOfMonitoredCards: createStateUpdaterFunction( 'retrievedListOfMonitoredCards' ), @@ -140,25 +195,18 @@ export const CombinedProvider = ({ children }) => { error: createStateUpdaterFunction('error'), // updatedChartData: createStateUpdaterFunction('updatedChartData'), currentChartData: createStateUpdaterFunction('currentChartData'), + simData: createStateUpdaterFunction('simData'), checkAndUpdateCardPrice: createStateUpdaterFunction( 'checkAndUpdateCardPrice' ), collectionData: createStateUpdaterFunction('collectionData'), allCollectionData: createStateUpdaterFunction('allCollectionData'), emittedResponses: createStateUpdaterFunction('emittedResponses'), - // allCollectionsUpdated: createStateUpdaterFunction('allCollectionsUpdated'), - // updatedCollectionData: createStateUpdaterFunction('updatedCollectionData'), - // allCollectionsUpdated: createStateUpdaterFunction('allCollectionsUpdated'), - // allUpdatedPrices: createStateUpdaterFunction('allUpdatedPrices'), - // allItemTypeData: createStateUpdaterFunction('allItemTypeData'), - // allItemData: createStateUpdaterFunction('allItemData'), - // allItemData2: createStateUpdaterFunction('allItemData2'), - // cardStatsArray: createStateUpdaterFunction('cardStatsArray'), + eventsTriggered: createStateUpdaterFunction('eventsTriggered'), isDelaying: createStateUpdaterFunction('isDelaying'), isCronJobTriggered: createStateUpdaterFunction('isCronJobTriggered'), }; - // setLoader with added error handling and documentation const setLoader = (isLoading) => { if (typeof isLoading !== 'boolean') { console.error('Invalid argument type for setLoader: Expected boolean'); @@ -166,7 +214,7 @@ export const CombinedProvider = ({ children }) => { } setState((prev) => ({ ...prev, isLoading })); }; - // setCronStatus with added error handling and documentation + const setCronStatus = (cronActive) => { if (typeof cronActive !== 'boolean') { console.error( @@ -176,11 +224,16 @@ export const CombinedProvider = ({ children }) => { } setState((prev) => ({ ...prev, cronActive })); }; - // ----------- XXX ----------- + useEffect(() => { + if (state.eventsTriggered) { + console.log(`Handling event: ${state.eventsTriggered.eventName}`); + // Additional logic to handle the event + } + }, [state.eventsTriggered]); + // ----------- XXX ----------- const listOfMonitoredCards = useMemo(() => { const cards = allCollections?.flatMap((collection) => collection?.cards); - // console.log('cards', cards); if (!cards) return []; const uniqueCards = Array.from(new Set(cards.map((card) => card?.id))).map( (id) => cards?.find((card) => card?.id === id) @@ -191,37 +244,35 @@ export const CombinedProvider = ({ children }) => { } if (uniqueCards && uniqueCards.length) { - return uniqueCards?.map((card) => ({ - name: card?.name, - id: card?.id, - previousPrice: card?.price, - updatedPrice: updatedPrices?.[card?.id] || card?.price, - })); - } - }, [allCollections]); - - const retrieveListOfMonitoredCards = useCallback(() => { - const cards = allCollections?.flatMap((collection) => collection?.cards); - // console.log('cards', cards); - const uniqueCards = Array.from(new Set(cards.map((card) => card?.id))).map( - (id) => cards.find((card) => card?.id === id) - ); - let updatedPrices = null; - if (state.cardPrices?.updatedPrices) { - updatedPrices = state.cardPrices?.updatedPrices; - } - - if (uniqueCards && uniqueCards.length) { - return uniqueCards?.map((card) => ({ - name: card?.name, - id: card?.id, - previousPrice: card?.price, - updatedPrice: updatedPrices?.[card?.id] || card?.price, - })); + return uniqueCards?.map((card) => { + const updatedPriceInfo = updatedPrices?.[card?.id]; + return { + _id: card?._id, // Assuming each card has a unique identifier + id: card?.id, + tag: 'monitored', + name: card?.name, + quantity: card?.quantity, + latestPrice: { + num: updatedPriceInfo ? updatedPriceInfo.num : card?.price, + timestamp: updatedPriceInfo + ? updatedPriceInfo.timestamp + : new Date().toISOString(), + // Add _id field for latestPrice if available + }, + lastSavedPrice: { + num: card?.price, + timestamp: new Date().toISOString(), // Modify this based on where you get the last saved timestamp + // Add _id field for lastSavedPrice if available + }, + priceHistory: card?.priceHistory || [], // Assuming priceHistory is part of the original card object + __v: card?.__v, // Version key, if applicable + }; + }); } - }, [allCollections]); + }, [allCollections, state.cardPrices?.updatedPrices]); // ----------- SOCKET EVENT HANDLERS ----------- + const safeEmit = useCallback( (event, data) => { try { @@ -230,12 +281,16 @@ export const CombinedProvider = ({ children }) => { } if (socket) { socket.emit(event, data); + console.log(`[Info] Emitted event: ${event}`); } else { console.warn('Socket is not connected. Cannot emit event:', event); } } catch (error) { console.error(`[Error] Failed to emit event: ${event}`, error); - setDataFunctions.error({ message: error.message, source: 'safeEmit' }); + setDataFunctions.error({ + message: error.message, + source: 'safeEmit', + }); } }, [socket] @@ -247,271 +302,213 @@ export const CombinedProvider = ({ children }) => { try { if (!validateData(data, event, handler.name)) { throw new Error(`Invalid data received for event: ${event}`); - } + } // Add this line to validate the data received + // console.log(`[Info] Handling event: ${event}`); handler(data); } catch (error) { console.error(`[Error] Failed to handle event: ${event}`, error); setDataFunctions.error({ message: error.message, source: event }); } }; - if (socket) { - socket.on(event, wrapper); - } else { - console.warn( - 'Socket is not connected. Cannot subscribe to event:', - event - ); - } + + socket.on(event, wrapper); // Add this line to register the event listener + return () => { - if (socket) { - socket.off(event, wrapper); - } + socket.off(event, wrapper); // Add this line to unregister the event listener when the component unmounts }; }, [socket] ); - useEffect(() => { - if (!socket) return; - const handleReceive = (message) => { - console.log('Received message:', message); - setDataFunctions.messageTest(message); - }; + const mergeUpdates = (currentArray, updates) => { + const updatedArray = [...currentArray]; + updates.forEach((update) => { + const index = updatedArray.findIndex((item) => item.id === update.id); + if (index !== -1) { + updatedArray[index] = { ...updatedArray[index], ...update }; + } else { + updatedArray.push(update); + } + }); + return updatedArray; + }; - const handleCronJobResponse = (message, collections) => { - console.log('MESSAGE: CRON JOB COMPLETE ==========]>:', message); - console.log('COLLECTIONS: CRON JOB COMPLETE ==========]>:', collections); - setDataFunctions.checkAndUpdateCardPrice({ collections }); - }; + const handleStatusUpdateCharts = (newData) => { + const { updates } = newData.data; + console.log('[STATUS_UPDATE_CHARTS] Data:', updates); + const updatedList = mergeUpdates(state.listOfSimulatedCards, updates); + setDataFunctions.listOfSimulatedCards(updatedList); + }; - const handleError = (errorData) => { - console.error('Server Error:', errorData.message); - console.error('Error Source:', errorData.source); - if (errorData.detail) { - console.error('Error Detail:', errorData.detail); - } - setDataFunctions.error(errorData); - }; + const handleReceive = (message) => { + console.log('Received message:', message); + setDataFunctions.messageTest(message); + }; - const handleEmittedResponses = (emittedResponses) => { - setDataFunctions.emittedResponses([ - ...state.emittedResponses, - emittedResponses, - ]); - }; + const handleSimulationPriceResponse = (updates) => { + console.log('Received scheduled price update:', updates); + setDataFunctions.simData(updates); // Update with new data + }; - const handleCronJobTracker = (message, existingCronData) => { - console.log('[AUTOMATED MESSAGE FROM SERVER]', message); - const data = existingCronData; - const filteredRuns = filterDuplicatePrices(data); - const runs = filteredRuns; - setDataFunctions.cronData({ runs }); - }; + const handleEventResponse = (newData) => { + const { message, data } = newData; + console.log('EVENT_RESPONSE:', message, data); + setDataFunctions.eventsTriggered(data); + }; - const handleExistingCollectionData = (userId, selectedCollection) => { - console.log('Received existing collection data:', selectedCollection); - setDataFunctions.collectionData({ selectedCollection }); - }; + const handleStatusUpdatePrice = (data) => { + console.log('STATUS_UPDATE_PRICE:', data); + setDataFunctions.priceDifference(data); + }; - const handleExistingChartData = (data) => { - if (!data || Array.isArray(data?.existingChartData)) { - console.error( - 'Unexpected data structure received for chart data update:', - data - ); - return; - } - const { existingChartData } = data; - setDataFunctions.existingChartData({ - existingChartData: existingChartData, - }); - }; + const handleStatusUpdateCron = (newData) => { + const { message, data } = newData; + console.log('[STATUS_UPDATE_CRON]', message, data); + setDataFunctions.data(data); + }; - const handleChartDatasetsUpdated = ({ - message, - collectionId, - currentChartDatasets, - }) => { - if (message) { - console.log('MESSAGE: HANDLING CHART DATA UPDATED ==========]>:', { - message, - }); - } - const currentChartData = { - [collectionId]: currentChartDatasets, - }; - setDataFunctions.currentChartData(currentChartData); - }; + const handleChartCronResponse = (newData) => { + const message = newData?.message; + const data = newData?.data; + console.log('[handleChartCronResponse]', message, data); + setDataFunctions.retrievedListOfMonitoredCards(data); + }; - const handleCardPricesUpdated = ({ message, data }) => { - if (message) { - console.log('MESSAGE: HANDLING CARD PRICES UPDATED ==========]>:', { - message, - }); - } + const handleCronJobResponse = (data) => { + console.log('Cron job response received:', data); + // Update state or perform other actions based on the cron job response + }; - const { pricingData } = data; + const handleError = (errorData) => { + console.error('Error received:', errorData); + setDataFunctions.error(errorData); + }; - if ( - !validateData( - pricingData, - 'SEND_PRICING_DATA_TO_CLIENT', - 'handleCardPricesUpdated' - ) - ) - return; + const handleEmittedResponses = (emittedData) => { + console.log('Emitted responses:', emittedData); + setDataFunctions.emittedResponses(emittedData); + }; - if ( - !pricingData || - !pricingData.updatedPrices || - !pricingData.previousPrices || - !pricingData.priceDifferences - ) { - console.error( - 'Unexpected data structure received for card prices update:', - pricingData - ); - return; - } + const handleCronJobTracker = (cronData) => { + console.log('Cron job data:', cronData); + setDataFunctions.cronData(cronData); + }; - const { updatedPrices, previousPrices, priceDifferences } = pricingData; + const handleExistingCollectionData = (collectionData) => { + console.log('Existing collection data:', collectionData); + setDataFunctions.collectionData(collectionData); + }; - const allPrices = { - ...updatedPrices, - ...previousPrices, - updatedPrices, - previousPrices, - priceDifferences, - }; + const handleExistingChartData = (chartData) => { + console.log('Existing chart data:', chartData); + setDataFunctions.existingChartData(chartData); + }; - setUpdatedPricesFromCombinedContext(allPrices); - setDataFunctions.cardPrices({ allPrices }); // Here you are passing an object with 'allPrices' property - }; + const handleChartDatasetsUpdated = (chartUpdate) => { + console.log('Chart datasets updated:', chartUpdate); + setDataFunctions.currentChartData(chartUpdate); + }; - const handleNoPricesChanged = ({ message }) => { - console.log('MESSAGE: NO PRICES CHANGED ==========]>:', { - message, - }); - // setUpdatedPricesFromCombinedContext(updatedData); - }; + const handleCardPricesUpdated = async (priceData) => { + console.log('Card prices retrieved:', priceData); + const processedPrices = processCardPrices(priceData); + console.log('Card prices updated:', processedPrices); - const handleNewCardDataObject = ({ message, updatedData }) => { - // setUpdatedPricesFromCombinedContext(updatedData); - console.log('MESSAGE: NEW CARD DATA OBJECT ==========]>:', { - message, - }); - setDataFunctions.data(updatedData); - }; + // Start with the current collection's state + let updatedCollection = { ...selectedCollection }; - const handleFinalUpdateToClient = ({ userId, message, updatedData }) => { - console.log('MESSAGE: FINAL UPDATE TO CLIENT ==========]>:', { - message, - }); - console.log('UPDATED DATA: FINAL UPDATE TO CLIENT ==========]>:', { - updatedData, - }); - // setUpdatedPricesFromCombinedContext(updatedData); - setDataFunctions.finalUpdateData(updatedData); - }; - const handleInitiateScheduleCheckCardPrices = ({ data }) => { - safeEmit('INITIATE_CHECK_CARD_PRICES', { - userId: data.userId, - selectedList: data.selectedList, - }); - }; + // Iterate over the array of processed prices + for (const card of priceData.data.data) { + // Update each card within the collection + updatedCollection = await updateOneFromCollection(card, 'update'); + } - const handleInitiateCheckCardPrices = ({ data }) => { - safeEmit('HANDLE_CHECK_CARD_PRICES', { - userId: data.userId, - selectedList: data.selectedList, - }); - }; + console.log('Updated collection in combined:', updatedCollection); + return updatedCollection; + }; - const handleUpdateUserData = ({ message, data }) => { - console.log('MESSAGE: INITIATING UPDATE OF USER DATA ==========]>:', { - message, - }); - safeEmit('HANDLE_UPDATE_USER_DATA', { - userId: data.userId, - pricingData: data.pricingData, - }); - }; - const handleUpdateUserCollection = ({ data }) => { - safeEmit('HANDLE_UPDATE_USER_COLLECTION', { - userId: data.userId, - updatedData: data.updatedUserData, - }); - }; + const handleNoPricesChanged = () => { + console.log('No prices changed'); + // Perform any necessary actions when no prices have changed + }; - const handleUpdateAndSyncUserCollection = ({ data }) => { - safeEmit('HANDLE_UPDATE_AND_SYNC_COLLECTION', { - userId: data.userId, - collectionId: data.collectionId, - body: data.body, - }); - }; - // const handleUpdateUserCollection = ({ userId, updatedData }) => { - // safeEmit('HANDLE_UPDATE_USER_COLLECTION', { userId, updatedData }); - // }; - // const handleUpdateAndSyncUserCollection = ({ - // userId, - // collectionId, - // body, - // }) => { - // safeEmit('HANDLE_UPDATE_AND_SYNC_COLLECTION', { - // userId, - // collectionId, - // body, - // }); - // }; - - // handleCheckCardPrices(userId, selectedList) { - // socket.emit('HANDLE_CHECK_CARD_PRICES', { userId, selectedList }); - // }, - // handleUpdateUserData(userId, pricingData) { - // socket.emit('HANDLE_UPDATE_USER_DATA', { userId, pricingData }); - // }, - // handleUpdateUserCollection(userId, updatedData) { - // socket.emit('HANDLE_UPDATE_USER_COLLECTION', { userId, updatedData }); - // }, - // handleUpdateAndSyncCollection(userId, collectionId, body) { - // socket.emit('HANDLE_UPDATE_AND_SYNC_COLLECTION', { userId, collectionId, body }); - // }, - const unsubscribeFunctions = [ + const handleNewCardDataObject = (cardData) => { + console.log('New card data received:', cardData); + setDataFunctions.data(cardData); + }; + + const handleFinalUpdateToClient = (finalUpdate) => { + console.log('Final update to client:', finalUpdate); + setDataFunctions.finalUpdateData(finalUpdate); + }; + + const handleInitiateScheduleCheckCardPrices = (scheduleData) => { + console.log('Initiating scheduled check of card prices:', scheduleData); + // Emit event to server or perform other actions + }; + + const handleInitiateCheckCardPrices = (checkPriceData) => { + console.log('Initiating check of card prices:', checkPriceData); + // Emit event to server or perform other actions + }; + + const handleUpdateUserData = (userData) => { + console.log('Updating user data:', userData); + // Emit event to server or perform other actions + }; + + const handleUpdateUserCollection = (collectionUpdate) => { + console.log('Updating user collection:', collectionUpdate); + // Emit event to server or perform other actions + }; + + const handleUpdateAndSyncUserCollection = (syncData) => { + console.log('Updating and syncing user collection:', syncData); + // Emit event to server or perform other actions + }; + useEffect(() => { + if (!socket) return; + + const eventHandlers = new Map([ + ['STATUS_UPDATE_CHARTS', handleStatusUpdateCharts], ['MESSAGE_TO_CLIENT', handleReceive], - ['RESPONSE_CRON_UPDATED_ALLCOLLECTIONS', handleCronJobResponse], + ['SIMULATION_CRON_PRICE_RESPONSE', handleSimulationPriceResponse], + ['EVENT_RESPONSE', handleEventResponse], + ['STATUS_UPDATE_PRICE', handleStatusUpdatePrice], + ['STATUS_UPDATE_CRON', handleStatusUpdateCron], + ['CHART_CRON_RESPONSE', handleChartCronResponse], + ['HANDLE_CRON_JOB_RESPONSE', handleCronJobResponse], ['ERROR', handleError], ['EMITTED_RESPONSES', handleEmittedResponses], ['RESPONSE_CRON_DATA', handleCronJobTracker], ['RESPONSE_EXISTING_COLLECTION_DATA', handleExistingCollectionData], ['RESPONSE_EXISTING_CHART_DATA', handleExistingChartData], + ['CHART_DATASETS_UPDATED', handleChartDatasetsUpdated], ['SEND_PRICING_DATA_TO_CLIENT', handleCardPricesUpdated], + ['NO_PRICE_CHANGES', handleNoPricesChanged], ['SEND_UPDATED_DATA_TO_CLIENT', handleNewCardDataObject], ['SEND_FINAL_UPDATE_TO_CLIENT', handleFinalUpdateToClient], - ['CHART_DATASETS_UPDATED', handleChartDatasetsUpdated], [ 'INITIATE_SCHEDULE_CHECK_CARD_PRICES', handleInitiateScheduleCheckCardPrices, ], ['INITIATE_HANDLE_CHECK_CARD_PRICES', handleInitiateCheckCardPrices], ['INITIATE_UPDATE_USER_DATA', handleUpdateUserData], - // ['INITIATE_UPDATE_USER_COLLECTION', handleUpdateUserCollection], ['INITIATE_UPDATE_USER_COLLECTIONS_SOCKET', handleUpdateUserCollection], - ['NO_PRICE_CHANGES', handleNoPricesChanged], ['COLLECTION_SYNCED', handleUpdateAndSyncUserCollection], - ['CRON_JOB_TRIGGERED', () => setDataFunctions.isCronJobTriggered(true)], - ['CRON_JOB_COMPLETED', () => setDataFunctions.isCronJobTriggered(false)], + // ... any other event handlers + ]); - // ... (other event subscriptions) - ].map(([event, handler]) => safeOn(event, handler)); - return () => unsubscribeFunctions.forEach((unsubscribe) => unsubscribe()); - }, [ - socket, - safeEmit, - safeOn, - setDataFunctions.isCronJobTriggered, - setUpdatedPricesFromCombinedContext, - ]); + eventHandlers.forEach((handler, event) => { + socket.on(event, handler); + }); + + return () => { + eventHandlers.forEach((_, event) => { + socket.off(event); + }); + }; + }, [socket, state, setDataFunctions]); // ----------- DATA PROCESSING & HANDLERS ----------- @@ -526,7 +523,7 @@ export const CombinedProvider = ({ children }) => { } socket.emit('REQUEST_EXISTING_COLLECTION_DATA', { userId, - selectedCollection: selectedCollection, + data: selectedCollection, }); }, chart: (userId, selectedCollection) => { @@ -554,7 +551,7 @@ export const CombinedProvider = ({ children }) => { socket.emit('REQUEST_EXISTING_CHART_DATA', { data: { userId, - chartData: chartData, + data: chartData, }, }); }, @@ -562,7 +559,7 @@ export const CombinedProvider = ({ children }) => { sendAction: { message: (message) => { if (!message) return console.error('Message content is missing.'); - socket.emit('MESSAGE_FROM_CLIENT', { message }); + socket.emit('MESSAGE_FROM_CLIENT', { message, data: message }); }, stopCronJob: (userId) => { if (!userId) return console.error('Missing userId for cron job stop.'); @@ -570,19 +567,45 @@ export const CombinedProvider = ({ children }) => { }, checkAndUpdateCardPrices: ( userId, - listOfMonitoredCards, - retrievedListOfMonitoredCards + listOfMonitoredCards + // retrievedListOfMonitoredCards ) => { if (!userId) return console.error('Missing userId or listOfMonitoredCards.'); - if (!retrievedListOfMonitoredCards || !listOfMonitoredCards) + if (!listOfMonitoredCards) return console.error('Missing retrievedListOfMonitoredCards.'); - const selectedList = listOfMonitoredCards - ? listOfMonitoredCards - : retrievedListOfMonitoredCards; + const selectedList = listOfMonitoredCards; socket.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { - data: { userId, selectedList }, + 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.'); + // if (!cardsWithChangedPrice) + // return console.log('Missing cardsWithChangedPrice.'); + + const selectedList = listOfMonitoredCards; + socket.emit('REQUEST_PRICES_ACTIVATE_CRON', { + userId, + data: { + userId, + selectedList, + allCollections, + cardsWithChangedPrice, + }, }); }, triggerCronJob: (userId, listOfMonitoredCards) => { @@ -613,8 +636,8 @@ export const CombinedProvider = ({ children }) => { // handleSocketInteraction.sendAction.updateChart(); handleSocketInteraction.sendAction.checkAndUpdateCardPrices( userId, - listOfMonitoredCards, - retrieveListOfMonitoredCards() + listOfMonitoredCards + // retrieveListOfMonitoredCards() ); } }, [userId, selectedCollection, socket]); @@ -635,9 +658,9 @@ export const CombinedProvider = ({ children }) => { setDataFunctions.allCollectionData(allCollections); } - setDataFunctions.retrievedListOfMonitoredCards( - retrieveListOfMonitoredCards() - ); + // setDataFunctions.retrievedListOfMonitoredCards( + // retrieveListOfMonitoredCards() + // ); } }, [allCollections]); @@ -657,6 +680,7 @@ export const CombinedProvider = ({ children }) => { const value = useMemo( () => ({ ...state, + ...setDataFunctions, listOfMonitoredCards, // emittedResponses: state.emittedResponses, // messageTest: state.messageTest, @@ -672,7 +696,11 @@ export const CombinedProvider = ({ children }) => { handleSendAllCardsInCollections: handleSocketInteraction.sendAction.checkAndUpdateCardPrices, // Ensure it's provided here handleRequestCronStop: handleSocketInteraction.sendAction.stopCronJob, // Ensure it's provided here - handleRetreiveListOfMonitoredCards: retrieveListOfMonitoredCards, + // handleRetreiveListOfMonitoredCards: retrieveListOfMonitoredCards, + + // MAIN LOGGER + handlePricesActivateCron: + handleSocketInteraction.sendAction.checkPriceUpdates, handleSocketInteraction, setDataFunctions, socket, @@ -683,9 +711,32 @@ export const CombinedProvider = ({ children }) => { [state, socket] ); + useEffect(() => { + console.log('COMBINED CONTEXT VALUE:', state); + }, [ + state.allData, + state.data, + state.messageTest, + state.existingChartData, + state.collectionData, + state.dcurrentChartData, + state.cronData, + state.finalUpdateData, + state.cardPrices, + state.eventsTriggered, + state.cardsWithChangedPrice, + state.previousDayTotalPrice, + state.dailyPriceChange, + state.priceDifference, + state.allCardPrices, + state.handleCardPricesUpdated, + state.retrievedListOfMonitoredCards, + ]); + const dataValues = { state: state, allData: value.allData, + eventsTriggered: value.eventsTriggered, data: value.data, messageTest: value.messageTest, finalUpdateData: value.finalUpdateData, @@ -703,14 +754,18 @@ export const CombinedProvider = ({ children }) => { error: value.error, }; - useEffect(() => { - console.log('COMBINED CONTEXT VALUE:', dataValues); - }, [ - dataValues.cronData, - dataValues.chartData, - dataValues.collectionData, - dataValues.messageTest, - ]); + // useEffect(() => { + // console.log('COMBINED CONTEXT VALUE:', dataValues); + // }, [ + // dataValues.cronData, + // dataValues.chartData, + // dataValues.collectionData, + // dataValues.messageTest, + // dataValues.emittedResponses, + // dataValues.retrievedListOfMonitoredCards, + // dataValues.emittedResponses, + // dataValues.eventsTriggered, + // ]); return ( diff --git a/src/context/CutstomLogger.jsx b/src/context/CutstomLogger.jsx new file mode 100644 index 0000000..fb746c0 --- /dev/null +++ b/src/context/CutstomLogger.jsx @@ -0,0 +1,23 @@ +let logCounter = 0; + +function CustomLogger(data) { + logCounter += 1; + const dataType = Array.isArray(data) ? 'Array' : typeof data; + let formattedData = ''; + + if (dataType === 'object') { + formattedData = JSON.stringify(data, null, 2); + } else if (dataType === 'Array') { + formattedData = data + .map((item) => JSON.stringify(item, null, 2)) + .join('\n'); + } else { + formattedData = data.toString(); + } + + console.log(`[LOG ${logCounter}] --- [Type: ${dataType}] -----------------`); + console.log(formattedData); + console.log(`[END OF LOG ${logCounter}] ---------------------------------`); +} + +export default CustomLogger; diff --git a/src/context/DeckContext/DeckContext.js b/src/context/DeckContext/DeckContext.js index 1731712..13822bf 100644 --- a/src/context/DeckContext/DeckContext.js +++ b/src/context/DeckContext/DeckContext.js @@ -233,6 +233,10 @@ export const DeckProvider = ({ children }) => { const foundCard = selectedDeck?.cards?.find((item) => item.id === cardId); return foundCard?.quantity || 0; }; + const getCardQuantity = (cardId) => { + const foundCard = selectedDeck?.cards?.find((item) => item.id === cardId); + return foundCard?.quantity || 0; + }; const contextValue = { deckData, @@ -245,10 +249,7 @@ export const DeckProvider = ({ children }) => { getTotalCost: () => selectedDeck?.cards?.reduce((acc, card) => acc + (card.cost || 0), 0) || 0, - getCardQuantity: (cardId) => { - const foundCard = selectedDeck?.cards?.find((item) => item.id === cardId); - return foundCard?.quantity || 0; - }, + getCardQuantity: getCardQuantity, updateAndSyncDeck, fetchAllDecksForUser: fetchAndSetDecks, }; diff --git a/src/context/SocketProvider.jsx b/src/context/SocketProvider.jsx index 9f2c688..5a62a1f 100644 --- a/src/context/SocketProvider.jsx +++ b/src/context/SocketProvider.jsx @@ -9,9 +9,9 @@ import io from 'socket.io-client'; const SocketContext = createContext(); -export const useSocket = () => { - return useContext(SocketContext); -}; +// export const useSocket = () => { +// return useContext(SocketContext); +// }; export const SocketProvider = ({ children }) => { const [socket, setSocket] = useState(null); @@ -34,3 +34,11 @@ export const SocketProvider = ({ children }) => { {children} ); }; + +export const useSocketContext = () => { + const context = useContext(SocketContext); + if (context === undefined) { + throw new Error('useSocketContext must be used within a SocketProvider'); + } + return context; +}; diff --git a/src/context/UserContext/UserContext.js b/src/context/UserContext/UserContext.js index 458788e..148159b 100644 --- a/src/context/UserContext/UserContext.js +++ b/src/context/UserContext/UserContext.js @@ -18,7 +18,7 @@ export const UserProvider = ({ children }) => { const [allCollections, setAllCollections] = useState([]); const { user, setUser } = useAuthContext(); // Use the useAuthContext hook - const { fetchCollections, fetchAllCollectionsForUser } = useCollectionStore(); + // const { fetchCollections, fetchAllCollectionsForUser } = useCollectionStore(); const triggerCronJob = async () => { // Add your code here diff --git a/src/context/hooks/collection.jsx b/src/context/hooks/collection.jsx index 4220c90..9e1fc10 100644 --- a/src/context/hooks/collection.jsx +++ b/src/context/hooks/collection.jsx @@ -1,13 +1,13 @@ -// useCollectionStore.js -import { useContext } from 'react'; -import { CollectionContext } from '../CollectionContext/CollectionContext'; +// // useCollectionStore.js +// import { useContext } from 'react'; +// import { CollectionContext } from '../CollectionContext/CollectionContext'; -export const useCollectionStore = () => { - const context = useContext(CollectionContext); - if (!context) { - throw new Error( - 'useCollectionStore must be used within a CollectionProvider' - ); - } - return context; -}; +// export const useCollectionStore = () => { +// const context = useContext(CollectionContext); +// if (!context) { +// throw new Error( +// 'useCollectionStore must be used within a CollectionProvider' +// ); +// } +// return context; +// }; diff --git a/src/context/hooks/useAppContext.jsx b/src/context/hooks/useAppContext.jsx new file mode 100644 index 0000000..125e6b7 --- /dev/null +++ b/src/context/hooks/useAppContext.jsx @@ -0,0 +1,16 @@ +// customHooks/useAppContext.js +import { useContext } from 'react'; +import { AppContext } from '../AppContextProvider'; + +const useAppContext = () => { + const context = useContext(AppContext); + + if (!context) { + console.error("The component isn't wrapped with AppContextProvider"); + return [null, false]; + } + + return [context, true]; +}; + +export default useAppContext; diff --git a/src/context/hooks/useDialog.jsx b/src/context/hooks/useDialog.jsx new file mode 100644 index 0000000..5208849 --- /dev/null +++ b/src/context/hooks/useDialog.jsx @@ -0,0 +1,32 @@ +// useDialog.js +import { useState, useCallback } from 'react'; + +const useDialog = (handleSnackbar, onClose) => { + const [isOpen, setIsOpen] = useState(false); + + const openDialog = useCallback(() => { + setIsOpen(true); + }, []); + + const closeDialog = useCallback(() => { + setIsOpen(false); + onClose?.(); + }, [onClose]); + + const handleCloseDialog = useCallback( + (event, reason) => { + if ( + reason && + (reason === 'backdropClick' || reason === 'escapeKeyDown') + ) { + handleSnackbar('Operation cancelled, no changes were made.'); + } + closeDialog(); + }, + [closeDialog, handleSnackbar] + ); + + return { isOpen, openDialog, handleCloseDialog, closeDialog }; +}; + +export default useDialog; diff --git a/src/context/hooks/useSnackBar.jsx b/src/context/hooks/useSnackBar.jsx new file mode 100644 index 0000000..6978e21 --- /dev/null +++ b/src/context/hooks/useSnackBar.jsx @@ -0,0 +1,28 @@ +// useSnackbar.js +import { useState, useCallback } from 'react'; + +const useSnackbar = () => { + const [snackbar, setSnackbar] = useState({ + open: false, + message: '', + severity: 'info', // default severity + }); + + const handleSnackbar = useCallback((message, severity = 'info') => { + setSnackbar({ open: true, message, severity }); + }, []); + + const handleCloseSnackbar = useCallback( + (event, reason) => { + if (reason === 'clickaway') { + return; + } + setSnackbar({ ...snackbar, open: false }); + }, + [snackbar] + ); + + return [snackbar, handleSnackbar, handleCloseSnackbar]; +}; + +export default useSnackbar; diff --git a/src/index.js b/src/index.js index 0cf2588..435859a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './assets/styles/index.css'; import App from './App'; import GlobalStyles from './assets/GlobalStyles'; import AuthProvider from './context/Auth/authContext'; @@ -20,12 +19,19 @@ import { SocketProvider } from './context/SocketProvider'; import { SidebarProvider } from './context/SideBarProvider'; import { ChartProvider } from './context/ChartContext/ChartContext'; import { UtilityProvider } from './context/UtilityContext/UtilityContext'; +import { AppContextProvider } from './context/AppContextProvider'; +import { createTheme } from '@mui/material'; const root = document.getElementById('root'); function Main() { - const { theme } = useMode(); - + // const { theme } = useMode(); + const darkTheme = createTheme({ + palette: { + mode: 'dark', + }, + }); + const theme = darkTheme; return ( @@ -45,7 +51,9 @@ function Main() { - + + + diff --git a/src/pages/CollectionPage.js b/src/pages/CollectionPage.js index 1993fd3..6e427ec 100644 --- a/src/pages/CollectionPage.js +++ b/src/pages/CollectionPage.js @@ -1,43 +1,89 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { useCookies } from 'react-cookie'; import LoadingIndicator from '../components/indicators/LoadingIndicator'; import ErrorIndicator from '../components/indicators/ErrorIndicator'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; import CollectionBanner from './pageStyles/CollectionBanner'; -import CollectionTitle from './pageStyles/CollectionTitle'; import CardPortfolio from '../components/collection/CardPortfolio'; -import { useCollectionStore } from '../context/hooks/collection'; -import HeaderTitle from '../components/reusable/HeaderTitle'; import Subheader from '../components/reusable/Subheader'; +import { useCollectionStore } from '../context/CollectionContext/CollectionContext'; +// Hero section with customizable props +const HeroCenter = ({ decorative, title, subtitle }) => ( + + + {decorative} + + + {title} + + + {subtitle} + + +); + +// Default props for the HeroCenter component for ease of use and maintainability +HeroCenter.defaultProps = { + decorative: 'All-in-One', + title: "Your Collection's Home", + subtitle: + // eslint-disable-next-line max-len + 'Welcome to your collection! Here you can view all of your cards, add new cards, and remove cards from your collection.', +}; + +// Main collection page component const CollectionPage = () => { - // const [defaultCollection, setDefaultCollection] = useState([]); - const { userCookie } = useCookies(['userCookie'])[0]; - const { - allCollections = [], - setAllCollections, - selectedCollection, - setSelectedCollection, - loading, - calculateTotalPrice, - error, - } = useCollectionStore(); + // Destructuring the first element directly from the useCookies hook for cleaner code + const [{ userCookie }] = useCookies(['userCookie']); + const { allCollections, selectedCollection, loading, error } = + useCollectionStore(); + const userId = userCookie?.id; + // Handling loading and error states upfront for better user experience if (loading) return ; if (error) return ; return ( -
- - - - - -
+ + + + + ); }; diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js index 72103ca..8affe01 100644 --- a/src/pages/HomePage.js +++ b/src/pages/HomePage.js @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useRef } from 'react'; import { Carousel } from 'react-responsive-carousel'; import 'react-responsive-carousel/lib/styles/carousel.min.css'; -import { Container, Typography, Box } from '@mui/material'; +import { Container, Typography, Box, Stack, Button } from '@mui/material'; import { makeStyles, useTheme } from '@mui/styles'; import { ColorModeContext } from '../context/ColorModeProvider'; import { useMode } from '../context/hooks/colormode'; @@ -109,11 +109,49 @@ const HomePage = () => { }} maxWidth="md" > - + /> */} + + + + Album layout + + + Something short and leading about the collection below—its + contents, the creator, etc. Make it short and sweet, but not too + short so folks don't simply skip over it entirely. + + + + + + + { const { selectedCollection } = useCollectionStore(); const { user, updateUser } = useUserContext(); - const [cookies] = useCookies(['userCookie']); + const [cookies] = useCookies(['user']); const [snackbarData, setSnackbarData] = useState({ open: false, message: '', @@ -50,8 +75,8 @@ const ProfilePage = () => { const openSnackbar = (message) => { setSnackbarData({ open: true, message }); }; - const userId = user?.userID; - // console.log('USER ID:', userId); + const userId = user?.id; + console.log('USER ID:', userId); // console.log('USER:', user); const handleSaveChanges = useCallback( (data) => { @@ -82,30 +107,10 @@ const ProfilePage = () => { }; const handleTriggerCronJob = () => { - // if (userId) { - // handleCronRequest(userId); - // console.log('TRIGGERING CRON JOB'); - // } - if (userId && listOfMonitoredCards && retrievedListOfMonitoredCards) { - handleSendAllCardsInCollections( - userId, - listOfMonitoredCards, - retrievedListOfMonitoredCards - ); + if (userId && listOfMonitoredCards) { + handleSendAllCardsInCollections(userId, listOfMonitoredCards); console.log('SENDING ALL CARDS IN COLLECTIONS'); openSnackbar('Triggered the cron job.'); - } else if ( - userId && - listOfMonitoredCards && - !retrievedListOfMonitoredCards - ) { - console.log('RETRIEVING LIST OF MONITORED CARDS'); - handleSendAllCardsInCollections( - userId, - listOfMonitoredCards, - handleRetreiveListOfMonitoredCards() - ); - openSnackbar('Triggered the cron job.'); } }; @@ -128,52 +133,57 @@ const ProfilePage = () => { - + + + Send Message to Server - + - Request Collection Data - + - Request Chart Data - + - Trigger Cron Job - - + Stop Cron Job - - - + + + + - + + Date: Thu, 16 Nov 2023 03:04:54 -0800 Subject: [PATCH 07/10] add --- .../actionButtons/CardActionButtons.jsx | 2 - .../actionButtons/ChooseCollectionDialog.jsx | 239 ---- .../buttons/{ => other}/CronTrigger.jsx | 4 +- .../buttons/{ => other}/ThemeToggleButton.jsx | 2 +- src/components/cards/GenericCard.jsx | 7 +- .../CollectionActionButtons.jsx | 2 +- .../{dialogs => cleanUp}/CollectionDialog.jsx | 0 .../{cards => cleanUp}/CustomPopover.jsx | 4 +- .../cleanUp/SelectCollectionDialog.jsx | 133 ++ .../{buttons => cleanUp}/button/Button.css | 0 .../{buttons => cleanUp}/button/Button.js | 0 src/components/collection/CardPortfolio.jsx | 26 - .../collection/SelectCollection.jsx | 4 +- .../dialogs/ChooseCollectionDialog.jsx | 101 ++ ...g.jsx => CreateOrEditCollectionDialog.jsx} | 14 +- src/components/dialogs/LoginDialog.jsx | 13 - src/components/dialogs/dialogStyles.jsx | 0 src/components/forms/formStyles.jsx | 23 + .../grids/collectionGrids/CardList.jsx | 8 +- src/components/media/CardMediaAndDetails.jsx | 2 +- .../other/CollectionValueTracker.jsx | 52 +- src/components/other/LinearChart.js | 187 +-- src/components/other/PortfolioChart.jsx | 89 +- src/components/other/StatisticsArea.jsx | 41 + src/components/other/TimeRangeSelector.jsx | 34 +- src/components/other/chartUtils.jsx | 318 ++++- .../CardDetailsContainer.jsx | 2 +- src/containers/PortfolioChartContainer.jsx | 4 +- src/context/ChartContext/ChartContext.jsx | 1 + src/context/CollectionContext/ChunkPaylod.jsx | 29 + .../CollectionContext/CollectionContext.jsx | 1245 ++--------------- .../CollectionContext/collectionUtility.jsx | 308 +++- src/pages/ProfilePage.js | 2 +- 33 files changed, 1205 insertions(+), 1691 deletions(-) delete mode 100644 src/components/buttons/actionButtons/ChooseCollectionDialog.jsx rename src/components/buttons/{ => other}/CronTrigger.jsx (82%) rename src/components/buttons/{ => other}/ThemeToggleButton.jsx (94%) rename src/components/{buttons => cleanUp}/CollectionActionButtons.jsx (94%) rename src/components/{dialogs => cleanUp}/CollectionDialog.jsx (100%) rename src/components/{cards => cleanUp}/CustomPopover.jsx (94%) create mode 100644 src/components/cleanUp/SelectCollectionDialog.jsx rename src/components/{buttons => cleanUp}/button/Button.css (100%) rename src/components/{buttons => cleanUp}/button/Button.js (100%) create mode 100644 src/components/dialogs/ChooseCollectionDialog.jsx rename src/components/dialogs/{SelectCollectionDialog.jsx => CreateOrEditCollectionDialog.jsx} (91%) create mode 100644 src/components/dialogs/dialogStyles.jsx create mode 100644 src/components/forms/formStyles.jsx create mode 100644 src/components/other/StatisticsArea.jsx rename src/{components/cards => containers}/CardDetailsContainer.jsx (93%) create mode 100644 src/context/CollectionContext/ChunkPaylod.jsx diff --git a/src/components/buttons/actionButtons/CardActionButtons.jsx b/src/components/buttons/actionButtons/CardActionButtons.jsx index 833c3bd..0afd4bc 100644 --- a/src/components/buttons/actionButtons/CardActionButtons.jsx +++ b/src/components/buttons/actionButtons/CardActionButtons.jsx @@ -12,8 +12,6 @@ const CardActionButtons = ({ card, quantity, context, closeModal }) => { return null; // Consider rendering an error boundary or a user-friendly error message instead. } - // console.log('CARD TO ADD', card); - // Action types const ADD = 'add'; const REMOVE_ONE = 'removeOne'; const REMOVE_ALL = 'removeAll'; diff --git a/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx b/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx deleted file mode 100644 index 5ea3488..0000000 --- a/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx +++ /dev/null @@ -1,239 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import { - Dialog, - DialogTitle, - Typography, - List, - ListItem, - ButtonBase, - ListItemText, - Divider, - Snackbar, -} from '@mui/material'; -import { makeStyles } from '@mui/styles'; -import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; - -const useStyles = makeStyles((theme) => ({ - listItem: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: theme.spacing(2), - backgroundColor: '#ffffff', - borderRadius: '8px', - marginBottom: theme.spacing(2), - boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', - }, - listItemText: { - flex: 1, - textAlign: 'left', - marginLeft: theme.spacing(3), - }, -})); - -const ChooseCollectionDialog = ({ onSave, isOpen, onClose }) => { - const { setSelectedCollection, allCollections } = useCollectionStore(); - const classes = useStyles(); - const [error, setError] = useState(null); - const [snackbarOpen, setSnackbarOpen] = useState(false); - - const handleSelectCollection = useCallback( - (collectionId) => { - const foundCollection = allCollections.find( - (collection) => collection._id === collectionId - ); - - if (foundCollection) { - setSelectedCollection(foundCollection); - if (onSave) onSave(foundCollection); - onClose(); - setSnackbarOpen(true); // open snackbar on successful selection - } else { - setError('Collection not found!'); - } - }, - [allCollections, setSelectedCollection, onSave, onClose] - ); - if (isOpen) { - console.log('DIALOG OPEEPEPPE opened!)'); - } - - return ( - - Select a Collection - - - {allCollections.map((collection) => ( - - - handleSelectCollection(collection._id)} - > - - - - - - ))} - - - {error && ( - - {error} - - )} - - setSnackbarOpen(false)} - message="Collection selected successfully!" - autoHideDuration={3000} - /> - - ); -}; - -export default ChooseCollectionDialog; - -// import React, { useCallback, useState } from 'react'; -// import { -// Dialog, -// DialogTitle, -// List, -// ListItem, -// ListItemText, -// ButtonBase, -// Typography, -// ListItemButton, -// } from '@mui/material'; -// import { useCollectionStore } from '../../../context/hooks/collection'; -// import { useModal } from '../../../context/hooks/modal'; -// import SelectCollectionList from '../../grids/collectionGrids/SelectCollectionList'; - -// const ChooseCollectionDialog = ({ -// onSave, -// isOpen, -// onClose, -// card, -// deckDialogIsOpen, -// }) => { -// const { -// allCollections, -// setSelectedCollection, -// selectedCollection, -// setOpenChooseCollectionDialog, -// } = useCollectionStore(); -// const { hideModal } = useModal(); -// const [error, setError] = useState(null); - -// // If handleSelectCollection is a function, define it -// const handleSelectCollection = (collectionId) => { -// if (!collectionId) return; - -// const foundCollection = allCollections?.find( -// (collection) => collection._id === collectionId -// ); - -// if (!foundCollection) { -// console.error('Collection not found with ID:', collectionId); -// setError('Collection not found!'); -// return; -// } - -// setSelectedCollection(foundCollection); -// onSave(foundCollection); -// setOpenChooseCollectionDialog(false); -// hideModal(); -// }; - -// const handleAddToCollection = (collectionId) => { -// if (!collectionId) return; - -// const foundCollection = allCollections?.find( -// (collection) => collection._id === collectionId -// ); - -// if (!foundCollection) { -// console.error('Collection not found with ID:', collectionId); -// setError('Collection not found!'); -// return; -// } - -// setSelectedCollection(foundCollection); -// onSave(foundCollection); -// setOpenChooseCollectionDialog(false); -// hideModal(); -// }; - -// const handleSave = useCallback( -// (collection) => { -// setSelectedCollection(collection); -// if (collection) { -// // Assuming you have some logic to add the card to the selected collection -// // handleAddToCollection(card); -// onSave(collection); -// // setOpenChooseCollectionDialog(false); -// onClose(); -// } else { -// setError('Collection not found!'); -// } -// }, -// [setSelectedCollection, onClose] -// ); - -// const handleOpenDialog = () => { -// if (deckDialogIsOpen) { -// hideModal(); -// } -// }; -// const openDialog = useCallback( -// (isNewCollection) => { -// setDialogOpen(true); -// setIsNew(isNewCollection); - -// if (isNewCollection) { -// setEditedName(''); -// setEditedDescription(''); -// } else if (selectedCollection) { -// setEditedName(selectedCollection.name); -// setEditedDescription(selectedCollection.description); -// } -// }, -// [selectedCollection] -// ); - -// const handleSave = useCallback( -// (collection) => { -// setSelectedCollection(collection); -// setShowCollections(false); -// setShowPortfolio(true); -// closeDialog(); -// }, -// [setSelectedCollection, closeDialog] -// ); -// console.log('SELECTED COLLECTION (SELECT COLLECTION):', selectedCollection); - -// return ( -// // -// -// Select a Collection -// -// {error && ( -// -// {error} -// -// )} -// -// ); -// }; - -// export default ChooseCollectionDialog; diff --git a/src/components/buttons/CronTrigger.jsx b/src/components/buttons/other/CronTrigger.jsx similarity index 82% rename from src/components/buttons/CronTrigger.jsx rename to src/components/buttons/other/CronTrigger.jsx index 41875ce..bcbb357 100644 --- a/src/components/buttons/CronTrigger.jsx +++ b/src/components/buttons/other/CronTrigger.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box, Button } from '@mui/material'; -import { useCombinedContext } from '../../context/CombinedProvider'; -import { useUserContext } from '../../context/UserContext/UserContext'; +import { useCombinedContext } from '../../../context/CombinedProvider'; +import { useUserContext } from '../../../context/UserContext/UserContext'; const CronTrigger = () => { const { stopCronJob, handleSendAllCardsInCollections, listOfMonitoredCards } = diff --git a/src/components/buttons/ThemeToggleButton.jsx b/src/components/buttons/other/ThemeToggleButton.jsx similarity index 94% rename from src/components/buttons/ThemeToggleButton.jsx rename to src/components/buttons/other/ThemeToggleButton.jsx index 23e35b5..35020e7 100644 --- a/src/components/buttons/ThemeToggleButton.jsx +++ b/src/components/buttons/other/ThemeToggleButton.jsx @@ -2,7 +2,7 @@ import React, { useContext, useState } from 'react'; import { IconButton, Menu, MenuItem } from '@mui/material'; import Brightness4Icon from '@mui/icons-material/Brightness4'; import Brightness7Icon from '@mui/icons-material/Brightness7'; -import { ColorModeContext } from '../../context/ColorModeProvider'; +import { ColorModeContext } from '../../../context/ColorModeProvider'; const ThemeToggleButton = () => { const [anchorEl, setAnchorEl] = useState(null); diff --git a/src/components/cards/GenericCard.jsx b/src/components/cards/GenericCard.jsx index 480c7b8..9bbcae6 100644 --- a/src/components/cards/GenericCard.jsx +++ b/src/components/cards/GenericCard.jsx @@ -22,13 +22,12 @@ const GenericCard = React.forwardRef((props, ref) => { const requiresDoubleButtons = context === 'Deck' || context === 'Collection'; // Added this line const checkCardInCollection = () => { if (selectedCollection) { - const cardIds = selectedCollection.cards.map((card) => card.id); - return cardIds.includes(card.id); + const cardIds = selectedCollection?.cards?.map((card) => card.id); + return cardIds?.includes(card.id); } return false; }; - // Function to handle click on the card, opening the modal and updating state - // Function to handle click on the card, opening the modal and updating state + const handleClick = () => { const cardIsInCollection = checkCardInCollection(); console.log('Modal opened with card:', card); diff --git a/src/components/buttons/CollectionActionButtons.jsx b/src/components/cleanUp/CollectionActionButtons.jsx similarity index 94% rename from src/components/buttons/CollectionActionButtons.jsx rename to src/components/cleanUp/CollectionActionButtons.jsx index 5e207ee..6290010 100644 --- a/src/components/buttons/CollectionActionButtons.jsx +++ b/src/components/cleanUp/CollectionActionButtons.jsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { makeStyles } from '@mui/styles'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; // Assuming you have a separate context for Collections import CardActionButtons from './CardActionButtons'; -import CollectionDialog from '../dialogs/CollectionDialog'; // Assuming you have a separate dialog for Collections +import CollectionDialog from './CollectionDialog'; // Assuming you have a separate dialog for Collections import CardCountDisplay from '../other/CardCountDisplay'; import { useCartStore } from '../../context/CartContext/CartContext'; diff --git a/src/components/dialogs/CollectionDialog.jsx b/src/components/cleanUp/CollectionDialog.jsx similarity index 100% rename from src/components/dialogs/CollectionDialog.jsx rename to src/components/cleanUp/CollectionDialog.jsx diff --git a/src/components/cards/CustomPopover.jsx b/src/components/cleanUp/CustomPopover.jsx similarity index 94% rename from src/components/cards/CustomPopover.jsx rename to src/components/cleanUp/CustomPopover.jsx index 47e744b..01f888d 100644 --- a/src/components/cards/CustomPopover.jsx +++ b/src/components/cleanUp/CustomPopover.jsx @@ -1,9 +1,9 @@ import React, { useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import Popover from '@mui/material/Popover'; -import CardToolTip from './CardToolTip'; +import CardToolTip from '../cards/CardToolTip'; import { makeStyles } from '@mui/styles'; -import GenericCard from './GenericCard'; +import GenericCard from '../cards/GenericCard'; const useStyles = makeStyles((theme) => ({ paper: { diff --git a/src/components/cleanUp/SelectCollectionDialog.jsx b/src/components/cleanUp/SelectCollectionDialog.jsx new file mode 100644 index 0000000..ed5732f --- /dev/null +++ b/src/components/cleanUp/SelectCollectionDialog.jsx @@ -0,0 +1,133 @@ +// import React from 'react'; +// import PropTypes from 'prop-types'; +// import { +// Dialog, +// DialogContent, +// Button, +// TextField, +// Typography, +// } from '@mui/material'; +// import { useCookies } from 'react-cookie'; +// import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; + +// const SelectCollectionDialog = ({ +// isDialogOpen, +// closeDialog, +// onSave, +// isNew, +// // userId, +// editedName, +// setEditedName, +// editedDescription, +// setEditedDescription, +// }) => { +// const { +// createUserCollection, +// addOneToCollection, +// removeCollection, +// selectedCollection, +// } = useCollectionStore(); +// const [cookies] = useCookies(['user']); +// const userId = cookies.user?.id; + +// const handleSave = () => { +// const newCollectionInfo = { +// name: editedName, +// description: editedDescription, +// userId: userId, +// }; + +// if (isNew) { +// createUserCollection( +// userId, +// newCollectionInfo, +// editedName, +// editedDescription, +// userId +// ); +// } else if (editedName && editedDescription) { +// addOneToCollection(newCollectionInfo); +// } else { +// console.error('No card to add to the collection'); +// } + +// onSave(newCollectionInfo); +// closeDialog(); +// }; + +// const handleRemove = (e) => { +// e.preventDefault(); + +// removeCollection(selectedCollection); +// closeDialog(); +// }; + +// return ( +// +// +// +// {isNew +// ? 'Fill in the data below to create a new collection' +// : 'Edit your collection'} +// +//
+// setEditedName(e.target.value)} +// variant="outlined" +// fullWidth +// margin="normal" +// /> +//
+//
+// setEditedDescription(e.target.value)} +// variant="outlined" +// multiline +// rows={4} +// fullWidth +// margin="normal" +// /> +//
+//
+// +// {!isNew && ( +// +// )} +//
+//
+//
+// ); +// }; + +// SelectCollectionDialog.propTypes = { +// isDialogOpen: PropTypes.bool.isRequired, +// closeDialog: PropTypes.func.isRequired, +// onSave: PropTypes.func.isRequired, +// isNew: PropTypes.bool, +// userId: PropTypes.string, +// editedName: PropTypes.string, +// setEditedName: PropTypes.func.isRequired, +// editedDescription: PropTypes.string, +// setEditedDescription: PropTypes.func.isRequired, +// }; + +// export default SelectCollectionDialog; diff --git a/src/components/buttons/button/Button.css b/src/components/cleanUp/button/Button.css similarity index 100% rename from src/components/buttons/button/Button.css rename to src/components/cleanUp/button/Button.css diff --git a/src/components/buttons/button/Button.js b/src/components/cleanUp/button/Button.js similarity index 100% rename from src/components/buttons/button/Button.js rename to src/components/cleanUp/button/Button.js diff --git a/src/components/collection/CardPortfolio.jsx b/src/components/collection/CardPortfolio.jsx index ee1966a..59bf124 100644 --- a/src/components/collection/CardPortfolio.jsx +++ b/src/components/collection/CardPortfolio.jsx @@ -59,29 +59,11 @@ const CardPortfolio = ({ allCollections }) => { hasRun.current = true; // Set the ref to true after the function has been run }; - // console.log('ALL COLLECTIONS:', allCollections); - // console.log('SELECTED COLLECTION:', selectedCollection); - // console.log('SELECTED CARDS:', selectedCards); - // console.log('SELECTED COLLECTION (CARD PORT):', selectedCollection); return ( {showCollections ? ( @@ -101,14 +83,6 @@ const CardPortfolio = ({ allCollections }) => { selectedCollection={selectedCollection} setSelectedCollection={setSelectedCollection} removeCard={removeOneFromCollection} - - // allCollections={allCollections} - // selectedCollection={selectedCollection} - // setSelectedCollection={setSelectedCollection} - // selectedCards={selectedCards} - // setSelectedCards={setSelectedCards} - // removeCard={removeOneFromCollection} - // chartData={UpdateChartData()} /> ) : ( ({ @@ -97,7 +97,7 @@ const SelectCollection = ({ openDialog={() => openDialog(false)} // Indicate that this is not a new collection /> - ({ + listItem: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(2), + backgroundColor: '#ffffff', + borderRadius: '8px', + marginBottom: theme.spacing(2), + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', + }, + listItemText: { + flex: 1, + textAlign: 'left', + marginLeft: theme.spacing(3), + }, +})); + +const ChooseCollectionDialog = ({ onSave, isOpen, onClose }) => { + const { setSelectedCollection, allCollections } = useCollectionStore(); + const classes = useStyles(); + const [error, setError] = useState(null); + const [snackbarOpen, setSnackbarOpen] = useState(false); + + const handleSelectCollection = useCallback( + (collectionId) => { + const foundCollection = allCollections.find( + (collection) => collection._id === collectionId + ); + + if (foundCollection) { + setSelectedCollection(foundCollection); + if (onSave) onSave(foundCollection); + onClose(); + setSnackbarOpen(true); // open snackbar on successful selection + } else { + setError('Collection not found!'); + } + }, + [allCollections, setSelectedCollection, onSave, onClose] + ); + if (isOpen) { + console.log('DIALOG OPEEPEPPE opened!)'); + } + + return ( + + Select a Collection + + + {allCollections.map((collection) => ( + + + handleSelectCollection(collection._id)} + > + + + + + + ))} + + + {error && ( + + {error} + + )} + + setSnackbarOpen(false)} + message="Collection selected successfully!" + autoHideDuration={3000} + /> + + ); +}; + +export default ChooseCollectionDialog; diff --git a/src/components/dialogs/SelectCollectionDialog.jsx b/src/components/dialogs/CreateOrEditCollectionDialog.jsx similarity index 91% rename from src/components/dialogs/SelectCollectionDialog.jsx rename to src/components/dialogs/CreateOrEditCollectionDialog.jsx index 93a7698..7da668b 100644 --- a/src/components/dialogs/SelectCollectionDialog.jsx +++ b/src/components/dialogs/CreateOrEditCollectionDialog.jsx @@ -10,7 +10,7 @@ import { import { useCookies } from 'react-cookie'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -const SelectCollectionDialog = ({ +const CreateOrEditCollectionDialog = ({ isDialogOpen, closeDialog, onSave, @@ -38,7 +38,13 @@ const SelectCollectionDialog = ({ }; if (isNew) { - createUserCollection(userId, newCollectionInfo); + createUserCollection( + userId, + newCollectionInfo, + editedName, + editedDescription, + userId + ); } else if (editedName && editedDescription) { addOneToCollection(newCollectionInfo); } else { @@ -112,7 +118,7 @@ const SelectCollectionDialog = ({ ); }; -SelectCollectionDialog.propTypes = { +CreateOrEditCollectionDialog.propTypes = { isDialogOpen: PropTypes.bool.isRequired, closeDialog: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired, @@ -124,4 +130,4 @@ SelectCollectionDialog.propTypes = { setEditedDescription: PropTypes.func.isRequired, }; -export default SelectCollectionDialog; +export default CreateOrEditCollectionDialog; diff --git a/src/components/dialogs/LoginDialog.jsx b/src/components/dialogs/LoginDialog.jsx index 916944b..3d24e74 100644 --- a/src/components/dialogs/LoginDialog.jsx +++ b/src/components/dialogs/LoginDialog.jsx @@ -27,16 +27,6 @@ function LoginDialog({ open, onClose, onLogin }) { const [roleData, setRoleData] = useState('admin'); // Adjusted to handle string value const { toggleColorMode, mode } = useMode(); const [cookies, setCookie, removeCookie] = useCookies(['isLoggedIn']); - // Flag to track if the component is mounted - // const isMounted = useRef(true); - - // useEffect(() => { - // return () => { - // isMounted.current = false; - // }; // Cleanup - // }, []); - - // New function to set cookies and call onLogin const setLoginState = (isLoggedIn, userId) => { const expires = new Date(); expires.setMinutes(expires.getMinutes() + 45); @@ -63,7 +53,6 @@ function LoginDialog({ open, onClose, onLogin }) { } if (response?.loggedIn) { - // Assuming `userId` is available in the `response`. const expires = new Date(); expires.setMinutes(expires.getMinutes() + 45); setCookie('isLoggedIn', true, { expires }); @@ -77,12 +66,10 @@ function LoginDialog({ open, onClose, onLogin }) { }; const handleLogout = () => { - // removeCookie('isLoggedIn', { path: '/' }); removeCookie('userId', { path: '/' }); // Remove userId cookie authContext.logout(); }; - // UseEffect to handle login state change useEffect(() => { if (authContext.isLoggedIn && window.location.pathname !== '/profile') { const isLoggedInFromCookie = cookies['isLoggedIn']; diff --git a/src/components/dialogs/dialogStyles.jsx b/src/components/dialogs/dialogStyles.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/forms/formStyles.jsx b/src/components/forms/formStyles.jsx new file mode 100644 index 0000000..8e4eaa9 --- /dev/null +++ b/src/components/forms/formStyles.jsx @@ -0,0 +1,23 @@ +import styled from 'styled-components'; +import { Button, TextField } from '@mui/material'; + +export const FormWrapper = styled('form')` + display: flex; + flex-direction: column; + gap: 20px; + max-width: 400px; + margin: auto; +`; + +export const StyledTextField = styled(TextField)` + && { + margin-bottom: 12px; + width: 100%; // Ensures that the text fields take the full width + } +`; + +export const StyledButton = styled(Button)` + && { + margin-top: 16px; + } +`; diff --git a/src/components/grids/collectionGrids/CardList.jsx b/src/components/grids/collectionGrids/CardList.jsx index 8ece1cd..6b074b9 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -33,6 +33,9 @@ const CardList = ({ selectedCards }) => { const { getTotalCost, selectedCollection, + getTotalPrice, + totalCost, + // totalPrice, removeOneFromCollection, addOneToCollection, } = useCollectionStore(); @@ -67,6 +70,9 @@ const CardList = ({ selectedCards }) => { addOneToCollection(card, card.id); cardLogger.logCardAction('Add Card', card); }; + // let totalPrice = getTotalPrice(); + + // console.log('TOTAL PRICE:', totalPrice); const chartDimensions = useMemo( () => @@ -203,7 +209,7 @@ const CardList = ({ selectedCards }) => { mt={2} sx={{ width: '100%' }} > - {`Total: $${selectedCollection?.totalPrice}`} + {`Total: $${totalCost}`} diff --git a/src/components/media/CardMediaAndDetails.jsx b/src/components/media/CardMediaAndDetails.jsx index e584941..91d5706 100644 --- a/src/components/media/CardMediaAndDetails.jsx +++ b/src/components/media/CardMediaAndDetails.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Grid } from '@mui/material'; import CardMediaSection from './CardMediaSection'; // Ensure correct import -import CardDetailsContainer from '../cards/CardDetailsContainer'; +import CardDetailsContainer from '../../containers/CardDetailsContainer'; // import { useStyles } from './detailsStyles'; // Assuming there are styles, uncomment if needed // This component doesn't need to maintain state or use hooks, hence it's a simple functional component diff --git a/src/components/other/CollectionValueTracker.jsx b/src/components/other/CollectionValueTracker.jsx index 27e4f32..3c828cf 100644 --- a/src/components/other/CollectionValueTracker.jsx +++ b/src/components/other/CollectionValueTracker.jsx @@ -1,37 +1,41 @@ import React, { useState, useEffect } from 'react'; import { Typography, Box, useTheme } from '@mui/material'; import { useCombinedContext } from '../../context/CombinedProvider'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; const CollectionValueTracker = ({ data }) => { const theme = useTheme(); const { allCardPrices } = useCombinedContext(); + const { totalCost } = useCollectionStore(); const [totalValue, setTotalValue] = useState(0); const [changeOverTime, setChangeOverTime] = useState(0); console.log('allCardPrices', data.allCardPrices); - useEffect(() => { - // const allPrices = data?.cards?.map((card) => card?.price); - // console.log('[1]allPrices', allPrices); - // const newValue = data?cards?.reduce((acc, card) => acc + (card?.price, 0)); - // console.log('[2]newValue', newValue); - // const change = data?cards?.reduce( - // (acc, card) => acc + (card?.latestPrice - card?.lastSavedPrice), - // 0 - // ); - // setTotalValue(newValue); - // setChangeOverTime(change); - }, [data]); + // useEffect(() => { + // const allPrices = data?.cards?.map((card) => card?.price); + // console.log('[1]allPrices', allPrices); + // const newValue = data?cards?.reduce((acc, card) => acc + (card?.price, 0)); + // console.log('[2]newValue', newValue); + // const change = data?cards?.reduce( + // (acc, card) => acc + (card?.latestPrice?.num - card?.lastSavedPrice?.num), + // 0 + // ); + // console.log('[3]change', change); - useEffect(() => { - // Update total value based on allCardPrices - if (Array.isArray(allCardPrices) && allCardPrices?.length > 0) { - const total = allCardPrices - .map((price) => parseFloat(price)) // Convert each price to a number - .filter((price) => !isNaN(price)) // Filter out non-numeric values - .reduce((acc, price) => acc + price, 0); // Sum up all prices - console.log('total', total); - setTotalValue(total); - } - }, [allCardPrices]); + // setTotalValue(newValue); + // setChangeOverTime(change); + // }, [data]); + + // useEffect(() => { + // // Update total value based on allCardPrices + // if (Array.isArray(allCardPrices) && allCardPrices?.length > 0) { + // const total = allCardPrices + // .map((price) => parseFloat(price)) // Convert each price to a number + // .filter((price) => !isNaN(price)) // Filter out non-numeric values + // .reduce((acc, price) => acc + price, 0); // Sum up all prices + // console.log('total', total); + // setTotalValue(total); + // } + // }, [allCardPrices]); const trend = changeOverTime > 0 ? 'increased' : 'decreased'; const trendColor = changeOverTime > 0 ? 'success.main' : 'error.main'; @@ -49,7 +53,7 @@ const CollectionValueTracker = ({ data }) => { variant="h6" sx={{ fontWeight: 'bold', color: theme.palette.text.primary }} > - Total Collection Value: ${totalValue?.toFixed(2)} + Total Collection Value: ${totalCost?.toFixed(2)} ({ chartContainer: { @@ -75,149 +83,10 @@ const useStyles = makeStyles((theme) => ({ // console.log('[5][LATEST DATA]:', JSON.stringify(latestData, null, 2)); // } -const CustomTooltip = ({ point }) => { - const theme = useTheme(); - const { serieId, data } = point; - const { label, xFormatted, yFormatted } = data || {}; - const series = { - type: { - Collection: 'Collection', - Card: 'Card', - Deck: 'Deck', - }, - }; - return ( - - - {`Series: ${serieId}`} - - {series.type[label] === 'Card' && ( - - {`Card: ${label}`} - - )} - - {`Time: ${new Date(xFormatted).toLocaleString()}`} - - - {`Value: $${parseFloat(yFormatted).toFixed(2)}`} - - - ); -}; - -const roundToNearestTenth = (value) => { - return Math.round(value * 10) / 10; -}; - -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) })); -}; - -const getAveragedData = (data) => { - // Use a regular function instead of a hook - if (!Array.isArray(data)) { - return []; - } - return data.map((row, index, total) => { - const start = Math.max(0, index - 6); - const end = index; - const subset = total.slice(start, end + 1); - const sum = subset.reduce((a, b) => a + b.y, 0); - return { - x: row.x, - y: sum / subset.length || 0, - }; - }); -}; - -const getTickValues = (timeRange) => { - const mapping = { - '15m': 'every 15 minutes', - '2h': 'every 2 hours', - '1d': 'every day', - '1w': 'every week', - }; - return mapping[timeRange] || 'every week'; // Default to 'every week' if no match -}; - -const useMovingAverage = (data, numberOfPricePoints) => { - return useMemo(() => { - if (!Array.isArray(data)) { - return []; - } - console.log('[1][Data]----------> [', data + ']'); - console.log( - '[2][NUMBER OF POINTS]----------> [', - numberOfPricePoints + ']' - ); - - return data.map((row, index, total) => { - const start = Math.max(0, index - numberOfPricePoints); - const end = index; - const subset = total.slice(start, end + 1); - const sum = subset.reduce((a, b) => a + b.y, 0); - return { - // x: String(row.x), - x: row.x, - y: sum / subset.length || 0, - }; - }); - }, [data, numberOfPricePoints]); -}; - -const useEventHandlers = () => { - const [hoveredData, setHoveredData] = useState(null); - - const handleMouseMove = useCallback((point) => { - if (point) { - setHoveredData({ - x: point.data.x, - y: point.data.y, - }); - } - }, []); - - const handleMouseLeave = useCallback(() => { - setHoveredData(null); - }, []); - - return { hoveredData, handleMouseMove, handleMouseLeave }; -}; - -const formatDateToString = (date) => { - if (!(date instanceof Date) || isNaN(date.getTime())) { - console.error('Invalid date:', date); - return ''; - } - return `${date.getFullYear()}-${(date.getMonth() + 1) - .toString() - .padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date - .getHours() - .toString() - .padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; -}; - const LinearChart = ({ filteredChartData, datesTimesValues, + nivoReadyData, latestData, dimensions, timeRanges, @@ -227,23 +96,24 @@ const LinearChart = ({ const [isZoomed, setIsZoomed] = useState(false); const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); - const filteredData = useMemo( - () => getFilteredData(filteredChartData, timeRange), - [filteredChartData, timeRange] - ); + // const filteredData = useMemo( + // () => getFilteredData(filteredChartData, timeRange), + // [filteredChartData, timeRange] + // ); + // console.log('filteredData', filteredData); - const dataForChart = useMemo(() => { - return datesTimesValues.dates.map((date, index) => ({ - x: formatDateToString( - new Date(`${date} ${datesTimesValues.times[index]}`) - ), - y: datesTimesValues.values[index], - })); - }, [datesTimesValues]); - CustomLogger('LinearChart', 'info', { - filteredChartData, - datesTimesValues, - }); + // const dataForChart = useMemo(() => { + // return datesTimesValues.dates.map((date, index) => ({ + // x: formatDateToString( + // new Date(`${date} ${datesTimesValues.times[index]}`) + // ), + // y: datesTimesValues.values[index], + // })); + // }, [datesTimesValues]); + // CustomLogger('LinearChart', 'info', { + // filteredChartData, + // datesTimesValues, + // }); const tickValues = useMemo(() => getTickValues(timeRange), [timeRange]); if ( @@ -338,7 +208,8 @@ const LinearChart = ({ const chartProps = { margin: { top: 50, right: 110, bottom: 50, left: 60 }, - data: [{ id: 'Data', data: dataForChart }], + // data: [{ id: 'Data', data: dataForChart }], + data: nivoReadyData, animate: true, motionStiffness: 90, motionDamping: 15, diff --git a/src/components/other/PortfolioChart.jsx b/src/components/other/PortfolioChart.jsx index 72928b1..6d38aa8 100644 --- a/src/components/other/PortfolioChart.jsx +++ b/src/components/other/PortfolioChart.jsx @@ -11,18 +11,16 @@ import { // debounce, } from '@mui/material'; import LinearChart from './LinearChart'; -import TimeRangeSelector from './TimeRangeSelector'; import { useChartContext } from '../../context/ChartContext/ChartContext'; import ErrorBoundary from '../../context/ErrorBoundary'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -import CollectionStatisticsSelector from './CollectionStatisticsSelector'; -import UpdateStatusBox from './UpdateStatusBox'; import { useCombinedContext } from '../../context/CombinedProvider'; import debounce from 'lodash/debounce'; import { convertDataForNivo, - getUniqueFilteredXYValues, + getUniqueValidData, groupAndAverageData, + convertDataForNivo2, } from './chartUtils'; // const ChartPaper = styled(Paper)(({ theme }) => ({ @@ -53,7 +51,7 @@ const ChartPaper = styled(Paper)(({ theme }) => ({ })); const PortfolioChart = () => { const theme = useTheme(); - const { latestData, setLatestData, timeRange, timeRanges } = + const { latestData, setLatestData, timeRange, timeRanges, currentValue } = useChartContext(); const [lastUpdateTime, setLastUpdateTime] = useState(null); const chartContainerRef = useRef(null); @@ -62,31 +60,66 @@ const PortfolioChart = () => { height: 0, }); const { selectedCollection } = useCollectionStore(); + let nivoReadyData = null; + let nivoReadyData2 = null; const { socket } = useCombinedContext(); - - const filteredChartData = useMemo(() => { - const allXYValues = selectedCollection?.chartData?.allXYValues; - return allXYValues ? getUniqueFilteredXYValues(allXYValues) : []; + const updateLastTimeAndGetThreshold = () => { + const currentTime = new Date().getTime(); + if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { + // 10 minutes + setLastUpdateTime(currentTime); + // setLatestData(filteredChartData[filteredChartData.length - 1]); + setLatestData(filteredChartData2[filteredChartData2.length - 1]); + } + return currentTime; + }; + // const filteredChartData = useMemo(() => { + // const allXYValues = selectedCollection?.chartData?.allXYValues; + // return allXYValues ? getUniqueFilteredXYValues(allXYValues) : []; + // }, [selectedCollection]); + // Filtered chart data based on unique valid data + // const filteredChartData = useMemo(() => { + // const currentChartData = selectedCollection?.currentChartDataSets; + // return currentChartData ? getUniqueValidData(currentChartData) : []; + // }, [selectedCollection]); + const filteredChartData2 = useMemo(() => { + const allXYValues2 = selectedCollection?.currentChartDataSets2; + console.log('ALL XY VALUES:', allXYValues2); + return allXYValues2 ? getUniqueValidData(allXYValues2) : []; }, [selectedCollection]); - const threshold = useMemo(() => timeRange * 0.1, [timeRange]); - const rawData = useMemo( - () => groupAndAverageData(filteredChartData, threshold), - [filteredChartData, threshold] + // Calculate threshold based on the last update time + const threshold = updateLastTimeAndGetThreshold(); + + // Group and average data using the calculated threshold + // const rawData = useMemo( + // () => groupAndAverageData(filteredChartData, threshold), + // [filteredChartData, threshold] + // ); + const rawData2 = useMemo( + () => groupAndAverageData(filteredChartData2, threshold), + [filteredChartData2, threshold] ); - const nivoReadyData = useMemo(() => convertDataForNivo(rawData), [rawData]); + // console.log('FILTERED CHART DATA:', filteredChartData); + // console.log('FILTERED CHART DATA 2:', filteredChartData2); + // const threshold = useMemo(() => timeRange * 0.1, [timeRange]); - // Now use this threshold when calling your data grouping function + console.log('THRESHOLD:', threshold); + // if (!rawData) { + // console.log('NO RAW DATA!'); + // } + // if (rawData) { + // console.log('RAW DATA:', rawData); + // nivoReadyData = useMemo(() => convertDataForNivo(rawData), [rawData]); + // } + if (rawData2) { + console.log('RAW DATA 2:', rawData2); + const nivoReady = useMemo(() => convertDataForNivo2(rawData2), [rawData2]); + nivoReadyData2 = nivoReady; + console.log('NIVO READY DATA 2:', nivoReadyData2); + } // Now use this threshold when calling your data grouping function - const updateLastTime = () => { - const currentTime = new Date().getTime(); - if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { - setLastUpdateTime(currentTime); - const lastDataset = filteredChartData[filteredChartData.length - 1]; - lastDataset && setLatestData(lastDataset); - } - }; useEffect(() => { const handleResize = debounce(() => { @@ -153,11 +186,13 @@ const PortfolioChart = () => { }} > - {filteredChartData.length > 0 ? ( + {filteredChartData2.length > 0 ? ( = 2 && + data.views.length >= 2 && ( + + + + + + + + + ) + ); +} + +StatisticsArea.propTypes = { + theme: PropTypes.object.isRequired, + data: PropTypes.object.isRequired, + CardChart: PropTypes.elementType, +}; + +export default withTheme(StatisticsArea); diff --git a/src/components/other/TimeRangeSelector.jsx b/src/components/other/TimeRangeSelector.jsx index 7edcaab..7ab072d 100644 --- a/src/components/other/TimeRangeSelector.jsx +++ b/src/components/other/TimeRangeSelector.jsx @@ -6,8 +6,13 @@ import { useChartContext } from '../../context/ChartContext/ChartContext'; const TimeRangeSelector = ({ onChange }) => { const { timeRanges, timeRange, setTimeRange, handleChange, currentValue } = useChartContext(); - const isInRange = timeRanges.some((option) => option.value === timeRange); - const safeTimeRange = isInRange ? timeRange : timeRanges[0].value; + console.log('timeRanges: ', timeRanges); + console.log('timeRange: ', timeRange); + console.log('currentValue: ', currentValue); + const isInRange = timeRanges.some((option) => option.value === currentValue); + console.log('isInRange: ', isInRange); + const safeTimeRange = isInRange ? timeRange : timeRanges[1].value; + console.log('safeTimeRange: ', safeTimeRange); return ( + {timeRanges.map((range) => ( + + {range.label} + + ))} + + ); +}; + +export default TimeRangeSelector; diff --git a/src/components/other/chartUtils.jsx b/src/components/other/chartUtils.jsx index c8b489f..2f5afe4 100644 --- a/src/components/other/chartUtils.jsx +++ b/src/components/other/chartUtils.jsx @@ -1,40 +1,128 @@ -export const getUniqueFilteredXYValues = (allXYValues) => { - if (!Array.isArray(allXYValues)) { - console.error('Invalid input: allXYValues should be an array'); +// export const getUniqueValidData = (allXYValues) => { +// if (!Array.isArray(allXYValues)) { +// console.error('Invalid input: allXYValues should be an array'); +// return []; +// } + +import { Box, Typography } from '@mui/material'; +import { useTheme } from '@mui/styles'; +import { useCallback, useMemo, useState } from 'react'; + +export const getUniqueValidData = (currentChartData) => { + if (!Array.isArray(currentChartData)) { + console.error('Invalid input: currentChartData should be an array'); return []; } + const uniqueLabels = new Set(); const uniqueXValues = new Set(); - return allXYValues + + return currentChartData .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 !== 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 groupAndAverageData = (data, threshold = 1) => { - if (!data || data.length === 0) return { dates: [], times: [], values: [] }; +// export const groupAndAverageData = (data, threshold = 600000) => { +// // 10 minutes in milliseconds +// if (!data || data.length === 0) return { dates: [], times: [], values: [] }; + +// const clusters = []; +// let currentCluster = [data[0]]; + +// for (let i = 1; i < data.length; i++) { +// const prevTime = new Date(data[i - 1].x).getTime(); +// const currentTime = new Date(data[i].x).getTime(); +// if (currentTime - prevTime <= threshold) { +// currentCluster.push(data[i]); +// } else { +// console.log('Current Cluster:', currentCluster); // Log the current cluster +// clusters.push(currentCluster); +// currentCluster = [data[i]]; +// } +// } +// clusters.push(currentCluster); // Push the last cluster +// console.log('All Clusters:', clusters); // Log all clusters + +// // For each cluster, find the middlemost x-value and average y-values +// let dates = []; +// let times = []; +// let values = []; + +// clusters.forEach((cluster) => { +// const middleIndex = Math.floor(cluster.length / 2); +// const middleDatum = cluster[middleIndex]; +// const date = new Date(middleDatum.x); +// const avgY = +// cluster.reduce((sum, point) => sum + point.y, 0) / cluster.length; + +// const hours = date.getHours(); +// const minutes = date.getMinutes(); +// const AMPM = hours >= 12 ? 'PM' : 'AM'; +// const adjustedHours = hours % 12 || 12; // Converts "0" to "12" + +// dates.push( +// `${date.getFullYear()}-${String(date.getMonth() + 1).padStart( +// 2, +// '0' +// )}-${String(date.getDate()).padStart(2, '0')}` +// ); +// times.push( +// `${String(adjustedHours).padStart(2, '0')}:${String(minutes).padStart( +// 2, +// '0' +// )} ${AMPM}` +// ); +// values.push(avgY); +// }); + +// return { dates, times, values }; +// }; + +export const groupAndAverageData = (data, threshold = 600000) => { + if (!data || data.length === 0) return []; const clusters = []; let currentCluster = [data[0]]; + console.log('Initial cluster with first data point: ', currentCluster); + for (let i = 1; i < data.length; i++) { const prevTime = new Date(data[i - 1].x).getTime(); const currentTime = new Date(data[i].x).getTime(); - if (currentTime - prevTime <= threshold) { + const timeDiff = currentTime - prevTime; + + console.log( + `Time difference between points ${i - 1} and ${i}: ${timeDiff}ms` + ); + + if (timeDiff <= threshold) { currentCluster.push(data[i]); } else { clusters.push(currentCluster); @@ -42,45 +130,91 @@ export const groupAndAverageData = (data, threshold = 1) => { } } clusters.push(currentCluster); // Push the last cluster + console.log('Final cluster: ', currentCluster); - // For each cluster, find the middlemost x-value and average y-values - let dates = []; - let times = []; - let values = []; - - clusters.forEach((cluster) => { + // Process each cluster to create the desired output format + clusters.map((cluster) => { const middleIndex = Math.floor(cluster.length / 2); const middleDatum = cluster[middleIndex]; - const date = new Date(middleDatum.x); const avgY = cluster.reduce((sum, point) => sum + point.y, 0) / cluster.length; - const hours = date.getHours(); - const minutes = date.getMinutes(); - const AMPM = hours >= 12 ? 'PM' : 'AM'; - const adjustedHours = hours % 12 || 12; // Converts "0" to "12" + const date = new Date(middleDatum.x); + const formattedDate = date.toISOString(); - dates.push( - `${date.getFullYear()}-${String(date.getMonth() + 1).padStart( - 2, - '0' - )}-${String(date.getDate()).padStart(2, '0')}` - ); - times.push( - `${String(adjustedHours).padStart(2, '0')}:${String(minutes).padStart( - 2, - '0' - )} ${AMPM}` - ); - values.push(avgY); + return { + label: `Price at ${formattedDate}`, + x: formattedDate, + y: avgY, + }; }); - return { dates, times, values }; + console.log('Processed clusters: ', clusters); + return clusters; +}; + +export const getAveragedData = (data) => { + // Use a regular function instead of a hook + if (!Array.isArray(data)) { + return []; + } + return data.map((row, index, total) => { + const start = Math.max(0, index - 6); + const end = index; + const subset = total.slice(start, end + 1); + const sum = subset.reduce((a, b) => a + b.y, 0); + return { + x: row.x, + y: sum / subset.length || 0, + }; + }); +}; + +export const getTickValues = (timeRange) => { + console.log('timeRange: ', timeRange); + const mapping = { + '15m': 'every 15 minutes', + '2h': 'every 2 hours', + '1d': 'every day', + '1w': 'every week', + }; + return mapping[timeRange] || 'every day'; // Default to 'every week' if no match +}; + +export const useMovingAverage = (data, numberOfPricePoints) => { + return useMemo(() => { + if (!Array.isArray(data)) { + return []; + } + console.log('[1][Data]----------> [', data + ']'); + console.log( + '[2][NUMBER OF POINTS]----------> [', + numberOfPricePoints + ']' + ); + + return data.map((row, index, total) => { + const start = Math.max(0, index - numberOfPricePoints); + const end = index; + const subset = total.slice(start, end + 1); + const sum = subset.reduce((a, b) => a + b.y, 0); + return { + // x: String(row.x), + x: row.x, + y: sum / subset.length || 0, + }; + }); + }, [data, numberOfPricePoints]); }; export const convertDataForNivo = ({ dates, times, values }) => { - if (!dates.length || !times.length || !values.length) { - console.error('Invalid data arrays provided'); + if ( + !dates || + !times || + !values || + dates.length !== times.length || + dates.length !== values.length + ) { + console.error('Invalid or mismatched data arrays provided'); return []; } @@ -102,3 +236,115 @@ export const convertDataForNivo = ({ dates, times, values }) => { }, ]; }; + +export const convertDataForNivo2 = (rawData2) => { + if (!Array.isArray(rawData2) || rawData2.length === 0) { + console.error('Invalid or empty rawData provided'); + return []; + } + + console.log('rawData2: ', rawData2); + console.log('rawData2.data: ', rawData2[0]); + // rawData is assumed to be an array of objects with 'label', 'x', and 'y' properties + const nivoData = rawData2[0].map((dataPoint) => ({ + x: dataPoint.x, // x value is already an ISO date string + y: dataPoint.y, // y value + })); + + // Wrapping the data for a single series. You can add more series similarly + return [ + { + id: 'Averaged Data', + color: 'hsl(0, 100%, 50%)', + data: nivoData, + }, + ]; +}; + +export const CustomTooltip = ({ point }) => { + const theme = useTheme(); + const { serieId, data } = point; + const { label, xFormatted, yFormatted } = data || {}; + const series = { + type: { + Collection: 'Collection', + Card: 'Card', + Deck: 'Deck', + }, + }; + return ( + + + {`Series: ${serieId}`} + + {series.type[label] === 'Card' && ( + + {`Card: ${label}`} + + )} + + {`Time: ${new Date(xFormatted).toLocaleString()}`} + + + {`Value: $${parseFloat(yFormatted).toFixed(2)}`} + + + ); +}; + +const roundToNearestTenth = (value) => { + return Math.round(value * 10) / 10; +}; + +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 useEventHandlers = () => { + const [hoveredData, setHoveredData] = useState(null); + + const handleMouseMove = useCallback((point) => { + if (point) { + setHoveredData({ + x: point.data.x, + y: point.data.y, + }); + } + }, []); + + const handleMouseLeave = useCallback(() => { + setHoveredData(null); + }, []); + + return { hoveredData, handleMouseMove, handleMouseLeave }; +}; + +export const formatDateToString = (date) => { + if (!(date instanceof Date) || isNaN(date.getTime())) { + console.error('Invalid date:', date); + return ''; + } + return `${date.getFullYear()}-${(date.getMonth() + 1) + .toString() + .padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date + .getHours() + .toString() + .padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; +}; diff --git a/src/components/cards/CardDetailsContainer.jsx b/src/containers/CardDetailsContainer.jsx similarity index 93% rename from src/components/cards/CardDetailsContainer.jsx rename to src/containers/CardDetailsContainer.jsx index 3b47c40..266ae68 100644 --- a/src/components/cards/CardDetailsContainer.jsx +++ b/src/containers/CardDetailsContainer.jsx @@ -1,7 +1,7 @@ // Import necessary modules from MUI or other libraries import React from 'react'; import { Typography, Grid } from '@mui/material'; -import { useStyles } from './cardStyles'; +import { useStyles } from '../components/cards/cardStyles'; const CardDetailsContainer = ({ card }) => { const classes = useStyles(); diff --git a/src/containers/PortfolioChartContainer.jsx b/src/containers/PortfolioChartContainer.jsx index 25b45d3..2f3348a 100644 --- a/src/containers/PortfolioChartContainer.jsx +++ b/src/containers/PortfolioChartContainer.jsx @@ -53,8 +53,8 @@ const PortfolioChartContainer = ({ selectedCards, removeCard }) => { const { allCollections, selectedCollection } = useCollectionStore(); const data = allCollections.map((collection) => { return { - name: collection.name, - data: collection.chartData.allXYValues, + name: collection?.name, + data: collection?.chartData?.allXYValues, }; }); return ( diff --git a/src/context/ChartContext/ChartContext.jsx b/src/context/ChartContext/ChartContext.jsx index f28f87a..d28e564 100644 --- a/src/context/ChartContext/ChartContext.jsx +++ b/src/context/ChartContext/ChartContext.jsx @@ -24,6 +24,7 @@ export const ChartProvider = ({ children }) => { const currentValue = timeRanges.find((option) => option.value === timeRange); + console.log('currentValue: ', currentValue); const handleChange = (e) => { setTimeRange(e.target.value); }; diff --git a/src/context/CollectionContext/ChunkPaylod.jsx b/src/context/CollectionContext/ChunkPaylod.jsx new file mode 100644 index 0000000..81b3b17 --- /dev/null +++ b/src/context/CollectionContext/ChunkPaylod.jsx @@ -0,0 +1,29 @@ +import { fetchWrapper } from './collectionUtility'; + +const chunkSize = 1024 * 1024 * 10; // 10 MB, adjust as needed + +// Function to break the payload into smaller chunks +export const chunkPayload = (payload) => { + const stringPayload = JSON.stringify(payload); + const payloadSize = new TextEncoder().encode(stringPayload).length; + + if (payloadSize <= chunkSize) { + return [payload]; // If payload is small, no need to chunk + } + + // If payload is too large, chunk it + const chunks = []; + for (let i = 0; i < stringPayload.length; i += chunkSize) { + const chunkStr = stringPayload.slice(i, i + chunkSize); + chunks.push(JSON.parse(chunkStr)); + } + + return chunks; +}; + +// Function to send chunks to the server +export const sendChunks = async (endpoint, method, chunks) => { + for (const chunk of chunks) { + await fetchWrapper(endpoint, method, chunk); + } +}; diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index 3b7a2be..2544b84 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -11,205 +11,34 @@ import React, { } from 'react'; import { useCookies } from 'react-cookie'; import { - filterOutDuplicateYValues, handleCardAddition, handleCardRemoval, - getUniqueFilteredXYValues, - calculateTotalFromAllCardPrices, - ensureNumber, - findCollectionIndex, createApiUrl, - handleApiResponse, - BASE_API_URL, fetchWrapper, - removeDuplicateCollections, getTotalCost, initialCollectionState, getCardPrice, defaultContextValue, validateUserIdAndData, - createPayload, - logError, - determineHttpMethod, + getUpdatedChartData, + getPriceChange, + constructPayloadWithDifferences, + getCurrentChartDataSets, } from './collectionUtility.jsx'; -import { useCombinedContext } from '../CombinedProvider.jsx'; -import { useUserContext } from '../UserContext/UserContext.js'; import moment from 'moment'; -import CustomLogger from '../CutstomLogger.jsx'; -import { - createNewDataSet, - getCollectionId, - // updateCardInCollection, -} from './cardHelpers.jsx'; -import { set } from 'date-fns'; +import { createNewDataSet } from './cardHelpers.jsx'; +import { chunkPayload, sendChunks } from './ChunkPaylod.jsx'; export const CollectionContext = createContext(defaultContextValue); -const constructPayloadWithDifferences = ( - existingData, - newData, - debug = false -) => { - const payload = {}; - let logContent = '[constructPayloadWithDifferences] Differences in data:\n'; - let typeMismatchContent = ''; // String to store type mismatch messages - let nonMatchingKeys = []; // List to store non-matching keys - - Object.keys(newData).forEach((key) => { - const isTypeDifferent = typeof newData[key] !== typeof existingData[key]; - const isValueDifferent = newData[key] !== existingData[key]; - - if (isValueDifferent || isTypeDifferent) { - payload[key] = newData[key]; - nonMatchingKeys.push(key); // Add the non-matching key to the list - - if (debug) { - if (isTypeDifferent) { - typeMismatchContent += ` - Field "${key}": Expected Type: ${typeof existingData[ - key - ]}, Received Type: ${typeof newData[key]}\n`; - } - if (isValueDifferent) { - logContent += ` - Field "${key}": Old Value: ${JSON.stringify( - existingData[key] - )}, New Value: ${JSON.stringify(newData[key])}\n`; - } - } - } - }); - - if (debug) { - console.log('1. Constructing payload with differences:', logContent); - if (typeMismatchContent) { - console.log('2. Type mismatches found:', typeMismatchContent); - } - } - return { payload, nonMatchingKeys, typeMismatchContent }; // Return both the payload, the list of non-matching keys, and type mismatch messages -}; - -function getCurrentChartDatasets(chartData) { - if (!chartData || !chartData.datasets) { - console.error('Invalid or missing chart data'); - return []; - } - - const currentChartDatasets = []; - - // Iterate over each dataset - chartData.datasets.forEach((dataset) => { - // Check if dataset has 'xys' array - if (dataset.data && dataset.data.length > 0) { - dataset.data.forEach((dataEntry) => { - if (dataEntry.xys && dataEntry.xys.length > 0) { - // Add all 'xys' entries to the currentChartDatasets array - currentChartDatasets.push(...dataEntry.xys.map((xy) => xy.data)); - } - }); - } - }); - - return currentChartDatasets; +function 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 + })); } -const getPriceChange = (collectionPriceHistory) => { - if ( - !Array.isArray(collectionPriceHistory) || - collectionPriceHistory.length === 0 - ) { - console.warn('Invalid or empty price history', collectionPriceHistory); - return 'n/a'; - } - - const mostRecentPrice = - collectionPriceHistory[collectionPriceHistory.length - 1]?.num; - const currentDate = new Date(); - - // Get the first price from the last 24 hours - const firstPriceFromLastDay = collectionPriceHistory - .slice() - .reverse() - .find((priceHistory) => { - const historyDate = new Date(priceHistory.timestamp); - return currentDate - historyDate <= 24 * 60 * 60 * 1000; // less than 24 hours - })?.num; - - if (mostRecentPrice && firstPriceFromLastDay) { - const priceChange = - ((mostRecentPrice - firstPriceFromLastDay) / firstPriceFromLastDay) * 100; - console.log( - `Price change over the last 24 hours is: ${priceChange.toFixed(2)}%` - ); - return priceChange.toFixed(2); - } else { - console.error('Could not calculate price change due to missing data'); - return null; - } -}; - -const getUpdatedChartData = (collection, newPrice) => { - const newXYValue = { - label: `Update - ${new Date().toISOString()}`, - x: new Date().toISOString(), - y: newPrice, - }; - console.log('Updating chart data with:', collection.chartData.allXYValues); - const allXYValues = collection.chartData?.allXYValues || []; - console.log('ALL XY VALUES:', allXYValues); - return { - ...collection, - chartData: { - ...collection.chartData, - allXYValues: [...(collection.chartData?.allXYValues ?? []), newXYValue], - }, - totalPrice: newPrice, - }; -}; - -const mergeCards = (existingCards, updatedCards) => { - const updatedCardMap = new Map(updatedCards.map((card) => [card.id, card])); - return existingCards.map((card) => { - if (updatedCardMap.has(card.id)) { - return { - ...card, - ...updatedCardMap.get(card.id), - price: updatedCardMap.get(card.id).price || card.price, - quantity: updatedCardMap.get(card.id).quantity || card.quantity, - }; - } - return card; - }); -}; - -const updateCardInCollection = (cards, cardToUpdate) => { - // Validate that cards is an array - if (!Array.isArray(cards)) { - throw new TypeError('The first argument must be an array of cards.'); - } - - // Validate that cardToUpdate is an object - if (typeof cardToUpdate !== 'object' || cardToUpdate === null) { - throw new TypeError('The card to update must be an object.'); - } - - // Validate that cardToUpdate has an id property - if (!('id' in cardToUpdate)) { - throw new Error('The card to update must have an "id" property.'); - } - - try { - // Attempt to update the card within the collection - const updatedCards = cards.map( - (card) => - card.id === cardToUpdate.id ? { ...card, ...cardToUpdate } : card // Update the card if the id matches - ); - console.log('3. Updated cards in collection:', updatedCards); - return updatedCards; - } catch (error) { - console.error('3. Failed to update card in collection:', error); - throw error; - } -}; - export const CollectionProvider = ({ children }) => { const [cookies] = useCookies(['user']); const [selectedCollection, setSelectedCollection] = useState( @@ -225,14 +54,19 @@ export const CollectionProvider = ({ children }) => { ] = useState([]); const [xyData, setXyData] = useState([]); + const [currentChartDataSets, setCurrentChartDataSets] = useState([]); + const [currentChartDataSets2, setCurrentChartDataSets2] = useState([]); const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = useState(false); const userId = cookies.user?.id; + // const currentChartDataSets = getCurrentChartDatasets( + // selectedCollection?.chartData + // ); const totalCost = useMemo( () => getTotalCost(selectedCollection), [selectedCollection] ); - + // console.log('CURRENT CHART DATASETS:', currentChartDataSets); const lastFetchedTime = useRef(null); const calculateTotalPrice = (collection) => collection.cards.reduce( @@ -240,40 +74,6 @@ export const CollectionProvider = ({ children }) => { 0 ); - // Consolidated state setters for collection - const setCollectionState = (newState) => { - setSelectedCollection(newState); - setAllCollections((prev) => - prev.map((c) => (c._id === newState._id ? newState : c)) - ); - }; - - const createUpdateInfo = ( - updatedCards, - updatedPrice, - userId, - selectedCollection, - collectionId - ) => { - const updateInfo = { - userId: userId, - name: selectedCollection.name, - description: selectedCollection.description, - cards: updatedCards, - totalCost: updatedPrice, - totalPrice: updatedPrice, - _id: collectionId, - allCardPrices: updatedCards.flatMap((card) => - Array(card.quantity).fill(card.card_prices?.[0]?.tcgplayer_price) - ), - quantity: updatedCards.length, - totalQuantity: updatedCards.reduce((acc, card) => acc + card.quantity, 0), - }; - - console.log('4. Created update information:', updateInfo); - return updateInfo; - }; - const fetchAndSetCollections = useCallback(async () => { // Throttle the fetch calls const currentTime = Date.now(); @@ -347,10 +147,13 @@ export const CollectionProvider = ({ children }) => { return; } - const payload = createPayload( - { name, description, userId }, - newCollectionInfo - ); + // const payload = createPayload(newCollectionInfo, name, description, userId); + const payload = { + ...newCollectionInfo, + name, + description, + userId, + }; const url = createApiUrl(`${userId}/collections`); console.log('Creating user collection with data:', { userId, @@ -360,10 +163,13 @@ export const CollectionProvider = ({ children }) => { }); console.log('Payload for user collection:', payload); - const savedCollection = await fetchWrapper(url, 'POST', payload); - console.log('6. Saved collection:', savedCollection); - updateCollectionData(savedCollection, 'allCollections'); - updateCollectionData(savedCollection, 'collectionData'); + const response = await fetchWrapper(url, 'POST', payload); + console.log('6. Saved collection:', response); + console.log('6. Saved collection:', response.data); + console.log('6. Saved collection:', response.message); + updateCollectionData(response.data, 'allCollections'); + updateCollectionData(response.data, 'collectionData'); + updateCollectionData(response.data, 'selectedCollection'); }; const removeCollection = async (collection) => { @@ -502,26 +308,35 @@ export const CollectionProvider = ({ children }) => { cardUpdate, operation ) => { - console.log('CARD UPDATE:', cardUpdate); - console.log('OPERATION', operation); - - console.log('COLLECTION WITH CARDS:', collectionWithCards); + const collectionId = selectedCollection._id || null; + if (collectionId) { + console.log('COLLECTION ID:', collectionId); + } + const isCreating = !collectionId; + const method = isCreating ? 'POST' : 'PUT'; + const updatedTotalPrice = calculateTotalPrice(collectionWithCards); + const newCollectionPriceHistoryObject = + createPriceHistoryObject(updatedTotalPrice); + // UPDATE CARDS IN COLLECTION WITH CARDS ENDPOINT --------------------------- + console.log('CARD UPDATE:', cardUpdate); const updatedCards = collectionWithCards.cards; - console.log('UPDATED CARDS:', updatedCards); - - // Calculate new total price for the collection - const updatedTotalPrice = updatedCards.reduce( - (total, card) => total + card.totalPrice, - 0 + let cardsPayload = { + cards: updatedCards, + }; + let cardsEndpoint = createApiUrl( + `${userId}/collections/${collectionWithCards._id}/updateCards` ); - console.log('UPDATED TOTAL PRICE:', updatedTotalPrice); - - // Construct a new collection price history object - const newCollectionPriceHistoryObject = - createPriceHistoryObject(updatedTotalPrice); + console.log( + `Sending ${method} request to ${cardsEndpoint} with payload:`, + cardsPayload + ); + let cardsResponse = await fetchWrapper(cardsEndpoint, method, cardsPayload); + console.log('Cards Update Response:', cardsResponse); + console.log('Cards Update Response Data:', cardsResponse.data); + console.log('Cards Update Response Message:', cardsResponse.message); - // Update collection chart data + // UPDATE CHARTS IN COLLECTION WITH CHARTS ENDPOINT -------------------------------- const updatedChartData = { ...selectedCollection.chartData, allXYValues: [ @@ -540,66 +355,94 @@ export const CollectionProvider = ({ children }) => { const testData = getUpdatedChartData(selectedCollection, updatedTotalPrice); console.log('TEST DATA:', testData); - // Ensure _id is included if updating an existing collection - const collectionId = selectedCollection._id || null; - if (collectionId) { - console.log('COLLECTION ID:', collectionId); - } + let chartDataPayload = { + chartData: updatedChartData, + }; + let chartDataEndpoint = createApiUrl( + `${userId}/collections/${collectionWithCards._id}/updateChartData` + ); + console.log( + `Sending ${method} request to ${chartDataEndpoint} with payload:`, + chartDataPayload + ); + let chartDataResponse = await fetchWrapper( + chartDataEndpoint, + method, + chartDataPayload + ); + console.log('Chart Data Update Response:', chartDataResponse); + console.log('Chart Data Update Response Data:', chartDataResponse.data); + console.log( + 'Chart Data Update Response Message:', + chartDataResponse.message + ); - // Construct the updated collection object + // UPDATE COLLECTION WITH COLLECTION ENDPOINT -------------------------------- const updatedCollection = { - ...selectedCollection, + // ...selectedCollection, allCardPrices: updatedCards.flatMap((card) => Array(card.quantity).fill(card.price) ), - description: selectedCollection.description, - name: selectedCollection.name, + description: collectionWithCards.description, + name: collectionWithCards.name, userId: userId, totalPrice: updatedTotalPrice, totalCost: updatedTotalPrice.toString(), totalQuantity: updatedCards.reduce((acc, card) => acc + card.quantity, 0), quantity: updatedCards.length, _id: collectionId, - cards: updatedCards, chartData: updatedChartData, + cards: updatedCards, dailyPriceChange: getPriceChange( + selectedCollection?.collectionPriceHistory + ), + currentChartDataSets: getCurrentChartDataSets(updatedChartData), + currentChartDataSets2: transformPriceHistoryToXY( selectedCollection.collectionPriceHistory ), - collectionPriceHistory: [ - ...selectedCollection.collectionPriceHistory, - newCollectionPriceHistoryObject, - ], + collectionPriceHistory: selectedCollection.collectionPriceHistory + ? [ + ...selectedCollection.collectionPriceHistory, + newCollectionPriceHistoryObject, + ] + : [newCollectionPriceHistoryObject], }; - console.log('UPDATED COLLECTION:', updatedCollection); - // Check if creating a new collection or updating an existing one - const isCreating = !collectionId; const endpoint = createApiUrl( `${userId}/collections/${collectionId || ''}` ); - const method = isCreating ? 'POST' : 'PUT'; - - // Here we only log what would be sent to the API, without making an actual call const { nonMatchingKeys, payload } = constructPayloadWithDifferences( selectedCollection, updatedCollection, true ); // Assume constructPayload does the necessary processing + console.log('NON-MATCHING KEYS:', nonMatchingKeys); console.log( `Sending ${method} request to ${endpoint} with payload:`, payload ); - console.log('PAYLOAD:', payload); - console.log('NON-MATCHING KEYS:', nonMatchingKeys); + setCurrentChartDataSets2( + transformPriceHistoryToXY(selectedCollection.collectionPriceHistory) + ); + // Break the payload into chunks if necessary + const chunks = chunkPayload(payload); + + console.log( + `Sending ${method} request to ${endpoint} with ${chunks.length} chunks.` + ); + const response = await fetchWrapper(endpoint, method, payload); console.log('RESPONSE', response); const updatedCollectionPostServer = response.data; - console.log('UPDATED COLLECTION POST SERVER:', updatedCollectionPostServer); - updateCollectionData(updatedCollectionPostServer, 'selectedCollection'); - updateCollectionData(updatedCollectionPostServer, 'collectionData'); + // const combinedCollectionUpdate = { + // ...updatedCard + + console.log('UPDATED COLLECTION POST SERVER:', updatedCollectionPostServer); updateCollectionData(updatedCollectionPostServer, 'allCollections'); + updateCollectionData(updatedCollectionPostServer, 'collectionData'); + updateCollectionData(updatedCollectionPostServer, 'selectedCollection'); return updatedCollection; }; @@ -620,6 +463,7 @@ export const CollectionProvider = ({ children }) => { operation ); console.log('UPDATED COLLECTION POST OP HANDLING:', updatedCollection); + updateCollectionData(updatedCollection, 'selectedCollection'); return updatedCollection; }; @@ -639,10 +483,12 @@ export const CollectionProvider = ({ children }) => { setUpdatedPricesFromCombinedContext, setOpenChooseCollectionDialog, calculateTotalPrice: () => getCardPrice(selectedCollection), + getTotalPrice: () => calculateTotalPrice(allCardPrices), getTotalCost: () => getTotalCost(selectedCollection), getCardQuantity: (cardId) => selectedCollection?.cards?.find((c) => c?.id === cardId)?.quantity || 0, - createUserCollection, + createUserCollection: (userId, newCollectionInfo, name, description) => + createUserCollection(userId, newCollectionInfo, name, description), removeCollection, addOneToCollection: (card) => handleCardOperation(card, 'add'), removeOneFromCollection: (card) => handleCardOperation(card, 'remove'), @@ -675,6 +521,21 @@ export const CollectionProvider = ({ children }) => { if (userId) fetchAndSetCollections(); }, [userId]); + useEffect(() => { + if (selectedCollection.chartData) + setCurrentChartDataSets( + getCurrentChartDataSets(selectedCollection.chartData) + ); + console.log('CURRENT CHART DATASETS:', currentChartDataSets); + }, [selectedCollection]); + + useEffect(() => { + if (selectedCollection.cards) { + setTotalPrice(calculateTotalPrice(selectedCollection)); + } + console.log('TOTAL PRICE:', totalPrice); + }, [selectedCollection]); + return ( {children} @@ -691,851 +552,3 @@ export const useCollectionStore = () => { } return context; }; -// const addOrRemoveCard = useCallback( -// async (card, cardInfo, operation) => { -// const collectionId = getCollectionId(selectedCollection, allCollections); -// if (!collectionId) { -// console.error('No valid collection selected.'); -// setOpenChooseCollectionDialog(true); -// return; -// } - -// let updatedCards = -// operation === 'update' -// ? updateCardInCollection(selectedCollection.cards, card) -// : getUpdatedCards(selectedCollection, card, operation); - -// const updatedPrice = calculateTotalFromAllCardPrices(updatedCards); -// const accumulatedTotal = selectedCollection.totalPrice + updatedPrice; - -// const newXYValue = { -// label: `Update - ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: accumulatedTotal, -// }; - -// // Update the chart data with the new accumulated total -// const updatedChartData = { -// ...selectedCollection.chartData, -// allXYValues: [...selectedCollection.chartData.allXYValues, newXYValue], -// }; - -// const updateInfo = createUpdateInfo( -// updatedCards, -// accumulatedTotal, -// cardInfo, -// userId, -// selectedCollection, -// collectionId, -// updatedChartData, -// xyData -// ); -// console.log('[UPDATE INFO]:', updateInfo); -// console.log('[ACCUMULATED]:', accumulatedTotal); -// const updatedCollection = { -// ...selectedCollection, -// ...updateInfo, -// chartData: updatedChartData, -// // totalPrice: accumulatedTotal, -// collectionPriceHistory: [ -// ...(selectedCollection.collectionPriceHistory || []), -// createPriceHistoryObject(accumulatedTotal), -// ], -// }; - -// console.log('[COLLECTION DATA][B4UPDATE]:', updatedCollection); -// await updateActiveCollection(updatedCollection, updateInfo.chartData); -// updateCollectionData(updatedCollection, 'selectedCollection'); -// updateCollectionData(updatedCollection, 'collectionData'); -// }, -// [ -// selectedCollection, -// allCollections, -// userId, -// openChooseCollectionDialog, -// handleCardAddition, -// handleCardRemoval, -// updateCollectionData, -// setOpenChooseCollectionDialog, -// ] -// ); - -// Main functions -// const addOrUpdateCardInCollection = (collection, card, newPrice) => { -// // Find the card in the collection or create a new one -// const existingCardIndex = collection.cards.findIndex( -// (c) => c.id === card.id -// ); -// const updatedCard = updateCardPriceDetails(card, newPrice); - -// let updatedCards = [...collection.cards]; -// if (existingCardIndex >= 0) { -// updatedCards[existingCardIndex] = updatedCard; // Update existing card -// } else { -// updatedCards = [...updatedCards, updatedCard]; // Add new card -// } - -// const updatedTotalPrice = updatedCards.reduce( -// (acc, card) => acc + card.totalPrice, -// 0 -// ); -// const updatedAllCardPrices = updatedCards.map((card) => card.price); - -// return { -// ...collection, -// cards: updatedCards, -// totalPrice: updatedTotalPrice, -// allCardPrices: updatedAllCardPrices, -// }; -// }; - -// Function to save the collection to the backend -// const saveCollectionToApi = async (collection, method) => { -// try { -// const url = `/api/users/collections/${ -// method === 'POST' ? '' : collection._id -// }`; -// const response = await fetchWrapper(url, method, collection); -// console.log( -// `Collection ${method === 'POST' ? 'created' : 'updated'}:`, -// response.data -// ); -// } catch (error) { -// console.error( -// `Failed to ${method === 'POST' ? 'create' : 'update'} collection:`, -// error -// ); -// } -// }; - -// const updateActiveCollection = useCallback( -// async (collectionData) => { -// if (!collectionData) throw new Error('No collection data provided.'); - -// CustomLogger({ 'COLLECTION DATA': collectionData }); -// const isCreatingNew = !collectionData?._id; -// const actionDescription = isCreatingNew -// ? 'create a new collection' -// : 'update the existing collection'; -// const method = determineHttpMethod( -// isCreatingNew, -// collectionData?.endpoint -// ); -// CustomLogger({ 'METHOD:': method }); -// try { -// if (isCreatingNew) { -// console.log( -// `Skipping fetch call to ${method} since it's a new collection` -// ); -// } else { -// const endpoint = createApiUrl( -// `${userId}/collections/${collectionData?._id}` -// ); -// console.log('ENDPOINT:', endpoint); - -// const payload = constructPayloadWithDifferences( -// initialCollectionState, -// collectionData -// ); -// console.log('PAYLOAD:', collectionData); -// console.log(`Debug: ${method} ${endpoint}`); -// const response = await fetchWrapper(endpoint, method, payload); -// console.log('[UPDATE RESPONSE][RAW]:', response); -// const updatedCollection = response?.data; -// console.log('[UPDATE RESPONSE][DES]:', updatedCollection); - -// if (!updatedCollection) -// throw new Error('No collection returned from server.'); -// updateCollectionChartData(updatedCollection, existingChartData); -// // updateChartDataForCollection(updatedCollection, existingChartData); -// updateCollectionData(updatedCollection, 'selectedCollection'); -// updateCollectionData(updatedCollection, 'collectionData'); -// updateCollectionData(updatedCollection, 'allCollections'); -// } -// } catch (error) { -// console.error( -// `Failed to update collection for user ${userId} with collectionId ${collectionData._id}:`, -// error -// ); -// logError('updateActiveCollection', actionDescription, error); -// console.error(`Failed to ${actionDescription}: ${error.message}`); -// } -// }, -// [userId, updateCollectionData] -// ); -// Adds or updates a card in the collection - -// const handleUpdatePrices = useCallback( -// (priceUpdates) => { -// const timestamp = new Date().toISOString(); - -// setSelectedCollection((prevCollection) => { -// const updatedCards = prevCollection.cards.map((card) => { -// const update = priceUpdates.find((u) => u.id === card.id); -// if (update) { -// return { -// ...card, -// price: update.newPrice, -// latestPrice: { num: update.newPrice, timestamp }, -// lastSavedPrice: card.latestPrice, -// priceHistory: -// card.latestPrice.num !== update.newPrice -// ? [...card.priceHistory, { num: update.newPrice, timestamp }] -// : card.priceHistory, -// chart_datasets: updateChartDatasets( -// card, -// update.newPrice * card.quantity, -// timestamp -// ), -// }; -// } -// return card; -// }); - -// const newTotalPrice = calculateTotalFromCardPrices(updatedCards); - -// // Save the updated collection to the backend -// updateCardPrices({ -// ...prevCollection, -// cards: updatedCards, -// totalPrice: newTotalPrice, -// }); - -// return { -// ...prevCollection, -// cards: updatedCards, -// totalPrice: newTotalPrice, -// }; -// }); -// }, -// [updateCardPrices] -// ); - -// Transform the card data to fit the collection item structure -// const transformCardToCollectionItem = (card) => { -// return { -// id: card.id, -// name: card.name, -// price: card.price, -// quantity: 1, // Default to 1 for a new card, adjust as needed -// ... any other card properties needed -// }; -// }; -// const updateCardPriceHistory = (card, newPrice) => { -// if (card.priceHistory.slice(-1)[0]?.num !== newPrice) { -// return [...card.priceHistory, createPriceHistoryEntry(newPrice)]; -// } -// return card.priceHistory; -// }; -// // Refactored getUpdatedCards function -// const getUpdatedCards = (activeCollection, card, operation) => { -// const handleOperation = -// operation === 'add' ? handleCardAddition : handleCardRemoval; -// const cardsToUpdate = handleOperation(activeCollection?.cards, card); - -// return cardsToUpdate.map(updateCardDetails); -// }; - -// // // Function to handle the update of card price and price history -// // const updateCardDetails = (card) => { -// // const cardPrice = card.card_prices?.[0]?.tcgplayer_price; -// // const computedPrice = cardPrice * card.quantity; - -// // card.chart_datasets = updateChartDatasets(card, computedPrice); -// // card.price = cardPrice; -// // card.totalPrice = computedPrice; - -// // const lastEntry = card.priceHistory?.slice(-1)?.[0]; -// // if (!lastEntry || lastEntry.num !== cardPrice) { -// // card.priceHistory = [ -// // ...(card.priceHistory || []), -// // createPriceHistoryObject(cardPrice), -// // ]; -// // } - -// // return card; -// // }; - -// // Update the allCardPrices array based on the new card's price -// const updateAllCardPrices = (card) => { -// return [...selectedCollection.allCardPrices, card.price]; -// }; -// // Update the collection's cards array with the new card -// const updatePriceHistory = (card, newPrice) => { -// const lastPriceEntry = card.priceHistory?.slice(-1)?.[0]; -// return lastPriceEntry && lastPriceEntry.num !== newPrice -// ? [ -// ...card.priceHistory, -// { num: newPrice, timestamp: new Date().toISOString() }, -// ] -// : card.priceHistory; -// }; - -// Only add a new entry if the price has changed -// Refactored addOrRemoveCard function -// const addOrRemoveCard = useCallback( -// async (card, cardInfo, operation) => { -// const collectionId = selectedCollection._id; -// if (!collectionId) { -// console.error('No valid collection selected.'); -// setOpenChooseCollectionDialog(true); -// return; -// } - -// const updatedCards = -// operation === 'update' -// ? updateCardInCollection(selectedCollection.cards, card) -// : getUpdatedCards(selectedCollection, card, operation); - -// const updateInfo = createUpdateInfo(selectedCollection, updatedCards); -// const updatedCollection = await updateActiveCollection( -// selectedCollection._id, -// updateInfo -// ); - -// updateCollectionData(updatedCollection, 'selectedCollection'); -// }, -// [selectedCollection, updateCollectionData, setOpenChooseCollectionDialog] -// ); - -// Function to update the active collection on the backend -// const updateActiveCollection = async (collectionId, updateInfo) => { -// const url = createApiUrl(`${userId}/collections/${collectionId._id}`); - -// try { -// const response = await fetchWrapper(url, 'PUT', updateInfo); -// if (!response.data) -// throw new Error('No collection returned from server.'); -// return response.data; -// } catch (error) { -// console.error(`Failed to update collection: ${error}`); -// throw error; -// } -// }; - -// const getUpdatedCards = (activeCollection, card, operation) => { -// const cardsToUpdate = -// operation === 'add' -// ? handleCardAddition(activeCollection?.cards, card) -// : handleCardRemoval(activeCollection?.cards, card); - -// return cardsToUpdate.map((card) => { -// const cardPrice = card.card_prices?.[0]?.tcgplayer_price; -// const computedPrice = cardPrice * card.quantity; -// console.log('COMPUTED PRICE:', computedPrice); - -// // Update chart_datasets if necessary -// const allDatasets = [ -// ...(card?.chart_datasets || []), -// { x: moment().format('YYYY-MM-DD HH:mm'), y: computedPrice }, -// ]; -// card.chart_datasets = filterOutDuplicateYValues(allDatasets); - -// // Set card's price and total price -// card.price = cardPrice; -// card.totalPrice = computedPrice; - -// // Create a new price history object -// const newPriceHistoryObject = createPriceHistoryObject(cardPrice); - -// // Update priceHistory only if the last entry's num is different from the new price -// if ( -// !card.priceHistory || -// card.priceHistory.length === 0 || -// card.priceHistory[card.priceHistory.length - 1].num !== cardPrice -// ) { -// card.priceHistory = [ -// ...(card.priceHistory || []), -// newPriceHistoryObject, -// ]; -// } -// card.tag = card.tag || 'monitored'; -// return card; -// }); -// }; - -// const createPriceHistoryObject = (price) => ({ -// timestamp: new Date().toISOString(), -// num: price, -// }); - -// // The main function for adding or removing a card -// const addOrRemoveCard = useCallback( -// async (card, cardInfo, operation) => { -// const collectionId = getCollectionId(selectedCollection, allCollections); -// if (!collectionId) { -// console.error('No valid collection selected.'); -// setOpenChooseCollectionDialog(true); -// return; -// } - -// let updatedCards = -// operation === 'update' -// ? updateCardInCollection(selectedCollection.cards, card) -// : getUpdatedCards(selectedCollection, card, operation); - -// const updatedPrice = calculateTotalFromAllCardPrices(updatedCards); -// const accumulatedTotal = selectedCollection.totalPrice + updatedPrice; - -// const newXYValue = { -// label: `Update - ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: accumulatedTotal, -// }; - -// // Update the chart data with the new accumulated total -// const updatedChartData = { -// ...selectedCollection.chartData, -// allXYValues: [...selectedCollection.chartData.allXYValues, newXYValue], -// }; - -// const updateInfo = createUpdateInfo( -// updatedCards, -// accumulatedTotal, -// cardInfo, -// userId, -// selectedCollection, -// collectionId, -// updatedChartData, -// xyData -// ); -// console.log('[UPDATE INFO]:', updateInfo); -// console.log('[ACCUMULATED]:', accumulatedTotal); -// const updatedCollection = { -// ...selectedCollection, -// ...updateInfo, -// chartData: updatedChartData, -// // totalPrice: accumulatedTotal, -// collectionPriceHistory: [ -// ...(selectedCollection.collectionPriceHistory || []), -// createPriceHistoryObject(accumulatedTotal), -// ], -// }; - -// console.log('[COLLECTION DATA][B4UPDATE]:', updatedCollection); -// await updateActiveCollection(updatedCollection, updateInfo.chartData); -// updateCollectionData(updatedCollection, 'selectedCollection'); -// updateCollectionData(updatedCollection, 'collectionData'); -// }, -// [ -// selectedCollection, -// allCollections, -// userId, -// openChooseCollectionDialog, -// handleCardAddition, -// handleCardRemoval, -// updateCollectionData, -// setOpenChooseCollectionDialog, -// ] -// ); - -// const updateCardCollection = async ( -// selectedCollection, -// priceData, -// operation -// ) => { -// if (operation !== 'update') { -// console.warn('Invalid operation:', operation); -// return; -// } - -// const updatedCards = priceData.data.data; -// let accumulatedTotal = selectedCollection.totalPrice || 0; -// let newXYValues = [...selectedCollection.chartData.allXYValues]; - -// updatedCards.forEach((card) => { -// // Only add to priceHistory if the num value is different from the last entry -// const lastPriceHistoryNum = card.priceHistory?.slice(-1)[0]?.num; -// if (card.latestPrice?.num !== lastPriceHistoryNum) { -// const newPriceHistoryEntry = { -// num: card.latestPrice?.num || 0, -// timestamp: new Date().toISOString(), -// }; -// card.priceHistory = [ -// ...(card.priceHistory || []), -// newPriceHistoryEntry, -// ]; -// } - -// card.lastSavedPrice = { -// num: card.lastSavedPrice?.num || card.latestPrice?.num || 0, -// timestamp: card.lastSavedPrice?.timestamp || new Date().toISOString(), -// }; - -// card.latestPrice = { -// num: card.latestPrice?.num || 0, -// timestamp: new Date().toISOString(), -// }; - -// card.tag = card.tag || 'monitored'; - -// // Calculate the new total price -// accumulatedTotal += card.latestPrice.num * card.quantity; - -// // Update the chart data -// newXYValues.push({ -// label: `Update - ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: accumulatedTotal, -// }); -// }); - -// // Update the chart data with accumulated values -// const updatedChartData = { -// ...selectedCollection.chartData, -// allXYValues: newXYValues, -// }; - -// const updatedCollectionWithChartData = { -// ...selectedCollection, -// cards: updatedCards, -// totalPrice: accumulatedTotal, -// chartData: updatedChartData, -// }; - -// await updateActiveCollection(updatedCollectionWithChartData); -// return updatedCollectionWithChartData; -// }; - -// const updateCollectionChartData = ( -// collection, -// updatedPrice, -// updatedChartData -// ) => { -// const chartData = collection.chartData || { -// name: `Chart for Collection: ${collection.name}`, -// userId: collection.userId, -// datasets: [], -// allXYValues: [], -// xys: [], -// }; - -// const newXYValue = { -// label: `Update - ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: updatedPrice, -// }; - -// // Update the allXYValues array with the new data point -// const allXYValues = [...collection.chartData.allXYValues, newXYValue]; - -// const uniqueFilteredXYValues = getUniqueFilteredXYValues(allXYValues); - -// return { -// ...collection, -// chartData: { -// ...collection.chartData, -// allXYValues: uniqueFilteredXYValues, -// }, -// totalPrice: updatedPrice, -// }; -// }; - -// Create a new XY value for the current update -// const newXYValue = { -// label: `Update ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: updatedPrice, -// additionalPriceData: { -// priceChanged: updatedPrice !== collection.totalPrice, -// initialPrice: collection.totalPrice, -// updatedPrice: updatedPrice, -// priceDifference: updatedPrice - collection.totalPrice, -// priceChange: parseFloat( -// ((updatedPrice - collection.totalPrice) / collection.totalPrice) * 100 -// ).toFixed(2), -// }, -// }; - -// Update datasets if newDataSet is provided -// if (newDataSet && Array.isArray(newDataSet.data)) { -// newDataSet.data.forEach((dataset) => { -// if (dataset.xys && Array.isArray(dataset.xys)) { -// chartData.datasets.push(dataset); -// } -// }); -// } -// if (newDataSet && Array.isArray(newDataSet.data)) { -// newDataSet.data.forEach((dataset) => { -// if (dataset.xys && Array.isArray(dataset.xys)) { -// chartData.datasets.push(...dataset.xys); -// } -// }); -// } -// Return the updated collection with the new chartData -// const updateCardCollection = async ( -// selectedCollection, -// priceData, -// operation -// ) => { -// const priceArray = priceData.data.data; -// let updatedCards = selectedCollection.cards.map((card) => { -// const matchingNewCard = priceArray.find( -// (newCard) => newCard.id === card.id -// ); -// if (matchingNewCard) { -// const { latestPrice, priceHistory, name, quantity, tag, _id } = -// matchingNewCard; -// return { ...card, latestPrice, priceHistory, name, quantity, tag, _id }; -// } -// return card; -// }); - -// let newTotalPrice = updatedCards.reduce( -// (total, card) => total + (card.latestPrice?.num * card.quantity || 0), -// 0 -// ); - -// let newAllCardPrices = updatedCards.flatMap((card) => -// Array(card.quantity).fill(card.latestPrice?.num) -// ); - -// const newDataSet = { -// data: [ -// { -// xys: [ -// { -// label: `Update ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: newTotalPrice, -// }, -// ], -// }, -// ], -// }; - -// const updatedCollectionWithChartData = updateCollectionChartData( -// selectedCollection, -// newTotalPrice, -// newDataSet -// ); - -// await updateActiveCollection(updatedCollectionWithChartData); - -// return updatedCollectionWithChartData; -// }; - -// function updateChartDataForCollection(collection, chartData) { -// if (!collection.chartData) { -// collection.chartData = { -// name: `Chart for Collection: ${collection.name}`, -// userId: collection.userId, -// datasets: [], -// allXYValues: [], -// }; -// } - -// if (chartData) { -// collection.chartData = { ...collection.chartData, ...chartData }; -// } - -// const newXYSData = getUniqueFilteredXYValues( -// collection.chartData.allXYValues -// ); -// const timestamp = new Date(); -// const newPrice = collection.totalPrice; -// const previousPrice = collection.previousDayTotalPrice || newPrice; -// const priceDifference = newPrice - previousPrice; -// const priceChange = previousPrice -// ? (priceDifference / previousPrice) * 100 -// : 0; - -// collection.chartData.allXYValues.push({ -// label: `Update ${timestamp.toISOString()}`, -// x: timestamp, -// y: newPrice, -// }); - -// const newDatasetEntry = { -// name: collection.name, -// data: newXYSData.map((xy) => ({ -// ...xy, -// additionalPriceData: { -// priceChanged: priceDifference !== 0, -// initialPrice: previousPrice, -// updatedPrice: newPrice, -// priceDifference: priceDifference, -// priceChange: parseFloat(priceChange.toFixed(2)), -// }, -// })), -// }; - -// collection.chartData.datasets.push(newDatasetEntry); - -// return collection; -// } -// useEffect(() => { -// if (!selectedCollection || typeof totalPrice !== 'number') return; - -// const updateNumber = -// selectedCollection.chartData?.datasets?.length + 1 || 1; -// const newDataSet = createNewDataSet(updateNumber, totalPrice); - -// const updatedChartData = updateCollectionChartData( -// selectedCollection, -// totalPrice, -// newDataSet -// ); - -// const updatedCollection = { -// ...selectedCollection, -// chartData: updatedChartData, -// totalPrice, -// allCardPrices, -// }; - -// updateActiveCollection(updatedCollection); -// }, [totalPrice, selectedCollection, allCardPrices]); - -// const createNewDataSet = (updateNumber, totalPrice) => ({ -// data: [ -// { -// xys: [ -// { -// label: `Update Number ${updateNumber}`, -// data: { x: new Date().toISOString(), y: totalPrice }, -// }, -// ], -// additionalPriceData: {}, // Additional data can be added here -// }, -// ], -// }); - -// Additional functions and context setup as necessary - -// const updateActiveCollection = useCallback( -// async (collectionData, existingChartData = {}) => { -// if (!collectionData) throw new Error('No collection data provided.'); - -// const isCreatingNew = !collectionData?._id; -// const actionDescription = isCreatingNew -// ? 'create a new collection' -// : 'update the existing collection'; -// const method = determineHttpMethod( -// isCreatingNew, -// collectionData?.endpoint -// ); - -// try { -// if (isCreatingNew) { -// console.log( -// `Skipping fetch call to ${method} since it's a new collection` -// ); -// updateChartDataForCollection(collectionData, existingChartData); -// } else { -// const endpoint = createApiUrl( -// `${userId}/collections/${collectionData._id}` -// ); -// console.log('ENDPOINT:', endpoint); - -// // Calculate the difference between the existing chart data and the new collection data -// const payload = constructPayloadWithDifferences( -// existingChartData, -// collectionData -// ); - -// console.log(`Debug: ${method} ${endpoint}`); -// const response = await fetchWrapper(endpoint, method, payload); -// console.log('RESPONSE:', response); -// const updatedCollection = response?.data; -// console.log('[COLLECTION DATA][A4UPDATE]:', updatedCollection); - -// if (!updatedCollection) -// throw new Error('No collection returned from server.'); - -// updateChartDataForCollection(updatedCollection, existingChartData); -// updateCollectionData(updatedCollection, 'selectedCollection'); -// updateCollectionData(updatedCollection, 'collectionData'); -// updateCollectionData(updatedCollection, 'allCollections'); -// } -// } catch (error) { -// console.error( -// `Failed to update collection for user ${userId} with collectionId ${collectionData._id}:`, -// error -// ); -// logError('updateActiveCollection', actionDescription, error); -// console.error(`Failed to ${actionDescription}: ${error.message}`); -// } -// }, -// [userId, updateCollectionData] -// ); - -// // Helper function to construct payload with only the differences -// function constructPayloadWithDifferences(existingData, newData) { -// const payload = {}; -// Object.keys(newData).forEach((key) => { -// if (newData[key] !== existingData[key]) { -// payload[key] = newData[key]; -// } -// }); -// return payload; -// } - -// function updateChartDataForCollection(collection, chartData) { -// if (!collection.chartData) { -// collection.chartData = { -// // Initialize with defaults if not present -// name: `Chart for Collection: ${collection.name}`, -// userId: collection.userId, -// datasets: [], -// allXYValues: [], -// }; -// } - -// // Merge in any additional chartData that was passed in -// if (chartData) { -// collection.chartData = { ...collection.chartData, ...chartData }; -// } - -// // Get new x and y values -// const newXYSData = getUniqueFilteredXYValues( -// collection.chartData.allXYValues -// ); - -// // Calculate new and previous price -// const timestamp = new Date(); -// const newPrice = collection.totalPrice; // Fixed the code to get the correct value -// const previousPrice = collection.previousDayTotalPrice || newPrice; // Assuming previousDayTotalPrice is a property -// const priceDifference = newPrice - previousPrice; -// const priceChange = previousPrice -// ? (priceDifference / previousPrice) * 100 -// : 0; - -// // Update the allXYValues field with the new data point -// collection.chartData.allXYValues.push({ -// label: `Update ${new Date().toISOString()}`, -// x: new Date(), -// y: newPrice, -// }); - -// const newDatasetEntry = { -// name: collection.name, // or some other way to name the dataset -// data: [ -// { -// xys: [ -// // newXYSData.map((xy) => ({ -// { -// label: `Update ${timestamp.toISOString()}`, -// data: { x: timestamp, y: newPrice }, -// }, -// // })), -// ], -// additionalPriceData: [ -// { -// priceChanged: priceDifference !== 0, -// initialPrice: previousPrice, -// updatedPrice: newPrice, -// priceDifference: priceDifference, -// priceChange: parseFloat(priceChange.toFixed(2)), -// }, -// ], -// }, -// ], -// }; - -// // Push the new Dataset to the datasets array -// collection.chartData.datasets.push(newDatasetEntry); - -// // Return the updated collection object -// return collection; -// } diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx index 52f6490..a7756f1 100644 --- a/src/context/CollectionContext/collectionUtility.jsx +++ b/src/context/CollectionContext/collectionUtility.jsx @@ -14,7 +14,7 @@ const initialCollectionState = { priceChange: 0, // Initialize as 0 if not provided allCardPrices: [], // Initialize as empty array if not provided cards: [], // Initialize as empty array if not provided - currentChartDatasets: [], // Initialize as empty array if not provided + currentChartDataSets: [], // Initialize as empty array if not provided xys: [], // Use defaultXyData or initialize as empty if not provided chartData: { name: '', // Initialize as empty string if not provided @@ -430,31 +430,108 @@ const determineHttpMethod = (isCreatingNew, endpoint) => { }; // Abstracted payload creation to reduce repetition -const createPayload = (info, data, defaultXyData) => ({ - userId: info.userId || data.userId, // Assuming this is an ObjectId string - name: info.name || data.name, - description: info.description || data.description, - totalCost: '', // Initialize as empty string if not provided - totalPrice: 0, // Initialize as 0 if not provided - quantity: 0, // Initialize as 0 if not provided - totalQuantity: 0, // Initialize as 0 if not provided - previousDayTotalPrice: 0, // Initialize as 0 if not provided - dailyPriceChange: 0, // Initialize as 0 if not provided - priceDifference: 0, // Initialize as 0 if not provided - priceChange: 0, // Initialize as 0 if not provided - collectionPriceHistory: [], // Initialize as empty array if not provided - allCardPrices: [], // Initialize as empty array if not provided - cards: [], // Initialize as empty array if not provided - currentChartDatasets: [], // Initialize as empty array if not provided - xys: defaultXyData || [], // Use defaultXyData or initialize as empty if not provided - chartData: { - name: '', // Initialize as empty string if not provided - userId: info.userId || data.userId, // Assuming this is an ObjectId string - datasets: [], // Initialize as empty array if not provided - xys: [], // Initialize as empty array if not provided - allXYValues: [], // Initialize as empty array if not provided - }, -}); +// const createPayload = (info, data, defaultXyData) => ({ +// userId: info.userId || data.userId, // Assuming this is an ObjectId string +// name: info.name || data.name || '', +// description: info.description || data.description || '', +// totalCost: info.totalCost || data.totalCost || '', +// totalPrice: info.totalPrice || data.totalPrice || 0, +// quantity: info.quantity || data.quantity || 0, +// totalQuantity: info.totalQuantity || data.totalQuantity || 0, +// previousDayTotalPrice: +// info.previousDayTotalPrice || data.previousDayTotalPrice || 0, +// dailyPriceChange: info.dailyPriceChange || data.dailyPriceChange || '', +// priceDifference: info.priceDifference || data.priceDifference || 0, +// priceChange: info.priceChange || data.priceChange || 0, +// collectionPriceHistory: Array.isArray(info.collectionPriceHistory) +// ? info.collectionPriceHistory +// : Array.isArray(data.collectionPriceHistory) +// ? data.collectionPriceHistory +// : [], +// allCardPrices: Array.isArray(info.allCardPrices) +// ? info.allCardPrices +// : Array.isArray(data.allCardPrices) +// ? data.allCardPrices +// : [], +// cards: Array.isArray(info.cards) +// ? info.cards +// : Array.isArray(data.cards) +// ? data.cards +// : [], +// currentChartDatasets: Array.isArray(info.currentChartDatasets) +// ? info.currentChartDatasets +// : Array.isArray(data.currentChartDatasets) +// ? data.currentChartDatasets +// : [], +// xys: +// defaultXyData || +// (Array.isArray(info.xys) +// ? info.xys +// : Array.isArray(data.xys) +// ? data.xys +// : []), +// chartData: { +// name: +// info.chartData?.name || +// data.chartData?.name || +// `Chart for ${info.name || data.name || 'Collection'}`, +// userId: +// info.chartData?.userId || +// data.chartData?.userId || +// info.userId || +// data.userId, +// datasets: Array.isArray(info.chartData?.datasets) +// ? info.chartData.datasets +// : Array.isArray(data.chartData?.datasets) +// ? data.chartData.datasets +// : [], +// allXYValues: Array.isArray(info.chartData?.allXYValues) +// ? info.chartData.allXYValues +// : Array.isArray(data.chartData?.allXYValues) +// ? data.chartData.allXYValues +// : [], +// xys: Array.isArray(info.chartData?.xys) +// ? info.chartData.xys +// : Array.isArray(data.chartData?.xys) +// ? data.chartData.xys +// : [], +// }, +// }); + +const createPayload = (info) => { + // Merge the 'info' and 'data' objects + // const mergedData = { ...data, ...info }; + + // console.log('MERGED DATA:', mergedData); + console.log('INFO:', info); + // console.log('DATA:', data); + return { + ...info, + userId: info.userId, + name: info.name, + description: info.description, + totalCost: info.totalCost || '', + totalPrice: info.totalPrice || 0, + quantity: info.quantity || 0, + totalQuantity: info.totalQuantity || 0, + previousDayTotalPrice: info.previousDayTotalPrice || 0, + dailyPriceChange: info.dailyPriceChange || 0, + priceDifference: info.priceDifference || 0, + priceChange: info.priceChange || 0, + collectionPriceHistory: info.collectionPriceHistory || [], + allCardPrices: info.allCardPrices || [], + cards: info.cards || [], + currentChartDataSets: info.currentChartDataSets || [], + xys: info.xys || [], + chartData: { + name: info.chartData?.name || `Chart for ${info.name || 'Collection'}`, + userId: info.chartData?.userId || info.userId, + datasets: info.chartData?.datasets || [], + allXYValues: info.chartData?.allXYValues || [], + xys: info.chartData?.xys || [], + }, + }; +}; const removeDuplicateCollections = (collections) => { const uniqueCollections = {}; @@ -464,6 +541,177 @@ const removeDuplicateCollections = (collections) => { return Object.values(uniqueCollections); }; +const constructPayloadWithDifferences = ( + existingData, + newData, + debug = false +) => { + const payload = {}; + let logContent = '[constructPayloadWithDifferences] Differences in data:\n'; + let typeMismatchContent = ''; // String to store type mismatch messages + let nonMatchingKeys = []; // List to store non-matching keys + + Object.keys(newData).forEach((key) => { + const isTypeDifferent = typeof newData[key] !== typeof existingData[key]; + const isValueDifferent = newData[key] !== existingData[key]; + + if (isValueDifferent || isTypeDifferent) { + payload[key] = newData[key]; + nonMatchingKeys.push(key); // Add the non-matching key to the list + + if (debug) { + if (isTypeDifferent) { + typeMismatchContent += ` - Field "${key}": Expected Type: ${typeof existingData[ + key + ]}, Received Type: ${typeof newData[key]}\n`; + } + if (isValueDifferent) { + logContent += ` - Field "${key}": Old Value: ${JSON.stringify( + existingData[key] + )}, New Value: ${JSON.stringify(newData[key])}\n`; + } + } + } + }); + + if (debug) { + console.log('1. Constructing payload with differences:', logContent); + if (typeMismatchContent) { + console.log('2. Type mismatches found:', typeMismatchContent); + } + } + return { payload, nonMatchingKeys, typeMismatchContent }; // Return both the payload, the list of non-matching keys, and type mismatch messages +}; + +function getCurrentChartDataSets(chartData) { + if (!chartData || !chartData.datasets) { + console.error('Invalid or missing chart data'); + return []; + } + + const currentChartDataSets = []; + + // Iterate over each dataset + chartData.datasets.forEach((dataset) => { + // Check if dataset has 'data' array + if (dataset.data && dataset.data.length > 0) { + dataset.data.forEach((dataEntry) => { + // Check if dataEntry has 'xys' array + if (dataEntry.xys && dataEntry.xys.length > 0) { + // Add both 'data' and 'label' from each 'xys' entry + currentChartDataSets.push( + ...dataEntry.xys.map((xy) => ({ + ...xy.data, // Spread the 'data' object + label: xy.label, // Include the 'label' + })) + ); + } + }); + } + }); + + return currentChartDataSets; +} + +const getPriceChange = (collectionPriceHistory) => { + if ( + !Array.isArray(collectionPriceHistory) || + collectionPriceHistory.length === 0 + ) { + console.warn('Invalid or empty price history', collectionPriceHistory); + return 'n/a'; + } + + const mostRecentPrice = + collectionPriceHistory[collectionPriceHistory.length - 1]?.num; + const currentDate = new Date(); + + // Get the first price from the last 24 hours + const firstPriceFromLastDay = collectionPriceHistory + .slice() + .reverse() + .find((priceHistory) => { + const historyDate = new Date(priceHistory.timestamp); + return currentDate - historyDate <= 24 * 60 * 60 * 1000; // less than 24 hours + })?.num; + + if (mostRecentPrice && firstPriceFromLastDay) { + const priceChange = + ((mostRecentPrice - firstPriceFromLastDay) / firstPriceFromLastDay) * 100; + console.log( + `Price change over the last 24 hours is: ${priceChange.toFixed(2)}%` + ); + return priceChange.toFixed(2); + } else { + console.error('Could not calculate price change due to missing data'); + return null; + } +}; + +const getUpdatedChartData = (collection, newPrice) => { + const newXYValue = { + label: `Update - ${new Date().toISOString()}`, + x: new Date().toISOString(), + y: newPrice, + }; + console.log('Updating chart data with:', collection?.chartData?.allXYValues); + const allXYValues = collection?.chartData?.allXYValues || []; + console.log('ALL XY VALUES:', allXYValues); + return { + ...collection, + chartData: { + ...collection?.chartData, + allXYValues: [...(collection?.chartData?.allXYValues ?? []), newXYValue], + }, + totalPrice: newPrice, + }; +}; + +const mergeCards = (existingCards, updatedCards) => { + const updatedCardMap = new Map(updatedCards.map((card) => [card.id, card])); + return existingCards.map((card) => { + if (updatedCardMap.has(card.id)) { + return { + ...card, + ...updatedCardMap.get(card.id), + price: updatedCardMap.get(card.id).price || card.price, + quantity: updatedCardMap.get(card.id).quantity || card.quantity, + }; + } + return card; + }); +}; + +const updateCardInCollection = (cards, cardToUpdate) => { + // Validate that cards is an array + if (!Array.isArray(cards)) { + throw new TypeError('The first argument must be an array of cards.'); + } + + // Validate that cardToUpdate is an object + if (typeof cardToUpdate !== 'object' || cardToUpdate === null) { + throw new TypeError('The card to update must be an object.'); + } + + // Validate that cardToUpdate has an id property + if (!('id' in cardToUpdate)) { + throw new Error('The card to update must have an "id" property.'); + } + + try { + // Attempt to update the card within the collection + const updatedCards = cards.map( + (card) => + card.id === cardToUpdate.id ? { ...card, ...cardToUpdate } : card // Update the card if the id matches + ); + console.log('3. Updated cards in collection:', updatedCards); + return updatedCards; + } catch (error) { + console.error('3. Failed to update card in collection:', error); + throw error; + } +}; + const getCardPrice = (collection) => console.log('CARD:', collection) || parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); @@ -493,4 +741,12 @@ module.exports = { determineHttpMethod, createPayload, logError, + constructPayloadWithDifferences, + getCurrentChartDataSets, + getPriceChange, + getUpdatedChartData, + mergeCards, + updateCardInCollection, + canMakeRequest, + updateLastRequestTime, }; diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index a1395d6..4f22110 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -16,7 +16,7 @@ import placeholder from '../assets/images/placeholder.jpeg'; import UserStats from '../components/other/UserStats'; import { useUserContext } from '../context/UserContext/UserContext'; import { useCookies } from 'react-cookie'; -import ThemeToggleButton from '../components/buttons/ThemeToggleButton'; +import ThemeToggleButton from '../components/buttons/other/ThemeToggleButton'; import { useCombinedContext } from '../context/CombinedProvider'; import { AvatarStyled, From 2fbbd9aabc0e032b10c5912dcf0bce0f99a519e2 Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Sat, 18 Nov 2023 19:31:07 -0800 Subject: [PATCH 08/10] updating cards in server correctly --- src/App.js | 15 +- .../{other => chart}/ChartErrorBoundary.jsx | 0 .../{other => chart}/ChartTooltip.jsx | 0 .../{other => chart}/LinearChart.js | 153 +--- src/components/chart/PortfolioChart.jsx | 132 +++ .../{other => chart}/chartUtils.jsx | 39 +- .../cleanUp/CalculateCollectionStatistics.jsx | 57 ++ .../CollectionEditPanel.jsx | 0 src/components/content/PortfolioContent.jsx | 2 +- .../dialogs/ChooseCollectionDialog.jsx | 20 +- .../dialogs/CollectionSelectorDialog.jsx | 20 +- .../dialogs/CreateOrEditCollectionDialog.jsx | 9 +- src/components/dialogs/dialogStyles.jsx | 35 + .../{other => grids}/DeckDisplay.js | 6 +- .../other/CalculateCollectionStatistics.jsx | 57 -- .../other/CollectionStatisticsSelector.jsx | 95 --- .../other/CollectionValueTracker.jsx | 139 ++-- .../{ => InputComponents}/CardNameInput.js | 14 +- .../CollectionStatisticsSelector.jsx | 186 +++++ .../{ => InputComponents}/CustomSelector.js | 0 .../InputComponents/TimeRangeSelector.jsx | 54 ++ .../{ => InputComponents}/UpdateStatusBox.jsx | 4 +- .../InputComponents/UpdateStatusBox2.jsx | 128 +++ src/components/other/PortfolioChart.jsx | 377 --------- src/components/other/TimeRangeSelector.jsx | 57 -- .../{other => reusable}/CustomPagination.jsx | 0 src/components/search/DeckSearch.js | 2 +- src/components/search/SearchBar.js | 4 +- src/containers/PortfolioChartContainer.jsx | 117 +-- .../DeckBuilderContainer.js | 72 +- .../CollectionContext/CollectionContext.jsx | 783 ++++++++++++------ .../checkServerForUpdates.jsx | 66 ++ .../CollectionContext/collectionUtility.jsx | 142 +++- src/context/CombinedProvider.jsx | 716 +++++++++++----- src/index.js | 14 +- src/pages/ProfilePage.js | 18 +- src/themeSettings.jsx | 4 +- 37 files changed, 2041 insertions(+), 1496 deletions(-) rename src/components/{other => chart}/ChartErrorBoundary.jsx (100%) rename src/components/{other => chart}/ChartTooltip.jsx (100%) rename src/components/{other => chart}/LinearChart.js (54%) create mode 100644 src/components/chart/PortfolioChart.jsx rename src/components/{other => chart}/chartUtils.jsx (89%) create mode 100644 src/components/cleanUp/CalculateCollectionStatistics.jsx rename src/components/{other => cleanUp}/CollectionEditPanel.jsx (100%) rename src/components/{other => grids}/DeckDisplay.js (92%) delete mode 100644 src/components/other/CalculateCollectionStatistics.jsx delete mode 100644 src/components/other/CollectionStatisticsSelector.jsx rename src/components/other/{ => InputComponents}/CardNameInput.js (58%) create mode 100644 src/components/other/InputComponents/CollectionStatisticsSelector.jsx rename src/components/other/{ => InputComponents}/CustomSelector.js (100%) create mode 100644 src/components/other/InputComponents/TimeRangeSelector.jsx rename src/components/other/{ => InputComponents}/UpdateStatusBox.jsx (96%) create mode 100644 src/components/other/InputComponents/UpdateStatusBox2.jsx delete mode 100644 src/components/other/PortfolioChart.jsx delete mode 100644 src/components/other/TimeRangeSelector.jsx rename src/components/{other => reusable}/CustomPagination.jsx (100%) create mode 100644 src/context/CollectionContext/checkServerForUpdates.jsx diff --git a/src/App.js b/src/App.js index 92e069a..2f74223 100644 --- a/src/App.js +++ b/src/App.js @@ -113,11 +113,11 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { setLastCronJobTriggerTime(currentTime); if (userId && listOfMonitoredCards) { console.log('RETRIEVING LIST OF MONITORED CARDS (paused)'); - // handleSendAllCardsInCollections( - // userId, - // listOfMonitoredCards - // // handleRetrieveListOfMonitoredCards() - // ); + handleSendAllCardsInCollections( + userId, + listOfMonitoredCards + // handleRetrieveListOfMonitoredCards() + ); console.log('Triggered the cron job.'); } } @@ -140,7 +140,7 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { const App = () => { const { user } = useUserContext(); const { isLoading, setIsContextLoading } = useUtilityContext(); - const { fetchAllCollectionsForUser } = useCollectionStore(); + const { fetchAllCollectionsForUser, allCollections } = useCollectionStore(); const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); useCronJob(lastCronJobTriggerTime, setLastCronJobTriggerTime); @@ -174,6 +174,7 @@ const App = () => { if (user && isMounted) { try { // const response = fet + await fetchAllCollectionsForUser(user.userID); if (isMounted) { console.log('Fetched collections because none were present.'); // Update state only if the component is still mounted @@ -192,7 +193,7 @@ const App = () => { return () => { isMounted = false; }; - }, [user, fetchAllCollectionsForUser]); + }, [user, fetchAllCollectionsForUser, allCollections]); return ( <> diff --git a/src/components/other/ChartErrorBoundary.jsx b/src/components/chart/ChartErrorBoundary.jsx similarity index 100% rename from src/components/other/ChartErrorBoundary.jsx rename to src/components/chart/ChartErrorBoundary.jsx diff --git a/src/components/other/ChartTooltip.jsx b/src/components/chart/ChartTooltip.jsx similarity index 100% rename from src/components/other/ChartTooltip.jsx rename to src/components/chart/ChartTooltip.jsx diff --git a/src/components/other/LinearChart.js b/src/components/chart/LinearChart.js similarity index 54% rename from src/components/other/LinearChart.js rename to src/components/chart/LinearChart.js index 3a0814a..054239e 100644 --- a/src/components/other/LinearChart.js +++ b/src/components/chart/LinearChart.js @@ -95,26 +95,33 @@ const LinearChart = ({ const theme = useTheme(); const [isZoomed, setIsZoomed] = useState(false); const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); - - // const filteredData = useMemo( - // () => getFilteredData(filteredChartData, timeRange), - // [filteredChartData, timeRange] - // ); - // console.log('filteredData', filteredData); - - // const dataForChart = useMemo(() => { - // return datesTimesValues.dates.map((date, index) => ({ - // x: formatDateToString( - // new Date(`${date} ${datesTimesValues.times[index]}`) - // ), - // y: datesTimesValues.values[index], - // })); - // }, [datesTimesValues]); - // CustomLogger('LinearChart', 'info', { - // filteredChartData, - // datesTimesValues, - // }); - const tickValues = useMemo(() => getTickValues(timeRange), [timeRange]); + const [format, setFormat] = useState('0,0'); + // Calculate tickValues and xFormat based on timeRange + const { tickValues, xFormat } = useMemo(() => { + let format, ticks; + switch (timeRange) { + case '2 hours': + format = '%H:%M'; + ticks = 'every 15 minutes'; + break; + case '24 hours': + format = '%H:%M'; + ticks = 'every 1 hour'; + break; + case '7 days': + format = '%b %d'; + ticks = 'every 1 day'; + break; + case '1 month': + format = '%b %d'; + ticks = 'every 3 days'; + break; + default: + format = '%b %d'; + ticks = 'every 1 day'; + } + return { tickValues: ticks, xFormat: `time:${format}` }; + }, [timeRange]); if ( !Array.isArray(filteredChartData) || @@ -126,86 +133,6 @@ const LinearChart = ({ ); } - // const classes = useStyles(); - // const theme = useTheme(); - - // // Hooks should be at the top level of your component - // const [isZoomed, setIsZoomed] = useState(false); - // const filteredData = useMemo( - // () => getFilteredData(filteredChartData, timeRange), - // [filteredChartData, timeRange] - // ); - // // const averagedData = useMemo( - // // () => getAveragedData(filteredData), - // // [filteredData] - // // ); - // const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); - - // if (!Array.isArray(filteredChartData)) { - // return No valid data available; - // } - - // if ( - // !datesTimesValues || - // !datesTimesValues.dates || - // !datesTimesValues.times || - // !datesTimesValues.values - // ) { - // console.error('Invalid averaged chart data:', datesTimesValues); - // return Invalid data for the chart; - // } - - // const dataForChart = useMemo(() => { - // return [ - // { - // id: 'Averaged Data', - // data: datesTimesValues.dates.map((date, index) => ({ - // x: formatDateToString( - // new Date(`${date} ${datesTimesValues.times[index]}`) - // ), - // y: datesTimesValues.values[index], - // })), - // }, - // ]; - // }, [datesTimesValues]); - - // if (dataForChart[0].data.length === 0) { - // return No valid data available; - // } - // const tickValues = useMemo(() => getTickValues(timeRange), [timeRange]); - - // if ( - // !Array.isArray(filteredChartData) || - // filteredChartData.some((d) => !d.x || !d.y) - // ) { - // return No valid data available; - // } - // // logReadableChartInfo( - // // dataForChart, - // // datesTimesValues, - // // filteredChartData, - // // latestData - // // ); - // try { - // const filteredData = useMemo( - // () => getFilteredData(filteredChartData, timeRange), - // [filteredChartData, timeRange] - // ); - // getAveragedData(filteredData); - // } catch (error) { - // console.error('Error processing data for chart:', error); - // } // console.log('averagedData', averagedData); - - // const lastData = useMemo(() => { - // if (filteredChartData && filteredChartData.length) { - // return { - // x: filteredChartData[filteredChartData.length - 1].x, - // y: filteredChartData[filteredChartData.length - 1].y, - // }; - // } - // return {}; - // }, [filteredChartData]); - const chartProps = { margin: { top: 50, right: 110, bottom: 50, left: 60 }, // data: [{ id: 'Data', data: dataForChart }], @@ -215,20 +142,26 @@ const LinearChart = ({ motionDamping: 15, xScale: { type: 'time', - format: '%Y-%m-%d %H:%M', + // format: '%Y-%m-%d %H:%M:%S', + format: '%Y-%m-%dT%H:%M:%S.%LZ', useUTC: false, - precision: 'minute', + precision: 'second', }, - yScale: { type: 'linear', min: 'auto', max: 'auto' }, + xFormat: 'time:%Y-%m-%d %H:%M:%S', axisBottom: { tickRotation: 0, legendOffset: -12, legend: 'Time', tickPadding: 10, tickSize: 10, - format: '%b %d', + // format: '%b %d', + // tickValues: 'every 2 days', + format: xFormat, tickValues: tickValues, + // tickValues: tickValues, }, + yScale: { type: 'linear', min: 'auto', max: 'auto' }, + axisLeft: { orient: 'left', legend: 'Value ($)', @@ -250,12 +183,12 @@ const LinearChart = ({ onMouseLeave: handleMouseLeave, onClick: () => setIsZoomed(!isZoomed), tooltip: CustomTooltip, - sliceTooltip: ({ slice }) => { - const point = slice.points.find( - (p) => p.id === 'Data' && p.data.x === latestData.x - ); - return point ? : null; - }, + // sliceTooltip: ({ slice }) => { + // const point = slice.points.find( + // (p) => p.id === 'Data' && p.data.x === latestData.x + // ); + // return point ? : null; + // }, }; return ( diff --git a/src/components/chart/PortfolioChart.jsx b/src/components/chart/PortfolioChart.jsx new file mode 100644 index 0000000..d120048 --- /dev/null +++ b/src/components/chart/PortfolioChart.jsx @@ -0,0 +1,132 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Box, Container, Grid, Paper, styled, useTheme } from '@mui/material'; +import LinearChart from './LinearChart'; +import { useChartContext } from '../../context/ChartContext/ChartContext'; +import ErrorBoundary from '../../context/ErrorBoundary'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import { useCombinedContext } from '../../context/CombinedProvider'; +import debounce from 'lodash/debounce'; +import { + convertDataForNivo2, + getFilteredData2, + groupAndAverageData, +} from './chartUtils'; + +const ChartPaper = styled(Paper)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.secondary, + padding: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + minHeight: '400px', + overflow: 'hidden', + margin: theme.spacing(2, 0), +})); + +const PortfolioChart = () => { + const theme = useTheme(); + const { latestData, setLatestData, timeRange } = useChartContext(); + const [lastUpdateTime, setLastUpdateTime] = useState(null); + const chartContainerRef = useRef(null); + // const [nivoReadyData2, setNivoReadyData2] = useState(null); // Declare state for nivoReadyData2 + + const [chartDimensions, setChartDimensions] = useState({ + width: 0, + height: 0, + }); + const { selectedCollection } = useCollectionStore(); + + const filteredChartData2 = getFilteredData2(selectedCollection); + // let nivoReadyData2 = []; + // const rawData2 = useMemo( + // () => groupAndAverageData(filteredChartData2, threshold), + // [filteredChartData2, threshold] + // ); + + const handleThresholdUpdate = () => { + const currentTime = new Date().getTime(); + if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { + // 10 minutes + setLastUpdateTime(currentTime); + return currentTime; + } + return lastUpdateTime; + }; + + const threshold = handleThresholdUpdate(); + const rawData2 = useMemo( + () => groupAndAverageData(filteredChartData2, threshold), + [filteredChartData2, threshold] + ); + + const nivoReadyData2 = useMemo( + () => convertDataForNivo2(rawData2), + [rawData2] + ); + + const HEIGHT_TO_WIDTH_RATIO = 2 / 3; + + useEffect(() => { + const handleResize = debounce(() => { + if (chartContainerRef.current) { + const width = chartContainerRef.current.offsetWidth; + const height = width * HEIGHT_TO_WIDTH_RATIO; + setChartDimensions({ width, height }); + } + }, 100); + + window.addEventListener('resize', handleResize); + handleResize(); + + return () => { + window.removeEventListener('resize', handleResize); + handleResize.cancel(); + }; + }, []); + + return ( + + + + + + {filteredChartData2.length > 0 ? ( + + ) : ( +
No data available
+ )} +
+
+
+
+
+ ); +}; + +export default PortfolioChart; diff --git a/src/components/other/chartUtils.jsx b/src/components/chart/chartUtils.jsx similarity index 89% rename from src/components/other/chartUtils.jsx rename to src/components/chart/chartUtils.jsx index 2f5afe4..937da8e 100644 --- a/src/components/other/chartUtils.jsx +++ b/src/components/chart/chartUtils.jsx @@ -7,6 +7,7 @@ import { Box, Typography } from '@mui/material'; import { useTheme } from '@mui/styles'; import { useCallback, useMemo, useState } from 'react'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; export const getUniqueValidData = (currentChartData) => { if (!Array.isArray(currentChartData)) { @@ -48,7 +49,16 @@ export const getUniqueValidData = (currentChartData) => { y: entry.y, })); }; - +export const getFilteredData2 = (selectedCollection) => { + const filteredChartData2 = useMemo(() => { + // const { selectedCollection } = useCollectionStore(); + const allXYValues2 = selectedCollection?.currentChartDataSets2; + // console.log('ALL XY VALUES:', allXYValues2); + return allXYValues2 ? getUniqueValidData(allXYValues2) : []; + }, [selectedCollection]); + + return filteredChartData2; +}; // export const groupAndAverageData = (data, threshold = 600000) => { // // 10 minutes in milliseconds // if (!data || data.length === 0) return { dates: [], times: [], values: [] }; @@ -111,16 +121,16 @@ export const groupAndAverageData = (data, threshold = 600000) => { const clusters = []; let currentCluster = [data[0]]; - console.log('Initial cluster with first data point: ', currentCluster); + // console.log('Initial cluster with first data point: ', currentCluster); for (let i = 1; i < data.length; i++) { const prevTime = new Date(data[i - 1].x).getTime(); const currentTime = new Date(data[i].x).getTime(); const timeDiff = currentTime - prevTime; - console.log( - `Time difference between points ${i - 1} and ${i}: ${timeDiff}ms` - ); + // console.log( + // `Time difference between points ${i - 1} and ${i}: ${timeDiff}ms` + // ); if (timeDiff <= threshold) { currentCluster.push(data[i]); @@ -130,7 +140,7 @@ export const groupAndAverageData = (data, threshold = 600000) => { } } clusters.push(currentCluster); // Push the last cluster - console.log('Final cluster: ', currentCluster); + // console.log('Final cluster: ', currentCluster); // Process each cluster to create the desired output format clusters.map((cluster) => { @@ -149,7 +159,7 @@ export const groupAndAverageData = (data, threshold = 600000) => { }; }); - console.log('Processed clusters: ', clusters); + // console.log('Processed clusters: ', clusters); return clusters; }; @@ -173,10 +183,13 @@ export const getAveragedData = (data) => { export const getTickValues = (timeRange) => { console.log('timeRange: ', timeRange); const mapping = { - '15m': 'every 15 minutes', - '2h': 'every 2 hours', - '1d': 'every day', - '1w': 'every week', + 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 }; @@ -243,8 +256,8 @@ export const convertDataForNivo2 = (rawData2) => { return []; } - console.log('rawData2: ', rawData2); - console.log('rawData2.data: ', rawData2[0]); + // console.log('rawData2: ', rawData2); + // console.log('rawData2.data: ', rawData2[0]); // rawData is assumed to be an array of objects with 'label', 'x', and 'y' properties const nivoData = rawData2[0].map((dataPoint) => ({ x: dataPoint.x, // x value is already an ISO date string diff --git a/src/components/cleanUp/CalculateCollectionStatistics.jsx b/src/components/cleanUp/CalculateCollectionStatistics.jsx new file mode 100644 index 0000000..fe3693a --- /dev/null +++ b/src/components/cleanUp/CalculateCollectionStatistics.jsx @@ -0,0 +1,57 @@ +// import React, { useMemo } from 'react'; +// import { MenuItem, Select, Typography } from '@mui/material'; + +// // Helper function to calculate statistics +// const calculateStatistics = (data, timeRange) => { +// // Filter the data according to the timeRange +// const filteredData = data.filter( +// (item) => new Date(item.x).getTime() >= Date.now() - timeRange +// ); + +// const prices = filteredData.map((d) => d.y); +// const high = Math.max(...prices); +// const low = Math.min(...prices); +// const percentChange = +// ((prices[prices.length - 1] - prices[0]) / prices[0]) * 100; +// const average = prices.reduce((a, b) => a + b, 0) / prices.length; + +// return { high, low, percentChange, average }; +// }; + +// const StatisticsSelector = ({ data, timeRange }) => { +// const [selectedStat, setSelectedStat] = React.useState(''); + +// // Compute the statistics when the data or timeRange changes +// const stats = useMemo( +// () => calculateStatistics(data, timeRange), +// [data, timeRange] +// ); + +// // Handle selection change +// const handleChange = (event) => { +// setSelectedStat(event.target.value); +// }; + +// return ( +// <> +// +// +// {selectedStat === 'high' && `High: $${stats.high}`} +// {selectedStat === 'low' && `Low: $${stats.low}`} +// {selectedStat === 'percentChange' && +// `Change: ${stats.percentChange.toFixed(2)}%`} +// {selectedStat === 'average' && `24hr Avg: $${stats.average.toFixed(2)}`} +// +// +// ); +// }; + +// export default StatisticsSelector; diff --git a/src/components/other/CollectionEditPanel.jsx b/src/components/cleanUp/CollectionEditPanel.jsx similarity index 100% rename from src/components/other/CollectionEditPanel.jsx rename to src/components/cleanUp/CollectionEditPanel.jsx diff --git a/src/components/content/PortfolioContent.jsx b/src/components/content/PortfolioContent.jsx index 8591778..57d9f1e 100644 --- a/src/components/content/PortfolioContent.jsx +++ b/src/components/content/PortfolioContent.jsx @@ -12,7 +12,7 @@ const PortfolioContent = ({ error, selectedCards, removeCard }) => { flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - p: 2, + // p: 2, }} > diff --git a/src/components/dialogs/ChooseCollectionDialog.jsx b/src/components/dialogs/ChooseCollectionDialog.jsx index 34e5f6d..ea68b2d 100644 --- a/src/components/dialogs/ChooseCollectionDialog.jsx +++ b/src/components/dialogs/ChooseCollectionDialog.jsx @@ -10,26 +10,8 @@ import { Divider, Snackbar, } from '@mui/material'; -import { makeStyles } from '@mui/styles'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; - -const useStyles = makeStyles((theme) => ({ - listItem: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: theme.spacing(2), - backgroundColor: '#ffffff', - borderRadius: '8px', - marginBottom: theme.spacing(2), - boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', - }, - listItemText: { - flex: 1, - textAlign: 'left', - marginLeft: theme.spacing(3), - }, -})); +import { useStyles } from './dialogStyles'; const ChooseCollectionDialog = ({ onSave, isOpen, onClose }) => { const { setSelectedCollection, allCollections } = useCollectionStore(); diff --git a/src/components/dialogs/CollectionSelectorDialog.jsx b/src/components/dialogs/CollectionSelectorDialog.jsx index 045a313..bf7661a 100644 --- a/src/components/dialogs/CollectionSelectorDialog.jsx +++ b/src/components/dialogs/CollectionSelectorDialog.jsx @@ -9,25 +9,7 @@ import { ListItemText, Divider, } from '@mui/material'; -import { makeStyles } from '@mui/styles'; - -const useStyles = makeStyles((theme) => ({ - listItem: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: theme.spacing(2), - backgroundColor: '#ffffff', - borderRadius: '8px', - marginBottom: theme.spacing(2), - boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', - }, - listItemText: { - flex: 1, - textAlign: 'left', - marginLeft: theme.spacing(3), - }, -})); +import { useStyles } from './dialogStyles'; const CollectionSelectorDialog = ({ open, diff --git a/src/components/dialogs/CreateOrEditCollectionDialog.jsx b/src/components/dialogs/CreateOrEditCollectionDialog.jsx index 7da668b..1740e0b 100644 --- a/src/components/dialogs/CreateOrEditCollectionDialog.jsx +++ b/src/components/dialogs/CreateOrEditCollectionDialog.jsx @@ -26,6 +26,7 @@ const CreateOrEditCollectionDialog = ({ addOneToCollection, removeCollection, selectedCollection, + updateCollectionDetails, } = useCollectionStore(); const [cookies] = useCookies(['user']); const userId = cookies.user?.id; @@ -35,6 +36,7 @@ const CreateOrEditCollectionDialog = ({ name: editedName, description: editedDescription, userId: userId, + tag: 'new', }; if (isNew) { @@ -46,7 +48,12 @@ const CreateOrEditCollectionDialog = ({ userId ); } else if (editedName && editedDescription) { - addOneToCollection(newCollectionInfo); + // addOneToCollection(newCollectionInfo); + updateCollectionDetails( + newCollectionInfo, + userId, + selectedCollection._id + ); } else { console.error('No card to add to the collection'); } diff --git a/src/components/dialogs/dialogStyles.jsx b/src/components/dialogs/dialogStyles.jsx index e69de29..a7f112c 100644 --- a/src/components/dialogs/dialogStyles.jsx +++ b/src/components/dialogs/dialogStyles.jsx @@ -0,0 +1,35 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'stretch', + height: '100%', + width: '50vw', + padding: theme.spacing(2), + }, + button: { + marginBottom: theme.spacing(2), + }, + list: { + flexGrow: 1, + }, + listItem: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(2), + backgroundColor: '#ffffff', + borderRadius: '8px', + marginBottom: theme.spacing(2), + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', + }, + listItemText: { + flex: 1, + textAlign: 'left', + marginLeft: theme.spacing(3), + }, +})); diff --git a/src/components/other/DeckDisplay.js b/src/components/grids/DeckDisplay.js similarity index 92% rename from src/components/other/DeckDisplay.js rename to src/components/grids/DeckDisplay.js index 4309e4e..fcce34c 100644 --- a/src/components/other/DeckDisplay.js +++ b/src/components/grids/DeckDisplay.js @@ -1,9 +1,9 @@ import React, { useContext, useEffect, useState } from 'react'; import { Paper, Button } from '@mui/material'; import { DeckContext } from '../../context/DeckContext/DeckContext'; -import DeckButtonList from '../grids/deckBuilderGrids/DeckButtonList'; -import CardsGrid from '../grids/deckBuilderGrids/CardsGrid'; -import DeckEditPanel from './DeckEditPanel'; +import DeckButtonList from './deckBuilderGrids/DeckButtonList'; +import CardsGrid from './deckBuilderGrids/CardsGrid'; +import DeckEditPanel from '../other/DeckEditPanel'; import { makeStyles } from '@mui/styles'; const useStyles = makeStyles((theme) => ({ diff --git a/src/components/other/CalculateCollectionStatistics.jsx b/src/components/other/CalculateCollectionStatistics.jsx deleted file mode 100644 index 83f4eaf..0000000 --- a/src/components/other/CalculateCollectionStatistics.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useMemo } from 'react'; -import { MenuItem, Select, Typography } from '@mui/material'; - -// Helper function to calculate statistics -const calculateStatistics = (data, timeRange) => { - // Filter the data according to the timeRange - const filteredData = data.filter( - (item) => new Date(item.x).getTime() >= Date.now() - timeRange - ); - - const prices = filteredData.map((d) => d.y); - const high = Math.max(...prices); - const low = Math.min(...prices); - const percentChange = - ((prices[prices.length - 1] - prices[0]) / prices[0]) * 100; - const average = prices.reduce((a, b) => a + b, 0) / prices.length; - - return { high, low, percentChange, average }; -}; - -const StatisticsSelector = ({ data, timeRange }) => { - const [selectedStat, setSelectedStat] = React.useState(''); - - // Compute the statistics when the data or timeRange changes - const stats = useMemo( - () => calculateStatistics(data, timeRange), - [data, timeRange] - ); - - // Handle selection change - const handleChange = (event) => { - setSelectedStat(event.target.value); - }; - - return ( - <> - - - {selectedStat === 'high' && `High: $${stats.high}`} - {selectedStat === 'low' && `Low: $${stats.low}`} - {selectedStat === 'percentChange' && - `Change: ${stats.percentChange.toFixed(2)}%`} - {selectedStat === 'average' && `24hr Avg: $${stats.average.toFixed(2)}`} - - - ); -}; - -export default StatisticsSelector; diff --git a/src/components/other/CollectionStatisticsSelector.jsx b/src/components/other/CollectionStatisticsSelector.jsx deleted file mode 100644 index ce90125..0000000 --- a/src/components/other/CollectionStatisticsSelector.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useState, useMemo } from 'react'; -import { MenuItem, Select, Typography, Box } from '@mui/material'; - -function calculateStatistics(data, timeRange) { - if (!data || data.length === 0) { - return {}; // Return an empty object if data is not available - } - - const filteredData = data.filter( - (item) => new Date(item.x).getTime() >= Date.now() - timeRange - ); - - if (filteredData.length === 0) { - return {}; // Return an empty object if filtered data is not available - } - - const sortedData = [...filteredData].sort((a, b) => a.y - b.y); - const highPoint = sortedData.at(-1)?.y || 0; - const lowPoint = sortedData[0]?.y || 0; - const percentChange = - sortedData.length > 1 ? ((highPoint - lowPoint) / lowPoint) * 100 : 0; - const sum = filteredData.reduce((acc, curr) => acc + curr.y, 0); - const average = sum / filteredData.length || 0; - const volume = filteredData.length; - - const mean = sum / volume; - const squaredDiffs = filteredData.map((item) => { - const diff = item.y - mean; - return diff * diff; - }); - const volatility = Math.sqrt( - squaredDiffs.reduce((a, b) => a + b, 0) / volume - ); - - return { - highPoint: highPoint.toFixed(2), - lowPoint: lowPoint.toFixed(2), - percentChange: percentChange.toFixed(2), - average: average.toFixed(2), - volume, - volatility: volatility.toFixed(2), - }; -} - -const CollectionStatisticsSelector = ({ data, timeRange }) => { - const [selectedStat, setSelectedStat] = useState(''); - - const stats = useMemo( - () => calculateStatistics(data, timeRange), - [data, timeRange] - ); - - const handleChange = (event) => { - setSelectedStat(event.target.value); - }; - - return ( - - - - - {selectedStat && - `${selectedStat.replace(/([A-Z])/g, ' $1').trim()}: $${ - stats[selectedStat] - }`} - - - ); -}; - -export default CollectionStatisticsSelector; diff --git a/src/components/other/CollectionValueTracker.jsx b/src/components/other/CollectionValueTracker.jsx index 3c828cf..df3f7f8 100644 --- a/src/components/other/CollectionValueTracker.jsx +++ b/src/components/other/CollectionValueTracker.jsx @@ -1,70 +1,89 @@ -import React, { useState, useEffect } from 'react'; -import { Typography, Box, useTheme } from '@mui/material'; -import { useCombinedContext } from '../../context/CombinedProvider'; +import React from 'react'; +import { Typography, Box, useTheme, Grid } from '@mui/material'; +import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; +import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import { styled } from '@mui/styles'; -const CollectionValueTracker = ({ data }) => { - const theme = useTheme(); - const { allCardPrices } = useCombinedContext(); - const { totalCost } = useCollectionStore(); - const [totalValue, setTotalValue] = useState(0); - const [changeOverTime, setChangeOverTime] = useState(0); - console.log('allCardPrices', data.allCardPrices); - // useEffect(() => { - // const allPrices = data?.cards?.map((card) => card?.price); - // console.log('[1]allPrices', allPrices); - // const newValue = data?cards?.reduce((acc, card) => acc + (card?.price, 0)); - // console.log('[2]newValue', newValue); - // const change = data?cards?.reduce( - // (acc, card) => acc + (card?.latestPrice?.num - card?.lastSavedPrice?.num), - // 0 - // ); - // console.log('[3]change', change); +const StatBox = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-around', + boxShadow: theme.shadows[2], +})); + +const StatItem = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + marginBottom: theme.spacing(1), +})); - // setTotalValue(newValue); - // setChangeOverTime(change); - // }, [data]); +const CollectionValueTracker = ({ stats }) => { + const theme = useTheme(); + const { + totalCost, + selectedCollection, + getTotalPrice, + getTotalPrice2, + totalPrice, + allCardPrices, + } = useCollectionStore(); + const twentyFourHourChange = stats.twentyFourHourAverage; + const newTotal = getTotalPrice(); - // useEffect(() => { - // // Update total value based on allCardPrices - // if (Array.isArray(allCardPrices) && allCardPrices?.length > 0) { - // const total = allCardPrices - // .map((price) => parseFloat(price)) // Convert each price to a number - // .filter((price) => !isNaN(price)) // Filter out non-numeric values - // .reduce((acc, price) => acc + price, 0); // Sum up all prices - // console.log('total', total); - // setTotalValue(total); - // } - // }, [allCardPrices]); + console.log('newTotal:', newTotal); - const trend = changeOverTime > 0 ? 'increased' : 'decreased'; - const trendColor = changeOverTime > 0 ? 'success.main' : 'error.main'; + const statsArray = [ + { + label: 'Total Collection Value', + value: `$${newTotal}`, + }, + { + label: '24 Hour Change', + value: `${twentyFourHourChange?.percentageChange}`, + isIncrease: twentyFourHourChange?.priceIncreased, + }, + // Additional stats + { label: 'Last Price', value: `${twentyFourHourChange?.lastPrice}` }, + { label: 'Average Price', value: `${twentyFourHourChange?.averagePrice}` }, + { label: 'Volume', value: `${twentyFourHourChange?.volume}` }, + { label: 'Volatility', value: `${twentyFourHourChange?.volatility}` }, + { label: 'High Point', value: `${twentyFourHourChange?.highPoint}` }, + { label: 'Low Point', value: `${twentyFourHourChange?.lowPoint}` }, + ]; - console.log('totalValue', totalValue); return ( - - - Total Collection Value: ${totalCost?.toFixed(2)} - - - Value {trend} in the last 24h: ${Math.abs(changeOverTime)?.toFixed(2)} - - + + {statsArray.map((stat, index) => { + const IconComponent = + stat?.isIncrease !== undefined + ? stat?.isIncrease + ? ArrowUpwardIcon + : ArrowDownwardIcon + : null; + const iconColor = stat?.isIncrease + ? theme.palette.success.main + : theme.palette.error.main; + + return ( + + {IconComponent && ( + + )} + + {stat?.label}: {stat?.value} + + + ); + })} + ); }; diff --git a/src/components/other/CardNameInput.js b/src/components/other/InputComponents/CardNameInput.js similarity index 58% rename from src/components/other/CardNameInput.js rename to src/components/other/InputComponents/CardNameInput.js index d9b600b..3be6fd7 100644 --- a/src/components/other/CardNameInput.js +++ b/src/components/other/InputComponents/CardNameInput.js @@ -1,22 +1,10 @@ import React from 'react'; import { Input } from '@mui/material'; -import { useCardStore } from '../../context/CardContext/CardStore'; +import { useCardStore } from '../../../context/CardContext/CardStore'; const CardNameInput = ({ value, setValue }) => { const { handleRequest } = useCardStore(); - // const handleKeyDown = (event) => { - // if (event.key === 'Enter') { - // handleRequest(searchParams); // Use handleRequest from context - // } - // }; - - // const handleChange = (event) => { - // setSearchParams((prevState) => ({ - // ...prevState, - // name: event.target.value, - // })); - // }; const handleKeyDown = (event) => { if (event.key === 'Enter') { handleRequest(); // Use handleRequest from context diff --git a/src/components/other/InputComponents/CollectionStatisticsSelector.jsx b/src/components/other/InputComponents/CollectionStatisticsSelector.jsx new file mode 100644 index 0000000..4b7b125 --- /dev/null +++ b/src/components/other/InputComponents/CollectionStatisticsSelector.jsx @@ -0,0 +1,186 @@ +import React, { useState, useMemo } from 'react'; +import { + MenuItem, + Select, + Typography, + Box, + Grid, + CardContent, + Card, +} from '@mui/material'; +import { getFilteredData2 } from '../../chart/chartUtils'; +import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; + +function calculatePriceChanges(data) { + const sortedData = data.sort((a, b) => new Date(a.x) - new Date(b.x)); + const latestDataPoint = sortedData[sortedData.length - 1]; + const latestTime = new Date(latestDataPoint.x).getTime(); + const twentyFourHoursAgo = latestTime - 24 * 60 * 60 * 1000; + + // Find the data point closest to 24 hours before the latest data point + let closestIndex = -1; + let closestTimeDifference = Number.MAX_SAFE_INTEGER; + + for (let i = 0; i < sortedData.length - 1; i++) { + const time = new Date(sortedData[i].x).getTime(); + const timeDifference = Math.abs(time - twentyFourHoursAgo); + + if (timeDifference < closestTimeDifference) { + closestTimeDifference = timeDifference; + closestIndex = i; + } + } + + if (closestIndex !== -1) { + const pastPrice = sortedData[closestIndex].y; + // console.log('pastPrice', pastPrice); + const priceChange = latestDataPoint.y - pastPrice; + // console.log('priceChange', priceChange); + const percentageChange = ((priceChange / pastPrice) * 100).toFixed(2); + // console.log('percentageChange', percentageChange); + + return [ + { + startDate: sortedData[closestIndex].x, + lowPoint: pastPrice.toFixed(2), + highPoint: latestDataPoint?.y?.toFixed(2), + endDate: latestDataPoint?.x, + priceChange: priceChange.toFixed(2), + percentageChange: `${percentageChange}%`, + priceIncreased: priceChange > 0, + }, + ]; + } + + return []; +} + +export const calculateStatistics = (data, timeRange) => { + if (!data || data.length === 0) return {}; + const filteredData = data?.data?.filter( + (item) => new Date(item?.x).getTime() >= Date.now() - timeRange + ); + if (filteredData.length === 0) return {}; + let highPoint = 0; + let lowPoint = 0; + let sum = 0; + let averageData = 0; + let average = 0; + let volume = 0; + let mean = 0; + let squaredDiffs = 0; + let volatility = 0; + // const filteredData2 = getFilteredData2(data, timeRange); + // console.log('filteredData2', filteredData2); + // console.log('filteredData', filteredData); + for (const data of filteredData) { + highPoint = Math.max(...filteredData.map((item) => item.y)); + lowPoint = Math.min(...filteredData.map((item) => item.y)); + sum = filteredData.reduce((acc, curr) => acc + curr.y, 0); + averageData = calculatePriceChanges(filteredData); + average = sum / filteredData.length || 0; + volume = filteredData.length; + mean = sum / volume; + squaredDiffs = filteredData.map((item) => { + const diff = item.y - mean; + return diff * diff; + }); + volatility = Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / volume); + } + // const highPoint = Math.max(...filteredData.map((item) => item.y)); + // const lowPoint = Math.min(...filteredData.map((item) => item.y)); + // const sum = filteredData.reduce((acc, curr) => acc + curr.y, 0); + // const averageData = calculatePriceChanges(filteredData); + // console.log('averageData', averageData); + // const average = sum / filteredData.length || 0; + // const volume = filteredData.length; + // const mean = sum / volume; + + // const squaredDiffs = filteredData.map((item) => { + // const diff = item.y - mean; + // return diff * diff; + // }); + + // const volatility = Math.sqrt( + // squaredDiffs.reduce((a, b) => a + b, 0) / volume + // ); + + return { + highPoint: highPoint.toFixed(2), + lowPoint: lowPoint.toFixed(2), + twentyFourHourAverage: { + startDate: averageData[0]?.startDate, + endDate: averageData[0]?.endDate, + lowPoint: averageData[0]?.lowPoint, + highPoint: averageData[0]?.highPoint, + priceChange: averageData[0]?.priceChange, + percentageChange: averageData[0]?.percentageChange, + priceIncreased: averageData[0]?.priceIncreased, + }, + average: average?.toFixed(2), + volume, + volatility: volatility?.toFixed(2), + }; +}; + +const CollectionStatisticsSelector = ({ data, timeRange, stats }) => { + const [selectedStat, setSelectedStat] = useState(''); + + const handleChange = (event) => setSelectedStat(event.target.value); + + const StatCard = ({ title, value }) => ( + + + + {title} + + + {value} + + + + ); + + return ( + + + + + {selectedStat && ( + + + + )} + + + ); +}; + +export default CollectionStatisticsSelector; diff --git a/src/components/other/CustomSelector.js b/src/components/other/InputComponents/CustomSelector.js similarity index 100% rename from src/components/other/CustomSelector.js rename to src/components/other/InputComponents/CustomSelector.js diff --git a/src/components/other/InputComponents/TimeRangeSelector.jsx b/src/components/other/InputComponents/TimeRangeSelector.jsx new file mode 100644 index 0000000..21a489f --- /dev/null +++ b/src/components/other/InputComponents/TimeRangeSelector.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { MenuItem, Select } from '@mui/material'; +import { useChartContext } from '../../../context/ChartContext/ChartContext'; + +// Remove setTimeRange(value); from here +// const TimeRangeSelector = ({ onChange }) => { +// const { timeRanges, timeRange, setTimeRange, handleChange, currentValue } = +// useChartContext(); +// console.log('timeRanges: ', timeRanges); +// console.log('timeRange: ', timeRange); +// console.log('currentValue: ', currentValue); +// const isInRange = timeRanges.some((option) => option.value === currentValue); +// console.log('isInRange: ', isInRange); +// const safeTimeRange = isInRange ? timeRange : timeRanges[1].value; +// console.log('safeTimeRange: ', safeTimeRange); + +// return ( +// +// ); +// }; + +// export default TimeRangeSelector; + +const TimeRangeSelector = () => { + const { timeRange, timeRanges, handleChange } = useChartContext(); + + return ( + + ); +}; + +export default TimeRangeSelector; diff --git a/src/components/other/UpdateStatusBox.jsx b/src/components/other/InputComponents/UpdateStatusBox.jsx similarity index 96% rename from src/components/other/UpdateStatusBox.jsx rename to src/components/other/InputComponents/UpdateStatusBox.jsx index 7398fff..bfbfff1 100644 --- a/src/components/other/UpdateStatusBox.jsx +++ b/src/components/other/InputComponents/UpdateStatusBox.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Typography } from '@mui/material'; -import { useCombinedContext } from '../../context/CombinedProvider'; +import { useCombinedContext } from '../../../context/CombinedProvider'; import { useCookies } from 'react-cookie'; const UpdateStatusBox = ({ socket }) => { @@ -36,7 +36,7 @@ const UpdateStatusBox = ({ socket }) => { if (socket) { socket.emit('STATUS_UPDATE_REQUEST', { message: 'Requesting status update...', - data: listOfSimulatedCards, + data: listOfMonitoredCards, }); } }; diff --git a/src/components/other/InputComponents/UpdateStatusBox2.jsx b/src/components/other/InputComponents/UpdateStatusBox2.jsx new file mode 100644 index 0000000..53d7a35 --- /dev/null +++ b/src/components/other/InputComponents/UpdateStatusBox2.jsx @@ -0,0 +1,128 @@ +import React, { useState, useEffect } from 'react'; +import { Snackbar, Typography } from '@mui/material'; +import { useCombinedContext } from '../../../context/CombinedProvider'; +import { useCookies } from 'react-cookie'; + +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', + }, +}; + +const UpdateStatusBox2 = ({ socket }) => { + const { + allCollectionData, + listOfMonitoredCards, + handleSendAllCardsInCollections, + } = useCombinedContext(); + const [currentTime, setCurrentTime] = useState(new Date()); + const [updateStatus, setUpdateStatus] = useState('Waiting for cron...'); + // const [openSnackbar, setOpenSnackbar] = useState(false); + const [cookies] = useCookies(['user']); + const [snackbarData, setSnackbarData] = useState({ + open: false, + message: '', + }); + const userId = cookies.user?.id; + const openSnackbar = (message) => { + setSnackbarData({ open: true, message }); + }; + useEffect(() => { + const timeInterval = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); + + const handleStatusUpdate = (statusUpdate) => { + setUpdateStatus(statusUpdate.message || 'Waiting for updates...'); + }; + + if (socket) { + socket.on('INITIAL_RESPONSE', handleStatusUpdate); + } + + // Cleanup function + return () => { + clearInterval(timeInterval); + if (socket) { + socket.off('INITIAL_RESPONSE', handleStatusUpdate); + } + }; + }, [socket]); + const handleTriggerCronJob = () => { + console.log('TRIGGERING CRON JOB'); + console.log('USER ID:', userId); + console.log('LIST OF MONITORED CARDS:', listOfMonitoredCards); + if (userId && listOfMonitoredCards) { + handleSendAllCardsInCollections(userId, listOfMonitoredCards); + console.log('SENDING ALL CARDS IN COLLECTIONS'); + openSnackbar('Triggered the cron job.'); + } + }; + + // const sendUpdateRequest = () => { + // if (socket) { + // socket.emit('STATUS_UPDATE_REQUEST', { + // message: 'Requesting status update...', + // data: listOfMonitoredCards, + // }); + // } + // }; + + // Styling for dark theme + + return ( +
+ + Current Time: {currentTime.toLocaleTimeString()} + +
+ Update Status: {updateStatus} +
+ + setSnackbarData({ ...snackbarData, open: false })} + message={snackbarData.message} + /> +
+ ); +}; + +export default UpdateStatusBox2; diff --git a/src/components/other/PortfolioChart.jsx b/src/components/other/PortfolioChart.jsx deleted file mode 100644 index 6d38aa8..0000000 --- a/src/components/other/PortfolioChart.jsx +++ /dev/null @@ -1,377 +0,0 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { - Box, - Container, - Grid, - MenuItem, - Paper, - Select, - styled, - useTheme, - // debounce, -} from '@mui/material'; -import LinearChart from './LinearChart'; -import { useChartContext } from '../../context/ChartContext/ChartContext'; -import ErrorBoundary from '../../context/ErrorBoundary'; -import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -import { useCombinedContext } from '../../context/CombinedProvider'; -import debounce from 'lodash/debounce'; -import { - convertDataForNivo, - getUniqueValidData, - groupAndAverageData, - convertDataForNivo2, -} from './chartUtils'; - -// const ChartPaper = styled(Paper)(({ theme }) => ({ -// borderRadius: theme.shape.borderRadius, -// boxShadow: theme.shadows[5], -// backgroundColor: theme.palette.background.default, -// color: theme.palette.text.secondary, -// padding: theme.spacing(3), -// [theme.breakpoints.down('sm')]: { minWidth: '300px' }, -// [theme.breakpoints.up('md')]: { minWidth: '500px' }, -// [theme.breakpoints.up('lg')]: { minWidth: '700px' }, -// })); -const ChartPaper = styled(Paper)(({ theme }) => ({ - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[5], - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, - padding: theme.spacing(3), - minHeight: '400px', - width: '100%', - display: 'flex', - marginLeft: 'auto', - marginRight: 'auto', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - overflow: 'hidden', -})); -const PortfolioChart = () => { - const theme = useTheme(); - const { latestData, setLatestData, timeRange, timeRanges, currentValue } = - useChartContext(); - const [lastUpdateTime, setLastUpdateTime] = useState(null); - const chartContainerRef = useRef(null); - const [chartDimensions, setChartDimensions] = useState({ - width: 0, - height: 0, - }); - const { selectedCollection } = useCollectionStore(); - let nivoReadyData = null; - let nivoReadyData2 = null; - const { socket } = useCombinedContext(); - const updateLastTimeAndGetThreshold = () => { - const currentTime = new Date().getTime(); - if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { - // 10 minutes - setLastUpdateTime(currentTime); - // setLatestData(filteredChartData[filteredChartData.length - 1]); - setLatestData(filteredChartData2[filteredChartData2.length - 1]); - } - return currentTime; - }; - // const filteredChartData = useMemo(() => { - // const allXYValues = selectedCollection?.chartData?.allXYValues; - // return allXYValues ? getUniqueFilteredXYValues(allXYValues) : []; - // }, [selectedCollection]); - // Filtered chart data based on unique valid data - // const filteredChartData = useMemo(() => { - // const currentChartData = selectedCollection?.currentChartDataSets; - // return currentChartData ? getUniqueValidData(currentChartData) : []; - // }, [selectedCollection]); - const filteredChartData2 = useMemo(() => { - const allXYValues2 = selectedCollection?.currentChartDataSets2; - console.log('ALL XY VALUES:', allXYValues2); - return allXYValues2 ? getUniqueValidData(allXYValues2) : []; - }, [selectedCollection]); - - // Calculate threshold based on the last update time - const threshold = updateLastTimeAndGetThreshold(); - - // Group and average data using the calculated threshold - // const rawData = useMemo( - // () => groupAndAverageData(filteredChartData, threshold), - // [filteredChartData, threshold] - // ); - const rawData2 = useMemo( - () => groupAndAverageData(filteredChartData2, threshold), - [filteredChartData2, threshold] - ); - // console.log('FILTERED CHART DATA:', filteredChartData); - // console.log('FILTERED CHART DATA 2:', filteredChartData2); - // const threshold = useMemo(() => timeRange * 0.1, [timeRange]); - - console.log('THRESHOLD:', threshold); - // if (!rawData) { - // console.log('NO RAW DATA!'); - // } - // if (rawData) { - // console.log('RAW DATA:', rawData); - // nivoReadyData = useMemo(() => convertDataForNivo(rawData), [rawData]); - // } - if (rawData2) { - console.log('RAW DATA 2:', rawData2); - const nivoReady = useMemo(() => convertDataForNivo2(rawData2), [rawData2]); - nivoReadyData2 = nivoReady; - console.log('NIVO READY DATA 2:', nivoReadyData2); - } - - // Now use this threshold when calling your data grouping function - - useEffect(() => { - const handleResize = debounce(() => { - if (chartContainerRef.current) { - setChartDimensions({ - width: chartContainerRef.current.offsetWidth, - height: chartContainerRef.current.offsetHeight, - }); - } - }, 100); - - window.addEventListener('resize', handleResize); - - handleResize(); - - return () => { - window.removeEventListener('resize', handleResize); - handleResize.cancel(); - }; - }, []); - return ( - - - - {/* - - - - */} - - - {filteredChartData2.length > 0 ? ( - - ) : ( -
No data available
- )} -
-
-
-
-
- ); -}; - -export default PortfolioChart; - -// import React, { useEffect, useMemo, useRef, useState } from 'react'; -// import { -// Box, -// Container, -// Grid, -// MenuItem, -// Paper, -// Select, -// styled, -// useTheme, -// // debounce, -// } from '@mui/material'; -// import LinearChart from './LinearChart'; -// import TimeRangeSelector from './TimeRangeSelector'; -// import { useChartContext } from '../../context/ChartContext/ChartContext'; -// import ErrorBoundary from '../../context/ErrorBoundary'; -// import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -// import CollectionStatisticsSelector from './CollectionStatisticsSelector'; -// import UpdateStatusBox from './UpdateStatusBox'; -// import { useCombinedContext } from '../../context/CombinedProvider'; -// import debounce from 'lodash/debounce'; -// import { -// convertDataForNivo, -// getUniqueFilteredXYValues, -// groupAndAverageData, -// } from './chartUtils'; - -// const ChartPaper = styled(Paper)(({ theme }) => ({ -// borderRadius: theme.shape.borderRadius, -// boxShadow: theme.shadows[5], -// backgroundColor: theme.palette.background.default, -// color: theme.palette.text.secondary, -// padding: theme.spacing(3), -// [theme.breakpoints.down('sm')]: { minWidth: '300px' }, -// [theme.breakpoints.up('md')]: { minWidth: '500px' }, -// [theme.breakpoints.up('lg')]: { minWidth: '700px' }, -// })); - -// const PortfolioChart = () => { -// const theme = useTheme(); -// const { selectedCollection } = useCollectionStore(); -// const { timeRange } = useChartContext(); -// const chartContainerRef = useRef(null); -// const [chartDimensions, setChartDimensions] = useState({ -// width: 400, -// height: 400, -// }); -// // const { latestData, setLatestData, timeRange, timeRanges } = -// useChartContext(); -// const [lastUpdateTime, setLastUpdateTime] = useState(null); -// const { socket } = useCombinedContext(); - -// const filteredChartData = useMemo(() => { -// const allXYValues = selectedCollection?.chartData?.allXYValues; -// return allXYValues ? getUniqueFilteredXYValues(allXYValues) : []; -// }, [selectedCollection]); - -// // const threshold = useMemo(() => timeRange * 0.1, [timeRange]); -// // const rawData = useMemo( -// // () => groupAndAverageData(filteredChartData, threshold), -// // [filteredChartData, threshold] -// // ); -// // const nivoReadyData = useMemo(() => convertDataForNivo(rawData), [rawData]); - -// // Now use this threshold when calling your data grouping function - -// // Now use this threshold when calling your data grouping function -// const updateLastTime = () => { -// const currentTime = new Date().getTime(); -// if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { -// setLastUpdateTime(currentTime); -// const lastDataset = filteredChartData[filteredChartData.length - 1]; -// lastDataset && setLatestData(lastDataset); -// } -// }; -// const filteredChartData = useMemo(() => { -// // Process data for the chart here using selectedCollection -// return selectedCollection?.chartData?.allXYValues || []; -// }, [selectedCollection]); - -// useEffect(() => { -// const handleResize = debounce(() => { -// if (chartContainerRef.current) { -// setChartDimensions({ -// width: chartContainerRef.current.offsetWidth, -// height: chartContainerRef.current.offsetHeight, -// }); -// } -// }, 100); - -// const resizeObserver = new ResizeObserver(handleResize); -// if (chartContainerRef.current) { -// resizeObserver.observe(chartContainerRef.current); -// } - -// return () => { -// resizeObserver.disconnect(); -// handleResize.cancel(); -// }; -// }, []); -// return ( -// -// -// {/* -// -// -// -// */} -// -// -// {filteredChartData.length > 0 ? ( -// -// ) : ( -//
No data available
-// )} -//
-//
-//
-//
-// ); -// }; - -// export default PortfolioChart; diff --git a/src/components/other/TimeRangeSelector.jsx b/src/components/other/TimeRangeSelector.jsx deleted file mode 100644 index 7ab072d..0000000 --- a/src/components/other/TimeRangeSelector.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { MenuItem, Select } from '@mui/material'; -import { useChartContext } from '../../context/ChartContext/ChartContext'; - -// Remove setTimeRange(value); from here -const TimeRangeSelector = ({ onChange }) => { - const { timeRanges, timeRange, setTimeRange, handleChange, currentValue } = - useChartContext(); - console.log('timeRanges: ', timeRanges); - console.log('timeRange: ', timeRange); - console.log('currentValue: ', currentValue); - const isInRange = timeRanges.some((option) => option.value === currentValue); - console.log('isInRange: ', isInRange); - const safeTimeRange = isInRange ? timeRange : timeRanges[1].value; - console.log('safeTimeRange: ', safeTimeRange); - - return ( - - ); -}; - -export default TimeRangeSelector; -import React from 'react'; -import { Select, MenuItem } from '@mui/material'; -import { useChartContext } from './ChartContext'; // adjust the import path as necessary - -const TimeRangeSelector = () => { - const { timeRange, timeRanges, handleChange } = useChartContext(); - - return ( - - ); -}; - -export default TimeRangeSelector; diff --git a/src/components/other/CustomPagination.jsx b/src/components/reusable/CustomPagination.jsx similarity index 100% rename from src/components/other/CustomPagination.jsx rename to src/components/reusable/CustomPagination.jsx diff --git a/src/components/search/DeckSearch.js b/src/components/search/DeckSearch.js index aee988a..1fa3f56 100644 --- a/src/components/search/DeckSearch.js +++ b/src/components/search/DeckSearch.js @@ -12,7 +12,7 @@ import { useCardStore } from '../../context/CardContext/CardStore'; import { useTheme } from '@emotion/react'; import SearchForm from './SearchForm'; import DeckSearchCardGrid from '../grids/searchResultGrids/DeckSearchCardGrid'; -import CustomPagination from '../other/CustomPagination'; +import CustomPagination from '../reusable/CustomPagination'; const DeckSearch = ({ userDecks }) => { const [searchTerm, setSearchTerm] = useState(''); diff --git a/src/components/search/SearchBar.js b/src/components/search/SearchBar.js index d8d8338..5895198 100644 --- a/src/components/search/SearchBar.js +++ b/src/components/search/SearchBar.js @@ -2,8 +2,8 @@ import React from 'react'; import { Grid, Box, Typography, Container } from '@mui/material'; import { useCardStore } from '../../context/CardContext/CardStore'; import SearchButton from '../buttons/other/SearchButton'; -import CardNameInput from '../other/CardNameInput'; -import CustomSelector from '../other/CustomSelector'; +import CardNameInput from '../other/InputComponents/CardNameInput'; +import CustomSelector from '../other/InputComponents/CustomSelector'; import { useCombinedContext } from '../../context/CombinedProvider'; const initialState = { diff --git a/src/containers/PortfolioChartContainer.jsx b/src/containers/PortfolioChartContainer.jsx index 2f3348a..a91e87e 100644 --- a/src/containers/PortfolioChartContainer.jsx +++ b/src/containers/PortfolioChartContainer.jsx @@ -1,25 +1,18 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Box, Grid, Paper } from '@mui/material'; -import PortfolioChart from '../components/other/PortfolioChart'; -import TimeRangeSelector from '../components/other/TimeRangeSelector'; -import CollectionStatisticsSelector from '../components/other/CollectionStatisticsSelector'; -import UpdateStatusBox from '../components/other/UpdateStatusBox'; +import PortfolioChart from '../components/chart/PortfolioChart'; +import TimeRangeSelector from '../components/other/InputComponents/TimeRangeSelector'; +import CollectionStatisticsSelector, { + calculateStatistics, +} from '../components/other/InputComponents/CollectionStatisticsSelector'; +import UpdateStatusBox from '../components/other/InputComponents/UpdateStatusBox'; import { useTheme } from '@mui/material/styles'; import { useSocketContext } from '../context/SocketProvider'; import { useChartContext } from '../context/ChartContext/ChartContext'; import { useCollectionStore } from '../context/CollectionContext/CollectionContext'; import CollectionValueTracker from '../components/other/CollectionValueTracker'; -const paperStyle = { - elevation: 3, - borderRadius: 2, - p: 2, - height: '25vh', // Set the container height to 25vh - display: 'flex', - flexDirection: 'row', // Change to 'row' to fit selectors horizontally - justifyContent: 'space-between', // Distribute space evenly between selectors - alignItems: 'center', // Align items vertically in the center - gap: 2, // Set a gap between the selectors -}; +import { useCombinedContext } from '../context/CombinedProvider'; +import UpdateStatusBox2 from '../components/other/InputComponents/UpdateStatusBox2'; const paperChartStyle = { elevation: 3, borderRadius: 2, @@ -41,55 +34,70 @@ const selectorStyle = { const PortfolioChartContainer = ({ selectedCards, removeCard }) => { const theme = useTheme(); - const selectorStyle = { - // width: '100%', - // maxWidth: '100vw', - // maxWidth: '250px', // Match the width of the update box if needed - mb: theme.spacing(2), - }; - - const { socket } = useSocketContext(); + // const { socket } = useSocketContext(); + const { socket } = useCombinedContext(); const { timeRange } = useChartContext(); const { allCollections, selectedCollection } = useCollectionStore(); - const data = allCollections.map((collection) => { - return { - name: collection?.name, - data: collection?.chartData?.allXYValues, - }; - }); + const data = allCollections.map((collection) => ({ + data: collection?.currentChartDataSets2, + })); + const dataForStats = data[0]; + const stats = useMemo( + () => calculateStatistics(dataForStats, timeRange), + [dataForStats, timeRange] + ); + + const containerStyle = { + maxWidth: '100%', + // margin: 'auto', + padding: theme.spacing(2), + overflow: 'hidden', + }; + + const paperStyle = { + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: 'auto', + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, + }; + + const gridItemStyle = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-between', + }; + return ( - + {/* Updaters Row */} - - - {/* */} + {/* */} + + + - - {/* */} + + {/* */} - - + + {/* Main Grid Container */} - + {/* Portfolio Chart Row */} - + { {/* Selectors Row */} - + @@ -110,6 +118,7 @@ const PortfolioChartContainer = ({ selectedCards, removeCard }) => { sx={selectorStyle} timeRange={timeRange} data={data} + stats={stats} />{' '} diff --git a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js index 7efbedd..b365cfd 100644 --- a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js +++ b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js @@ -1,64 +1,6 @@ -// import React from 'react'; -// import { Grid, useMediaQuery } from '@mui/material'; -// import { useTheme } from '@emotion/react'; -// import DeckDisplay from '../../components/other/DeckDisplay'; -// import DeckSearch from '../../components/search/DeckSearch'; -// import { makeStyles } from '@mui/styles'; - -// const useStyles = makeStyles((theme) => ({ -// root: { -// overflow: 'auto', -// backgroundColor: '#f4f6f8', -// padding: theme.spacing(3), -// borderRadius: '10px', -// boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', -// width: '100%', // Add this to make sure the container takes full width -// }, -// searchGrid: { -// [theme.breakpoints.up('lg')]: { flexBasis: '35%' }, // Increased from 30% -// [theme.breakpoints.between('md', 'lg')]: { flexBasis: '50%' }, // Increased from 45% -// [theme.breakpoints.down('sm')]: { flexBasis: '50%' }, // Increased from 40% -// }, -// displayGrid: { -// flex: 1, -// padding: theme.spacing(1), -// }, -// })); - -// const DeckBuilderContainer = ({ userDecks }) => { -// const classes = useStyles(); -// const theme = useTheme(); -// const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md')); -// const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); - -// const getXsValue = () => { -// if (isSmallScreen || isMediumScreen) return 5; // Increased from 4 -// return 3; -// }; - -// const getDisplayXsValue = () => { -// if (isSmallScreen || isMediumScreen) return 7; // Decreased from 8 -// return 9; -// }; - -// return ( -// -// -// -// -// -// -// -// -// ); -// }; - -// export default DeckBuilderContainer; import React from 'react'; import { Grid, useMediaQuery, useTheme } from '@mui/material'; -// Removed import { useTheme } from '@emotion/react'; - -import DeckDisplay from '../../components/other/DeckDisplay'; +import DeckDisplay from '../../components/grids/DeckDisplay'; import DeckSearch from '../../components/search/DeckSearch'; import { styled } from '@mui/system'; // Use @mui/system for Emotion styling @@ -91,22 +33,10 @@ const DeckBuilderContainer = ({ userDecks }) => { const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const getXsValue = () => { - // if (isSmallScreen || isMediumScreen) return 5; - // return 3; if (isLargeScreen) return 3; if (isMediumScreen) return 4; return 5; }; - // case 'xs': - // return 5; - // case 'sm': - // return 5; - // case 'md': - // return 5; - // case 'lg': - // return 7; - // }; - const getDisplayXsValue = () => { // if (isSmallScreen || isMediumScreen) return 7; // return 9; diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index 2544b84..df096f3 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -24,6 +24,7 @@ import { getPriceChange, constructPayloadWithDifferences, getCurrentChartDataSets, + calculateCollectionValue, } from './collectionUtility.jsx'; import moment from 'moment'; import { createNewDataSet } from './cardHelpers.jsx'; @@ -32,13 +33,65 @@ import { chunkPayload, sendChunks } from './ChunkPaylod.jsx'; export const CollectionContext = createContext(defaultContextValue); function 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 + 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 })); } +const getAllCardPrices = (cards) => + cards.flatMap((card) => Array(card.quantity).fill(card.price)); + +function 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()); +} + +function filterCollectionData(collection) { + if (!collection) return null; + + if (!collection?.chartData) { + console.warn('Collection data is missing chart data.'); + return collection; + } + collection.chartData.allXYValues = filterUniqueDataPoints( + collection?.chartData?.allXYValues + ); + collection.currentChartDataSets = filterUniqueDataPoints( + collection?.currentChartDataSets + ); + collection.currentChartDataSets2 = filterUniqueDataPoints( + collection?.currentChartDataSets2 + ); + + collection?.chartData?.datasets.forEach((dataset) => { + dataset?.data?.forEach((dataEntry) => { + dataEntry.xys = filterUniqueDataPoints(dataEntry?.xys); + }); + }); + + // Apply the filter function to 'xys' in 'chartData' + collection.chartData.xys = filterUniqueDataPoints(collection.chartData.xys); + + // If the 'cards' array contains structures with 'label', 'x', and 'y' properties + collection.cards.forEach((card) => { + if (card.chart_datasets) { + card.chart_datasets = filterUniqueDataPoints(card.chart_datasets); + } + }); + + return collection; +} + export const CollectionProvider = ({ children }) => { const [cookies] = useCookies(['user']); const [selectedCollection, setSelectedCollection] = useState( @@ -47,82 +100,79 @@ export const CollectionProvider = ({ children }) => { const [collectionData, setCollectionData] = useState(initialCollectionState); const [allCollections, setAllCollections] = useState([]); const [totalPrice, setTotalPrice] = useState(0); - const [allCardPrices, setAllCardPrices] = useState([]); + const [totalCost, setTotalCost] = useState(''); const [ updatedPricesFromCombinedContext, setUpdatedPricesFromCombinedContext, ] = useState([]); - const [xyData, setXyData] = useState([]); const [currentChartDataSets, setCurrentChartDataSets] = useState([]); const [currentChartDataSets2, setCurrentChartDataSets2] = useState([]); const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = useState(false); const userId = cookies.user?.id; - // const currentChartDataSets = getCurrentChartDatasets( - // selectedCollection?.chartData - // ); - const totalCost = useMemo( - () => getTotalCost(selectedCollection), - [selectedCollection] - ); - // console.log('CURRENT CHART DATASETS:', currentChartDataSets); const lastFetchedTime = useRef(null); - const calculateTotalPrice = (collection) => - collection.cards.reduce( - (total, card) => total + (card.price || 0) * (card.quantity || 0), - 0 - ); const fetchAndSetCollections = useCallback(async () => { - // Throttle the fetch calls - const currentTime = Date.now(); - const fetchDelay = 60000; // 1 minute - if ( - lastFetchedTime.current && - currentTime - lastFetchedTime.current < fetchDelay - ) - return; - lastFetchedTime.current = currentTime; + const shouldFetch = () => { + const fetchDelay = 60000; // 1 minute + const currentTime = Date.now(); + return ( + !lastFetchedTime.current || + currentTime - lastFetchedTime.current >= fetchDelay + ); + }; + + if (!shouldFetch()) return; try { + lastFetchedTime.current = Date.now(); const response = await fetchWrapper( createApiUrl(`${userId}/collections`), 'GET' ); + const collections = response.data || []; - console.log('FETCHED COLLECTIONS:', response); - console.log('5. Fetched and set collections:', response); + console.log('FETCHED COLLECTIONS:', collections); - setAllCollections(response.data || []); - setCollectionData(response.data?.[0] || initialCollectionState); - setSelectedCollection(response.data?.[0] || initialCollectionState); + if (collections.length > 0) { + setAllCollections(collections); + setCollectionData(collections[0]); + setSelectedCollection(collections[0]); + } else { + console.warn('No collections found.'); + // Optionally, set a default or empty state if no collections are found + } } catch (error) { console.error(`Failed to fetch collections: ${error}`); } - }, [userId]); + }, [userId, setAllCollections, setCollectionData, setSelectedCollection]); + + 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)); + }; const updateCollectionData = useCallback( (newData, collectionType) => { - switch (collectionType) { - case 'allCollections': - setAllCollections((prev) => - prev.findIndex((c) => c._id === newData._id) === -1 - ? [...prev, newData] - : prev.map((c) => (c._id === newData._id ? newData : c)) - ); - break; - case 'selectedCollection': - setSelectedCollection(newData); - break; - case 'collectionData': - setCollectionData(newData); - break; - default: - console.warn( - '6. Unknown collection type for update:', - collectionType - ); + try { + switch (collectionType) { + case 'allCollections': + setAllCollections((prev) => updateCollectionArray(prev, newData)); + break; + case 'selectedCollection': + setSelectedCollection(newData); + break; + case 'collectionData': + setCollectionData(newData); + break; + default: + console.warn('Unknown collection type for update:', collectionType); + } + } catch (error) { + console.error('Error updating collection data:', error); } }, [setAllCollections, setSelectedCollection, setCollectionData] @@ -191,9 +241,6 @@ export const CollectionProvider = ({ children }) => { }; const getUpdatedCards = (activeCollection, cardUpdate, operation) => { - console.log('CARD UPDATE:', cardUpdate); - console.log('OPERATION', operation); - let cardsToUpdate; switch (operation) { @@ -204,266 +251,422 @@ export const CollectionProvider = ({ children }) => { cardsToUpdate = handleCardRemoval(activeCollection?.cards, cardUpdate); break; case 'update': - // Find the card by some unique identifier, e.g., id // eslint-disable-next-line no-case-declarations const cardIndex = activeCollection.cards.findIndex( (c) => c.id === cardUpdate.id ); if (cardIndex === -1) { console.error('Card not found in the collection.'); - return activeCollection.cards; // Return the unchanged cards array + return activeCollection.cards; } // eslint-disable-next-line no-case-declarations const existingCard = activeCollection.cards[cardIndex]; - // eslint-disable-next-line no-case-declarations - const updatedCard = { - ...existingCard, - latestPrice: cardUpdate.latestPrice, // assuming latestPrice is an object { num, timestamp, _id } - lastSavedPrice: cardUpdate.lastSavedPrice, // assuming lastSavedPrice is an object { num, timestamp, _id } - name: cardUpdate.name, - quantity: cardUpdate.quantity, - tag: cardUpdate.tag, - // Update priceHistory, ensure it's an array and append the new price - priceHistory: Array.isArray(existingCard.priceHistory) - ? [...existingCard.priceHistory, cardUpdate.priceHistory[0]] - : [cardUpdate.priceHistory[0]], - }; - console.log('UPDATED CARD:', updatedCard); - - // Replace the old card with the updated card - cardsToUpdate = [ - ...activeCollection.cards.slice(0, cardIndex), + const updatedPriceHistory = updatePriceHistory( + existingCard, + cardUpdate + ); + // eslint-disable-next-line no-case-declarations + const updatedCard = getUpdatedCard( + existingCard, + cardUpdate, + updatedPriceHistory, + activeCollection._id + ); + cardsToUpdate = replaceCardInArray( + activeCollection.cards, updatedCard, - ...activeCollection.cards.slice(cardIndex + 1), - ]; - console.log('UPDATED CARD:', updatedCard); - return cardsToUpdate; // Directly return the updated array of cards - + cardIndex + ); + break; default: console.error('Unsupported operation:', operation); - cardsToUpdate = activeCollection.cards; // Return the unchanged cards array + return activeCollection.cards; } - console.log('CARDS TO UPDATE:', cardsToUpdate); - return cardsToUpdate.map((existingCard) => { - console.log('EXISTING CARD BEFORE UPDATE:', existingCard); + return cardsToUpdate; + }; - // Calculate new card values - let cardPrice = null; - if (existingCard.price) { - cardPrice = existingCard.price; - } else { - cardPrice = existingCard.card_prices[0].tcgplayer_price; - } + function replaceCardInArray(cardsArray, newCard, index) { + return [ + ...cardsArray.slice(0, index), + newCard, + ...cardsArray.slice(index + 1), + ]; + } - console.log('EXISTING CARD PRICE:', cardPrice); - const computedPrice = - cardPrice * (cardUpdate.quantity || existingCard.quantity); - console.log('EXISTING CARD TOTALPRICE:', computedPrice); + function getUpdatedCard(card, update, priceHistory, collectionId) { + const cardPrice = determineCardPrice(card, update); + const newChartDataEntry = createChartDataEntry(totalPrice); + + return { + ...card, + price: cardPrice, + quantity: update.quantity || card.quantity, + collectionId: collectionId, + totalPrice: cardPrice * (update.quantity || card.quantity), + lastSavedPrice: update.lastSavedPrice, + latestPrice: update.latestPrice, + tag: 'monitored', + chart_datasets: [...(card.chart_datasets || []), newChartDataEntry], + priceHistory: priceHistory, + }; + } - // Generate chart data and price history - const newChartDataEntry = { - x: moment().format('YYYY-MM-DD HH:mm'), - y: computedPrice, - }; - const newPriceHistoryEntry = createPriceHistoryObject(computedPrice); - const valueOfMostRecentEntry = computedPrice; - // existingCard.chart_datasets?.[existingCard.chart_datasets?.length - 1] - // ?.data?.y - - // existingCard.chart_datasets?.[existingCard.chart_datasets?.length - 2] - // ?.data?.y; - console.log('VALUE OF MOST RECENT ENTRY:', computedPrice); - - const updatedCard = { - ...existingCard, - price: cardPrice, - totalPrice: computedPrice, - tag: 'monitored', - chart_datasets: [ - ...(existingCard.chart_datasets || []), - newChartDataEntry, - ], - priceHistory: - existingCard.priceHistory && - existingCard.priceHistory[existingCard.priceHistory.length - 1] !== - valueOfMostRecentEntry // Check if the new price history entry is different from the last one - ? [...existingCard.priceHistory, newPriceHistoryEntry] - : [newPriceHistoryEntry], - }; + function determineCardPrice(card, update) { + if (update?.latestPrice?.num) return update.latestPrice.num; + if (card.price) return card.price; + return card.card_prices[0].tcgplayer_price; + } - console.log('UPDATED CARD:', updatedCard); - return updatedCard; - }); - }; + function updatePriceHistory(card, update) { + const newPriceHistoryEntry = createPriceHistoryObject( + update.latestPrice.num + ); + const lastPriceHistoryEntry = + card.priceHistory[card.priceHistory.length - 1]; - const createPriceHistoryObject = (price) => ({ - timestamp: new Date().toISOString(), - num: price, - }); + if ( + !lastPriceHistoryEntry || + lastPriceHistoryEntry.num !== newPriceHistoryEntry.num + ) { + return [...card.priceHistory, newPriceHistoryEntry]; + } + return card.priceHistory; + } - const getUpdatedCollection = async ( + function createChartDataEntry(price) { + return { + x: moment().format('YYYY-MM-DD HH:mm'), + y: price, + }; + } + + function createPriceHistoryObject(price) { + return { + num: price, + timestamp: new Date(), + }; + } + + // Helper function to get updated collection data + const getUpdatedCollectionData = ( collectionWithCards, - cardUpdate, - operation + updatedTotalPrice, + newCollectionPriceHistoryObject, + updatedChartData, + updatedTotalQuantity, + updatedCards ) => { - const collectionId = selectedCollection._id || null; - if (collectionId) { - console.log('COLLECTION ID:', collectionId); + // Check for null or undefined collectionWithCards + if (!collectionWithCards) { + console.error('No collection data provided'); + return null; // or an appropriate default object } - const isCreating = !collectionId; - const method = isCreating ? 'POST' : 'PUT'; - const updatedTotalPrice = calculateTotalPrice(collectionWithCards); - const newCollectionPriceHistoryObject = - createPriceHistoryObject(updatedTotalPrice); - // UPDATE CARDS IN COLLECTION WITH CARDS ENDPOINT --------------------------- - console.log('CARD UPDATE:', cardUpdate); - const updatedCards = collectionWithCards.cards; - let cardsPayload = { - cards: updatedCards, + const { + allCardPrices = [], + description = '', + name = '', + // _id = '', + collectionPriceHistory = [], + cards = [], + } = collectionWithCards; + + return { + allCardPrices, + description, + name, + userId: userId, // Make sure 'userId' is defined in the scope + totalPrice: updatedTotalPrice || 0, + totalCost: updatedTotalPrice ? updatedTotalPrice.toString() : '0', + totalQuantity: cards.reduce((acc, card) => acc + (card.quantity || 0), 0), + quantity: cards.length, + // _id, + dailyPriceChange: + getPriceChange(currentChartDataSets2)[0]?.priceChange || '', + currentChartDataSets: filterUniqueDataPoints( + getCurrentChartDataSets(updatedChartData) + ), + currentChartDataSets2: filterUniqueDataPoints( + transformPriceHistoryToXY(collectionPriceHistory) + ), + collectionPriceHistory: [ + ...collectionPriceHistory, + newCollectionPriceHistoryObject, + ], }; - let cardsEndpoint = createApiUrl( - `${userId}/collections/${collectionWithCards._id}/updateCards` - ); - console.log( - `Sending ${method} request to ${cardsEndpoint} with payload:`, - cardsPayload - ); - let cardsResponse = await fetchWrapper(cardsEndpoint, method, cardsPayload); - console.log('Cards Update Response:', cardsResponse); - console.log('Cards Update Response Data:', cardsResponse.data); - console.log('Cards Update Response Message:', cardsResponse.message); - - // UPDATE CHARTS IN COLLECTION WITH CHARTS ENDPOINT -------------------------------- - const updatedChartData = { - ...selectedCollection.chartData, + }; + + const getFilteredChartData = (chartData, updatedTotalPrice) => { + const filteredChartData = { + ...chartData, + allXYValues: filterUniqueDataPoints(chartData?.allXYValues), + datasets: chartData?.datasets.map((dataset) => ({ + ...dataset, + data: dataset?.data.map((dataEntry) => ({ + ...dataEntry, + xys: filterUniqueDataPoints(dataEntry?.xys), + })), + })), + }; + return { + ...filteredChartData, allXYValues: [ - ...(selectedCollection.chartData?.allXYValues || []), + ...filteredChartData.allXYValues, { label: `Update - ${new Date().toISOString()}`, x: new Date().toISOString(), y: updatedTotalPrice, }, ], - datasets: [ - ...(selectedCollection.chartData?.datasets || []), - createNewDataSet(updatedTotalPrice, selectedCollection), - ], }; + }; - const testData = getUpdatedChartData(selectedCollection, updatedTotalPrice); - console.log('TEST DATA:', testData); - let chartDataPayload = { - chartData: updatedChartData, - }; - let chartDataEndpoint = createApiUrl( - `${userId}/collections/${collectionWithCards._id}/updateChartData` + const removeCardsFromCollection = async (userId, collectionId, cardIds) => { + const endpoint = `/api/${userId}/collections/${collectionId}/removeCards`; + const method = 'POST'; + const payload = { cardIds }; + + try { + const response = await fetchWrapper(endpoint, method, payload); + return response; // The response contains the updated cards list + } catch (error) { + console.error('Error removing cards:', error); + throw error; + } + }; + + const getUpdatedCollection = async ( + collectionWithCards, // updated cards + cardUpdate, // updated card + operation, + userId + ) => { + const collectionId = collectionWithCards?._id || collectionData?._id; + if (!collectionId) { + console.error('Collection ID is missing.'); + return; + } + + const cardExists = collectionWithCards.cards.some( + (card) => card.id === cardUpdate.id ); - console.log( - `Sending ${method} request to ${chartDataEndpoint} with payload:`, - chartDataPayload + + // Determine the method and endpoint based on operation and card existence + const method = + operation === 'remove' ? 'DELETE' : cardExists ? 'PUT' : 'POST'; + const endpointSuffix = + operation === 'remove' ? 'removeCards' : 'updateCards'; + const endpoint = createApiUrl( + `${userId}/collections/${collectionId}/${endpointSuffix}` ); - let chartDataResponse = await fetchWrapper( - chartDataEndpoint, - method, - chartDataPayload + + console.log('CARDS BEFORE: ', collectionWithCards); + const updatedCards = getUpdatedCards( + collectionWithCards, + cardUpdate, + operation ); - console.log('Chart Data Update Response:', chartDataResponse); - console.log('Chart Data Update Response Data:', chartDataResponse.data); - console.log( - 'Chart Data Update Response Message:', - chartDataResponse.message + console.log('CARDS AFTER: ', updatedCards); + + const updatedTotalPrice = calculateCollectionValue(updatedCards); + setTotalPrice(updatedTotalPrice); + + // const updatedTotalPrice = updatedCards.reduce( + // (total, card) => total + card.price * card.quantity, + // 0 + // ); + const updatedTotalQuantity = updatedCards.reduce( + (total, card) => total + card.quantity, + 0 ); + const newCollectionPriceHistoryObject = + createPriceHistoryObject(updatedTotalPrice); - // UPDATE COLLECTION WITH COLLECTION ENDPOINT -------------------------------- - const updatedCollection = { - // ...selectedCollection, - allCardPrices: updatedCards.flatMap((card) => - Array(card.quantity).fill(card.price) - ), - description: collectionWithCards.description, - name: collectionWithCards.name, - userId: userId, - totalPrice: updatedTotalPrice, - totalCost: updatedTotalPrice.toString(), - totalQuantity: updatedCards.reduce((acc, card) => acc + card.quantity, 0), - quantity: updatedCards.length, - _id: collectionId, - chartData: updatedChartData, - cards: updatedCards, - dailyPriceChange: getPriceChange( - selectedCollection?.collectionPriceHistory - ), - currentChartDataSets: getCurrentChartDataSets(updatedChartData), - currentChartDataSets2: transformPriceHistoryToXY( - selectedCollection.collectionPriceHistory - ), - collectionPriceHistory: selectedCollection.collectionPriceHistory - ? [ - ...selectedCollection.collectionPriceHistory, - newCollectionPriceHistoryObject, - ] - : [newCollectionPriceHistoryObject], - }; - // Check if creating a new collection or updating an existing one - const endpoint = createApiUrl( - `${userId}/collections/${collectionId || ''}` + let cardsResponse; + // if (operation === 'remove') { + // const cardIds = updatedCards.map((card) => card.id); + // cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); + // } else { + // const cardsPayload = { cards: updatedCards }; + // cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); + // } + const cardsPayload = { cards: updatedCards }; + cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); + const { cardMessage } = cardsResponse; + + const updatedChartData = getFilteredChartData( + collectionWithCards.chartData, + updatedTotalPrice ); + const chartDataPayload = { chartData: updatedChartData }; + const chartDataEndpoint = createApiUrl( + `${userId}/collections/${collectionId}/updateChartData` + ); + const chartDataResponse = await fetchWrapper( + chartDataEndpoint, + 'PUT', + chartDataPayload + ); + + const { chartMessage } = chartDataResponse; - const { nonMatchingKeys, payload } = constructPayloadWithDifferences( + // const updatedTotalPrice = calculateCollectionValue(selectedCollection); + // console.log('NEW VALUE:', newVal); + + // Prepare the updated collection data + const updatedCollection = getUpdatedCollectionData( selectedCollection, - updatedCollection, - true - ); // Assume constructPayload does the necessary processing - console.log('NON-MATCHING KEYS:', nonMatchingKeys); - console.log( - `Sending ${method} request to ${endpoint} with payload:`, - payload + updatedTotalPrice, + newCollectionPriceHistoryObject, + // updatedChartData, + updatedTotalQuantity + // updatedCards ); - setCurrentChartDataSets2( - transformPriceHistoryToXY(selectedCollection.collectionPriceHistory) - ); - // Break the payload into chunks if necessary - const chunks = chunkPayload(payload); - console.log( - `Sending ${method} request to ${endpoint} with ${chunks.length} chunks.` + // Update the collection + const collectionEndpoint = createApiUrl( + `${userId}/collections/${collectionId}` ); + const collectionResponse = await fetchWrapper(collectionEndpoint, 'PUT', { + updatedCollection, + }); + + const { collectionMessage } = collectionResponse; + + console.log('********** [--------------------] **********'); + console.log('********** [CARDS] **********'); + console.log(`********** [${cardMessage}] **********`); + console.log(`********** [${cardsResponse.cards}] **********`); + console.log('********** [--------------------] **********'); + console.log('********** [CHARTS] **********'); + console.log(`********** [${chartMessage}] **********`); + console.log(`********** [${chartDataResponse.chartData}] **********`); + console.log('********** [--------------------] **********'); + console.log('********** [COLLECTION] **********'); + console.log(`********** [${collectionMessage}] **********`); + console.log(`********** [${collectionResponse.collectionData}] **********`); + console.log('********** [--------------------] **********'); + + // Restructure the collection object + // Optionally, update context or state with the new collection data + // updateCollectionData(collectionResponse.collectionData, 'allCollections'); + // updateCollectionData( + // collectionResponse.collectionData, + // 'selectedCollection' + // ); + // updateCollectionData(collectionResponse.collectionData, 'collectionData'); + setTotalPrice(calculateCollectionValue(updatedCards)); + const restructuredCollection = { + ...collectionResponse.collectionData, + cards: cardsResponse.cards, + chartData: chartDataResponse.chartData, + }; - const response = await fetchWrapper(endpoint, method, payload); - console.log('RESPONSE', response); - const updatedCollectionPostServer = response.data; + console.log('RESTRUCTURED COLLECTION:', restructuredCollection); + // Return the updated collection data along with responses for cards and chart data + return { + restructuredCollection, + }; + }; - // const combinedCollectionUpdate = { - // ...updatedCard + const updateCollectionDetails = async (updatedInfo, userId, collectionId) => { + const { name, description } = updatedInfo; + if (selectedCollection && collectionId) { + const updatedCollection = { + ...selectedCollection, + name, + description, + }; - console.log('UPDATED COLLECTION POST SERVER:', updatedCollectionPostServer); - updateCollectionData(updatedCollectionPostServer, 'allCollections'); - updateCollectionData(updatedCollectionPostServer, 'collectionData'); - updateCollectionData(updatedCollectionPostServer, 'selectedCollection'); + // Update in the state first + setSelectedCollection(updatedCollection); + setAllCollections((prevCollections) => + prevCollections.map((collection) => + collection._id === collectionId ? updatedCollection : collection + ) + ); - return updatedCollection; + try { + // Update the collection in the backend + const collectionEndpoint = createApiUrl( + `${userId}/collections/${collectionId}` + ); + const collectionResponse = await fetchWrapper( + collectionEndpoint, + 'PUT', + { + updatedCollection, + } + ); + + const { collectionMessage } = collectionResponse; + console.log(collectionMessage); + + // Optionally handle the response here, e.g., update the state with the response data if necessary + } catch (error) { + console.error('Error updating collection details:', error); + // Handle error, e.g., revert state changes or display error message to the user + } + } else { + console.error('Selected collection or collection ID is missing.'); + } }; - const handleCardOperation = async (card, operation) => { + const handleCardOperation = async ( + card, + operation, + selectedCollection, + userId, + allCollections + ) => { if (!card) { console.error('Card is undefined.', card); return; } - const updatedCards = getUpdatedCards(selectedCollection, card, operation); - console.log('UPDATED CARDS:', updatedCards); - const collectionWithCards = { ...selectedCollection, cards: updatedCards }; - console.log('COLLECTION WITH CARDS:', collectionWithCards); + // Check if selectedCollection is defined, if not set to first collection in allCollections + if (!selectedCollection || !selectedCollection._id) { + if (allCollections && allCollections.length > 0) { + selectedCollection = allCollections[0]; + } else { + console.error('Selected collection or allCollections is empty.'); + return; // Stop execution if no valid collection is available + } + } + + const collectionId = selectedCollection._id; + + const updatedCards = getUpdatedCards( + selectedCollection, + card, + operation, + collectionId + ); + + const collectionWithCards = { + ...selectedCollection, + cards: updatedCards, + _id: selectedCollection._id, + }; + const updatedCollection = await getUpdatedCollection( collectionWithCards, card, - operation + operation, + userId ); - console.log('UPDATED COLLECTION POST OP HANDLING:', updatedCollection); - updateCollectionData(updatedCollection, 'selectedCollection'); + + if (updatedCollection) { + console.log('UPDATED COLLECTION:', updatedCollection); + updateCollectionData(updatedCollection, 'allCollections'); + updateCollectionData(updatedCollection, 'selectedCollection'); + updateCollectionData(updatedCollection, 'collectionData'); + } else { + console.error('Failed to update collection.'); + } return updatedCollection; }; @@ -475,24 +678,41 @@ export const CollectionProvider = ({ children }) => { collectionData, totalCost, totalPrice, + totalQuantity: selectedCollection?.totalQuantity || 0, allCardPrices: selectedCollection?.allCardPrices || [], xys: xyData || [], openChooseCollectionDialog, - setAllCardPrices, + calculateCollectionValue, + // setAllCardPrices, + setTotalPrice, setXyData, setUpdatedPricesFromCombinedContext, setOpenChooseCollectionDialog, - calculateTotalPrice: () => getCardPrice(selectedCollection), - getTotalPrice: () => calculateTotalPrice(allCardPrices), + getTotalPrice2: () => getCardPrice(selectedCollection), + getTotalPrice: () => calculateCollectionValue(selectedCollection), + getNewTotalPrice: (values) => calculateCollectionValue(values), getTotalCost: () => getTotalCost(selectedCollection), getCardQuantity: (cardId) => selectedCollection?.cards?.find((c) => c?.id === cardId)?.quantity || 0, createUserCollection: (userId, newCollectionInfo, name, description) => createUserCollection(userId, newCollectionInfo, name, description), removeCollection, - addOneToCollection: (card) => handleCardOperation(card, 'add'), - removeOneFromCollection: (card) => handleCardOperation(card, 'remove'), + addOneToCollection: (card) => + handleCardOperation(card, 'add', selectedCollection, userId), + removeOneFromCollection: (card) => + handleCardOperation(card, 'remove', selectedCollection, userId), updateOneFromCollection: (card) => handleCardOperation(card, 'update'), + updateCollection: (collectionWithCards, card, operation, userId) => + getUpdatedCollection(collectionWithCards, card, operation, userId), + updateCollectionState: (collection) => updateCollectionData(collection), + updateCollectionDetails: (updatedInfo, userId, collectionId) => + updateCollectionDetails(updatedInfo, userId, collectionId), + // updateCollectionDetails: (collection) => { + // console.log('UPDATING COLLECTION DETAILS:', collection); + // updateCollectionData(collection, 'allCollections'); + // updateCollectionData(collection, 'selectedCollection'); + // updateCollectionData(collection, 'collectionData'); + // } fetchAllCollectionsForUser: fetchAndSetCollections, fetchAllCollections: fetchAndSetCollections, setSelectedCollection, @@ -505,6 +725,7 @@ export const CollectionProvider = ({ children }) => { console.log('CONTEXT STATE:', { totalCost, totalPrice, + totalQuantity: selectedCollection?.totalQuantity || 0, selectedCollection, allCollections, collectionData, @@ -522,19 +743,51 @@ export const CollectionProvider = ({ children }) => { }, [userId]); useEffect(() => { - if (selectedCollection.chartData) + if (selectedCollection?.chartData) setCurrentChartDataSets( - getCurrentChartDataSets(selectedCollection.chartData) + getCurrentChartDataSets(selectedCollection?.chartData) ); console.log('CURRENT CHART DATASETS:', currentChartDataSets); }, [selectedCollection]); useEffect(() => { - if (selectedCollection.cards) { - setTotalPrice(calculateTotalPrice(selectedCollection)); + if (selectedCollection?.cards) { + setTotalPrice(calculateCollectionValue(selectedCollection)); + setTotalCost(getTotalCost(selectedCollection)); } - console.log('TOTAL PRICE:', totalPrice); - }, [selectedCollection]); + }, [selectedCollection, setTotalPrice]); + + useEffect(() => { + if (allCollections?.length === 0 || allCollections === undefined) { + fetchAndSetCollections(); + } + }, [allCollections, fetchAndSetCollections]); + + // setCurrentChartDataSets2( + // transformPriceHistoryToXY(selectedCollection?.chartData) + // ); + // console.log('CURRENT CHART DATASETS 2:', currentChartDataSets2); + // } + + useEffect(() => { + if (selectedCollection === null || selectedCollection === undefined) { + setSelectedCollection(allCollections?.[0]); + } + }, [userId]); + + // setTotalPrice(calculateTotalPrice(selectedCollection)); + // console.log('TOTAL PRICE:', totalPrice); + // }, [selectedCollection, setTotalPrice]); + + // useEffect(() => { + // const updatedSelectedCollection + // { + // ...selectedCollection, + + // } + + // getUpdatedCollection + // }, [setAllCardPrices, setTotalPrice]); return ( diff --git a/src/context/CollectionContext/checkServerForUpdates.jsx b/src/context/CollectionContext/checkServerForUpdates.jsx new file mode 100644 index 0000000..b622890 --- /dev/null +++ b/src/context/CollectionContext/checkServerForUpdates.jsx @@ -0,0 +1,66 @@ +// Client-side JavaScript (e.g., in a React component or similar) +import axios from 'axios'; +import { useContext, useState } from 'react'; +import { CollectionContext } from './CollectionContext'; +import { useCombinedContext } from '../CombinedProvider'; +import { createApiUrl, fetchWrapper } from './collectionUtility'; +import { useCookies } from 'react-cookie'; + +async function checkServerForUpdates() { + const { socket } = useCombinedContext(); + const [updateResponse, setUpdateResponse] = useState(null); + try { + // Send an API request to check for updates + socket.emit('UPDATE_REQUEST_CHECK', { + message: 'Checking for updates...', + }); + + socket.on('UPDATE_CHECK_INITIAL_RESPONSE', (response) => { + console.log('UPDATE_CHECK_INITIAL_RESPONSE', response); + setUpdateResponse(response.message); + }); + + socket.on('UPDATES_RECEIVED', (response) => { + console.log('UPDATE_CHECK_RESPONSE', response); + if (response.hasUpdates) { + // If there are updates, call your client-side update function + updateCollectionClientSide(response.data); + } + setUpdateResponse(response.message); + }); + } catch (error) { + console.error('Error checking for updates:', error); + } +} +setInterval(checkServerForUpdates, 300000); // Check every 5 minutes + +const updateCollectionClientSide = async (response) => { + const { collection, setCollection, updateCollectionState } = + useContext(CollectionContext); + try { + updateCollectionState(response); + // Now, send an API request to save the updated collection data + await saveUpdatedCollectionData(response); + } catch (error) { + console.error('Error updating collection client-side:', error); + } +}; + +const saveUpdatedCollectionData = async (response) => { + const [cookies] = useCookies(['user']); + const userId = cookies.user?.id; + const collectionId = response._id; + try { + const collectionEndpoint = createApiUrl( + `${userId}/collections/${collectionId || ''}` + ); + const collectionResponse = await fetchWrapper(collectionEndpoint, 'PUT', { + response, + }); + + console.log('collectionResponse', collectionResponse); + console.log('Collection data saved successfully'); + } catch (error) { + console.error('Error saving updated collection data:', error); + } +}; diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx index a7756f1..3b2858f 100644 --- a/src/context/CollectionContext/collectionUtility.jsx +++ b/src/context/CollectionContext/collectionUtility.jsx @@ -387,8 +387,11 @@ const defaultContextValue = { setUpdatedPricesFromCombinedContext: () => {}, // eslint-disable-next-line @typescript-eslint/no-empty-function setOpenChooseCollectionDialog: () => {}, + updateCollection: () => {}, calculateTotalPrice: () => {}, getTotalCost: () => {}, + getNewTotalPrice: () => {}, + getTotalPrice: () => {}, createUserCollection: () => {}, removeCollection: () => {}, fetchAllCollectionsForUser: () => {}, @@ -613,40 +616,119 @@ function getCurrentChartDataSets(chartData) { return currentChartDataSets; } -const getPriceChange = (collectionPriceHistory) => { +const calculateCollectionValue = (cards) => { + if (!cards?.cards && !Array.isArray(cards) && !cards?.name) { + console.warn('Invalid or missing collection', cards); + return 0; + } + if (cards?.tag === 'new') { + return 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); +}; + +// const getPriceChange = (collectionPriceHistory) => { +// if ( +// !Array.isArray(collectionPriceHistory) || +// collectionPriceHistory.length === 0 +// ) { +// console.warn('Invalid or empty price history', collectionPriceHistory); +// return 'n/a'; +// } + +// const mostRecentPrice = +// collectionPriceHistory[collectionPriceHistory.length - 1]?.num; +// const currentDate = new Date(); + +// // Get the first price from the last 24 hours +// const firstPriceFromLastDay = collectionPriceHistory +// .slice() +// .reverse() +// .find((priceHistory) => { +// const historyDate = new Date(priceHistory.timestamp); +// return currentDate - historyDate <= 24 * 60 * 60 * 1000; // less than 24 hours +// })?.num; + +// if (mostRecentPrice && firstPriceFromLastDay) { +// const priceChange = +// ((mostRecentPrice - firstPriceFromLastDay) / firstPriceFromLastDay) * 100; +// console.log( +// `Price change over the last 24 hours is: ${priceChange.toFixed(2)}%` +// ); +// return priceChange.toFixed(2); +// } else { +// console.error('Could not calculate price change due to missing data'); +// return null; +// } +// }; + +function getPriceChange(currentChartDataSets2) { if ( - !Array.isArray(collectionPriceHistory) || - collectionPriceHistory.length === 0 + !Array.isArray(currentChartDataSets2) || + currentChartDataSets2.length === 0 ) { - console.warn('Invalid or empty price history', collectionPriceHistory); - return 'n/a'; + console.warn('Invalid or empty chart data sets provided'); + return []; } - const mostRecentPrice = - collectionPriceHistory[collectionPriceHistory.length - 1]?.num; - const currentDate = new Date(); - - // Get the first price from the last 24 hours - const firstPriceFromLastDay = collectionPriceHistory - .slice() - .reverse() - .find((priceHistory) => { - const historyDate = new Date(priceHistory.timestamp); - return currentDate - historyDate <= 24 * 60 * 60 * 1000; // less than 24 hours - })?.num; - - if (mostRecentPrice && firstPriceFromLastDay) { - const priceChange = - ((mostRecentPrice - firstPriceFromLastDay) / firstPriceFromLastDay) * 100; - console.log( - `Price change over the last 24 hours is: ${priceChange.toFixed(2)}%` - ); - return priceChange.toFixed(2); - } else { - console.error('Could not calculate price change due to missing data'); - return null; + const sortedData = currentChartDataSets2 + .filter((dataPoint) => dataPoint && dataPoint.x && dataPoint.y != null) // Filter out invalid data points + .sort((a, b) => new Date(a.x) - new Date(b.x)); + + if (sortedData.length === 0) { + console.error('No valid chart data points after filtering'); + return []; } -}; + + const latestDataPoint = sortedData[sortedData.length - 1]; + const latestTime = new Date(latestDataPoint.x).getTime(); + const twentyFourHoursAgo = latestTime - 24 * 60 * 60 * 1000; + + let closestIndex = -1; + let closestTimeDifference = Number.MAX_SAFE_INTEGER; + + for (let i = 0; i < sortedData.length - 1; i++) { + const time = new Date(sortedData[i].x).getTime(); + const timeDifference = Math.abs(time - twentyFourHoursAgo); + + if (timeDifference < closestTimeDifference) { + closestTimeDifference = timeDifference; + closestIndex = i; + } + } + + if (closestIndex !== -1) { + const pastPrice = sortedData[closestIndex].y; + const priceChange = latestDataPoint.y - pastPrice; + const percentageChange = ((priceChange / pastPrice) * 100).toFixed(2); + + return [ + { + startDate: sortedData[closestIndex].x, + lowPoint: pastPrice.toFixed(2), + highPoint: latestDataPoint?.y?.toFixed(2), + endDate: latestDataPoint?.x, + priceChange: priceChange.toFixed(2), + percentageChange: `${percentageChange}%`, + priceIncreased: priceChange > 0, + }, + ]; + } + + return []; +} const getUpdatedChartData = (collection, newPrice) => { const newXYValue = { @@ -713,7 +795,6 @@ const updateCardInCollection = (cards, cardToUpdate) => { }; const getCardPrice = (collection) => - console.log('CARD:', collection) || parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); module.exports = { @@ -749,4 +830,5 @@ module.exports = { updateCardInCollection, canMakeRequest, updateLastRequestTime, + calculateCollectionValue, }; diff --git a/src/context/CombinedProvider.jsx b/src/context/CombinedProvider.jsx index bc0d6ba..f7fa7d1 100644 --- a/src/context/CombinedProvider.jsx +++ b/src/context/CombinedProvider.jsx @@ -17,10 +17,10 @@ const initialState = { allData: {}, data: {}, messageTest: {}, - chartData: {}, + // chartData: {}, existingChartData: {}, collectionData: {}, - currentChartData: {}, + // currentChartData: {}, allCollectionsUpdated: {}, simData: {}, allCollectionData: {}, @@ -82,37 +82,44 @@ const filterDuplicatePrices = (data) => { }); }; -function processCardPrices(cardPrices) { - if (!cardPrices || !cardPrices.data || !Array.isArray(cardPrices.data.data)) { - console.error('Invalid cardPrices data structure.'); - return; - } +// function processCardPrices(cardPrices, selectedCollection) { +// if (!cardPrices || !cardPrices.data || !Array.isArray(cardPrices.data.data)) { +// console.error('Invalid cardPrices data structure.'); +// return; +// } - const priceArray = []; - let totalPrice = 0; +// console.log('Card prices retrieved:', cardPrices); +// const priceArray = []; +// let totalPrice = 0; - cardPrices.data.data.forEach((card) => { - const { latestPrice, quantity } = card; +// cardPrices.data.data.forEach((card) => { +// const { latestPrice, quantity } = card; - if (!latestPrice || !quantity) { - console.error(`Missing price or quantity for card ID: ${card.id}`); - return; - } +// if (!latestPrice || !quantity) { +// console.error(`Missing price or quantity for card ID: ${card.id}`); +// return; +// } - for (let i = 0; i < quantity; i++) { - priceArray.push(latestPrice.num); - totalPrice += latestPrice.num; - } - }); +// for (let i = 0; i < quantity; i++) { +// priceArray.push(latestPrice.num); +// totalPrice += latestPrice.num; +// } +// }); + +// const filteredCards = cardPrices.data.data.filter((card) => { +// const cardIds = selectedCollection?.cards?.map((card) => card.id); +// return cardIds?.includes(card.id); +// }); - console.log('Price Array:', priceArray); - console.log('Total Price:', totalPrice.toFixed(2)); +// console.log('Price Array:', priceArray); +// console.log('Total Price:', totalPrice.toFixed(2)); +// // console.log('Filtered Cards:', filteredCards); - // Save priceArray and totalPrice as needed - // For example, you might want to set them to your application's state +// // Save priceArray and totalPrice as needed +// // For example, you might want to set them to your application's state - return { priceArray, totalPrice: totalPrice.toFixed(2) }; -} +// return { priceArray, totalPrice: totalPrice.toFixed(2) }; +// } const isEmpty = (obj) => { return ( @@ -122,20 +129,38 @@ const isEmpty = (obj) => { }; const validateData = (data, eventName, functionName) => { - const dataType = typeof data; + const dataType = typeof data || data.data || data.data.data || data.message; console.log( - `[SUCCESS] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` + '----------------------------------------------------------------------------------------------------' + ); + console.log( + `| [SUCCESS] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}] |` + ); + console.log( + '----------------------------------------------------------------------------------------------------' ); if (data === null || data === undefined) { + console.log( + '----------------------------------------------------------------------------------------------------' + ); console.warn( `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` ); + console.log( + '----------------------------------------------------------------------------------------------------' + ); return false; } - if (isEmpty(data)) { + if (isEmpty(data) && isEmpty(data?.data) && isEmpty(data?.data?.data)) { + console.log( + '----------------------------------------------------------------------------------------------------' + ); console.error( `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` ); + console.log( + '----------------------------------------------------------------------------------------------------' + ); return false; } return true; @@ -148,9 +173,12 @@ export const CombinedProvider = ({ children }) => { const { selectedCollection, allCollections, + getNewTotalPrice, updateOneFromCollection, + updateCollection, processAndUpdateCardPrices, setAllCardPrices, + setTotalPrice, setAllCollections, updatedPricesFromCombinedContext, officialCollectionDatasets, @@ -164,6 +192,8 @@ export const CombinedProvider = ({ children }) => { (data) => { setState((prev) => { let newData; + + // validateData(data, key, 'createStateUpdaterFunction'); if (Array.isArray(data)) { newData = [...data]; } else if (typeof data === 'object' && data !== null) { @@ -232,114 +262,349 @@ export const CombinedProvider = ({ children }) => { } }, [state.eventsTriggered]); // ----------- XXX ----------- - const listOfMonitoredCards = useMemo(() => { - const cards = allCollections?.flatMap((collection) => collection?.cards); - if (!cards) return []; - const uniqueCards = Array.from(new Set(cards.map((card) => card?.id))).map( - (id) => cards?.find((card) => card?.id === id) + const generateListOfMonitoredCards = (allCollections) => { + if (!allCollections) return []; + + // // Ensure cardPrices is an array + // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; + + // // Flatten all cards from all collections, including collection ID + // 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, + })) ); - let updatedPrices = null; - if (state.cardPrices?.updatedPrices) { - updatedPrices = state.cardPrices?.updatedPrices; - } - if (uniqueCards && uniqueCards.length) { - return uniqueCards?.map((card) => { - const updatedPriceInfo = updatedPrices?.[card?.id]; - return { - _id: card?._id, // Assuming each card has a unique identifier - id: card?.id, - tag: 'monitored', - name: card?.name, - quantity: card?.quantity, - latestPrice: { - num: updatedPriceInfo ? updatedPriceInfo.num : card?.price, - timestamp: updatedPriceInfo - ? updatedPriceInfo.timestamp - : new Date().toISOString(), - // Add _id field for latestPrice if available - }, - lastSavedPrice: { - num: card?.price, - timestamp: new Date().toISOString(), // Modify this based on where you get the last saved timestamp - // Add _id field for lastSavedPrice if available - }, - priceHistory: card?.priceHistory || [], // Assuming priceHistory is part of the original card object - __v: card?.__v, // Version key, if applicable - }; - }); - } - }, [allCollections, state.cardPrices?.updatedPrices]); + const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card.id)); - // ----------- SOCKET EVENT HANDLERS ----------- + return Array.from(uniqueCardIds).map((id) => { + const originalCard = cardsWithCollectionId.find((card) => card.id === id); - const safeEmit = useCallback( - (event, data) => { - try { - if (!validateData(data, event, 'safeEmit')) { - throw new Error(`Invalid data emitted for event: ${event}`); - } - if (socket) { - socket.emit(event, data); - console.log(`[Info] Emitted event: ${event}`); - } else { - console.warn('Socket is not connected. Cannot emit event:', event); - } - } catch (error) { - console.error(`[Error] Failed to emit event: ${event}`, error); - setDataFunctions.error({ - message: error.message, - source: 'safeEmit', - }); + return { + ...originalCard, + priceHistory: originalCard.priceHistory || [], + }; + }); + }; + const updateCardPricesInList = (listOfMonitoredCards, cardPrices) => { + return listOfMonitoredCards.map((originalCard) => { + const updatedCardInfo = + cardPrices.find((price) => price.id === originalCard.id) || {}; + + // If latestPrice is different, update lastSavedPrice and priceHistory + if (updatedCardInfo.latestPrice?.num !== originalCard.latestPrice?.num) { + return { + ...originalCard, + ...updatedCardInfo, + quantity: originalCard.quantity, + price: updatedCardInfo.latestPrice?.num || originalCard.price, + lastSavedPrice: + updatedCardInfo.lastSavedPrice || + updatedCardInfo.priceHistory[ + updatedCardInfo.priceHistory.length - 1 + ], + priceHistory: [ + ...originalCard.priceHistory, + updatedCardInfo.latestPrice, + ], + }; } - }, - [socket] + + return originalCard; + }); + }; + const listOfMonitoredCards = useMemo( + () => generateListOfMonitoredCards(allCollections), + [allCollections] ); - const safeOn = useCallback( - (event, handler) => { - const wrapper = (data) => { - try { - if (!validateData(data, event, handler.name)) { - throw new Error(`Invalid data received for event: ${event}`); - } // Add this line to validate the data received - // console.log(`[Info] Handling event: ${event}`); - handler(data); - } catch (error) { - console.error(`[Error] Failed to handle event: ${event}`, error); - setDataFunctions.error({ message: error.message, source: event }); - } - }; + // const listOfMonitoredCards = useMemo(() => { + // if (!allCollections) return []; - socket.on(event, wrapper); // Add this line to register the event listener + // const cardPrices = Array.isArray(state.cardPrices) + // ? state.cardPrices + // : [state.cardPrices]; - return () => { - socket.off(event, wrapper); // Add this line to unregister the event listener when the component unmounts - }; - }, - [socket] - ); + // const cardsWithCollectionId = allCollections.flatMap((collection) => + // collection?.cards?.map((card) => ({ + // ...card, + // collectionId: collection._id, + // })) + // ); - const mergeUpdates = (currentArray, updates) => { - const updatedArray = [...currentArray]; - updates.forEach((update) => { - const index = updatedArray.findIndex((item) => item.id === update.id); - if (index !== -1) { - updatedArray[index] = { ...updatedArray[index], ...update }; - } else { - updatedArray.push(update); - } - }); - return updatedArray; - }; + // const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card.id)); + + // return Array.from(uniqueCardIds).map((id) => { + // const originalCard = cardsWithCollectionId.find((card) => card.id === id); + // const updatedCardInfo = cardPrices.find((price) => price.id === id) || {}; + + // const latestPrice = + // updatedCardInfo.latestPrice || originalCard.latestPrice; + // const lastSavedPrice = + // updatedCardInfo.lastSavedPrice || originalCard.lastSavedPrice; + + // return { + // ...originalCard, + // ...updatedCardInfo, + // latestPrice, + // lastSavedPrice, + // price: latestPrice?.num || originalCard.price, + // priceHistory: updatedCardInfo.priceHistory || originalCard.priceHistory, + // }; + // }); + // }, [allCollections, state.cardPrices]); + + // const updateCardPricesState = (currentCardPrices, updatedCardsList) => { + // // Ensure both currentCardPrices and updatedCardsList are arrays + // if (!Array.isArray(currentCardPrices)) currentCardPrices = []; + // if (!Array.isArray(updatedCardsList)) updatedCardsList = []; + + // // Create a map for easy lookup of current card prices by ID + // const currentCardPricesMap = new Map( + // currentCardPrices.map((card) => [card.id, card]) + // ); - const handleStatusUpdateCharts = (newData) => { - const { updates } = newData.data; - console.log('[STATUS_UPDATE_CHARTS] Data:', updates); - const updatedList = mergeUpdates(state.listOfSimulatedCards, updates); - setDataFunctions.listOfSimulatedCards(updatedList); + // // Update the card prices with new data from updatedCardsList + // return updatedCardsList.map((updatedCard) => { + // const currentCardPrice = currentCardPricesMap.get(updatedCard.id) || {}; + + // return { + // ...currentCardPrice, + // latestPrice: updatedCard.latestPrice || currentCardPrice.latestPrice, + // lastSavedPrice: + // updatedCard.lastSavedPrice || currentCardPrice.lastSavedPrice, + // price: updatedCard.latestPrice?.num || currentCardPrice.price, + // priceHistory: updatedCard.priceHistory || currentCardPrice.priceHistory, + // }; + // }); + // }; + + const emitUpdatedCards = (socket, updatedCards) => { + socket.emit('UPDATED_MONITORED_CARDS', updatedCards); }; + // Usage + // Now you can set the state with newCardPrices + + // const listOfMonitoredCards = useMemo(() => { + // if (!allCollections) return []; + + // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; + + // // Flatten all cards from all collections with additional collection ID + // const cardsWithCollectionId = allCollections.flatMap((collection) => + // collection?.cards?.map((card) => ({ + // ...card, + // collectionId: collection._id, + // })) + // ); + + // // Reduce the cards to a map, merging cards with the same ID + // const mergedCardsMap = cardsWithCollectionId.reduce((acc, card) => { + // const updatedCardInfo = cardPrices.find((price) => price.id === card.id) || {}; + // const existingCard = acc.get(card.id) || {}; + + // const mergedCard = { + // ...existingCard, + // ...card, + // ...updatedCardInfo, + // latestPrice: updatedCardInfo.latestPrice || card.latestPrice || existingCard.latestPrice, + // lastSavedPrice: updatedCardInfo.lastSavedPrice || card.lastSavedPrice || existingCard.lastSavedPrice, + // price: updatedCardInfo.latestPrice?.num || card.price || existingCard.price, + // }; + + // acc.set(card.id, mergedCard); + // return acc; + // }, new Map()); + + // // Convert the map values to an array + // return Array.from(mergedCardsMap.values()); + // }, [allCollections, state.cardPrices]); + + // const listOfMonitoredCards = useMemo(() => { + // if (!allCollections) return []; + + // // Ensure cardPrices is an array + // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; + + // // Flatten all cards from all collections, including collection ID + // const cardsWithCollectionId = allCollections.flatMap((collection) => + // collection?.cards?.map((card) => ({ + // ...card, + // collectionId: collection._id, + // })) + // ); + + // // Create a unique set of card IDs + // const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card.id)); + + // // Map over unique card IDs to find corresponding card and update with new prices if available + // return cardsWithCollectionId.map((card) => { + // const updatedCardInfo = + // cardPrices.find((price) => price.id === card.id) || {}; + + // return { + // ...card, + // latestPrice: updatedCardInfo?.latestPrice || card?.latestPrice, + // lastSavedPrice: updatedCardInfo?.lastSavedPrice || card?.lastSavedPrice, + // price: updatedCardInfo?.latestPrice?.num || card.price, // Assuming you want to update the price field as well + // _id: updatedCardInfo?._id || card?._id, + // id: updatedCardInfo?.id || card?.id, + // collectionId: updatedCardInfo?.collectionId || card?.collectionId, + // tag: updatedCardInfo?.tag || card?.tag, + // name: updatedCardInfo?.name || card?.name, + // quantity: updatedCardInfo?.quantity || card?.quantity, + // priceHistory: updatedCardInfo?.priceHistory || card?.priceHistory, + // // __v: updatedCardInfo?.__v || originalCard.__v, + // // _id: card?._id, + // // id: card?.id, + // // collectionId: card?.collectionId, // Include collection ID in the returned object + // // tag: 'monitored', + // // name: card?.name, + // // quantity: card?.quantity, + // // price: card?.price, + // // latestPrice: { + // // num: updatedLatestPrice, + // // timestamp: card?.latestPrice?.timestamp + // // ? card?.latestPrice?.timestamp + // // : new Date().toISOString(), + // // }, + // // lastSavedPrice: { + // // num: updatedLastSavedPrice, + // // timestamp: card?.lastSavedPrice?.timestamp + // // ? card?.lastSavedPrice?.timestamp + // // : new Date().toISOString(), + // // }, + // // priceHistory: card?.priceHistory, + // // __v: card?.__v, + // }; + // }); + // }, [allCollections, state.cardPrices]); + + // ----------- SOCKET EVENT HANDLERS ----------- + + // const safeEmit = useCallback( + // (event, data) => { + // try { + // if (!validateData(data, event, 'safeEmit')) { + // throw new Error(`Invalid data emitted for event: ${event}`); + // } + // if (socket) { + // socket.emit(event, data); + // console.log(`[Info] Emitted event: ${event}`); + // } else { + // console.warn('Socket is not connected. Cannot emit event:', event); + // } + // } catch (error) { + // console.error(`[Error] Failed to emit event: ${event}`, error); + // setDataFunctions.error({ + // message: error.message, + // source: 'safeEmit', + // }); + // } + // }, + // [socket] + // ); + + // const safeOn = useCallback( + // (event, handler) => { + // const wrapper = (data) => { + // try { + // if (!validateData(data, event, handler.name)) { + // throw new Error(`Invalid data received for event: ${event}`); + // } // Add this line to validate the data received + // // console.log(`[Info] Handling event: ${event}`); + // handler(data); + // } catch (error) { + // console.error(`[Error] Failed to handle event: ${event}`, error); + // setDataFunctions.error({ message: error.message, source: event }); + // } + // }; + + // socket.on(event, wrapper); // Add this line to register the event listener + + // return () => { + // socket.off(event, wrapper); // Add this line to unregister the event listener when the component unmounts + // }; + // }, + // [socket] + // ); + + // const mergeUpdates = (currentArray, updates) => { + // const updatedArray = [...currentArray]; + // updates.forEach((update) => { + // const index = updatedArray.findIndex((item) => item.id === update.id); + // if (index !== -1) { + // updatedArray[index] = { ...updatedArray[index], ...update }; + // } else { + // updatedArray.push(update); + // } + // }); + // return updatedArray; + // }; + + // const handleStatusUpdateCharts = (newData) => { + // const { updates } = newData.data; + // console.log('[STATUS_UPDATE_CHARTS] Data:', updates); + // const updatedList = mergeUpdates(state.listOfSimulatedCards, updates); + // setDataFunctions.listOfSimulatedCards(updatedList); + // }; + const handleStatusUpdateCharts = async (newData) => { + console.log('[STATUS_UPDATE_CHARTS] Data:', newData); + console.log('Card prices retrieved:', newData); + // const processedPrices = processCardPrices(newData, selectedCollection); + console.log('Card prices updated:', newData); + + // Filter out cards which are not in the selected collection + // const filteredCards = newData.data.data.filter((card) => { + // const cardIds = selectedCollection?.cards?.map((card) => card.id); + // return cardIds?.includes(card.id); + // }); + + // Merge the selectedCollection cards with the filteredCards by adding the latestPrice, lastSavedPrice and priceHistory + // selectedCollection should receive: price: latestPrice.num, priceHistory: [...priceHistory, latestPrice.num], lastSavedPrice: latestPrice + // monitoredCards should then be updated with the new values of quantity from the card data in selectedCollection + // const updatedAllCardPrices = processedPrices.priceArray; + // const updatedTotalPrice = processedPrices.totalPrice; + // const filteredCards = processedPrices.filteredCards; + // console.log('********** [--------------] **********'); + // console.log('********** [FILTERED CARDS] **********', filteredCards); + + // console.log('********** [--------------] **********'); + // console.log('********** [UPDATED PRICES] **********', updatedAllCardPrices); + + // console.log('********** [--------------] **********'); + // console.log('********** [UPDATED TOTAL] **********', updatedTotalPrice); + + // console.log('********** [--------------] **********'); + + // Start with the current collection's state + let updatedCollection = { ...selectedCollection }; + + // setTotalPrice(updatedTotalPrice); + // setAllCardPrices(updatedAllCardPrices); + // console.log('Updated collection in combined:', updatedCollection); + // Iterate over the array of processed prices + // for (const card of filteredCards) { + // updatedCollection = await updateOneFromCollection(card); + // } + + // return updatedCollection; + // const updatedCollection = await updateCollection({ + // ...selectedCollection, + // cards: filteredCards, + // }); + + console.log('Updated collection in combined:', updatedCollection); + return updatedCollection; + }; const handleReceive = (message) => { console.log('Received message:', message); setDataFunctions.messageTest(message); @@ -399,32 +664,72 @@ export const CombinedProvider = ({ children }) => { setDataFunctions.collectionData(collectionData); }; - const handleExistingChartData = (chartData) => { - console.log('Existing chart data:', chartData); - setDataFunctions.existingChartData(chartData); - }; + // const handleExistingChartData = (chartData) => { + // console.log('Existing chart data:', chartData); + // setDataFunctions.existingChartData(chartData); + // }; - const handleChartDatasetsUpdated = (chartUpdate) => { - console.log('Chart datasets updated:', chartUpdate); - setDataFunctions.currentChartData(chartUpdate); - }; + // const handleChartDatasetsUpdated = (chartUpdate) => { + // console.log('Chart datasets updated:', chartUpdate); + // setDataFunctions.currentChartData(chartUpdate); + // }; - const handleCardPricesUpdated = async (priceData) => { + const handleCardPricesUpdated = (priceData) => { console.log('Card prices retrieved:', priceData); - const processedPrices = processCardPrices(priceData); - console.log('Card prices updated:', processedPrices); + // Update listOfMonitoredCards based on the updated card prices + const currentListOfMonitoredCards = + generateListOfMonitoredCards(allCollections); + console.log( + `[currentListOfMonitoredCards: $${getNewTotalPrice( + currentListOfMonitoredCards + )}] | `, + currentListOfMonitoredCards + ); + const updatedCardPrices = priceData.data.data; + // updatedCardPrices.forEach( + // ( + // card // Update the price of each card in the listOfMonitoredCards + // ) => { + // card.price = currentListOfMonitoredCards.price || card.price; + // } + // ); + + setDataFunctions.cardPrices(updatedCardPrices); + // console.log( + // `[updatedCardPrices: $${getNewTotalPrice(updatedCardPrices)}] | `, + // updatedCardPrices + // ); + // console.log( + // `[state.cardPrices.data: $${getNewTotalPrice(state.cardPrices.data)}] | `, + // state.cardPrices.data + // ); + const updatedListOfMonitoredCards = updateCardPricesInList( + currentListOfMonitoredCards, + updatedCardPrices + ); + console.log( + `[updatedListOfMonitoredCards: $${getNewTotalPrice( + updatedListOfMonitoredCards + )}] | `, + updatedListOfMonitoredCards + ); - // Start with the current collection's state - let updatedCollection = { ...selectedCollection }; + // Now update the listOfMonitoredCards in your state + setDataFunctions.allCardPrices(updatedListOfMonitoredCards); + const updatedCollectionResult = updateCollection( + selectedCollection, + null, // since we're not updating a specific card + 'update', // Assuming 'update' is the operation when prices change + userId + ); - // Iterate over the array of processed prices - for (const card of priceData.data.data) { - // Update each card within the collection - updatedCollection = await updateOneFromCollection(card, 'update'); + if (updatedCollectionResult) { + // Do something with the updated collection + console.log('Updated Collection:', updatedCollectionResult); + // Update your state/context or perform additional actions } - - console.log('Updated collection in combined:', updatedCollection); - return updatedCollection; + // Additional processing if required + // ... }; const handleNoPricesChanged = () => { @@ -482,8 +787,8 @@ export const CombinedProvider = ({ children }) => { ['EMITTED_RESPONSES', handleEmittedResponses], ['RESPONSE_CRON_DATA', handleCronJobTracker], ['RESPONSE_EXISTING_COLLECTION_DATA', handleExistingCollectionData], - ['RESPONSE_EXISTING_CHART_DATA', handleExistingChartData], - ['CHART_DATASETS_UPDATED', handleChartDatasetsUpdated], + // ['RESPONSE_EXISTING_CHART_DATA', handleExistingChartData], + // ['CHART_DATASETS_UPDATED', handleChartDatasetsUpdated], ['SEND_PRICING_DATA_TO_CLIENT', handleCardPricesUpdated], ['NO_PRICE_CHANGES', handleNoPricesChanged], ['SEND_UPDATED_DATA_TO_CLIENT', handleNewCardDataObject], @@ -500,9 +805,11 @@ export const CombinedProvider = ({ children }) => { ]); eventHandlers.forEach((handler, event) => { + // validateData(event, 'event', 'useEffect'); socket.on(event, handler); }); + validateData(eventHandlers, 'eventHandlers', 'useEffect'); return () => { eventHandlers.forEach((_, event) => { socket.off(event); @@ -575,6 +882,10 @@ export const CombinedProvider = ({ children }) => { if (!listOfMonitoredCards) return console.error('Missing retrievedListOfMonitoredCards.'); + console.log( + 'SENDING CHECK AND UPDATE CARD PRICES', + listOfMonitoredCards + ); const selectedList = listOfMonitoredCards; socket.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { userId, @@ -634,11 +945,11 @@ export const CombinedProvider = ({ children }) => { handleSocketInteraction.sendAction.message('Hello from client!'); // handleSocketInteraction.sendAction.updateCollection(); // handleSocketInteraction.sendAction.updateChart(); - handleSocketInteraction.sendAction.checkAndUpdateCardPrices( - userId, - listOfMonitoredCards - // retrieveListOfMonitoredCards() - ); + // handleSocketInteraction.sendAction.checkAndUpdateCardPrices( + // userId, + // listOfMonitoredCards + // // retrieveListOfMonitoredCards() + // ); } }, [userId, selectedCollection, socket]); useEffect(() => { @@ -646,11 +957,30 @@ export const CombinedProvider = ({ children }) => { setDataFunctions.collectionData(selectedCollection); }, [selectedCollection]); + // useEffect(() => { + // if (state.allCardPrices) { + // console.log('ALL PRICE DATA', state.allCardPrices); + // // const oldTotal = getNewTotalPrice(state.cardPrices); + // const oldTotal2 = getNewTotalPrice(listOfMonitoredCards); + + // console.log('OLD TOTAL', oldTotal2); + // if ( + // JSON.stringify(state.allCardPrices) !== JSON.stringify(state.cardPrices) + // ) { + // console.log('SETTING SELECTED COLLECTION'); + // const newTotal = getNewTotalPrice(state.allCardPrices); + // console.log('NEW TOTAL COMBINED', newTotal); + // setAllCollections(state.collectionData); + // } + // } + // }, [state.allCardPrices]); + useEffect(() => { if (allCollections) { // console.log('allCollections', allCollections); // console.log('listOfMonitoredCards', listOfMonitoredCards); + console.log('ALLL', allCollections); if ( JSON.stringify(allCollections) !== JSON.stringify(state.allCollectionData) @@ -668,104 +998,46 @@ export const CombinedProvider = ({ children }) => { // ----------- CONTEXT VALUE ----------- // useEffect(() => { - // if (listOfMonitoredCards.length) + // if (listOfMonitoredCards.length === 0) // console.log('listOfMonitoredCards', listOfMonitoredCards); - // handleSocketInteraction.sendAction.triggerCronJob( - // userId, - // listOfMonitoredCards - // ); - // }, [listOfMonitoredCards]); + // setDataFunctions.listOfMonitoredCards({ listOfMonitoredCards }); + // // handleSocketInteraction.sendAction.checkAndUpdateCardPrices( + // // userId, + // // listOfMonitoredCards + // // ); + // }, [listOfMonitoredCards, allCollections]); const value = useMemo( () => ({ ...state, ...setDataFunctions, listOfMonitoredCards, - // emittedResponses: state.emittedResponses, - // messageTest: state.messageTest, - toast, confirm, setLoader, setCronStatus, - handleCronRequest: handleSocketInteraction.sendAction.triggerCronJob, // Ensure it's provided here + handleCronRequest: handleSocketInteraction.sendAction.triggerCronJob, handleSend: handleSocketInteraction.sendAction.message, handleRequestCollectionData: handleSocketInteraction.requestData.collection, - handleRequestChartData: handleSocketInteraction.requestData.chart, // Assuming this is the correct mapping + // handleRequestChartData: handleSocketInteraction.requestData.chart, handleSendAllCardsInCollections: - handleSocketInteraction.sendAction.checkAndUpdateCardPrices, // Ensure it's provided here - handleRequestCronStop: handleSocketInteraction.sendAction.stopCronJob, // Ensure it's provided here - // handleRetreiveListOfMonitoredCards: retrieveListOfMonitoredCards, - - // MAIN LOGGER + handleSocketInteraction.sendAction.checkAndUpdateCardPrices, + handleRequestCronStop: handleSocketInteraction.sendAction.stopCronJob, handlePricesActivateCron: handleSocketInteraction.sendAction.checkPriceUpdates, handleSocketInteraction, - setDataFunctions, socket, isDelaying: state.isDelaying, isCronJobTriggered: state.isCronJobTriggered, - // setDataFunctions, }), [state, socket] ); + // Log combined context value for debugging useEffect(() => { console.log('COMBINED CONTEXT VALUE:', state); - }, [ - state.allData, - state.data, - state.messageTest, - state.existingChartData, - state.collectionData, - state.dcurrentChartData, - state.cronData, - state.finalUpdateData, - state.cardPrices, - state.eventsTriggered, - state.cardsWithChangedPrice, - state.previousDayTotalPrice, - state.dailyPriceChange, - state.priceDifference, - state.allCardPrices, - state.handleCardPricesUpdated, - state.retrievedListOfMonitoredCards, - ]); - - const dataValues = { - state: state, - allData: value.allData, - eventsTriggered: value.eventsTriggered, - data: value.data, - messageTest: value.messageTest, - finalUpdateData: value.finalUpdateData, - chartData: value.chartData, - emittedResponses: value.emittedResponses, - currentChartData: value.currentChartData, - existingChartData: value.existingChartData, - collectionData: value.collectionData, - allCollectionData: value.allCollectionData, - allCollectionsUpdated: value.allCollectionsUpdated, - cronData: value.cronData, - cardPrices: value.cardPrices, - retrievedListOfMonitoredCards: value.retrievedListOfMonitoredCards, - listOfMonitoredCards: value.listOfMonitoredCards, - error: value.error, - }; - - // useEffect(() => { - // console.log('COMBINED CONTEXT VALUE:', dataValues); - // }, [ - // dataValues.cronData, - // dataValues.chartData, - // dataValues.collectionData, - // dataValues.messageTest, - // dataValues.emittedResponses, - // dataValues.retrievedListOfMonitoredCards, - // dataValues.emittedResponses, - // dataValues.eventsTriggered, - // ]); + }, [state]); return ( diff --git a/src/index.js b/src/index.js index 435859a..6d584fe 100644 --- a/src/index.js +++ b/src/index.js @@ -25,13 +25,13 @@ import { createTheme } from '@mui/material'; const root = document.getElementById('root'); function Main() { - // const { theme } = useMode(); - const darkTheme = createTheme({ - palette: { - mode: 'dark', - }, - }); - const theme = darkTheme; + const { theme } = useMode(); + // const darkTheme = createTheme({ + // palette: { + // mode: 'dark', + // }, + // }); + // const theme = darkTheme; return ( diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 4f22110..0e641f2 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -106,13 +106,13 @@ const ProfilePage = () => { } }; - const handleTriggerCronJob = () => { - if (userId && listOfMonitoredCards) { - handleSendAllCardsInCollections(userId, listOfMonitoredCards); - console.log('SENDING ALL CARDS IN COLLECTIONS'); - openSnackbar('Triggered the cron job.'); - } - }; + // const handleTriggerCronJob = () => { + // if (userId && listOfMonitoredCards) { + // handleSendAllCardsInCollections(userId, listOfMonitoredCards); + // console.log('SENDING ALL CARDS IN COLLECTIONS'); + // openSnackbar('Triggered the cron job.'); + // } + // }; const handleStopCronJob = () => { if (userId) { @@ -160,13 +160,13 @@ const ProfilePage = () => { Request Chart Data - Trigger Cron Job - + */} { @@ -30,7 +31,8 @@ export const themeSettings = (mode) => { light: colors.grey[100], }, background: { - default: mode === 'dark' ? colors.primary[500] : '#fcfcfc', + default: mode === 'dark' ? '#121212' : '#ffffff', + paper: mode === 'dark' ? colors.grey[300] : '#ffffff', }, error: { main: colors.redAccent[500], From 50bc63e84d8328c6265582a0b6c6063ac10300a7 Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Thu, 23 Nov 2023 21:29:20 -0800 Subject: [PATCH 09/10] add --- package.json | 5 + src/App.js | 196 +-- .../animations}/CardDeckAnimation.js | 2 +- .../pageStyles => assets/animations}/Cube.jsx | 0 .../animations/LoadingCardAnimation.jsx | 228 +++ .../animations}/ThreeJsCube.js | 0 src/assets/example.json | 32 - src/assets/styles/CSStoRemove.css | 769 --------- src/assets/styles/ProductListing.css | 24 - src/assets/styles/chakraStylesReset.css | 1 - src/assets/styles/reset.css | 129 -- src/assets/styles/themes.js | 29 - src/{ => assets}/themeSettings.jsx | 75 +- src/{ => assets}/tokens.jsx | 0 .../actionButtons/CardActionButtons.jsx | 194 ++- .../actionButtons/GenericActionButtons.jsx | 147 +- src/components/cards/CardToolTip.jsx | 20 +- src/components/cards/GenericCard.jsx | 252 ++- src/components/cards/cardStyles.jsx | 16 + src/components/chart/LinearChart.js | 236 +-- src/components/chart/PortfolioChart.jsx | 74 +- src/components/chart/chartUtils.jsx | 180 +-- src/{ => components/cleanUp}/AppWrapper.jsx | 0 .../cleanUp/CollectionActionButtons.jsx | 2 +- src/components/cleanUp/Hero.jsx | 49 + src/{ => components/cleanUp}/theme.jsx | 0 src/components/collection/CardPortfolio.jsx | 4 +- .../collection/SelectCollection.jsx | 2 +- src/components/content/PortfolioContent.jsx | 62 - src/components/forms/SearchForm.jsx | 72 + .../customerCheckoutForm/CustomerForm.js | 20 +- src/components/grids/DeckDisplay.js | 56 +- .../grids/collectionGrids/CardList.jsx | 278 +++- .../collectionGrids/SelectCollectionList.jsx | 52 +- .../TablePaginationActions.jsx | 3 +- .../grids/deckBuilderGrids/CardsGrid.js | 48 +- .../grids/deckBuilderGrids/DeckButtonList.js | 48 +- src/components/grids/gridStyles.jsx | 100 ++ .../searchResultGrids/DeckSearchCardGrid.jsx | 131 +- .../storeSearchResultsGrid/ProductGrid.js | 11 +- src/components/headings/header/Header.js | 1 + .../headings/header/MenuItemComponent.jsx | 15 - .../headings/navigation/MenuItems.js | 146 -- .../headings/navigation/SideBar.jsx | 94 -- src/components/headings/navigation/TopBar.jsx | 95 +- src/components/headings/navigation/styled.js | 3 +- "Icon\r" => src/components/index.jsx | 0 src/components/media/CardMedia.jsx | 26 +- src/components/media/CardMediaAndDetails.jsx | 6 +- src/components/media/CardMediaSection.js | 46 +- src/components/media/DeckCardMedia.jsx | 38 - src/components/media/mediaStyles.jsx | 6 + src/components/modals/GenericCardModal.jsx | 60 +- .../modals/stripeModal/StripeCheckoutModal.js | 2 +- src/components/other/AddressForm.jsx | 111 ++ src/components/other/CardCountDisplay.jsx | 23 - src/components/other/Checkout.jsx | 126 ++ .../other/CollectionValueTracker.jsx | 180 ++- src/components/other/DeckEditPanel.js | 53 +- .../CollectionStatisticsSelector.jsx | 19 +- .../InputComponents/TimeRangeSelector.jsx | 29 - src/components/other/PaymentForm.jsx | 65 + src/components/other/ProgressCircle.jsx | 23 + src/components/other/Review.jsx | 88 ++ src/components/other/StatBox.jsx | 43 + src/components/other/TopCardsDisplay.jsx | 181 +++ .../other/dataDisplay/CardCountDisplay.jsx | 42 + .../{Auth => reusable}/PrivateRoute.jsx | 0 .../indicators/ErrorIndicator.js | 21 +- .../indicators/LoadingIndicator.js | 0 .../reusable/indicators/LoadingIndicator2.js | 13 + src/components/reusable/indicators/styles.js | 21 + src/components/search/DeckSearch.js | 3 +- src/components/search/SearchBar.js | 124 +- src/components/search/SearchForm.jsx | 32 - .../search/SearchResultsDisplay.jsx | 0 src/components/search/search.json | 54 + src/containers/PortfolioChartContainer.jsx | 130 -- src/containers/PortfolioListContainer.jsx | 26 - .../CartContainer.jsx | 0 .../cartPageContainers}/CartContent.js | 6 +- .../CartContentContainer.js | 4 +- .../CustomerFormContainer.js | 2 +- .../PortfolioChartContainer.jsx | 77 + .../PortfolioContent.jsx | 90 ++ .../PortfolioListContainer.jsx | 33 + .../DeckBuilderContainer.js | 53 +- src/context/AppContext/AppContextProvider.jsx | 54 + src/context/AppContextProvider.jsx | 24 - src/context/CardContext/CardStore.js | 5 - .../CardImagesContext/CardImagesContext.jsx | 112 ++ src/context/CartContext/keepsaved.js | 240 --- src/context/ChartContext/ChartContext.jsx | 2 +- src/context/CollectionContext/ChunkPaylod.jsx | 29 - .../CollectionContext/CollectionContext.jsx | 358 +++-- .../checkServerForUpdates.jsx | 66 - .../CollectionContext/collectionUtility.jsx | 111 +- src/context/CollectionContext/copy.js | 926 ----------- .../CollectionContext/exampleImport.js | 123 -- src/context/CollectionContext/fml.jsx | 1389 ----------------- src/context/CollectionContext/fmljsx | 1 - src/context/CollectionContext/index.js | 453 ------ .../CollectionContext/validateCollection.jsx | 68 - src/context/ColorModeProvider.jsx | 11 +- src/context/CombinedProvider.jsx | 640 +------- src/context/CronJobContext/CronJobContext.jsx | 52 + src/context/CutstomLogger.jsx | 23 - src/context/DeckContext/new.js | 645 -------- src/context/Helpers.jsx | 70 +- src/context/ModalContext/ModalContext.jsx | 20 +- src/context/PopoverContext/PopoverContext.jsx | 26 + src/context/SocketManager.jsx | 16 - src/context/SocketProvider.jsx | 4 - src/context/UserContext/UserContext.js | 13 - src/context/UtilityContext/UtilityContext.jsx | 81 +- src/context/cleanUp/ApiServiceProvider.jsx | 139 -- src/context/cleanUp/CombinedFetcher.jsx | 50 - src/context/cleanUp/SocketActions.jsx | 171 -- src/context/cleanUp/SocketHandler.js | 115 -- src/context/cleanUp/useSocketEvent.js | 16 - src/context/cleanUp/useUpdateChartData.jsx | 72 - src/context/hooks/collection.jsx | 13 - src/context/hooks/useAppContext.jsx | 6 +- src/context/hooks/useUpdateContext.jsx | 29 + .../hooks}/useWindowSize.jsx | 0 src/index.js | 68 +- src/pages/CartPage.js | 14 +- src/pages/CollectionPage.js | 52 +- src/pages/DeckBuilderPage.js | 85 +- src/pages/HomePage.js | 331 ++-- src/pages/ProfilePage.js | 33 +- src/pages/Splash.css | 113 -- src/pages/Splash.js | 242 --- src/pages/StorePage.js | 24 +- src/pages/StyledComponents.jsx | 45 - src/pages/pageStyles/CollectionBanner.jsx | 15 - src/pages/pageStyles/CollectionTitle.jsx | 10 - src/pages/pageStyles/DeckBuilderBanner.jsx | 13 - src/pages/pageStyles/DeckBuilderTitle.jsx | 10 - src/pages/pageStyles/Hero.jsx | 49 - src/pages/pageStyles/StyledComponents.jsx | 218 +++ src/pages/{ => pageStyles}/styles.jsx | 57 + src/pages/pages.json | 49 + src/setupProxy.js | 18 + 144 files changed, 4274 insertions(+), 9102 deletions(-) rename src/{pages => assets/animations}/CardDeckAnimation.js (97%) rename src/{pages/pageStyles => assets/animations}/Cube.jsx (100%) create mode 100644 src/assets/animations/LoadingCardAnimation.jsx rename src/{pages => assets/animations}/ThreeJsCube.js (100%) delete mode 100644 src/assets/example.json delete mode 100644 src/assets/styles/CSStoRemove.css delete mode 100644 src/assets/styles/ProductListing.css delete mode 100644 src/assets/styles/chakraStylesReset.css delete mode 100644 src/assets/styles/reset.css delete mode 100644 src/assets/styles/themes.js rename src/{ => assets}/themeSettings.jsx (62%) rename src/{ => assets}/tokens.jsx (100%) rename src/{ => components/cleanUp}/AppWrapper.jsx (100%) create mode 100644 src/components/cleanUp/Hero.jsx rename src/{ => components/cleanUp}/theme.jsx (100%) delete mode 100644 src/components/content/PortfolioContent.jsx create mode 100644 src/components/forms/SearchForm.jsx delete mode 100644 src/components/headings/navigation/MenuItems.js rename "Icon\r" => src/components/index.jsx (100%) delete mode 100644 src/components/media/DeckCardMedia.jsx create mode 100644 src/components/other/AddressForm.jsx delete mode 100644 src/components/other/CardCountDisplay.jsx create mode 100644 src/components/other/Checkout.jsx create mode 100644 src/components/other/PaymentForm.jsx create mode 100644 src/components/other/ProgressCircle.jsx create mode 100644 src/components/other/Review.jsx create mode 100644 src/components/other/StatBox.jsx create mode 100644 src/components/other/TopCardsDisplay.jsx create mode 100644 src/components/other/dataDisplay/CardCountDisplay.jsx rename src/components/{Auth => reusable}/PrivateRoute.jsx (100%) rename src/components/{ => reusable}/indicators/ErrorIndicator.js (68%) rename src/components/{ => reusable}/indicators/LoadingIndicator.js (100%) create mode 100644 src/components/reusable/indicators/LoadingIndicator2.js create mode 100644 src/components/reusable/indicators/styles.js delete mode 100644 src/components/search/SearchForm.jsx delete mode 100644 src/components/search/SearchResultsDisplay.jsx create mode 100644 src/components/search/search.json delete mode 100644 src/containers/PortfolioChartContainer.jsx delete mode 100644 src/containers/PortfolioListContainer.jsx rename src/containers/{ => cartPageContainers}/CartContainer.jsx (100%) rename src/{components/content => containers/cartPageContainers}/CartContent.js (84%) rename src/containers/{ => cartPageContainers}/CartContentContainer.js (79%) rename src/containers/{ => cartPageContainers}/CustomerFormContainer.js (72%) create mode 100644 src/containers/collectionPageContainers/PortfolioChartContainer.jsx create mode 100644 src/containers/collectionPageContainers/PortfolioContent.jsx create mode 100644 src/containers/collectionPageContainers/PortfolioListContainer.jsx create mode 100644 src/context/AppContext/AppContextProvider.jsx delete mode 100644 src/context/AppContextProvider.jsx create mode 100644 src/context/CardImagesContext/CardImagesContext.jsx delete mode 100644 src/context/CartContext/keepsaved.js delete mode 100644 src/context/CollectionContext/ChunkPaylod.jsx delete mode 100644 src/context/CollectionContext/checkServerForUpdates.jsx delete mode 100644 src/context/CollectionContext/copy.js delete mode 100644 src/context/CollectionContext/exampleImport.js delete mode 100644 src/context/CollectionContext/fml.jsx delete mode 100644 src/context/CollectionContext/fmljsx delete mode 100644 src/context/CollectionContext/index.js delete mode 100644 src/context/CollectionContext/validateCollection.jsx create mode 100644 src/context/CronJobContext/CronJobContext.jsx delete mode 100644 src/context/CutstomLogger.jsx delete mode 100644 src/context/DeckContext/new.js create mode 100644 src/context/PopoverContext/PopoverContext.jsx delete mode 100644 src/context/SocketManager.jsx delete mode 100644 src/context/cleanUp/ApiServiceProvider.jsx delete mode 100644 src/context/cleanUp/CombinedFetcher.jsx delete mode 100644 src/context/cleanUp/SocketActions.jsx delete mode 100644 src/context/cleanUp/SocketHandler.js delete mode 100644 src/context/cleanUp/useSocketEvent.js delete mode 100644 src/context/cleanUp/useUpdateChartData.jsx delete mode 100644 src/context/hooks/collection.jsx create mode 100644 src/context/hooks/useUpdateContext.jsx rename src/{components/headings/navigation => context/hooks}/useWindowSize.jsx (100%) delete mode 100644 src/pages/Splash.css delete mode 100644 src/pages/Splash.js delete mode 100644 src/pages/StyledComponents.jsx delete mode 100644 src/pages/pageStyles/CollectionBanner.jsx delete mode 100644 src/pages/pageStyles/CollectionTitle.jsx delete mode 100644 src/pages/pageStyles/DeckBuilderBanner.jsx delete mode 100644 src/pages/pageStyles/DeckBuilderTitle.jsx delete mode 100644 src/pages/pageStyles/Hero.jsx create mode 100644 src/pages/pageStyles/StyledComponents.jsx rename src/pages/{ => pageStyles}/styles.jsx (67%) create mode 100644 src/pages/pages.json create mode 100644 src/setupProxy.js diff --git a/package.json b/package.json index 15f8115..576afc4 100644 --- a/package.json +++ b/package.json @@ -33,13 +33,17 @@ "react-helmet": "^6.1.0", "react-icons": "^4.10.1", "react-if": "^4.1.5", + "react-material-ui-carousel": "^3.4.2", "react-pro-sidebar": "^1.1.0-alpha.1", "react-redux": "^8.1.1", "react-responsive-carousel": "^3.2.23", "react-router-dom": "^6.17.0", "react-scripts": "5.0.1", + "react-slick": "^0.29.0", "react-spinners": "^0.13.8", "react-stripe-checkout": "^2.6.3", + "react-swipeable-views": "^0.14.0", + "slick-carousel": "^1.8.1", "socket.io-client": "^4.7.2", "stripe": "^12.14.0", "styled-components": "^6.0.5", @@ -75,6 +79,7 @@ "eslint": "^8.46.0", "eslint-config-prettier": "^8.9.0", "eslint-plugin-prettier": "^5.0.0", + "http-proxy-middleware": "^2.0.6", "prettier": "^3.0.0" } } diff --git a/src/App.js b/src/App.js index 2f74223..e56bc47 100644 --- a/src/App.js +++ b/src/App.js @@ -1,18 +1,12 @@ // External Imports -import React, { useEffect, useRef, useState } from 'react'; -import { - BrowserRouter as Router, - Route, - Routes, - useLocation, -} from 'react-router-dom'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import { Helmet } from 'react-helmet'; -import styled, { createGlobalStyle } from 'styled-components'; // Component Imports import Header from './components/headings/header/Header'; import Footer from './components/headings/footer/Footer'; -import PrivateRoute from './components/Auth/PrivateRoute'; +import PrivateRoute from './components/reusable/PrivateRoute'; // Page Imports import SplashPage from './pages/SplashPage'; @@ -22,182 +16,54 @@ import CartPage from './pages/CartPage'; import ProfilePage from './pages/ProfilePage'; import CollectionPage from './pages/CollectionPage'; import DeckBuilderPage from './pages/DeckBuilderPage'; -import ThreeJsCube from './pages/ThreeJsCube'; -import CardDeckAnimation from './pages/CardDeckAnimation'; +import ThreeJsCube from './assets/animations/ThreeJsCube'; +import CardDeckAnimation from './assets/animations/CardDeckAnimation'; // Context Hooks Imports -import { useCombinedContext } from './context/CombinedProvider'; import { useUserContext } from './context/UserContext/UserContext'; import { useCollectionStore } from './context/CollectionContext/CollectionContext'; import { useUtilityContext } from './context/UtilityContext/UtilityContext'; +import { AppContainer } from './pages/pageStyles/StyledComponents'; +import { useCardImages } from './context/CardImagesContext/CardImagesContext'; +import { useCookies } from 'react-cookie'; +const App = () => { + const [cookies] = useCookies(['user']); -// Styled Components -const AppContainer = styled.div` - display: flex; - flex-direction: column; - height: 100vh; -`; - -const GlobalStyle = createGlobalStyle` - /* Your global styles here */ -`; + const user = cookies.user; + const [currentPage, setCurrentPage] = useState(''); + // const { setContext } = useAppContext(); // Assuming useAppContext provides setContext + const { fetchAllCollectionsForUser, selectedCollection } = + useCollectionStore(); + const { isLoading, setIsLoading } = useUtilityContext(); -// Custom Hook for Cron Job -const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { - const { - cronData, - handleSendAllCardsInCollections, - listOfMonitoredCards, - // handleRetreiveListOfMonitoredCards, - // retrievedListOfMonitoredCards, - allCollectionsUpdated, - } = useCombinedContext(); - const { allCollections } = useCollectionStore(); - const [priceHistory, setPriceHistory] = useState([]); + // const { getRandomCardImages } = useCardImages(); // Add this line - const { handlePricesActivateCron, cardsWithChangedPrice } = - useCombinedContext(); - const { user } = useUserContext(); - const [priceFlux, setPriceFlux] = useState(null); - const userId = user?.userID; - useEffect(() => { - setLastCronJobTriggerTime(new Date().getTime()); - }, [setLastCronJobTriggerTime]); - // --------------------------------------------------------------- // useEffect(() => { - // handlePricesActivateCron( - // userId, - // listOfMonitoredCards, - // allCollections, - // cardsWithChangedPrice - // ); - // }, [ - // userId, - // listOfMonitoredCards, - // allCollections, - // cardsWithChangedPrice, - // priceFlux, - // ]); - - useEffect(() => { - const handleTriggerCronJob = () => { - const currentTime = new Date().getTime(); - const timeDifference = currentTime - lastCronJobTriggerTime; - const previousTotalPrice = allCollections?.totalPrice; - if (!priceHistory.includes(previousTotalPrice)) { - priceHistory.push(previousTotalPrice); - } - const minutes = Math.floor(timeDifference / 60000); // 1 minute = 60000 milliseconds - const seconds = Math.floor((timeDifference % 60000) / 1000); // remainder in milliseconds converted to seconds - - // Output the remaining time in minutes and seconds - console.log( - `REMAINING TIME: ${minutes} minute(s) and ${seconds} second(s)` - ); - - for (const collection of allCollections) { - if ( - collection?.cards && - collection?.cards?.length > 0 && - collection.totalPrice !== previousTotalPrice // Implement your logic here - ) { - setPriceFlux(new Date().getTime()); // Trigger a re-render - console.log('PRICE FLUX:', priceFlux); - } - } - // if (collection.totalPrice !== previousTotalPrice) { - // // Update dependencies of useEffect - // setPriceFlux(new Date().getTime()); // Trigger a re-render - // } - if (currentTime - lastCronJobTriggerTime >= 60000) { - setLastCronJobTriggerTime(currentTime); - if (userId && listOfMonitoredCards) { - console.log('RETRIEVING LIST OF MONITORED CARDS (paused)'); - handleSendAllCardsInCollections( - userId, - listOfMonitoredCards - // handleRetrieveListOfMonitoredCards() - ); - console.log('Triggered the cron job.'); - } - } - }; - - const interval = setInterval(handleTriggerCronJob, 60000); - return () => clearInterval(interval); - }, [ - cronData, - lastCronJobTriggerTime, - allCollectionsUpdated, - // handleRetrieveListOfMonitoredCards, - handleSendAllCardsInCollections, - listOfMonitoredCards, - userId, - ]); -}; - -// Main Component -const App = () => { - const { user } = useUserContext(); - const { isLoading, setIsContextLoading } = useUtilityContext(); - const { fetchAllCollectionsForUser, allCollections } = useCollectionStore(); - const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); - - useCronJob(lastCronJobTriggerTime, setLastCronJobTriggerTime); + // getRandomCardImages(10); // Fetch 10 random images on app start + // }, []); // Add this useEffect useEffect(() => { if (user) { - console.log('Private routes now available'); + fetchAllCollectionsForUser(user.id) + .then(() => { + setIsLoading(false); + }) + .catch((error) => { + console.error('Error fetching collections:', error); + setIsLoading(false); + }); } + }, [user, fetchAllCollectionsForUser, setIsLoading, selectedCollection]); - return () => { - console.log('Private routes no longer available'); - }; - }, [user]); - + // Handle initial loading state useEffect(() => { if (!isLoading) { - setIsContextLoading(false); + setIsLoading(false); } - }, [isLoading, setIsContextLoading]); - - useEffect(() => { - const timer = setTimeout(() => { - setIsContextLoading(false); - }, 5500); - return () => clearTimeout(timer); - }, [setIsContextLoading]); - - useEffect(() => { - let isMounted = true; // Add a flag to track if the component is mounted - const fetchCollections = async () => { - if (user && isMounted) { - try { - // const response = fet - await fetchAllCollectionsForUser(user.userID); - if (isMounted) { - console.log('Fetched collections because none were present.'); - // Update state only if the component is still mounted - console.log('RESPONSE:', isMounted); - // setOfficialCollectionDatasets(response.data); - } - } catch (err) { - console.error('Failed to fetch collections:', err); - } - } - }; - - fetchCollections(); - - // Cleanup function to cancel any ongoing tasks when the component unmounts - return () => { - isMounted = false; - }; - }, [user, fetchAllCollectionsForUser, allCollections]); + }, [isLoading, setIsLoading]); return ( <> - { // Initialize the scene, camera, and renderer here diff --git a/src/pages/pageStyles/Cube.jsx b/src/assets/animations/Cube.jsx similarity index 100% rename from src/pages/pageStyles/Cube.jsx rename to src/assets/animations/Cube.jsx diff --git a/src/assets/animations/LoadingCardAnimation.jsx b/src/assets/animations/LoadingCardAnimation.jsx new file mode 100644 index 0000000..12a4f02 --- /dev/null +++ b/src/assets/animations/LoadingCardAnimation.jsx @@ -0,0 +1,228 @@ +import React, { useEffect, useState } from 'react'; +import * as THREE from 'three'; +import { useCardImages } from '../../context/CardImagesContext/CardImagesContext'; +import placeholderImage from '../images/placeholder.jpeg'; // Adjust the path as necessary +function LoadingCardAnimation() { + const { cards, isLoading, error } = useCardImages(); + const [randomCardImage, setRandomCardImage] = useState(); + + useEffect(() => { + if (cards && cards.length > 0) { + const randomCard = cards[Math.floor(Math.random() * cards.length)]; + if (randomCard.card_images && randomCard.card_images.length > 0) { + // Constructing the local file name + const name = randomCard.name.replace(/[/\\?%*:|"<>]/g, ''); + console.log('name', name); + const folder = 'images'; + console.log('folder', folder); + const folder2 = 'cards'; + const folder3 = `${ + randomCard?.type === 'Spell Card' || randomCard?.type === 'Trap Card' + ? 'spell_trap' + : 'monster' + }`; + console.log('folder2', folder2); + const extension = 'jpg'; // Assuming all images are jpg + console.log('naextensionme', extension); + const fullName = `${name}_${randomCard.race}_${randomCard.type}${ + randomCard.level ? '_lvl' + randomCard.level : '' + }${ + randomCard.attribute ? '_' + randomCard.attribute : '' + }.${extension}`; + console.log('fullName', fullName); + const filePath = `../${folder}/${folder2}/${folder3}/${fullName}`; + console.log('filePath', filePath); + console.log('placeholderImage', placeholderImage); + setRandomCardImage(filePath); + } + } + console.log('cards', cards); + console.log('randomCardImage', randomCardImage); + }, [cards]); + + useEffect(() => { + if (!randomCardImage) return; + + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera( + 75, + window.innerWidth / window.innerHeight, + 0.1, + 1000 + ); + const renderer = new THREE.WebGLRenderer({ alpha: true }); + renderer.setSize(window.innerWidth, window.innerHeight); + + const loadingIconContainer = document.getElementById('loadingIcon'); + if (!loadingIconContainer) return; + + loadingIconContainer.appendChild(renderer.domElement); + + // Load the placeholder texture + const placeholderTexture = new THREE.TextureLoader().load(placeholderImage); + + // Load the card image texture + new THREE.TextureLoader().load( + randomCardImage, + (texture) => { + const cardMaterialFront = new THREE.MeshBasicMaterial({ map: texture }); + const cardMaterialBack = new THREE.MeshBasicMaterial({ + map: placeholderTexture, + }); + + const cardGeometry = new THREE.BoxGeometry(1, 1.4, 0.01); + const card = new THREE.Mesh(cardGeometry, [ + cardMaterialBack, // Left side + cardMaterialBack, // Right side + cardMaterialBack, // Top side + cardMaterialBack, // Bottom side + cardMaterialFront, // Front side + cardMaterialBack, // Back side + ]); + scene.add(card); + camera.position.z = 5; + + const animate = () => { + requestAnimationFrame(animate); + card.rotation.y += 0.05; + renderer.render(scene, camera); + }; + + animate(); + }, + undefined, // onProgress callback not supported + (err) => { + console.error('An error occurred while loading the texture:', err); + } + ); + + return () => { + if (loadingIconContainer.contains(renderer.domElement)) { + loadingIconContainer.removeChild(renderer.domElement); + } + }; + }, [randomCardImage]); + + if (isLoading) return
Loading...
; + if (error) return
Error: {error}
; + + return ( +
+ Three.js animation should appear here +
+ ); +} + +export default LoadingCardAnimation; + +// import * as THREE from 'three'; +// import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +// import placeholderImage from '../images/placeholder.jpeg'; +// import { useCardImages } from '../../context/CardImagesContext/CardImagesContext'; + +// function LoadingCardAnimation() { +// const { selectedCollection, allCollections } = useCollectionStore(); +// const [randomCardImage, setRandomCardImage] = useState(null); +// const { images, isLoading, error, downloadCardImages, getRandomCardImages } = +// useCardImages(); + +// if (isLoading) { +// console.log('Loading...'); +// return
Loading...
; +// } + +// if (error) { +// console.error(error); +// return
Error: {error}
; +// } + +// if (images && images.length > 0) { +// return ( +//
+// {images.map((image, index) => ( +// card +// ))} +//
+// ); +// } + +// useEffect(() => { +// const selectRandomCardImage = (collection) => { +// const filteredCards = collection.cards.filter( +// (card) => card.card_images && card.card_images.length > 0 +// ); +// if (filteredCards.length > 0) { +// const randomCard = +// filteredCards[Math.floor(Math.random() * filteredCards.length)]; +// return randomCard.card_images[0].image_url; +// } +// return placeholderImage; +// }; + +// const collection = +// selectedCollection || +// (allCollections.length > 0 +// ? allCollections[Math.floor(Math.random() * allCollections.length)] +// : null); +// if (collection) { +// setRandomCardImage(selectRandomCardImage(collection)); +// } else { +// setRandomCardImage(placeholderImage); +// } +// }, [selectedCollection, allCollections]); + +// useEffect(() => { +// if (!randomCardImage) return; + +// const loadingIconContainer = document.getElementById('loadingIcon'); +// if (!loadingIconContainer) { +// console.error('Loading icon container not found'); +// return; +// } + +// const scene = new THREE.Scene(); +// const camera = new THREE.PerspectiveCamera(75, 400 / 400, 0.1, 1000); +// const renderer = new THREE.WebGLRenderer({ alpha: true }); +// renderer.setSize(400, 400); + +// loadingIconContainer.appendChild(renderer.domElement); + +// const placeholderTexture = new THREE.TextureLoader().load(placeholderImage); +// const randomCardTexture = new THREE.TextureLoader().load(randomCardImage); +// const cardMaterialFront = new THREE.MeshBasicMaterial({ +// map: placeholderTexture, +// }); +// const cardMaterialBack = new THREE.MeshBasicMaterial({ +// map: randomCardTexture, +// }); + +// const cardGeometry = new THREE.BoxGeometry(1, 1.4, 0.01); +// const card = new THREE.Mesh(cardGeometry, [ +// cardMaterialBack, +// cardMaterialBack, +// cardMaterialBack, +// cardMaterialBack, +// cardMaterialFront, +// cardMaterialBack, +// ]); +// scene.add(card); + +// camera.position.z = 5; + +// const animate = function () { +// requestAnimationFrame(animate); +// card.rotation.y += 0.05; +// renderer.render(scene, camera); +// }; + +// animate(); + +// return () => { +// loadingIconContainer.removeChild(renderer.domElement); +// }; +// }, [randomCardImage]); + +// return
; +// } + +// export default LoadingCardAnimation; diff --git a/src/pages/ThreeJsCube.js b/src/assets/animations/ThreeJsCube.js similarity index 100% rename from src/pages/ThreeJsCube.js rename to src/assets/animations/ThreeJsCube.js diff --git a/src/assets/example.json b/src/assets/example.json deleted file mode 100644 index 88ce308..0000000 --- a/src/assets/example.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "chartData": { - "_id": 64163367, - "name": "\"A\" Cell Incubator", - "userId": "Spell Card", - "collectionId": "spell", - "chartId": "Each time an A-Counter(s) is removed from play by a card effect, place 1 A-Counter on this card. When this card is destroyed, distribute the A-Counters on this card among face-up monsters.", - "datasets": [ - { - "label": "Set x", - "totalquantity": 37, - "data": { - "points": [ - "card name": { - "x": "x/x/2023", - "y": 3, - "price change": "prev y - y", - "price change %": "price change / prev y", - "total quantity": "totalquantity", - "total price": "totalquantity * y" - }, - - } - ], - }, - "backgroundColor": "rgba(255, 99, 132, 0.2)", - "borderColor": "rgb(255, 99, 132)", - "borderWidth": 1 - } - ] - } -} diff --git a/src/assets/styles/CSStoRemove.css b/src/assets/styles/CSStoRemove.css deleted file mode 100644 index 5dacdbe..0000000 --- a/src/assets/styles/CSStoRemove.css +++ /dev/null @@ -1,769 +0,0 @@ -code { - font-family: - source-code-pro, - Menlo, - Monaco, - Consolas, - Courier New, - monospace; -} -#header { - background-color: #4caf50; - color: #fff; - padding: 10px; -} - -#header:after { - clear: both; - content: ''; - display: table; -} - -#navbar { - float: left; - margin: 0; - padding: 0; -} - -#navbar li { - display: inline-block; - margin-left: 70px; -} - -#navbar li a { - color: #fff; - display: block; - padding: 14px 16px; - text-align: center; - text-decoration: none; -} - -#navbar li a:hover { - background-color: #111; -} -#home .header-content { - color: #fff; - position: relative; - text-align: center; - top: 50%; - -webkit-transform: translateY(-50%); - transform: translateY(-50%); -} - -#home .header-content h1 { - font-size: 3.5rem; - margin: 0; -} - -#home .header-content p { - font-size: 1.5rem; - margin-bottom: 1.25rem; -} - -#about .about-heading { - text-align: center; -} - -#about .about-heading h2 { - font-size: 3rem; -} - -#about .about-content { - align-items: center; - display: flex; - justify-content: space-between; -} - -#about .about-content .left-column { - flex-basis: 50%; - padding-right: 1rem; -} - -#about .about-content .right-column { - flex-basis: 50%; - padding-left: 1rem; -} - -#services .service-item { - margin-bottom: 2rem; - text-align: center; -} - -#services .service-item h2 { - font-size: 2.5rem; -} - -#services .service-item p { - font-size: 1rem; -} -#portfolio .portfolio-item .how-title { - color: #fff; - font-size: 16px; - font-weight: 700; -} - -#portfolio .portfolio-item .skill { - align-items: center; - display: flex; - margin-top: 5px; -} - -#portfolio .portfolio-item .skill p { - font-size: 14px; - font-weight: 600; - margin: 0; -} - -#portfolio .portfolio-item .skill-bar { - background-color: #fff; - height: 6px; - margin: 0 5px; - position: relative; - width: 100%; -} - -#portfolio .portfolio-item .skill-bar:before { - background-color: #00baff; - content: ''; - height: 100%; - left: 0; - position: absolute; - top: 0; - transition: width 0.3s ease-out; -} - -#portfolio .portfolio-item .skill1 .skill-count1:after, -#portfolio .portfolio-item .skill2 .skill-count2:after, -#portfolio .portfolio-item .skill3 .skill-count3:after { - content: '%'; -} - -#portfolio .portfolio-item .skill-count1, -#portfolio .portfolio-item .skill-count2, -#portfolio .portfolio-item .skill-count3 { - color: #00baff; - font-size: 14px; - font-weight: 600; -} - -#portfolio .portfolio-item .skill1 .skill-bar:before { - width: 95%; -} - -#portfolio .portfolio-item .skill2 .skill-bar:before { - width: 85%; -} - -#portfolio .portfolio-item .skill3 .skill-bar:before { - width: 75%; -} - -#portfolio .portfolio-item .skill .skill-count { - color: #18d26e; - font-size: 24px; - font-weight: 700; - letter-spacing: 1px; - position: absolute; - right: -40px; - top: -35px; -} - -#portfolio .portfolio-item .skill1 .skill-count1 { - content: '95%'; -} - -#portfolio .portfolio-item .skill2 .skill-count2 { - content: '85%'; -} - -#portfolio .portfolio-item .skill3 .skill-count3 { - content: '75%'; -} -.project-choices { - display: flex; - flex-wrap: wrap; - justify-content: space-between; -} -.skill-bar { - background-color: #f5f5f5; - border-radius: 50px; - height: 15px; - position: relative; -} - -.skill-count1, -.skill-count2, -.skill-count3 { - font-size: 12px; - font-weight: 600; - position: absolute; - right: 0; - top: -25px; -} - -.skill1 .skill-bar:before { - background-color: #007bff; - width: 95%; -} - -.skill1 .skill-bar:before, -.skill2 .skill-bar:before { - border-radius: 50px; - bottom: 0; - content: ''; - left: 0; - position: absolute; - top: 0; -} - -.skill2 .skill-bar:before { - background-color: #dc3545; - width: 85%; -} - -.skill3 .skill-bar:before { - background-color: #ffc107; - border-radius: 50px; - bottom: 0; - content: ''; - left: 0; - position: absolute; - top: 0; - width: 75%; -} -#portfolio .portfolio-item .wrapper { - background-color: rgba(0, 0, 0, 0.7); - color: #fff; - margin-bottom: auto; - margin-top: auto; - position: absolute; - width: auto; -} -.modal-dialog { - margin: 1.75rem auto; - max-width: 800px; -} - -.modal-body { - padding: 20px 30px; -} - -.modal-content .close { - background-color: #f5f5f5; - padding: 1rem; - right: 0; - top: 0; - z-index: 1; -} - -.modal-content .close:hover { - color: #f6a50b; -} - -.modal-dialog { - max-width: 80%; -} - -.modal-header { - background-color: #f6a50b; - color: #fff; -} - -.modal-header .modal-title { - font-size: 24px; - font-weight: 600; -} - -.modal-body .project-date { - font-size: 14px; - font-weight: 700; - margin-top: 15px; -} - -.modal-body .project-description { - margin-top: 15px; -} - -.modal-body .project-description p { - margin-bottom: 5px; -} - -.modal-body .project-title-settings { - font-size: 20px; - font-weight: 600; - line-height: 24px; - margin-bottom: 0; - margin-top: 30px; -} - -.modal-body p { - font-size: 16px; - line-height: 24px; - margin: 0; -} - -.modal-body .project-description { - font-size: 16px; - line-height: 24px; - margin-bottom: 0; - margin-top: 30px; -} - -.modal-body ul { - margin-bottom: 0; - margin-top: 30px; - padding-left: 20px; -} - -.modal-body ul li { - font-size: 16px; - line-height: 24px; -} - -.modal-content { - border: none; - border-radius: 0; -} - -.modal-header { - border-bottom: none; -} - -.modal-footer { - border-top: none; -} - -.modal-content .close { - color: #222; - font-size: 28px; - font-weight: 400; - opacity: 1; - position: absolute; - right: 15px; - top: 15px; -} - -.modal-content .close:hover { - color: #4c4c4c; - opacity: 1; -} - -.modal-content .close:focus { - outline: none; -} - -.modal-dialog { - margin: 0; - max-width: none; - width: 100%; -} - -@media (min-width: 576px) { - .modal-dialog { - max-width: 750px; - } -} - -@media (min-width: 768px) { - .modal-dialog { - max-width: 970px; - } -} - -@media (min-width: 992px) { - .modal-dialog { - max-width: 1170px; - } - - .modal-content { - min-height: 600px; - } - - .modal-body { - padding: 60px 80px; - } - - .modal-body .project-title-settings { - font-size: 30px; - line-height: 36px; - margin-top: 40px; - } - - .modal-body .project-date { - font-size: 18px; - font-weight: 500; - margin-top: 10px; - } - - .modal-body .project-description { - font-size: 20px; - font-weight: 500; - line-height: 26px; - margin-top: 20px; - } - - .modal-body .project-link { - margin-top: 30px; - } - - .modal-body .project-link a { - background-color: #6c63ff; - border-color: #6c63ff; - border-radius: 5px; - color: #fff; - font-size: 20px; - font-weight: 600; - padding: 10px 25px; - text-decoration: none; - transition: all 0.3s ease; - } - - .modal-body .project-link a:hover { - background-color: #5b54d5; - border-color: #5b54d5; - transition: all 0.3s ease; - } -} -.title-styles { - font-family: Raleway, sans-serif; - font-size: 250%; -} -#about .background .transparentbox #portfolio .aboutCol { - align-items: center; - display: flex; - flex-grow: 1; - justify-content: center; - padding: 10px; - width: 50%; -} -.input-group > div { - border: 1px solid #ddd; - display: table-cell; - vertical-align: middle; -} - -.input-group-icon { - background: #eee; - color: #777; - padding: 0 12px; -} - -.input-group-area { - width: 100%; -} -.github-corner:hover .octo-arm { - -webkit-animation: octocat-wave 0.56s ease-in-out; - animation: octocat-wave 0.56s ease-in-out; -} - -@-webkit-keyframes octocat-wave { - 0%, - to { - -webkit-transform: rotate(0); - transform: rotate(0); - } - - 20%, - 60% { - -webkit-transform: rotate(-25deg); - transform: rotate(-25deg); - } - - 40%, - 80% { - -webkit-transform: rotate(10deg); - transform: rotate(10deg); - } -} - -@keyframes octocat-wave { - 0%, - to { - -webkit-transform: rotate(0); - transform: rotate(0); - } - - 20%, - 60% { - -webkit-transform: rotate(-25deg); - transform: rotate(-25deg); - } - - 40%, - 80% { - -webkit-transform: rotate(10deg); - transform: rotate(10deg); - } -} - -@media (max-width: 500px) { - .github-corner:hover .octo-arm { - -webkit-animation: none; - animation: none; - } - - .github-corner .octo-arm { - -webkit-animation: octocat-wave 0.56s ease-in-out; - animation: octocat-wave 0.56s ease-in-out; - } -} -.slider-image { - border: 5px solid #d7caaa; -} - -.slider-tab { - background-color: #d7caaa; - height: 25px; -} - -.slider-iconfiy { - margin-top: 10px; -} - -.styles_typicalWrapper__1_Uvh:after { - cursor: none !important; - display: none; -} -.polaroid span { - background: #fff; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); - display: inline-block; - margin: 55px 75px 30px; - padding: 15px 15px 30px; - position: relative; - text-align: center; - text-decoration: none; - transition: all 0.2s linear; - z-index: 0; -} - -.link-href { - color: #a3a3a3; -} - -.wave { - font-size: 160%; -} - -.font-trebuchet { - font-family: - Trebuchet MS, - Lucida Sans Unicode, - Lucida Grande, - Lucida Sans, - Arial, - sans-serif; -} - -#preview { - background-color: #ebeaf5; - margin-bottom: 15%; - padding: 15px; - position: relative; - width: 500px; -} -.modal-inside .modal-content { - background: #fff; -} - -.bars { - float: left; - padding: 0; - text-align: left; - width: 95%; -} - -.bars .skills { - list-style: none; - margin-top: 36px; -} - -.bars li { - background: #ccc; - border-radius: 3px; - height: 42px; - margin-bottom: 60px; - position: relative; -} - -.bars li em { - color: #313131; - font: - 15px opensans-bold, - sans-serif; - font-weight: 400; - letter-spacing: 2px; - position: relative; - text-transform: uppercase; - top: -36px; -} - -.bar-expand { - background: #313131; - border-radius: 3px 0 0 3px; - display: inline-block; - height: 42px; - left: 0; - line-height: 42px; - margin: 0; - padding-right: 24px; - position: absolute; - top: 0; -} - -.modal-close { - cursor: pointer; - padding: 10px 15px; - text-align: right; -} - -.close-icon { - color: #000; - font-weight: lighter !important; -} - -.modal-description { - font-size: 12px; - margin-bottom: 20px; - padding: 5px 5px 0; - text-align: justify; -} - -.awssld__next, -.awssld__prev { - outline: none !important; -} - -.loader-bar-color { - color: #000 !important; -} -.portfolio .portfolio-item .portfolio-item-caption { - background-color: rgba(51, 51, 51, 0.9); - opacity: 0; - transition: all 0.5s ease; -} - -.portfolio .portfolio-item .portfolio-item-caption:hover { - opacity: 1; -} - -.portfolio - .portfolio-item - .portfolio-item-caption - .portfolio-item-caption-content { - font-size: 1.5rem; -} - -@media (min-width: 576px) { - .portfolio .closeButtonResponsive { - display: block; - } - - .portfolio .portfolio-item { - margin-bottom: 30px; - } -} -@-webkit-keyframes cd-bounce-1 { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - } - - 60% { - opacity: 1; - -webkit-transform: scale(1.2); - } - - to { - -webkit-transform: scale(1); - } -} - -@keyframes cd-bounce-1 { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 60% { - opacity: 1; - -webkit-transform: scale(1.2); - transform: scale(1.2); - } - - to { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@-webkit-keyframes cd-bounce-2 { - 0% { - opacity: 0; - -webkit-transform: translateX(-100px); - } - - 60% { - opacity: 1; - -webkit-transform: translateX(20px); - } - - to { - -webkit-transform: translateX(0); - } -} - -@keyframes cd-bounce-2 { - 0% { - opacity: 0; - -webkit-transform: translateX(-100px); - transform: translateX(-100px); - } - - 60% { - opacity: 1; - -webkit-transform: translateX(20px); - transform: translateX(20px); - } - - to { - -webkit-transform: translateX(0); - transform: translateX(0); - } -} - -@-webkit-keyframes cd-bounce-2-inverse { - 0% { - opacity: 0; - -webkit-transform: translateX(100px); - } - - 60% { - opacity: 1; - -webkit-transform: translateX(-20px); - } - - to { - -webkit-transform: translateX(0); - } -} - -@keyframes cd-bounce-2-inverse { - 0% { - opacity: 0; - -webkit-transform: translateX(100px); - transform: translateX(100px); - } - - 60% { - opacity: 1; - -webkit-transform: translateX(-20px); - transform: translateX(-20px); - } - - to { - -webkit-transform: translateX(0); - transform: translateX(0); - } -} diff --git a/src/assets/styles/ProductListing.css b/src/assets/styles/ProductListing.css deleted file mode 100644 index e6a0a59..0000000 --- a/src/assets/styles/ProductListing.css +++ /dev/null @@ -1,24 +0,0 @@ -/* src/styles/ProductListing.css */ -.product-listing { - padding: 20px; -} - -.product-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 20px; -} - -.product-card { - border: 1px solid #ccc; - border-radius: 8px; - padding: 16px; -} - -.product-card img { - width: 100%; - height: 200px; - object-fit: cover; - border-radius: 8px; - margin-bottom: 10px; -} diff --git a/src/assets/styles/chakraStylesReset.css b/src/assets/styles/chakraStylesReset.css deleted file mode 100644 index 80fe103..0000000 --- a/src/assets/styles/chakraStylesReset.css +++ /dev/null @@ -1 +0,0 @@ -@import '@chakra-ui/css-reset'; diff --git a/src/assets/styles/reset.css b/src/assets/styles/reset.css deleted file mode 100644 index a3f7681..0000000 --- a/src/assets/styles/reset.css +++ /dev/null @@ -1,129 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, -body, -div, -span, -applet, -object, -iframe, -h1, -h2, -h3, -h4, -h5, -h6, -p, -blockquote, -pre, -a, -abbr, -acronym, -address, -big, -cite, -code, -del, -dfn, -em, -img, -ins, -kbd, -q, -s, -samp, -small, -strike, -strong, -sub, -sup, -tt, -var, -b, -u, -i, -center, -dl, -dt, -dd, -ol, -ul, -li, -fieldset, -form, -label, -legend, -table, -caption, -tbody, -tfoot, -thead, -tr, -th, -td, -article, -aside, -canvas, -details, -embed, -figure, -figcaption, -footer, -header, -hgroup, -menu, -nav, -output, -ruby, -section, -summary, -time, -mark, -audio, -video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -menu, -nav, -section { - display: block; -} -body { - line-height: 1; -} -ol, -ul { - list-style: none; -} -blockquote, -q { - quotes: none; -} -blockquote:before, -blockquote:after, -q:before, -q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} diff --git a/src/assets/styles/themes.js b/src/assets/styles/themes.js deleted file mode 100644 index da1f593..0000000 --- a/src/assets/styles/themes.js +++ /dev/null @@ -1,29 +0,0 @@ -import { createTheme } from '@mui/material/styles'; - -const theme = createTheme({ - typography: { - fontFamily: "'Bebas Neue', sans-serif", - }, - breakpoints: { - values: { - xs: 0, - sm: 600, - md: 950, // Set md breakpoint to 950px - lg: 1280, - xl: 1920, - }, - }, - palette: { - background: { - default: '#fafafa', // You can set your default background color here - }, - primary: { - main: '#1976d2', // Add the main property with the desired color value - contrastText: '#ffffff', // You can set your contrast text color here - spacing: 4, // You can set your spacing scale here - }, - }, - spacing: 4, // You can set your spacing scale here -}); - -export default theme; diff --git a/src/themeSettings.jsx b/src/assets/themeSettings.jsx similarity index 62% rename from src/themeSettings.jsx rename to src/assets/themeSettings.jsx index 9814311..23cf87f 100644 --- a/src/themeSettings.jsx +++ b/src/assets/themeSettings.jsx @@ -1,4 +1,3 @@ -import { grey } from '@mui/material/colors'; import { tokens } from './tokens'; export const themeSettings = (mode) => { @@ -9,9 +8,24 @@ export const themeSettings = (mode) => { palette: { mode: mode, primary: { - main: mode === 'dark' ? colors.primary[500] : colors.primary[100], + // main: mode === 'dark' ? colors.primary[500] : colors.primary[100], + main: colors.greenAccent[500], + light: colors.greenAccent[200], + dark: colors.greenAccent[600], + default: colors.grey[700], + // contrastText: '#fff', contrastText: mode === 'dark' ? '#ffffff' : '#000000', + + // main: colors.greenAccent[500], }, + + chartColors: [ + colors.greenAccent[500], + colors.greenAccent[400], + colors.greenAccent[300], + colors.greenAccent[200], + colors.greenAccent[100], + ], primaryLight: { main: colors.primary[300], }, @@ -23,25 +37,39 @@ export const themeSettings = (mode) => { black: colors.grey[900], }, secondary: { - main: colors.greenAccent[500], + // main: colors.greenAccent[500], + main: colors.greenAccent[200], + light: colors.greenAccent[100], + dark: colors.greenAccent[400], + contrastText: '#000', }, neutral: { dark: colors.grey[700], - main: colors.grey[500], + main: colors.primary[300], light: colors.grey[100], }, background: { - default: mode === 'dark' ? '#121212' : '#ffffff', - paper: mode === 'dark' ? colors.grey[300] : '#ffffff', + main: '#333', + dark: '#222', + secondary: '#444', + tertiary: '#555', + quaternary: '#666', + quinternary: '#999', + default: mode === 'dark' ? '#777' : '#ffffff', + paper: colors.greenAccent[100], + // paper: mode === 'dark' ? colors.grey[300] : '#ffffff', }, error: { main: colors.redAccent[500], + dark: colors.redAccent[700], }, warning: { main: colors.redAccent[500], }, success: { + light: colors.greenAccent[100], main: colors.greenAccent[500], + dark: colors.greenAccent[200], }, info: { main: colors.blueAccent[500], @@ -55,6 +83,41 @@ export const themeSettings = (mode) => { hover: mode === 'dark' ? colors.grey[800] : colors.grey[200], }, }, + // chart: { + // palette: { + // primary: { + // main: colors.greenAccent[500], + // light: colors.greenAccent[300], + // dark: colors.greenAccent[700], + // contrastText: '#fff', + // }, + // secondary: { + // main: colors.greenAccent[200], + // light: colors.greenAccent[100], + // dark: colors.greenAccent[400], + // contrastText: '#000', + // }, + // background: { + // paper: colors.greenAccent[100], + // default: colors.greenAccent[200], + // }, + // text: { + // primary: colors.greenAccent[800], + // secondary: colors.greenAccent[600], + // }, + // chartColors: [ + // colors.greenAccent[500], + // colors.greenAccent[400], + // colors.greenAccent[300], + // colors.greenAccent[200], + // colors.greenAccent[100], + // ], + // }, + // typography: { + // // Define typography styles if needed + // }, + // }, + spacing: (factor) => `${0.25 * factor}rem`, shape: { borderRadius: 4, diff --git a/src/tokens.jsx b/src/assets/tokens.jsx similarity index 100% rename from src/tokens.jsx rename to src/assets/tokens.jsx diff --git a/src/components/buttons/actionButtons/CardActionButtons.jsx b/src/components/buttons/actionButtons/CardActionButtons.jsx index 0afd4bc..ea451a0 100644 --- a/src/components/buttons/actionButtons/CardActionButtons.jsx +++ b/src/components/buttons/actionButtons/CardActionButtons.jsx @@ -1,97 +1,171 @@ import React, { useCallback } from 'react'; -import { Button, Grid } from '@mui/material'; +import { Box, Button, Grid } from '@mui/material'; import { useStyles } from '../buttonStyles'; import useAppContext from '../../../context/hooks/useAppContext'; +import { useMode } from '../../../context/hooks/colormode'; +import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; +import Logger from '../../grids/collectionGrids/Logger'; +import { useDeckStore } from '../../../context/DeckContext/DeckContext'; +import { useCartStore } from '../../../context/CartContext/CartContext'; +const cardOtherLogger = new Logger([ + 'Action', + 'Card Name', + 'Quantity', + 'Total Price', +]); +const CardActionButtons = ({ card, context, closeModal }) => { + const { theme } = useMode(); // Using the theme hook -const CardActionButtons = ({ card, quantity, context, closeModal }) => { const classes = useStyles(); - const [contextProps, isContextAvailable] = useAppContext(context); - - if (!isContextAvailable) { - console.error(`The component isn't wrapped with the ${context}Provider`); - return null; // Consider rendering an error boundary or a user-friendly error message instead. - } + const { contextProps, isContextAvailable } = useAppContext(context); // Changed to object destructuring + const { addOneToCollection, removeOneFromCollection } = useCollectionStore(); + const { addToDeck, removeFromDeck } = useDeckStore(); + const { addToCart, removeFromCart } = useCartStore(); + // const { totalQuantity } = context[contextProps?.totalQuantity]; + // const { totalQuantity } = useCollectionStore(); + // if (!isContextAvailable || !contextProps) { + // console.error(`The component isn't wrapped with the ${context}Provider`); + // return Context not available; + // } + const styles = { + box: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: theme.spacing(2), // Using theme spacing + backgroundColor: theme.palette.background.paper, // Using theme background color + }, + button: { + margin: theme.spacing(1), // Using theme spacing + color: theme.palette.background.primary, // Using theme text color + backgroundColor: theme.palette.success.main, // Using theme background color + }, + gridItem: { + textAlign: 'center', + }, + }; const ADD = 'add'; const REMOVE_ONE = 'removeOne'; const REMOVE_ALL = 'removeAll'; + // const performAction = useCallback( + // (action, card) => { + // console.log( + // `action --> ${action}`, + // `payload --> ${card}`, + // `context --> ${context}` + // ); + // try { + // if (context === 'Collection') { + // if (action === ADD) { + // addOneToCollection(card, card.id); + // cardOtherLogger.logCardAction('Add Card', card); + // } else if (action === REMOVE_ONE) { + // removeOneFromCollection(card, card.id); + // cardOtherLogger.logCardAction('Remove Card', card); + // } + // } + // // Additional context handling can be implemented here + // } catch (error) { + // console.error( + // `Error performing action '${action}' with payload`, + // card, + // error + // ); + // } + // }, + // [context, addOneToCollection, removeOneFromCollection, cardOtherLogger] + // ); - // Action handler const performAction = useCallback( - (action, payload) => { - console.log(`action --> ${action}`, `payload --> ${payload}`); + (action, card) => { try { - switch (action) { - case ADD: - contextProps[context][`addOneTo${context}`](payload); + switch (context) { + case 'Collection': + if (action === 'add') { + addOneToCollection(card, card.id); + } else if (action === 'removeOne') { + removeOneFromCollection(card, card.id); + } break; - case REMOVE_ONE: - contextProps[context].removeOne(payload); + case 'Deck': + if (action === 'add') { + addToDeck(card, card.id); + } else if (action === 'removeOne') { + removeFromDeck(card, card.id); + } break; - case REMOVE_ALL: - contextProps[context].removeAll(payload); + case 'Cart': + if (action === 'add') { + addToCart(card, card.id); + } else if (action === 'removeOne') { + removeFromCart(card, card.id); + } break; default: - console.error(`Unhandled action type: ${action}`); + console.error(`Unhandled context: ${context}`); } + cardOtherLogger.logCardAction(`${action} Card`, card); } catch (error) { console.error( `Error performing action '${action}' with payload`, - payload, + card, error ); } }, - [context, contextProps] + [ + addOneToCollection, + removeOneFromCollection, + addToDeck, + removeFromDeck, + addToCart, + removeFromCart, + context, + ] ); const handleAddClick = () => { + console.log('handleAddClick', card); performAction(ADD, card); - closeModal(); + closeModal?.(); + }; + + const handleRemoveOne = () => { + performAction(REMOVE_ONE, card); + closeModal?.(); }; - const handleClickOutside = () => { - if (quantity === 0) { - closeModal(); - } + const handleRemoveAll = () => { + performAction(REMOVE_ALL, card); + closeModal?.(); }; return ( -
- {card.quantity > 0 ? ( - <> - - - {`In ${context}: `} {card.quantity} - - - - - - - + - - ) : ( - - )} -
+
+
+ +
); }; diff --git a/src/components/buttons/actionButtons/GenericActionButtons.jsx b/src/components/buttons/actionButtons/GenericActionButtons.jsx index ff7a5dc..820ecc4 100644 --- a/src/components/buttons/actionButtons/GenericActionButtons.jsx +++ b/src/components/buttons/actionButtons/GenericActionButtons.jsx @@ -1,63 +1,118 @@ -import React, { useEffect } from 'react'; -import { makeStyles } from '@mui/styles'; -import GenericCardModal from '../../modals/GenericCardModal'; +import React, { useContext } from 'react'; import CardActionButtons from './CardActionButtons'; import useAppContext from '../../../context/hooks/useAppContext'; +import { ModalContext } from '../../../context/ModalContext/ModalContext'; +import { Box } from '@mui/material'; -const useStyles = makeStyles({ - root: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-evenly', - height: '100%', - }, -}); - -const GenericActionButtons = ({ - card, - context, - open, - openModal, - closeModal, - isModalOpen, - setModalOpen, -}) => { - const classes = useStyles(); - const [contextProps, isContextAvailable] = useAppContext(context); - - if (!isContextAvailable) { - console.error(`The component isn't wrapped with the ${context}Provider`); - return null; // Consider rendering an error boundary or user-friendly error message instead. +const GenericActionButtons = ({ card, context }) => { + const contextProps = useAppContext(); // Assuming useAppContext returns the context object + const { closeModal, isModalOpen, setModalOpen } = useContext(ModalContext); + + if (!contextProps) { + return ( + Provider not found for {context} + ); } - // Ensure contextProps is an object with the expected methods before using them - if ( - typeof contextProps !== 'object' || - typeof contextProps[context] !== 'object' - ) { - console.error(`Invalid contextProps provided for the context: ${context}`); - return null; // Consider rendering an error boundary or user-friendly error message instead. + let modifiedContextProps = contextProps[context]; + + if (!modifiedContextProps) { + return ( + + Invalid context provided: {context} + + ); } return ( -
+ setModalOpen(false)} - open={open} - /> - {/* */} -
+ /> +
); }; export default GenericActionButtons; + +// import React, { useContext, useEffect } from 'react'; +// import { makeStyles } from '@mui/styles'; +// import GenericCardModal from '../../modals/GenericCardModal'; +// import CardActionButtons from './CardActionButtons'; +// import useAppContext from '../../../context/hooks/useAppContext'; +// import { ModalContext } from '../../../context/ModalContext/ModalContext'; + +// const useStyles = makeStyles({ +// root: { +// display: 'flex', +// flexDirection: 'column', +// justifyContent: 'space-evenly', +// height: '100%', +// }, +// }); + +// const GenericActionButtons = ({ +// card, +// context, +// open, +// openModal, +// // closeModal, +// // isModalOpen, +// // setModalOpen, +// }) => { +// const classes = useStyles(); +// const [contextProps, isContextAvailable] = useAppContext(context); +// const { +// openModalWithCard, +// closeModal, +// isModalOpen, +// setModalOpen, +// modalContent, +// } = useContext(ModalContext); +// if (!isContextAvailable) { +// console.error(`The component isn't wrapped with the ${context}Provider`); +// return null; // Consider rendering an error boundary or user-friendly error message instead. +// } + +// // Ensure contextProps is an object with the expected methods before using them +// if ( +// typeof contextProps !== 'object' || +// typeof contextProps[context] !== 'object' +// ) { +// console.error(`Invalid contextProps provided for the context: ${context}`); +// return null; // Consider rendering an error boundary or user-friendly error message instead. +// } + +// return ( +//
+// setModalOpen(false)} +// open={open} +// /> +// {/* */} +//
+// ); +// }; + +// export default GenericActionButtons; diff --git a/src/components/cards/CardToolTip.jsx b/src/components/cards/CardToolTip.jsx index 47585b9..14f9b48 100644 --- a/src/components/cards/CardToolTip.jsx +++ b/src/components/cards/CardToolTip.jsx @@ -2,25 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { makeStyles } from '@mui/styles'; import { Tooltip } from '@mui/material'; - -const useStyles = makeStyles((theme) => ({ - tooltip: { - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - overflow: 'hidden', - }, - tooltipTitle: { - marginBottom: theme.spacing(1), - }, - attributeSpan: { - display: 'block', - marginBottom: theme.spacing(0.5), - }, - descriptionSpan: { - marginTop: theme.spacing(1), - }, -})); +import { useStyles } from './cardStyles'; const formatKey = (key) => { return key diff --git a/src/components/cards/GenericCard.jsx b/src/components/cards/GenericCard.jsx index 9bbcae6..18b2292 100644 --- a/src/components/cards/GenericCard.jsx +++ b/src/components/cards/GenericCard.jsx @@ -1,108 +1,216 @@ -import React, { useEffect } from 'react'; -import { Card, CardContent, CardActions, Typography } from '@mui/material'; +import React, { useContext, useEffect } from 'react'; +import { + Card, + CardContent, + CardActions, + Typography, + styled, +} from '@mui/material'; import CardMediaSection from '../media/CardMediaSection'; import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons'; import placeholderImage from '../../assets/images/placeholder.jpeg'; -import { useStyles } from './cardStyles'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import { ModalContext } from '../../context/ModalContext/ModalContext'; +import { PopoverContext } from '../../context/PopoverContext/PopoverContext'; + +const AspectRatioBox = styled('div')(({ theme }) => ({ + width: '100%', // Full width of the parent container + // paddingTop: '2%', // Aspect ratio of 16:9 + position: 'relative', +})); + +const StyledCard = styled(Card)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + maxWidth: '100%', // Ensure it doesn't exceed the parent width + width: 'auto', // Adjust if needed + maxHeight: '80vh', // Max height based on the viewport height + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + transition: 'transform 0.3s ease-in-out', + '&:hover': { + transform: 'scale(1.03)', + }, +})); const GenericCard = React.forwardRef((props, ref) => { - const { - card, - context, - isModalOpen, - setModalOpen, - hoveredCard, - setHoveredCard, - setIsPopoverOpen, - setClickedCard, - } = props; - const classes = useStyles(); + const { card, context, setClickedCard } = props; const { selectedCollection } = useCollectionStore(); - const requiresDoubleButtons = context === 'Deck' || context === 'Collection'; // Added this line - const checkCardInCollection = () => { - if (selectedCollection) { - const cardIds = selectedCollection?.cards?.map((card) => card.id); - return cardIds?.includes(card.id); - } - return false; - }; + const { openModalWithCard, setModalOpen } = useContext(ModalContext); + const { setHoveredCard, setIsPopoverOpen, hoveredCard } = + useContext(PopoverContext); const handleClick = () => { - const cardIsInCollection = checkCardInCollection(); - console.log('Modal opened with card:', card); - console.log('Card is in collection:', cardIsInCollection); - + openModalWithCard(card); setModalOpen(true); setIsPopoverOpen(false); }; - // Function to handle hover interactions with the card const handleInteraction = (hoverState) => { - if (!isModalOpen) { - setHoveredCard((prev) => (hoverState ? card : null)); + if (!hoverState) { + setHoveredCard(hoverState ? card : null); setIsPopoverOpen(hoverState); } }; - // Effect to close popover when modal is open or reactivate when modal closes useEffect(() => { - setIsPopoverOpen(isModalOpen ? false : hoveredCard === card); - if (isModalOpen) { - setHoveredCard(null); - } - }, [isModalOpen, hoveredCard, card, setIsPopoverOpen, setHoveredCard]); + setIsPopoverOpen(hoveredCard === card); + }, [hoveredCard, card, setIsPopoverOpen]); - // Get the card image URL, or use placeholder if not available const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; const price = `Price: ${card?.card_prices?.[0]?.tcgplayer_price || 'N/A'}`; - console.log(typeof handleInteraction); // Should log 'function' return ( - - - + + + + + + {card?.name} {price} - - {/* Conditionally render action buttons based on context */} - {requiresDoubleButtons ? ( - <> - {/* */} - - - ) : ( - - )} + + - + ); }); GenericCard.displayName = 'GenericCard'; export default GenericCard; + +// import React, { useContext, useEffect } from 'react'; +// import { Card, CardContent, CardActions, Typography } from '@mui/material'; +// import CardMediaSection from '../media/CardMediaSection'; +// import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons'; +// import placeholderImage from '../../assets/images/placeholder.jpeg'; +// import { useStyles } from './cardStyles'; +// import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +// import { ModalContext } from '../../context/ModalContext/ModalContext'; +// import { PopoverContext } from '../../context/PopoverContext/PopoverContext'; + +// const GenericCard = React.forwardRef((props, ref) => { +// const { +// card, +// context, +// // isModalOpen, +// // setModalOpen, +// // hoveredCard, +// // setHoveredCard, +// // setIsPopoverOpen, +// setClickedCard, +// } = props; +// const classes = useStyles(); +// const { selectedCollection } = useCollectionStore(); +// const { +// openModalWithCard, +// closeModal, +// isModalOpen, +// setModalOpen, +// modalContent, +// } = useContext(ModalContext); +// const { setHoveredCard, setIsPopoverOpen, hoveredCard } = +// useContext(PopoverContext); + +// const requiresDoubleButtons = context === 'Deck' || context === 'Collection'; // Added this line +// const checkCardInCollection = () => { +// if (selectedCollection) { +// const cardIds = selectedCollection?.cards?.map((card) => card.id); +// return cardIds?.includes(card.id); +// } +// return false; +// }; + +// const handleClick = () => { +// const cardIsInCollection = checkCardInCollection(); +// console.log('Modal opened with card:', card); +// openModalWithCard(card); +// console.log('Card is in collection:', cardIsInCollection); + +// setModalOpen(true); +// setIsPopoverOpen(false); +// }; +// // setIsPopoverOpen(false); + +// // Function to handle hover interactions with the card +// // const handleInteraction = (hoverState) => { +// // if (!isModalOpen) { +// // setHoveredCard((prev) => (hoverState ? card : null)); +// // setIsPopoverOpen(hoverState); +// // } +// // }; +// const handleInteraction = (hoverState) => { +// if (!isModalOpen) { +// setHoveredCard(hoverState ? card : null); +// setIsPopoverOpen(hoverState); +// } +// }; +// // Effect to close popover when modal is open or reactivate when modal closes +// useEffect(() => { +// setIsPopoverOpen(isModalOpen ? false : hoveredCard === card); +// if (isModalOpen) { +// setHoveredCard(null); +// } +// }, [isModalOpen, hoveredCard, card, setIsPopoverOpen, setHoveredCard]); + +// // Get the card image URL, or use placeholder if not available +// const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; +// const price = `Price: ${card?.card_prices?.[0]?.tcgplayer_price || 'N/A'}`; +// console.log(typeof handleInteraction); // Should log 'function' + +// return ( +// +// +// +// {card?.name} +// +// {price} +// +// +// +// {requiresDoubleButtons ? ( +// <> +// +// {/* TODO fix card to display buttons for both collections and decks */} +// +// ) : ( +// +// )} +// +// +// ); +// }); + +// GenericCard.displayName = 'GenericCard'; + +// export default GenericCard; diff --git a/src/components/cards/cardStyles.jsx b/src/components/cards/cardStyles.jsx index fb39acc..95af636 100644 --- a/src/components/cards/cardStyles.jsx +++ b/src/components/cards/cardStyles.jsx @@ -41,6 +41,22 @@ export const useStyles = makeStyles((theme) => ({ justifyContent: 'center', // centers the buttons width: '100%', }, + tooltip: { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + overflow: 'hidden', + }, + tooltipTitle: { + marginBottom: theme.spacing(1), + }, + attributeSpan: { + display: 'block', + marginBottom: theme.spacing(0.5), + }, + descriptionSpan: { + marginTop: theme.spacing(1), + }, })); // import { makeStyles } from '@mui/styles'; diff --git a/src/components/chart/LinearChart.js b/src/components/chart/LinearChart.js index 054239e..d4c4be3 100644 --- a/src/components/chart/LinearChart.js +++ b/src/components/chart/LinearChart.js @@ -1,102 +1,93 @@ import React, { useState, useMemo, useCallback } from 'react'; import { ResponsiveLine } from '@nivo/line'; -import { makeStyles, useTheme } from '@mui/styles'; -import Typography from '@mui/material/Typography'; -import Box from '@mui/material/Box'; -import Tooltip from '@mui/material/Tooltip'; +import { Typography, Box, Tooltip, useTheme } from '@mui/material'; +import { styled } from '@mui/material/styles'; import ChartErrorBoundary from './ChartErrorBoundary'; -// import CustomLogger from '../../context/CutstomLogger'; -import { - formatDateToString, - useEventHandlers, - getFilteredData, - getTickValues, - getAveragedData, - CustomTooltip, -} from './chartUtils'; +import { tokens } from '../../assets/tokens'; +import { useMode } from '../../context/hooks/colormode'; -const useStyles = makeStyles((theme) => ({ - chartContainer: { - position: 'relative', - width: '100%', - height: '100%', - }, - loadingContainer: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100%', - }, - xAxisLabel: { - position: 'absolute', - bottom: 0, - left: '50%', - transform: 'translateX(-50%)', - textAlign: 'center', - marginTop: theme.spacing(2), - fontSize: '1rem', - fontWeight: 'bold', - marginBottom: theme.spacing(4), - color: theme.palette.text.primary, - }, - yAxisLabel: { - position: 'absolute', - top: '50%', - left: 0, - transform: 'translateY(-50%) rotate(-90deg)', - textAlign: 'center', - marginTop: theme.spacing(2), - fontSize: '1rem', - fontWeight: 'bold', // Make the label text bold - marginLeft: theme.spacing(4), // Increase spacing from the chart - color: theme.palette.text.primary, - }, - customTooltip: { - borderRadius: theme.shape.borderRadius, - padding: theme.spacing(1), - backgroundColor: theme.palette.background.paper, - boxShadow: theme.shadows[3], - }, - customGridLine: { - stroke: theme.palette.text.secondary, - strokeWidth: 2, - strokeDasharray: 'none', - }, +const AxisLabel = styled(Typography)(({ theme, axis }) => ({ + position: 'absolute', + [axis === 'x' ? 'bottom' : 'top']: 0, + [axis === 'x' ? 'left' : 'right']: '50%', + transform: + axis === 'x' ? 'translateX(-50%)' : 'translateY(-50%) rotate(-90deg)', + textAlign: 'center', + margin: theme.spacing(2), + fontSize: '1rem', + fontWeight: 'bold', + color: theme.palette.text.primary, })); -// function logReadableChartInfo( -// // chartDimensions, -// dataForChart, -// datesTimesValues, -// filteredChartData, -// latestData -// ) { -// console.log('[7][DATA FOR CHART]:', JSON.stringify(dataForChart, null, 2)); -// console.log( -// '[8][DATES TIMES VALUES]:', -// JSON.stringify(datesTimesValues, null, 2) -// ); -// console.log( -// '[4][FILTERED CHART DATA]:', -// JSON.stringify(filteredChartData, null, 2) -// ); -// console.log('[5][LATEST DATA]:', JSON.stringify(latestData, null, 2)); -// } +const CustomTooltip = ({ point }) => { + const theme = useTheme(); + const { serieId, data: { label, xFormatted, yFormatted } = {} } = point; + return ( + + + + {`Card: ${label}`} + + + {`Time: ${new Date(xFormatted).toLocaleString()}`} + + + {`Value: $${parseFloat(yFormatted).toFixed(2)}`} + + + + ); +}; + +export const useEventHandlers = () => { + const [hoveredData, setHoveredData] = useState(null); + const handleMouseMove = useCallback((point) => { + setHoveredData(point ? { x: point.data.x, y: point.data.y } : null); + }, []); + const handleMouseLeave = useCallback(() => setHoveredData(null), []); + return { hoveredData, handleMouseMove, handleMouseLeave }; +}; const LinearChart = ({ filteredChartData, - datesTimesValues, nivoReadyData, - latestData, dimensions, - timeRanges, timeRange, }) => { - const theme = useTheme(); + const { theme } = useMode(); + // const colors = tokens(theme.palette.mode); + const greenAccent = { + 100: '#dbf5ee', + 200: '#b7ebde', + 300: '#94e2cd', + 400: '#70d8bd', + 500: '#4cceac', + 600: '#3da58a', + 700: '#2e7c67', + 800: '#1e5245', + 900: '#0f2922', + }; const [isZoomed, setIsZoomed] = useState(false); - const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); - const [format, setFormat] = useState('0,0'); - // Calculate tickValues and xFormat based on timeRange + const { handleMouseMove, handleMouseLeave } = useEventHandlers(); + + // const { tickValues, xFormat } = useMemo(() => { + // const timeFormats = { + // '2 hours': { format: '%H:%M', ticks: 'every 15 minutes' }, + // '24 hours': { format: '%H:%M', ticks: 'every 1 hour' }, + // '7 days': { format: '%b %d', ticks: 'every 1 day' }, + // '1 month': { format: '%b %d', ticks: 'every 3 days' }, + // default: { format: '%b %d', ticks: 'every 1 day' }, + // }; + // return timeFormats[timeRange] || timeFormats.default; + // }, [timeRange]); + const { tickValues, xFormat } = useMemo(() => { let format, ticks; switch (timeRange) { @@ -133,16 +124,18 @@ const LinearChart = ({ ); } + const chartProps = { + // theme: theme.chart, margin: { top: 50, right: 110, bottom: 50, left: 60 }, - // data: [{ id: 'Data', data: dataForChart }], data: nivoReadyData, animate: true, motionStiffness: 90, motionDamping: 15, + background: '#2c2121', + xScale: { type: 'time', - // format: '%Y-%m-%d %H:%M:%S', format: '%Y-%m-%dT%H:%M:%S.%LZ', useUTC: false, precision: 'second', @@ -150,15 +143,12 @@ const LinearChart = ({ xFormat: 'time:%Y-%m-%d %H:%M:%S', axisBottom: { tickRotation: 0, - legendOffset: -12, + legendOffset: -24, legend: 'Time', tickPadding: 10, - tickSize: 10, - // format: '%b %d', - // tickValues: 'every 2 days', + tickSize: 1, format: xFormat, tickValues: tickValues, - // tickValues: tickValues, }, yScale: { type: 'linear', min: 'auto', max: 'auto' }, @@ -171,14 +161,60 @@ const LinearChart = ({ tickPadding: 10, tickSize: 10, }, - pointSize: 6, - pointBorderWidth: 1, - pointColor: theme.palette.primary.main, - colors: theme.palette.chartColors, - theme: theme.chart, + pointSize: 8, + pointBorderWidth: 2, + + pointColor: theme.palette.success.light, + colors: theme.palette.primaryDark.main, lineWidth: 3, curve: 'monotoneX', useMesh: true, + theme: { + chart: { + axis: { + domain: { + line: { + stroke: greenAccent[800], + strokeWidth: 1, + }, + }, + ticks: { + line: { + stroke: greenAccent[700], + strokeWidth: 1, + }, + text: { + fill: greenAccent[900], + fontSize: 12, + }, + }, + }, + grid: { + line: { + stroke: greenAccent[200], + strokeWidth: 1, + }, + }, + legends: { + text: { + fill: greenAccent[800], + fontSize: 12, + }, + }, + tooltip: { + container: { + background: greenAccent[100], + color: greenAccent[800], + fontSize: 12, + borderRadius: 4, + boxShadow: '0 2px 4px rgba(0,0,0,0.25)', + }, + }, + points: { + borderColor: greenAccent[800], + }, + }, + }, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, onClick: () => setIsZoomed(!isZoomed), @@ -187,14 +223,16 @@ const LinearChart = ({ // const point = slice.points.find( // (p) => p.id === 'Data' && p.data.x === latestData.x // ); - // return point ? : null; - // }, + // xFormat, + // tickValues, }; return (
+ {/* Time + Value ($) */}
); diff --git a/src/components/chart/PortfolioChart.jsx b/src/components/chart/PortfolioChart.jsx index d120048..ccaa994 100644 --- a/src/components/chart/PortfolioChart.jsx +++ b/src/components/chart/PortfolioChart.jsx @@ -8,7 +8,7 @@ import { useCombinedContext } from '../../context/CombinedProvider'; import debounce from 'lodash/debounce'; import { convertDataForNivo2, - getFilteredData2, + getUniqueValidData, groupAndAverageData, } from './chartUtils'; @@ -28,48 +28,55 @@ const ChartPaper = styled(Paper)(({ theme }) => ({ margin: theme.spacing(2, 0), })); +function handleThresholdUpdate(lastUpdateTime, setLastUpdateTime) { + const currentTime = new Date().getTime(); + if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { + // 10 minutes + setLastUpdateTime(currentTime); + return currentTime; + } + return lastUpdateTime; +} +const getFilteredData2 = (collection) => { + if (!collection) { + console.error('Invalid input: selectedCollection should not be null'); + return []; + } + return getUniqueValidData(collection.currentChartDataSets2 || []); +}; const PortfolioChart = () => { const theme = useTheme(); const { latestData, setLatestData, timeRange } = useChartContext(); const [lastUpdateTime, setLastUpdateTime] = useState(null); const chartContainerRef = useRef(null); - // const [nivoReadyData2, setNivoReadyData2] = useState(null); // Declare state for nivoReadyData2 - const [chartDimensions, setChartDimensions] = useState({ width: 0, height: 0, }); const { selectedCollection } = useCollectionStore(); - const filteredChartData2 = getFilteredData2(selectedCollection); - // let nivoReadyData2 = []; - // const rawData2 = useMemo( - // () => groupAndAverageData(filteredChartData2, threshold), - // [filteredChartData2, threshold] - // ); - - const handleThresholdUpdate = () => { - const currentTime = new Date().getTime(); - if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { - // 10 minutes - setLastUpdateTime(currentTime); - return currentTime; - } - return lastUpdateTime; - }; - - const threshold = handleThresholdUpdate(); + const threshold = useMemo( + () => handleThresholdUpdate(lastUpdateTime, setLastUpdateTime), + [lastUpdateTime] + ); + const filteredChartData2 = useMemo( + () => getFilteredData2(selectedCollection), + [selectedCollection] + ); const rawData2 = useMemo( () => groupAndAverageData(filteredChartData2, threshold), [filteredChartData2, threshold] ); - const nivoReadyData2 = useMemo( () => convertDataForNivo2(rawData2), [rawData2] ); - const HEIGHT_TO_WIDTH_RATIO = 2 / 3; + console.log('Selected Collection:', selectedCollection); + console.log('Filtered Chart Data:', filteredChartData2); + console.log('Raw Data:', rawData2); + console.log('Nivo Ready Data:', nivoReadyData2); + const HEIGHT_TO_WIDTH_RATIO = 7 / 10; useEffect(() => { const handleResize = debounce(() => { @@ -98,19 +105,32 @@ const PortfolioChart = () => { alignItems: 'center', padding: theme.spacing(2), gap: theme.spacing(2), - backgroundColor: theme.palette.background.paper, - color: theme.palette.text.primary, + // backgroundColor: theme.palette.background.paper, + background: '#333', // Darker background for Paper + + color: '#fff', // White text color + border: '1px solid #555', + borderRadius: 2, + // color: theme.palette.text.primary, }} > - + - {filteredChartData2.length > 0 ? ( + {filteredChartData2?.length > 0 && rawData2?.length > 0 ? ( { @@ -49,16 +49,7 @@ export const getUniqueValidData = (currentChartData) => { y: entry.y, })); }; -export const getFilteredData2 = (selectedCollection) => { - const filteredChartData2 = useMemo(() => { - // const { selectedCollection } = useCollectionStore(); - const allXYValues2 = selectedCollection?.currentChartDataSets2; - // console.log('ALL XY VALUES:', allXYValues2); - return allXYValues2 ? getUniqueValidData(allXYValues2) : []; - }, [selectedCollection]); - - return filteredChartData2; -}; + // export const groupAndAverageData = (data, threshold = 600000) => { // // 10 minutes in milliseconds // if (!data || data.length === 0) return { dates: [], times: [], values: [] }; @@ -194,65 +185,65 @@ export const getTickValues = (timeRange) => { return mapping[timeRange] || 'every day'; // Default to 'every week' if no match }; -export const useMovingAverage = (data, numberOfPricePoints) => { - return useMemo(() => { - if (!Array.isArray(data)) { - return []; - } - console.log('[1][Data]----------> [', data + ']'); - console.log( - '[2][NUMBER OF POINTS]----------> [', - numberOfPricePoints + ']' - ); - - return data.map((row, index, total) => { - const start = Math.max(0, index - numberOfPricePoints); - const end = index; - const subset = total.slice(start, end + 1); - const sum = subset.reduce((a, b) => a + b.y, 0); - return { - // x: String(row.x), - x: row.x, - y: sum / subset.length || 0, - }; - }); - }, [data, numberOfPricePoints]); -}; +// export const useMovingAverage = (data, numberOfPricePoints) => { +// return useMemo(() => { +// if (!Array.isArray(data)) { +// return []; +// } +// console.log('[1][Data]----------> [', data + ']'); +// console.log( +// '[2][NUMBER OF POINTS]----------> [', +// numberOfPricePoints + ']' +// ); -export const convertDataForNivo = ({ dates, times, values }) => { - if ( - !dates || - !times || - !values || - dates.length !== times.length || - dates.length !== values.length - ) { - console.error('Invalid or mismatched data arrays provided'); - return []; - } +// return data.map((row, index, total) => { +// const start = Math.max(0, index - numberOfPricePoints); +// const end = index; +// const subset = total.slice(start, end + 1); +// const sum = subset.reduce((a, b) => a + b.y, 0); +// return { +// // x: String(row.x), +// x: row.x, +// y: sum / subset.length || 0, +// }; +// }); +// }, [data, numberOfPricePoints]); +// }; - // Assuming that dates, times, and values arrays are of equal length - const nivoData = dates.map((date, index) => { - const dateTime = new Date(`${date} ${times[index]}`); - // Nivo chart requires the date to be either a Date object or an ISO date string - return { - x: dateTime.toISOString(), - y: values[index], - }; - }); +// export const convertDataForNivo = ({ dates, times, values }) => { +// if ( +// !dates || +// !times || +// !values || +// dates.length !== times.length || +// dates.length !== values.length +// ) { +// console.error('Invalid or mismatched data arrays provided'); +// return []; +// } - // Wrapping the data for a single series, you can add more series similarly - return [ - { - id: 'Averaged Data', - data: nivoData, - }, - ]; -}; +// // Assuming that dates, times, and values arrays are of equal length +// const nivoData = dates.map((date, index) => { +// const dateTime = new Date(`${date} ${times[index]}`); +// // Nivo chart requires the date to be either a Date object or an ISO date string +// return { +// x: dateTime.toISOString(), +// y: values[index], +// }; +// }); + +// Wrapping the data for a single series, you can add more series similarly +// return [ +// { +// id: 'Averaged Data', +// data: nivoData, +// }, +// ]; +// }; export const convertDataForNivo2 = (rawData2) => { - if (!Array.isArray(rawData2) || rawData2.length === 0) { - console.error('Invalid or empty rawData provided'); + if (!Array.isArray(rawData2) || rawData2?.length === 0) { + console.error('Invalid or empty rawData provided', rawData2); return []; } @@ -268,50 +259,12 @@ export const convertDataForNivo2 = (rawData2) => { return [ { id: 'Averaged Data', - color: 'hsl(0, 100%, 50%)', + color: '#4cceac', data: nivoData, }, ]; }; -export const CustomTooltip = ({ point }) => { - const theme = useTheme(); - const { serieId, data } = point; - const { label, xFormatted, yFormatted } = data || {}; - const series = { - type: { - Collection: 'Collection', - Card: 'Card', - Deck: 'Deck', - }, - }; - return ( - - - {`Series: ${serieId}`} - - {series.type[label] === 'Card' && ( - - {`Card: ${label}`} - - )} - - {`Time: ${new Date(xFormatted).toLocaleString()}`} - - - {`Value: $${parseFloat(yFormatted).toFixed(2)}`} - - - ); -}; - const roundToNearestTenth = (value) => { return Math.round(value * 10) / 10; }; @@ -330,25 +283,6 @@ export const getFilteredData = (data, timeRange) => { .map((d) => ({ ...d, y: roundToNearestTenth(d.y) })); }; -export const useEventHandlers = () => { - const [hoveredData, setHoveredData] = useState(null); - - const handleMouseMove = useCallback((point) => { - if (point) { - setHoveredData({ - x: point.data.x, - y: point.data.y, - }); - } - }, []); - - const handleMouseLeave = useCallback(() => { - setHoveredData(null); - }, []); - - return { hoveredData, handleMouseMove, handleMouseLeave }; -}; - export const formatDateToString = (date) => { if (!(date instanceof Date) || isNaN(date.getTime())) { console.error('Invalid date:', date); diff --git a/src/AppWrapper.jsx b/src/components/cleanUp/AppWrapper.jsx similarity index 100% rename from src/AppWrapper.jsx rename to src/components/cleanUp/AppWrapper.jsx diff --git a/src/components/cleanUp/CollectionActionButtons.jsx b/src/components/cleanUp/CollectionActionButtons.jsx index 6290010..a5ddde6 100644 --- a/src/components/cleanUp/CollectionActionButtons.jsx +++ b/src/components/cleanUp/CollectionActionButtons.jsx @@ -3,7 +3,7 @@ import { makeStyles } from '@mui/styles'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; // Assuming you have a separate context for Collections import CardActionButtons from './CardActionButtons'; import CollectionDialog from './CollectionDialog'; // Assuming you have a separate dialog for Collections -import CardCountDisplay from '../other/CardCountDisplay'; +import CardCountDisplay from '../other/dataDisplay/CardCountDisplay'; import { useCartStore } from '../../context/CartContext/CartContext'; const useStyles = makeStyles({ diff --git a/src/components/cleanUp/Hero.jsx b/src/components/cleanUp/Hero.jsx new file mode 100644 index 0000000..7bc8866 --- /dev/null +++ b/src/components/cleanUp/Hero.jsx @@ -0,0 +1,49 @@ +// import React from 'react'; +// import { Box, Container, Typography } from '@mui/material'; +// import { makeStyles } from '@mui/styles'; +// import { Canvas } from '@react-three/fiber'; +// import Cube from '../../assets/animations/Cube'; + +// const useStyles = makeStyles((theme) => ({ +// bannerBox: { +// backgroundImage: `linear-gradient(to right, ${theme.palette.primary.light}, ${theme.palette.primary.main})`, +// minHeight: '100vh', +// padding: theme.spacing(4), +// display: 'flex', +// alignItems: 'center', +// justifyContent: 'center', +// }, +// })); + +// const Hero = () => { +// const classes = useStyles(); +// return ( +// +// +// +// {/* eslint-disable-next-line react/no-unknown-property */} +// +// +// +// +// +// Buy Amazing Cards +// +// +// +// ); +// }; + +// export default Hero; diff --git a/src/theme.jsx b/src/components/cleanUp/theme.jsx similarity index 100% rename from src/theme.jsx rename to src/components/cleanUp/theme.jsx diff --git a/src/components/collection/CardPortfolio.jsx b/src/components/collection/CardPortfolio.jsx index 59bf124..aa2b248 100644 --- a/src/components/collection/CardPortfolio.jsx +++ b/src/components/collection/CardPortfolio.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import SelectCollection from './SelectCollection'; -import PortfolioContent from '../content/PortfolioContent'; +import PortfolioContent from '../../containers/collectionPageContainers/PortfolioContent'; import { Box, Typography } from '@mui/material'; import CollectionContainer from '../../containers/collectionPageContainers/CollectionContainer'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; @@ -89,7 +89,7 @@ const CardPortfolio = ({ allCollections }) => { display="flex" alignItems="center" justifyContent="center" - minHeight="100vh" + // minHeight="100vh" backgroundColor="#f1f1f1" > No Collection Selected diff --git a/src/components/collection/SelectCollection.jsx b/src/components/collection/SelectCollection.jsx index 8fd8b84..ba03979 100644 --- a/src/components/collection/SelectCollection.jsx +++ b/src/components/collection/SelectCollection.jsx @@ -15,7 +15,7 @@ const useStyles = makeStyles((theme) => ({ justifyContent: 'space-between', alignItems: 'stretch', height: '100%', - width: '50vw', + // width: '50vw', padding: theme.spacing(2), }, button: { diff --git a/src/components/content/PortfolioContent.jsx b/src/components/content/PortfolioContent.jsx deleted file mode 100644 index 57d9f1e..0000000 --- a/src/components/content/PortfolioContent.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { Box, Paper } from '@mui/material'; -import PortfolioHeader from '../headings/PortfolioHeader'; -import PortfolioListContainer from '../../containers/PortfolioListContainer'; -import PortfolioChartContainer from '../../containers/PortfolioChartContainer'; - -const PortfolioContent = ({ error, selectedCards, removeCard }) => { - return ( - - - - - - - - - - - - ); -}; - -export default PortfolioContent; diff --git a/src/components/forms/SearchForm.jsx b/src/components/forms/SearchForm.jsx new file mode 100644 index 0000000..358bc91 --- /dev/null +++ b/src/components/forms/SearchForm.jsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { TextField, Button, Paper } from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import { useMode } from '../../context/hooks/colormode'; + +const useStyles = makeStyles((theme) => ({ + formContainer: { + width: '100%', + padding: theme.spacing(3), + borderRadius: theme.shape.borderRadius, + backgroundColor: theme.palette.background.default, + boxShadow: theme.shadows[3], // Use the theme's predefined shadows + maxWidth: 400, + margin: 'auto', + }, + form: { + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + padding: theme.spacing(2), + }, + textField: { + '& label.Mui-focused': { + color: theme.palette.primary.main, + }, + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: theme.palette.primary.light, + }, + '&.Mui-focused fieldset': { + borderColor: theme.palette.primary.main, + }, + }, + }, + submitButton: { + backgroundColor: theme.palette.success.main, + color: theme.palette.getContrastText(theme.palette.success.main), + '&:hover': { + backgroundColor: theme.palette.success.dark, + }, + }, +})); + +const SearchForm = ({ searchTerm, handleChange, handleSubmit }) => { + const { theme } = useMode(); + const classes = useStyles(theme); + + return ( + +
+ + + +
+ ); +}; + +export default SearchForm; diff --git a/src/components/forms/customerCheckoutForm/CustomerForm.js b/src/components/forms/customerCheckoutForm/CustomerForm.js index 080959d..854c76e 100644 --- a/src/components/forms/customerCheckoutForm/CustomerForm.js +++ b/src/components/forms/customerCheckoutForm/CustomerForm.js @@ -1,17 +1,25 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useContext } from 'react'; import { Box, Container, Typography } from '@mui/material'; import { useCartStore } from '../../../context/CartContext/CartContext'; import CustomerInfoFields from './CustomerInfoFields'; import CartActions from './CartActions'; import StripeCheckoutModal from '../../modals/stripeModal/StripeCheckoutModal'; +import { ModalContext } from '../../../context/ModalContext/ModalContext'; const CustomerForm = () => { const { getTotalCost, cartData } = useCartStore(); - const [isModalOpen, setIsModalOpen] = useState(false); - - const handleModalOpen = useCallback(() => setIsModalOpen(true), []); - const handleModalClose = useCallback(() => setIsModalOpen(false), []); - + // const [isModalOpen, setIsModalOpen] = useState(false); + const { + openModalWithCard, + closeModal, + isModalOpen, + setModalOpen, + modalContent, + } = useContext(ModalContext); + // const handleModalOpen = useCallback(() => setIsModalOpen(true), []); + // const handleModalClose = useCallback(() => setIsModalOpen(false), []); + const handleModalOpen = useCallback(() => setModalOpen(true), []); + const handleModalClose = useCallback(() => setModalOpen(false), []); const onToken = useCallback( (token) => { console.log(token); diff --git a/src/components/grids/DeckDisplay.js b/src/components/grids/DeckDisplay.js index fcce34c..c5e9be0 100644 --- a/src/components/grids/DeckDisplay.js +++ b/src/components/grids/DeckDisplay.js @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useState } from 'react'; -import { Paper, Button } from '@mui/material'; +import { Paper, Button, Typography, Box } from '@mui/material'; import { DeckContext } from '../../context/DeckContext/DeckContext'; import DeckButtonList from './deckBuilderGrids/DeckButtonList'; import CardsGrid from './deckBuilderGrids/CardsGrid'; @@ -7,24 +7,39 @@ import DeckEditPanel from '../other/DeckEditPanel'; import { makeStyles } from '@mui/styles'; const useStyles = makeStyles((theme) => ({ - root: { backgroundColor: '#f4f6f8' }, - paper: { + root: { padding: theme.spacing(3), - borderRadius: '10px', - boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', + backgroundColor: theme.palette.background.default, + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + margin: 'auto', // Centering the form }, - deckEditPanel: { + paper: { padding: theme.spacing(2), - backgroundColor: '#ffffff', - border: '1px solid #ddd', - borderRadius: theme.spacing(1), - boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', - transition: 'opacity 0.5s ease-in-out', + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[4], + backgroundColor: theme.palette.background.paper, + }, + button: { + margin: theme.spacing(1), + backgroundColor: theme.palette.primary.main, + color: theme.palette.primary.contrastText, + '&:hover': { + backgroundColor: theme.palette.primary.dark, + }, }, noCardsText: { - color: '#666', + marginTop: theme.spacing(2), + textAlign: 'center', + color: theme.palette.text.secondary, fontStyle: 'italic', }, + title: { + fontWeight: 'bold', + color: theme.palette.text.primary, + marginBottom: theme.spacing(2), + }, + // Other styles... })); const DeckDisplay = ({ userDecks = [] }) => { @@ -46,9 +61,15 @@ const DeckDisplay = ({ userDecks = [] }) => { }; return ( -
+ - {showAllDecks && ( @@ -61,16 +82,17 @@ const DeckDisplay = ({ userDecks = [] }) => { )} {selectedCards.length > 0 ? ( ) : ( -
No cards to display
+ + No cards to display + )}
-
+ ); }; diff --git a/src/components/grids/collectionGrids/CardList.jsx b/src/components/grids/collectionGrids/CardList.jsx index 6b074b9..787831d 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -14,13 +14,17 @@ import { TableRow, Button, TableHead, + useTheme, + ButtonGroup, + Divider, } from '@mui/material'; import AssessmentIcon from '@mui/icons-material/Assessment'; import TablePaginationActions from './TablePaginationActions'; -import Logger from './Logger'; import PropTypes from 'prop-types'; import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; - +import { makeStyles, styled } from '@mui/styles'; +import { useMode } from '../../../context/hooks/colormode'; +import Logger from './Logger'; // Instantiate logger outside of the component const cardLogger = new Logger([ 'Action', @@ -29,7 +33,100 @@ const cardLogger = new Logger([ 'Total Price', ]); +const StyledContainer = styled(Container)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + height: '100%', + alignItems: 'center', + background: theme.palette.background.dark, + color: '#fff', // White text color + padding: 2, + borderRadius: 2, +})); + +// const Paper = styled(MuiPaper)(({ theme }) => ({ +// padding: theme.spacing(2), +// borderRadius: 2, +// width: '100%', +// color: '#fff', +// background: theme.palette.background.main, +// })); + +const StyledButtonGroup = styled(ButtonGroup)(({ theme }) => ({ + display: 'flex', + width: '100%', + borderRadius: '5px', + overflow: 'hidden', + + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'center', + boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', +})); + +// const TableContainer = styled(MuiTableContainer)(({ theme }) => ({ +// background: theme.palette.background.secondary, +// })); + +// const TableHead = styled(MuiTableHead)(({ theme }) => ({ +// backgroundColor: '#555', +// color: '#fff', +// })); + +// const TablePagination = styled(MuiTablePagination)(({ theme }) => ({ +// color: '#fff', +// })); +const StyledTableHeader = styled(TableHead)(({ theme }) => ({ + backgroundColor: '#555', // Darker shade for header + color: '#fff', +})); +// const TableFooter = styled(MuiTableFooter)(({ theme }) => ({ +// color: '#fff', +// })); + +// const TableBody = styled(MuiTableBody)(({ theme }) => ({ +// color: '#fff', +// })); + +// const Table = styled(MuiTable)(({ theme }) => ({ +// color: '#fff', +// })); + +const StyledTableCell = styled(TableCell)(({ theme }) => ({ + color: '#ddd', // Lighter text for better readability +})); +const Title = styled(TableCell)(({ theme }) => ({ + color: '#4cceac', // Green color for the title +})); + +// const Button = styled(MuiButton)(({ theme }) => ({ +// margin: theme.spacing(1), +// color: theme.palette.primary.contrastText, +// backgroundColor: theme.palette.primary.main, +// '&:hover': { +// backgroundColor: theme.palette.primary.dark, +// }, +// })); + +// const ButtonGroup = styled(MuiButtonGroup)(({ theme }) => ({ +// display: 'flex', +// width: '100%', +// borderRadius: '5px', +// overflow: 'hidden', +// flexDirection: 'column', +// justifyContent: 'space-between', +// alignItems: 'center', +// boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', +// })); + +// const TablePaginationActions = styled(MuiTablePaginationActions)( +// ({ theme }) => ({ +// color: '#fff', +// }) +// ); + const CardList = ({ selectedCards }) => { + const { theme } = useMode(); const { getTotalCost, selectedCollection, @@ -43,6 +140,10 @@ const CardList = ({ selectedCards }) => { const [rowsPerPage, setRowsPerPage] = useState(5); const chartContainerRef = useRef(null); const count = selectedCards?.length || 0; + const [chartDimensions, setChartDimensions] = useState({ + width: 0, + height: 0, + }); const emptyRows = useMemo(() => { // <-- Use useMemo for better performance return page > 0 @@ -70,58 +171,62 @@ const CardList = ({ selectedCards }) => { addOneToCollection(card, card.id); cardLogger.logCardAction('Add Card', card); }; - // let totalPrice = getTotalPrice(); - - // console.log('TOTAL PRICE:', totalPrice); - - const chartDimensions = useMemo( - () => - chartContainerRef.current?.getBoundingClientRect() || { - width: 400, - height: 600, - }, - [chartContainerRef.current] - ); return ( - + - + Cards in Portfolio {/* Include the CronTrigger button */} - +
- + - Name - Price - Total Price - Quantity - TCGPlayer Price - Actions + Name + Price + Total Price + Quantity + TCGPlayer Price + Actions - + {(rowsPerPage > 0 @@ -133,53 +238,86 @@ const CardList = ({ selectedCards }) => { : selectedCards )?.map((card, index) => ( - + {card?.name} - - {card?.price} - {card?.totalPrice} - {card?.quantity} - + + {card?.price} + + {card?.totalPrice} + + + {card?.quantity} + + {card.card_prices && card.card_prices[0] && card.card_prices[0].tcgplayer_price ? `$${card.card_prices[0].tcgplayer_price}` : 'Price not available'} - - - - - + + + + + + + + ))} {emptyRows > 0 && ( - + )} @@ -209,10 +347,12 @@ const CardList = ({ selectedCards }) => { mt={2} sx={{ width: '100%' }} > - {`Total: $${totalCost}`} + + {`Total: $${totalCost}`} + - + ); }; diff --git a/src/components/grids/collectionGrids/SelectCollectionList.jsx b/src/components/grids/collectionGrids/SelectCollectionList.jsx index dec753a..7b5de9b 100644 --- a/src/components/grids/collectionGrids/SelectCollectionList.jsx +++ b/src/components/grids/collectionGrids/SelectCollectionList.jsx @@ -6,11 +6,13 @@ import { ListItemText, Divider, Button, + Grid, + Typography, } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { useCookies } from 'react-cookie'; import PropTypes from 'prop-types'; -import LoadingIndicator from '../../indicators/LoadingIndicator'; +import LoadingIndicator from '../../reusable/indicators/LoadingIndicator'; import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; const useStyles = makeStyles((theme) => ({ @@ -43,6 +45,16 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: theme.palette.primary.dark, }, }, + gridItem: { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'center', + padding: theme.spacing(1), + }, + gridItemText: { + fontWeight: 'bold', + }, })); const SelectCollectionList = ({ @@ -83,11 +95,6 @@ const SelectCollectionList = ({ [openDialog, setSelectedCollection] ); - // console.log( - // 'SELECTED COLLECTION (SELECT COLLECTIN LIST):', - // selectedCollection - // ); - // The rendering part of the component return ( <> {isLoading ? ( @@ -103,10 +110,35 @@ const SelectCollectionList = ({ sx={{ width: '100%' }} onClick={() => handleSelect(collection?._id)} > - + + + + Name: + + {collection?.name} + + + + Value: + + {/* Replace with actual value */} + ${collection?.currentValue} + + + + Performance: + + {/* Replace with actual data */} + {collection?.performance} % + + + + Cards: + + {/* Replace with actual count */} + {collection?.numberOfCards} + + + )} + + + + + )} + + + + + ); +} diff --git a/src/components/other/CollectionValueTracker.jsx b/src/components/other/CollectionValueTracker.jsx index df3f7f8..73ddfcd 100644 --- a/src/components/other/CollectionValueTracker.jsx +++ b/src/components/other/CollectionValueTracker.jsx @@ -1,37 +1,71 @@ import React from 'react'; -import { Typography, Box, useTheme, Grid } from '@mui/material'; +import { + Typography, + Box, + useTheme, + Grid, + Accordion, + AccordionSummary, + AccordionDetails, + Container, +} from '@mui/material'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; import { styled } from '@mui/styles'; - +import { GridExpandMoreIcon } from '@mui/x-data-grid'; +import ChangeHistoryIcon from '@mui/icons-material/ChangeHistory'; const StatBox = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.palette.background.default, borderRadius: theme.shape.borderRadius, padding: theme.spacing(2), + boxShadow: theme.shadows[3], + color: theme.palette.text.primary, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'space-around', - boxShadow: theme.shadows[2], + gap: theme.spacing(1), // Adds spacing between items })); const StatItem = styled(Box)(({ theme }) => ({ display: 'flex', alignItems: 'center', marginBottom: theme.spacing(1), + '&:hover': { + backgroundColor: theme.palette.action.hover, // Hover effect + }, })); - +const styles = { + container: { + // padding: '15px', + border: '2px solid #444', + borderRadius: '8px', + backgroundColor: '#222', + color: '#fff', + boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', + fontFamily: '"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', + maxWidth: '100%', // Adjusted for full width + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'center', + height: '100%', + }, + statusBox: { + width: '100%', // Set to full width + // marginTop: '15px', + padding: '3px', + margin: '2px', + // padding: '10px', + background: '#333', + borderRadius: '6px', + border: '1px solid #555', + }, +}; const CollectionValueTracker = ({ stats }) => { const theme = useTheme(); - const { - totalCost, - selectedCollection, - getTotalPrice, - getTotalPrice2, - totalPrice, - allCardPrices, - } = useCollectionStore(); + const { getTotalPrice, selectedCollection } = useCollectionStore(); const twentyFourHourChange = stats.twentyFourHourAverage; const newTotal = getTotalPrice(); @@ -43,47 +77,105 @@ const CollectionValueTracker = ({ stats }) => { value: `$${newTotal}`, }, { - label: '24 Hour Change', + label: '24 Hour Change (%)', value: `${twentyFourHourChange?.percentageChange}`, isIncrease: twentyFourHourChange?.priceIncreased, }, + { + label: '24 Hour Change ($)', + value: `$${twentyFourHourChange?.priceChange}`, + isIncrease: twentyFourHourChange?.priceIncreased, + }, // Additional stats - { label: 'Last Price', value: `${twentyFourHourChange?.lastPrice}` }, - { label: 'Average Price', value: `${twentyFourHourChange?.averagePrice}` }, - { label: 'Volume', value: `${twentyFourHourChange?.volume}` }, - { label: 'Volatility', value: `${twentyFourHourChange?.volatility}` }, + { + label: 'Last Price', + value: `${selectedCollection?.lastSavedPrice?.num}`, + }, + { label: 'Average Price', value: `${stats?.average}` }, + { label: 'Volume', value: `${stats?.volume}` }, + { label: 'Volatility', value: `${stats?.volatility}` }, { label: 'High Point', value: `${twentyFourHourChange?.highPoint}` }, { label: 'Low Point', value: `${twentyFourHourChange?.lowPoint}` }, + // TODO: add stats for top performing cards in certain sets ]; return ( - - {statsArray.map((stat, index) => { - const IconComponent = - stat?.isIncrease !== undefined - ? stat?.isIncrease - ? ArrowUpwardIcon - : ArrowDownwardIcon - : null; - const iconColor = stat?.isIncrease - ? theme.palette.success.main - : theme.palette.error.main; - - return ( - - {IconComponent && ( - - )} - - {stat?.label}: {stat?.value} +
+ + +
+ + Performance:{' '} + {twentyFourHourChange?.percentageChange > 0 + ? 'Positive' + : 'Negative'} - - ); - })} - + {/*
*/} + {/*
*/} + + {' '} + {twentyFourHourChange?.percentageChange}% + + + } + aria-controls="collection-stats-content" + id="collection-stats-header" + > + Collection Statistics + + + + {statsArray.map((stat, index) => { + const IconComponent = + stat?.isIncrease !== undefined + ? stat?.isIncrease + ? ArrowUpwardIcon + : ArrowDownwardIcon + : null; + const iconColor = stat?.isIncrease + ? theme.palette.success.main + : theme.palette.error.main; + + return ( + + {IconComponent && ( + + )} + + {stat?.label}: + + + {stat?.value} + + + ); + })} + + + +
+
+
+
); }; diff --git a/src/components/other/DeckEditPanel.js b/src/components/other/DeckEditPanel.js index c544dc0..dd79ce3 100644 --- a/src/components/other/DeckEditPanel.js +++ b/src/components/other/DeckEditPanel.js @@ -1,53 +1,60 @@ import React, { useState } from 'react'; import { makeStyles } from '@mui/styles'; -import { Button, TextField, Paper } from '@mui/material'; +import { Button, TextField, Paper, Typography } from '@mui/material'; +import { useMode } from '../../context/hooks/colormode'; const useStyles = makeStyles((theme) => ({ root: { - padding: theme.spacing(3), + padding: theme.spacing(4), + margin: theme.spacing(2), borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[3], + boxShadow: theme.shadows[3], // Utilize theme's predefined shadows + backgroundColor: theme.palette.background.paper, // Use theme's paper background color + }, + title: { + marginBottom: theme.spacing(3), + fontWeight: theme.typography.fontWeightBold, // Use theme's font weight for bold text + fontSize: theme.typography.h6.fontSize, // Align font size with h6 variant + color: theme.palette.text.primary, // Use theme's primary text color }, textField: { - marginBottom: theme.spacing(2), + marginBottom: theme.spacing(3), '& .MuiOutlinedInput-root': { '&:hover fieldset': { - borderColor: theme.palette.primary.main, + borderColor: theme.palette.secondary.light, // Use secondary color for hover state }, '&.Mui-focused fieldset': { - borderColor: theme.palette.primary.dark, + borderColor: theme.palette.secondary.main, // Use secondary main color for focus }, }, + '& .MuiInputBase-input': { + fontSize: theme.typography.subtitle1.fontSize, // Align with subtitle1 font size + }, }, saveButton: { - boxShadow: 'none', + boxShadow: theme.shadows[1], // Use a softer shadow from theme textTransform: 'none', - fontSize: 16, - padding: theme.spacing(1, 2), - border: `1px solid ${theme.palette.primary.main}`, - lineHeight: 1.5, - backgroundColor: theme.palette.primary.main, - color: theme.palette.primary.contrastText, + fontSize: theme.typography.button.fontSize, // Align with button font size + padding: theme.spacing(1, 4), + lineHeight: theme.typography.button.lineHeight, // Align with button line height + backgroundColor: theme.palette.secondary.main, // Use secondary color for button + color: theme.palette.secondary.contrastText, // Contrast text for readability '&:hover': { - backgroundColor: theme.palette.primary.dark, - borderColor: theme.palette.primary.dark, - boxShadow: 'none', + backgroundColor: theme.palette.secondary.dark, // Darken on hover + boxShadow: theme.shadows[2], // Slightly elevate shadow on hover }, }, })); const DeckEditPanel = ({ selectedDeck, onSave }) => { - const classes = useStyles(); + const { theme } = useMode(); + const classes = useStyles(theme); const [name, setName] = useState(selectedDeck?.name || ''); const [description, setDescription] = useState( selectedDeck?.description || '' ); const handleSave = () => { - // Log the new deck name and description before saving - console.log('New Deck Name:', name); - console.log('New Deck Description:', description); - onSave({ ...selectedDeck, name, @@ -57,6 +64,9 @@ const DeckEditPanel = ({ selectedDeck, onSave }) => { return ( + + Edit Deck + { /> + } + backButton={ + + } + /> + + ); +}; + +export default TopCardsDisplay; diff --git a/src/components/other/dataDisplay/CardCountDisplay.jsx b/src/components/other/dataDisplay/CardCountDisplay.jsx new file mode 100644 index 0000000..ecf90b4 --- /dev/null +++ b/src/components/other/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.background.paper, + 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/Auth/PrivateRoute.jsx b/src/components/reusable/PrivateRoute.jsx similarity index 100% rename from src/components/Auth/PrivateRoute.jsx rename to src/components/reusable/PrivateRoute.jsx diff --git a/src/components/indicators/ErrorIndicator.js b/src/components/reusable/indicators/ErrorIndicator.js similarity index 68% rename from src/components/indicators/ErrorIndicator.js rename to src/components/reusable/indicators/ErrorIndicator.js index f19f6e1..f3c5fa2 100644 --- a/src/components/indicators/ErrorIndicator.js +++ b/src/components/reusable/indicators/ErrorIndicator.js @@ -5,26 +5,7 @@ import { makeStyles } from '@mui/styles'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import Container from '@mui/material/Container'; import { Alert, AlertTitle } from '@mui/material'; - -const useStyles = makeStyles((theme) => ({ - paper: { - padding: theme.spacing(2), - margin: theme.spacing(2), - backgroundColor: theme.palette.error.light, - }, - typography: { - color: theme.palette.error.dark, - }, - container: { - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-start', - }, - icon: { - marginRight: theme.spacing(1), - color: theme.palette.error.main, - }, -})); +import { useStyles } from './styles'; const ErrorIndicator = ({ error }) => { const classes = useStyles(); diff --git a/src/components/indicators/LoadingIndicator.js b/src/components/reusable/indicators/LoadingIndicator.js similarity index 100% rename from src/components/indicators/LoadingIndicator.js rename to src/components/reusable/indicators/LoadingIndicator.js diff --git a/src/components/reusable/indicators/LoadingIndicator2.js b/src/components/reusable/indicators/LoadingIndicator2.js new file mode 100644 index 0000000..b1e75b1 --- /dev/null +++ b/src/components/reusable/indicators/LoadingIndicator2.js @@ -0,0 +1,13 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import LoadingCardAnimation from '../../../assets/animations/LoadingCardAnimation'; + +const LoadingIndicator2 = () => { + return ( + + + + ); +}; + +export default LoadingIndicator2; diff --git a/src/components/reusable/indicators/styles.js b/src/components/reusable/indicators/styles.js new file mode 100644 index 0000000..c5f9378 --- /dev/null +++ b/src/components/reusable/indicators/styles.js @@ -0,0 +1,21 @@ +import { makeStyles } from '@mui/styles'; + +export const useStyles = makeStyles((theme) => ({ + paper: { + padding: theme.spacing(2), + margin: theme.spacing(2), + backgroundColor: theme.palette.error.light, + }, + typography: { + color: theme.palette.error.dark, + }, + container: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + }, + icon: { + marginRight: theme.spacing(1), + color: theme.palette.error.main, + }, +})); diff --git a/src/components/search/DeckSearch.js b/src/components/search/DeckSearch.js index 1fa3f56..6f58b6b 100644 --- a/src/components/search/DeckSearch.js +++ b/src/components/search/DeckSearch.js @@ -9,8 +9,7 @@ import { Pagination, } from '@mui/material'; import { useCardStore } from '../../context/CardContext/CardStore'; -import { useTheme } from '@emotion/react'; -import SearchForm from './SearchForm'; +import SearchForm from '../forms/SearchForm'; import DeckSearchCardGrid from '../grids/searchResultGrids/DeckSearchCardGrid'; import CustomPagination from '../reusable/CustomPagination'; diff --git a/src/components/search/SearchBar.js b/src/components/search/SearchBar.js index 5895198..2de0086 100644 --- a/src/components/search/SearchBar.js +++ b/src/components/search/SearchBar.js @@ -1,72 +1,51 @@ import React from 'react'; -import { Grid, Box, Typography, Container } from '@mui/material'; +import { Grid, Box, Typography, Container, Paper } from '@mui/material'; import { useCardStore } from '../../context/CardContext/CardStore'; import SearchButton from '../buttons/other/SearchButton'; import CardNameInput from '../other/InputComponents/CardNameInput'; import CustomSelector from '../other/InputComponents/CustomSelector'; import { useCombinedContext } from '../../context/CombinedProvider'; - -const initialState = { - name: '', - race: '', - type: '', - attribute: '', - level: '', -}; +import { makeStyles } from '@mui/styles'; +import search from './search.json'; +const useStyles = makeStyles((theme) => ({ + searchContainer: { + padding: theme.spacing(3), + borderRadius: theme.shape.borderRadius, + backgroundColor: theme.palette.background.paper, + boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.12)', + margin: theme.spacing(2, 'auto'), + width: '100%', + maxWidth: 'md', + }, + title: { + marginBottom: theme.spacing(3), + fontWeight: 600, + color: theme.palette.primary.main, + }, + searchGrid: { + gap: theme.spacing(2), + }, + searchButtonContainer: { + display: 'flex', + width: '100%', + justifyContent: 'center', + marginTop: theme.spacing(2), + }, +})); const SearchBar = () => { - const { searchParams, setSearchParams } = useCombinedContext(initialState); + const { searchParams, setSearchParams } = useCombinedContext(); const { handleRequest } = useCardStore(); - - const filters = [ - { - label: 'Level', - name: 'level', - values: [ - 'Unset', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', - ], - }, - { label: 'Race', name: 'race', values: ['Unset', 'Aqua', 'Beast'] }, - { - label: 'Type', - name: 'type', - values: ['Unset', 'Effect Monster', 'Flip Effect Monster'], - }, - { - label: 'Attribute', - name: 'attribute', - values: [ - 'Unset', - 'Dark', - 'Divine', - 'Earth', - 'Fire', - 'Light', - 'Water', - 'Wind', - ], - }, - ]; + const classes = useStyles(); + const { initialState, filters } = search; // Destructure the data from JSON return ( - - - + + + Search Cards - + { /> {filters?.map((filter) => ( - - setSearchParams((prevState) => ({ - ...prevState, - [filter?.name]: newValue, - })) - } - values={filter?.values} - /> + + + setSearchParams((prevState) => ({ + ...prevState, + [filter?.name]: newValue, + })) + } + values={filter?.values} + /> + ))} - + + + - + ); }; diff --git a/src/components/search/SearchForm.jsx b/src/components/search/SearchForm.jsx deleted file mode 100644 index 923b629..0000000 --- a/src/components/search/SearchForm.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { TextField, Button } from '@mui/material'; -import { makeStyles } from '@mui/styles'; -const useStyles = makeStyles((theme) => ({ - form: { - // minWidth: '90%', // Set the min-width - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), // You can control the gap between the TextField and Button - }, -})); - -const SearchForm = ({ searchTerm, handleChange, handleSubmit }) => { - const classes = useStyles(); - - return ( -
- - - - ); -}; - -export default SearchForm; diff --git a/src/components/search/SearchResultsDisplay.jsx b/src/components/search/SearchResultsDisplay.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/search/search.json b/src/components/search/search.json new file mode 100644 index 0000000..d5b07c4 --- /dev/null +++ b/src/components/search/search.json @@ -0,0 +1,54 @@ +{ + "initialState": { + "name": "", + "race": "", + "type": "", + "attribute": "", + "level": "" + }, + "filters": [ + { + "label": "Level", + "name": "level", + "values": [ + "Unset", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ] + }, + { + "label": "Race", + "name": "race", + "values": ["Unset", "Aqua", "Beast"] + }, + { + "label": "Type", + "name": "type", + "values": ["Unset", "Effect Monster", "Flip Effect Monster"] + }, + { + "label": "Attribute", + "name": "attribute", + "values": [ + "Unset", + "Dark", + "Divine", + "Earth", + "Fire", + "Light", + "Water", + "Wind" + ] + } + ] +} diff --git a/src/containers/PortfolioChartContainer.jsx b/src/containers/PortfolioChartContainer.jsx deleted file mode 100644 index a91e87e..0000000 --- a/src/containers/PortfolioChartContainer.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import React, { useMemo } from 'react'; -import { Box, Grid, Paper } from '@mui/material'; -import PortfolioChart from '../components/chart/PortfolioChart'; -import TimeRangeSelector from '../components/other/InputComponents/TimeRangeSelector'; -import CollectionStatisticsSelector, { - calculateStatistics, -} from '../components/other/InputComponents/CollectionStatisticsSelector'; -import UpdateStatusBox from '../components/other/InputComponents/UpdateStatusBox'; -import { useTheme } from '@mui/material/styles'; -import { useSocketContext } from '../context/SocketProvider'; -import { useChartContext } from '../context/ChartContext/ChartContext'; -import { useCollectionStore } from '../context/CollectionContext/CollectionContext'; -import CollectionValueTracker from '../components/other/CollectionValueTracker'; -import { useCombinedContext } from '../context/CombinedProvider'; -import UpdateStatusBox2 from '../components/other/InputComponents/UpdateStatusBox2'; -const paperChartStyle = { - elevation: 3, - borderRadius: 2, - p: 2, - // height: '25vh', // Set the container height to 25vh - maxWidth: '100vw', - display: 'flex', - flexDirection: 'row', // Change to 'row' to fit selectors horizontally - justifyContent: 'space-between', // Distribute space evenly between selectors - alignItems: 'center', // Align items vertically in the center - gap: 2, // Set a gap between the selectors -}; -const selectorStyle = { - flex: 1, // Allow the selector to grow - display: 'flex', - flexDirection: 'column', - height: '100%', // Set the selector height to 100% -}; - -const PortfolioChartContainer = ({ selectedCards, removeCard }) => { - const theme = useTheme(); - // const { socket } = useSocketContext(); - const { socket } = useCombinedContext(); - const { timeRange } = useChartContext(); - const { allCollections, selectedCollection } = useCollectionStore(); - const data = allCollections.map((collection) => ({ - data: collection?.currentChartDataSets2, - })); - const dataForStats = data[0]; - const stats = useMemo( - () => calculateStatistics(dataForStats, timeRange), - [dataForStats, timeRange] - ); - - const containerStyle = { - maxWidth: '100%', - // margin: 'auto', - padding: theme.spacing(2), - overflow: 'hidden', - }; - - const paperStyle = { - padding: theme.spacing(2), - marginBottom: theme.spacing(2), - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - height: 'auto', - boxShadow: theme.shadows[3], - borderRadius: theme.shape.borderRadius, - }; - - const gridItemStyle = { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'space-between', - }; - - return ( - - {/* Updaters Row */} - {/* */} - - - - - - {/* */} - - - - - - - {/* Main Grid Container */} - - {/* Portfolio Chart Row */} - - - - - - - {/* Selectors Row */} - - - - - - - - - {' '} - - - - - ); -}; - -export default PortfolioChartContainer; diff --git a/src/containers/PortfolioListContainer.jsx b/src/containers/PortfolioListContainer.jsx deleted file mode 100644 index 5bffa50..0000000 --- a/src/containers/PortfolioListContainer.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { Grid, Paper } from '@mui/material'; -import CardList from '../components/grids/collectionGrids/CardList'; - -const PortfolioListContainer = ({ selectedCards, removeCard }) => { - return ( - - - - - - ); -}; - -export default PortfolioListContainer; diff --git a/src/containers/CartContainer.jsx b/src/containers/cartPageContainers/CartContainer.jsx similarity index 100% rename from src/containers/CartContainer.jsx rename to src/containers/cartPageContainers/CartContainer.jsx diff --git a/src/components/content/CartContent.js b/src/containers/cartPageContainers/CartContent.js similarity index 84% rename from src/components/content/CartContent.js rename to src/containers/cartPageContainers/CartContent.js index a3d926f..533bf48 100644 --- a/src/components/content/CartContent.js +++ b/src/containers/cartPageContainers/CartContent.js @@ -1,9 +1,9 @@ import React from 'react'; import { Typography } from '@mui/material'; import { useCartStore } from '../../context/CartContext/CartContext'; -import CartContainer from '../../containers/CartContainer'; -import CartItem from '../grids/CartItem'; -import CartTotal from '../other/CartTotal'; +import CartContainer from './CartContainer'; +import CartItem from '../../components/grids/CartItem'; +import CartTotal from '../../components/other/CartTotal'; const CartContent = () => { const { cartData, getTotalCost } = useCartStore(); diff --git a/src/containers/CartContentContainer.js b/src/containers/cartPageContainers/CartContentContainer.js similarity index 79% rename from src/containers/CartContentContainer.js rename to src/containers/cartPageContainers/CartContentContainer.js index 89161b4..5dd3823 100644 --- a/src/containers/CartContentContainer.js +++ b/src/containers/cartPageContainers/CartContentContainer.js @@ -1,7 +1,7 @@ import React from 'react'; import { Box } from '@mui/system'; -import LoadingIndicator from '../components/indicators/LoadingIndicator'; -import CartContent from '../components/content/CartContent'; +import LoadingIndicator from '../../components/reusable/indicators/LoadingIndicator'; +import CartContent from './CartContent'; const CartContentContainer = ({ cartData, diff --git a/src/containers/CustomerFormContainer.js b/src/containers/cartPageContainers/CustomerFormContainer.js similarity index 72% rename from src/containers/CustomerFormContainer.js rename to src/containers/cartPageContainers/CustomerFormContainer.js index d55887a..46d79bb 100644 --- a/src/containers/CustomerFormContainer.js +++ b/src/containers/cartPageContainers/CustomerFormContainer.js @@ -1,6 +1,6 @@ import React from 'react'; import { Box } from '@mui/system'; -import CustomerForm from '../components/forms/customerCheckoutForm/CustomerForm'; +import CustomerForm from '../../components/forms/customerCheckoutForm/CustomerForm'; const CustomerFormContainer = () => { return ( diff --git a/src/containers/collectionPageContainers/PortfolioChartContainer.jsx b/src/containers/collectionPageContainers/PortfolioChartContainer.jsx new file mode 100644 index 0000000..f5f3fe1 --- /dev/null +++ b/src/containers/collectionPageContainers/PortfolioChartContainer.jsx @@ -0,0 +1,77 @@ +import React, { useMemo } from 'react'; +import { Box, Grid, Paper, Container, useTheme } from '@mui/material'; +import PortfolioChart from '../../components/chart/PortfolioChart'; +import TimeRangeSelector from '../../components/other/InputComponents/TimeRangeSelector'; +import CollectionStatisticsSelector, { + calculateStatistics, +} from '../../components/other/InputComponents/CollectionStatisticsSelector'; +import UpdateStatusBox2 from '../../components/other/InputComponents/UpdateStatusBox2'; +import TopCardsDisplay from '../../components/other/TopCardsDisplay'; +import { useChartContext } from '../../context/ChartContext/ChartContext'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import { useCombinedContext } from '../../context/CombinedProvider'; + +const PortfolioChartContainer = ({ selectedCards, removeCard }) => { + const theme = useTheme(); + const { timeRange } = useChartContext(); + const { allCollections } = useCollectionStore(); + const { socket } = useCombinedContext(); + + const data = allCollections.map((collection) => ({ + data: collection?.currentChartDataSets2, + })); + const dataForStats = data[0]; + const stats = useMemo( + () => calculateStatistics(dataForStats, timeRange), + [dataForStats, timeRange] + ); + + return ( + + {/* Updaters Row */} + + + + + + + + + + {/* Additional Components */} + + + + {/* Main Grid Container */} + + {/* Portfolio Chart Row */} + + + + + {/* Additional Rows */} + + + + + + ); +}; + +export default PortfolioChartContainer; diff --git a/src/containers/collectionPageContainers/PortfolioContent.jsx b/src/containers/collectionPageContainers/PortfolioContent.jsx new file mode 100644 index 0000000..692a4aa --- /dev/null +++ b/src/containers/collectionPageContainers/PortfolioContent.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { Box, Container, Grid, Paper, useTheme } from '@mui/material'; +import PortfolioHeader from '../../components/headings/PortfolioHeader'; +import PortfolioListContainer from './PortfolioListContainer'; +import PortfolioChartContainer from './PortfolioChartContainer'; +import HeaderTitle from '../../components/reusable/HeaderTitle'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import { useMode } from '../../context/hooks/colormode'; + +const PortfolioContent = ({ error, selectedCards, removeCard }) => { + const { theme } = useMode(); + const { selectedCollection } = useCollectionStore(); + + return ( + + + + + + + + + + + + + + + + + ); +}; + +export default PortfolioContent; diff --git a/src/containers/collectionPageContainers/PortfolioListContainer.jsx b/src/containers/collectionPageContainers/PortfolioListContainer.jsx new file mode 100644 index 0000000..579f87b --- /dev/null +++ b/src/containers/collectionPageContainers/PortfolioListContainer.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Box, Grid, Paper } from '@mui/material'; +import CardList from '../../components/grids/collectionGrids/CardList'; +import { useTheme } from '@mui/material/styles'; +import { useMode } from '../../context/hooks/colormode'; + +const PortfolioListContainer = ({ selectedCards, removeCard }) => { + const { theme } = useMode(); + + return ( + + + + + + + + ); +}; + +export default PortfolioListContainer; diff --git a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js index b365cfd..2f1a775 100644 --- a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js +++ b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js @@ -1,56 +1,45 @@ import React from 'react'; -import { Grid, useMediaQuery, useTheme } from '@mui/material'; +import { Grid } from '@mui/material'; import DeckDisplay from '../../components/grids/DeckDisplay'; import DeckSearch from '../../components/search/DeckSearch'; -import { styled } from '@mui/system'; // Use @mui/system for Emotion styling +import { styled } from '@mui/styles'; +import { useMode } from '../../context/hooks/colormode'; -// Define your components using the styled function from @mui/system const SearchGrid = styled(Grid)(({ theme }) => ({ - [theme.breakpoints.up('xl')]: { flexBasis: '60%' }, - [theme.breakpoints.up('lg')]: { flexBasis: '50%' }, - [theme.breakpoints.between('md', 'lg')]: { flexBasis: '50%' }, - [theme.breakpoints.down('sm')]: { flexBasis: '50%' }, + padding: theme.spacing(2), + [theme.breakpoints.down('md')]: { + width: '50%', // Half width on small and medium screens + }, })); const DisplayGrid = styled(Grid)(({ theme }) => ({ - flex: 1, - padding: theme.spacing(1), + padding: theme.spacing(2), + [theme.breakpoints.down('md')]: { + width: '50%', // Half width on small and medium screens + }, })); const RootGrid = styled(Grid)(({ theme }) => ({ overflow: 'auto', - backgroundColor: '#f4f6f8', + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', // Ensure wrapping on smaller screens + backgroundColor: theme.palette.background.secondary, padding: theme.spacing(3), - borderRadius: '10px', - boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], width: '100%', })); const DeckBuilderContainer = ({ userDecks }) => { - const theme = useTheme(); // Use the theme from Emotion - const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg')); - const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md')); - const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); - - const getXsValue = () => { - if (isLargeScreen) return 3; - if (isMediumScreen) return 4; - return 5; - }; - const getDisplayXsValue = () => { - // if (isSmallScreen || isMediumScreen) return 7; - // return 9; - if (isLargeScreen) return 9; - if (isMediumScreen) return 8; - return 7; - }; + const { theme } = useMode(); return ( - - + + - + diff --git a/src/context/AppContext/AppContextProvider.jsx b/src/context/AppContext/AppContextProvider.jsx new file mode 100644 index 0000000..295d4ff --- /dev/null +++ b/src/context/AppContext/AppContextProvider.jsx @@ -0,0 +1,54 @@ +// CombinedContext.js +import React, { createContext, useContext, useState } from 'react'; +import { DeckContext } from '../DeckContext/DeckContext'; +import { CartContext } from '../CartContext/CartContext'; +import { CollectionContext } from '../CollectionContext/CollectionContext'; + +// Create the combined context +export const AppContext = createContext({}); + +// Create a provider component that combines the contexts +export const AppContextProvider = ({ children }) => { + const [context, setContext] = useState({}); + const Deck = useContext(DeckContext); + const Cart = useContext(CartContext); + const Collection = useContext(CollectionContext); + + // Combine the context values into one object + const appContextValues = { Deck, Cart, Collection }; + + return ( + + {children} + + ); +}; + +// import React, { createContext, useContext, useState } from 'react'; +// import { DeckContext } from '../DeckContext/DeckContext'; +// import { CartContext } from '../CartContext/CartContext'; +// import { CollectionContext } from '../CollectionContext/CollectionContext'; + +// export const AppContext = createContext({ +// Deck: {}, +// Cart: {}, +// Collection: {}, +// context: '', +// // eslint-disable-next-line @typescript-eslint/no-empty-function +// setContext: () => {}, +// }); + +// export const AppContextProvider = ({ children }) => { +// const [context, setContext] = useState('Collection'); // Default context +// const Deck = useContext(DeckContext); +// const Cart = useContext(CartContext); +// const Collection = useContext(CollectionContext); + +// const appContextValues = { Deck, Cart, Collection, context, setContext }; + +// return ( +// +// {children} +// +// ); +// }; diff --git a/src/context/AppContextProvider.jsx b/src/context/AppContextProvider.jsx deleted file mode 100644 index 1bb9d65..0000000 --- a/src/context/AppContextProvider.jsx +++ /dev/null @@ -1,24 +0,0 @@ -// CombinedContext.js -import React, { createContext, useContext } from 'react'; -import { DeckContext } from './DeckContext/DeckContext'; -import { CartContext } from './CartContext/CartContext'; -import { CollectionContext } from './CollectionContext/CollectionContext'; - -// Create the combined context -export const AppContext = createContext({}); - -// Create a provider component that combines the contexts -export const AppContextProvider = ({ children }) => { - const Deck = useContext(DeckContext); - const Cart = useContext(CartContext); - const Collection = useContext(CollectionContext); - - // Combine the context values into one object - const appContextValues = { Deck, Cart, Collection }; - - return ( - - {children} - - ); -}; diff --git a/src/context/CardContext/CardStore.js b/src/context/CardContext/CardStore.js index 404a196..491c2fb 100644 --- a/src/context/CardContext/CardStore.js +++ b/src/context/CardContext/CardStore.js @@ -8,10 +8,7 @@ const CardContext = createContext(); export const CardProvider = ({ children }) => { const [cookies, setCookie] = useCookies(['cart'], ['deckData']); const initialStore = cookies.store || []; - // const initialCart = cookies.cart || []; - // console.log('Initial cart:', initialCart); const [cardsArray, setCardsArray] = useState(initialStore); - // console.log('Initial store:', initialStore); const currentCart = cookies.cart || []; const [currenCartArray, setCurrentCartArray] = useState(currentCart); const [searchData, setSearchData] = useState([]); @@ -19,7 +16,6 @@ export const CardProvider = ({ children }) => { const [savedDeckData, setSavedDeckData] = useState(currentDeckData); const [deckSearchData, setDeckSearchData] = useState([]); - // console.log('Current cart:', currentCart); if (!currenCartArray || !savedDeckData) { return
Loading...
; } @@ -82,7 +78,6 @@ export const CardProvider = ({ children }) => { searchData, deckSearchData, savedDeckData, - setSearchData, setDeckSearchData, setSavedDeckData, diff --git a/src/context/CardImagesContext/CardImagesContext.jsx b/src/context/CardImagesContext/CardImagesContext.jsx new file mode 100644 index 0000000..d6cad47 --- /dev/null +++ b/src/context/CardImagesContext/CardImagesContext.jsx @@ -0,0 +1,112 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +const CardImagesContext = createContext(); + +export const useCardImages = () => useContext(CardImagesContext); + +const fetchWrapper = async (url, method, body = null) => { + const options = { + method, + headers: { + 'Content-Type': 'application/json', + // 'Access-Control-Allow-Origin': '*', + // crossOrigin: 'anonymous', + }, + ...(body && { body: JSON.stringify(body) }), + }; + + try { + const response = await fetch(url, options); + if (!response.ok) { + // We handle non-ok responses immediately + throw new Error(`API request failed with status ${response.status}`); + } + // updateLastRequestTime(method); // Assumed to be a function that updates some kind of state + return await response.json(); // Directly returning the JSON response + } catch (error) { + console.error(`Fetch failed: ${error}`); + console.trace(); + throw error; // Re-throwing the error for upstream catch blocks to handle + } +}; + +export const CardImagesProvider = ({ children }) => { + const [cards, setCards] = useState([]); + const [images, setImages] = useState([]); // [ + const [randomCardImage, setRandomCardImage] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const BASE_API_URL = 'http://localhost:3001/api/card-image'; + + const downloadCardImages = async () => { + setIsLoading(true); + try { + const response = await fetchWrapper(BASE_API_URL + '/download', 'GET'); + console.log('Response from fetchWrapper:', response); + + // If response is already JSON + setCards(response.data); // Directly setting the response if it's already in the desired format + + cards.forEach((card) => { + if (card.card_images && card.card_images.length > 0) { + // Adding a dummy GET parameter to bypass caching + const imageUrl = + card.card_images[0].image_url + '?dummy=' + Date.now(); + setImages(imageUrl); + } + }); + + // If response needs to be parsed as JSON + // const jsonResponse = await response.json(); // Uncomment if response is in JSON format + // setCards(jsonResponse); // Assuming jsonResponse is an array of cards + } catch (error) { + console.error('Error in downloadCardImages:', error); + setError(error.message); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + downloadCardImages(); + }, []); + + useEffect(() => { + if (cards && cards.length > 0) { + const randomCard = cards[Math.floor(Math.random() * cards.length)]; + if (randomCard.card_images && randomCard.card_images.length > 0) { + // Adding a dummy GET parameter to bypass caching + const imageUrl = + randomCard.card_images[0].image_url + '?dummy=' + Date.now(); + setRandomCardImage(imageUrl); + } + } + }, [cards]); + + useEffect(() => { + if (images && images.length > 0) { + const randomCard = images[Math.floor(Math.random() * images.length)]; + if (randomCard.card_images && randomCard.card_images.length > 0) { + setRandomCardImage(randomCard.card_images[0].image_url); + } + } + }, [images]); + + return ( + + {children} + + ); +}; + +export default CardImagesProvider; diff --git a/src/context/CartContext/keepsaved.js b/src/context/CartContext/keepsaved.js deleted file mode 100644 index 98c7d5d..0000000 --- a/src/context/CartContext/keepsaved.js +++ /dev/null @@ -1,240 +0,0 @@ -// /* eslint-disable @typescript-eslint/no-empty-function */ -// import React, { createContext, useState, useEffect, useCallback } from 'react'; -// import { useCookies } from 'react-cookie'; -// import { useCardStore } from './CardStore'; - -// export const CartContext = createContext({ -// cart: [], -// getCardQuantity: () => {}, -// addOneToCart: () => {}, -// removeOneFromCart: () => {}, -// deleteFromCart: () => {}, -// getTotalCost: () => {}, -// setCart: () => {}, -// fetchUserCart: () => {}, -// }); - -// export const CartProvider = ({ children }) => { -// const [cartData, setCartData] = useState([]); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(null); -// const { getCardData } = useCardStore(); -// const [cookies, setCookie] = useCookies(['userCookie', 'cart']); - -// const userId = cookies.userCookie?.id; // Update the way to get the userId -// console.log('cartData', cartData); - -// useEffect(() => { -// let isMounted = true; - -// const fetchAndSetUserCart = async (userId) => { -// try { -// setLoading(true); -// const cartData = await fetchUserCart(userId); -// console.log('Fetched cartData:', cartData); - -// if (isMounted) { -// setCartData(cartData); -// setLoading(false); -// } -// } catch (error) { -// if (isMounted) { -// setError(`Failed to fetch user cart data: ${error.message}`); -// setLoading(false); -// } -// } -// }; - -// if (userId && typeof userId === 'string') { -// fetchAndSetUserCart(userId); -// } - -// return () => { -// isMounted = false; -// }; -// }, [userId]); - -// // Add new function here -// const createUserCart = async (userId) => { -// const newCartResponse = await fetch( -// `${process.env.REACT_APP_SERVER}/api/carts/newCart/${userId}`, -// { -// method: 'POST', -// } -// ); - -// if (!newCartResponse.ok) { -// throw new Error(`HTTP error! status: ${newCartResponse.status}`); -// } - -// const newCartData = await newCartResponse.json(); -// console.log('CART CREATED:', newCartData); -// const newCartItems = Array.isArray(newCartData.cart) -// ? newCartData.cart -// : []; -// setCookie('activeCartCardsArray', newCartItems, { path: '/' }); -// setCookie('allCarts', newCartItems, { path: '/' }); -// return newCartItems; -// }; - -// const fetchUserCart = useCallback( -// async (userId) => { -// const response = await fetch( -// `${process.env.REACT_APP_SERVER}/api/carts/userCart/${userId}` -// ); - -// if (!response.ok) { -// if (response.status === 404) { -// // Call the new function here -// return createUserCart(userId); -// } else { -// throw new Error(`HTTP error! status: ${response.status}`); -// } -// } else { -// const data = await response.json(); -// console.log('CART EXISTS:', data); -// setCookie( -// 'activeCartCardsArray', -// Array.isArray(data.cart) ? data.cart : [], -// { -// path: '/', -// } -// ); -// setCookie('allCarts', Array.isArray(data) ? data : {}, { -// path: '/', -// }); -// return data; -// } -// }, -// [setCookie, createUserCart] // Added createUserCart to the dependencies array -// ); - -// const updateCartInBackend = async (cartId, cartData) => { -// console.log('received values:', 'cartId', cartId, 'cartData', cartData); - -// // Ensure cartData is in the correct format -// const formattedCartData = cartData.map((item) => ({ -// cardId: item.id, -// quantity: item.quantity, -// })); - -// try { -// const response = await fetch( -// `${process.env.REACT_APP_SERVER}/api/carts/${cartId}`, -// { -// method: 'PUT', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify(formattedCartData), -// } -// ); - -// if (!response.ok) { -// throw new Error(`HTTP error! status: ${response.status}`); -// } - -// const data = await response.json(); -// return data; -// } catch (error) { -// console.error(`Failed to update cart in backend: ${error.message}`); -// } -// }; - -// const getCardQuantity = (cardInfo) => { -// const cartItem = cartData?.cart?.find((item) => item.id === cardInfo.id); -// return cartItem ? cartItem.quantity : 0; -// }; - -// const addOneToCart = async (cardInfo) => { -// const itemExistsInCart = cartData?.cart?.some( -// (item) => item.id === cardInfo.id -// ); - -// let updatedCart; -// if (itemExistsInCart) { -// updatedCart = -// cartData?.cart?.map((item) => { -// if (item.id === cardInfo.id) { -// return { -// ...item, -// quantity: item.quantity + 1, -// }; -// } -// return item; -// }) ?? []; -// } else { -// updatedCart = [...(cartData?.cart || []), { ...cardInfo, quantity: 1 }]; -// } - -// const newCartData = await updateCartInBackend(cartData?._id, updatedCart); -// setCartData(newCartData || []); -// }; - -// const removeOneFromCart = async (cardInfo) => { -// const quantity = getCardQuantity(cardInfo.id); -// const updatedCart = -// quantity === 1 -// ? cartData?.cart?.filter( -// (currentCard) => currentCard.id !== cardInfo.id -// ) ?? [] -// : cartData?.cart?.map((card) => -// card.id === cardInfo.id -// ? { ...card, quantity: card.quantity - 1 } -// : card -// ) ?? []; - -// const newCartData = await updateCartInBackend(cartData?._id, updatedCart); -// setCartData(newCartData || []); -// }; - -// const deleteFromCart = async (cardInfo) => { -// const updatedCart = -// cartData?.cart?.filter( -// (currentCard) => currentCard.cardId !== cardInfo.id -// ) ?? []; - -// const newCartData = await updateCartInBackend(cartData?._id, updatedCart); -// setCartData(newCartData || []); -// }; - -// const getTotalCost = () => { -// if (!cartData?.cart || cartData.cart.length === 0) { -// return 0; -// } - -// console.log('cartData:', cartData); -// return cartData.cart.reduce((totalCost, cartItem) => { -// const cardData = getCardData(cartItem.id); -// return totalCost + cardData.price * cartItem.quantity; -// }, 0); -// }; - -// const setCart = async (cart) => { -// const updatedCart = Array.isArray(cart) ? cart : []; -// setCartData(updatedCart); -// // update in backend -// const newCartData = await updateCartInBackend(cartData?._id, updatedCart); -// setCartData(newCartData || []); -// }; - -// const contextValue = { -// cart: cartData.cart, -// getCardQuantity, -// addOneToCart, -// createUserCart, -// removeOneFromCart, -// fetchUserCart, -// deleteFromCart, -// getTotalCost, -// setCart, -// loading, -// error, -// }; - -// return ( -// {children} -// ); -// }; - -// export default CartProvider; diff --git a/src/context/ChartContext/ChartContext.jsx b/src/context/ChartContext/ChartContext.jsx index d28e564..eefb826 100644 --- a/src/context/ChartContext/ChartContext.jsx +++ b/src/context/ChartContext/ChartContext.jsx @@ -24,7 +24,7 @@ export const ChartProvider = ({ children }) => { const currentValue = timeRanges.find((option) => option.value === timeRange); - console.log('currentValue: ', currentValue); + // console.log('currentValue: ', currentValue); const handleChange = (e) => { setTimeRange(e.target.value); }; diff --git a/src/context/CollectionContext/ChunkPaylod.jsx b/src/context/CollectionContext/ChunkPaylod.jsx deleted file mode 100644 index 81b3b17..0000000 --- a/src/context/CollectionContext/ChunkPaylod.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { fetchWrapper } from './collectionUtility'; - -const chunkSize = 1024 * 1024 * 10; // 10 MB, adjust as needed - -// Function to break the payload into smaller chunks -export const chunkPayload = (payload) => { - const stringPayload = JSON.stringify(payload); - const payloadSize = new TextEncoder().encode(stringPayload).length; - - if (payloadSize <= chunkSize) { - return [payload]; // If payload is small, no need to chunk - } - - // If payload is too large, chunk it - const chunks = []; - for (let i = 0; i < stringPayload.length; i += chunkSize) { - const chunkStr = stringPayload.slice(i, i + chunkSize); - chunks.push(JSON.parse(chunkStr)); - } - - return chunks; -}; - -// Function to send chunks to the server -export const sendChunks = async (endpoint, method, chunks) => { - for (const chunk of chunks) { - await fetchWrapper(endpoint, method, chunk); - } -}; diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index df096f3..f964a05 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -27,8 +27,6 @@ import { calculateCollectionValue, } from './collectionUtility.jsx'; import moment from 'moment'; -import { createNewDataSet } from './cardHelpers.jsx'; -import { chunkPayload, sendChunks } from './ChunkPaylod.jsx'; export const CollectionContext = createContext(defaultContextValue); @@ -240,49 +238,76 @@ export const CollectionProvider = ({ children }) => { } }; - const getUpdatedCards = (activeCollection, cardUpdate, operation) => { + const getUpdatedCards = (collection, cardUpdate, operation) => { let cardsToUpdate; switch (operation) { case 'add': - cardsToUpdate = handleCardAddition(activeCollection?.cards, cardUpdate); + cardsToUpdate = handleCardAddition(collection?.cards, cardUpdate); break; case 'remove': - cardsToUpdate = handleCardRemoval(activeCollection?.cards, cardUpdate); + cardsToUpdate = handleCardRemoval(collection?.cards, cardUpdate); + console.log('CARD REMOVAL:', cardUpdate); + console.log('CARD REMOVAL:', cardsToUpdate); break; case 'update': - // eslint-disable-next-line no-case-declarations - const cardIndex = activeCollection.cards.findIndex( - (c) => c.id === cardUpdate.id - ); - if (cardIndex === -1) { - console.error('Card not found in the collection.'); - return activeCollection.cards; + if (!collection?.cards) { + console.error('No cards found in the collection.'); + return collection?.cards; + } + if (!cardUpdate?.id) { + console.warn('Card ID is missing.', cardUpdate); + // return collection?.cards; } - - // eslint-disable-next-line no-case-declarations - const existingCard = activeCollection.cards[cardIndex]; - // eslint-disable-next-line no-case-declarations - const updatedPriceHistory = updatePriceHistory( - existingCard, - cardUpdate - ); // eslint-disable-next-line no-case-declarations - const updatedCard = getUpdatedCard( - existingCard, - cardUpdate, - updatedPriceHistory, - activeCollection._id - ); - cardsToUpdate = replaceCardInArray( - activeCollection.cards, - updatedCard, - cardIndex - ); + const cards = collection?.cards; + + for (let i = 0; i < cards?.length; i++) { + // eslint-disable-next-line no-case-declarations + // const cardIndex = selectedCollection?.cards?.findIndex( + // (c) => c?.id === cardUpdate?.id + // ); + if (!cards[i]?.id) { + console.warn('Card ID is missing.', cards[i]); + continue; + } + cardUpdate = cards[i]; + const cardIndex = selectedCollection?.cards?.findIndex( + (c) => c?.id === cardUpdate?.id + ); + + if (cardIndex === -1) { + console.error( + 'Card not found in the collection.', + collection?.cards[cardIndex] + ); + return collection?.cards; + } + + // eslint-disable-next-line no-case-declarations + const existingCard = collection?.cards[cardIndex]; + // eslint-disable-next-line no-case-declarations + const updatedPriceHistory = updatePriceHistory( + existingCard, + cardUpdate + ); + // eslint-disable-next-line no-case-declarations + const updatedCard = getUpdatedCard( + existingCard, + cardUpdate, + updatedPriceHistory, + collection?._id + ); + cardsToUpdate = replaceCardInArray( + collection?.cards, + updatedCard, + cardIndex + ); + } break; default: console.error('Unsupported operation:', operation); - return activeCollection.cards; + return collection?.cards; } return cardsToUpdate; @@ -306,7 +331,11 @@ export const CollectionProvider = ({ children }) => { quantity: update.quantity || card.quantity, collectionId: collectionId, totalPrice: cardPrice * (update.quantity || card.quantity), - lastSavedPrice: update.lastSavedPrice, + // lastSavedPrice: update.lastSavedPrice, + lastSavedPrice: { + num: card.price || card.card_prices[0].tcgplayer_price, + timestamp: new Date(), + }, latestPrice: update.latestPrice, tag: 'monitored', chart_datasets: [...(card.chart_datasets || []), newChartDataEntry], @@ -322,18 +351,18 @@ export const CollectionProvider = ({ children }) => { function updatePriceHistory(card, update) { const newPriceHistoryEntry = createPriceHistoryObject( - update.latestPrice.num + update?.latestPrice?.num ); const lastPriceHistoryEntry = - card.priceHistory[card.priceHistory.length - 1]; + card?.priceHistory[card?.priceHistory?.length - 1]; if ( !lastPriceHistoryEntry || - lastPriceHistoryEntry.num !== newPriceHistoryEntry.num + lastPriceHistoryEntry?.num !== newPriceHistoryEntry?.num ) { return [...card.priceHistory, newPriceHistoryEntry]; } - return card.priceHistory; + return card?.priceHistory; } function createChartDataEntry(price) { @@ -383,12 +412,16 @@ export const CollectionProvider = ({ children }) => { totalCost: updatedTotalPrice ? updatedTotalPrice.toString() : '0', totalQuantity: cards.reduce((acc, card) => acc + (card.quantity || 0), 0), quantity: cards.length, - // _id, + lastSavedPrice: { + num: collectionWithCards?.totalPrice || 0, + timestamp: collectionWithCards?.lastSavedPrice?.timeStamp || new Date(), + }, + latestPrice: { + num: updatedTotalPrice || 0, + timestamp: new Date(), + }, dailyPriceChange: getPriceChange(currentChartDataSets2)[0]?.priceChange || '', - currentChartDataSets: filterUniqueDataPoints( - getCurrentChartDataSets(updatedChartData) - ), currentChartDataSets2: filterUniqueDataPoints( transformPriceHistoryToXY(collectionPriceHistory) ), @@ -424,20 +457,6 @@ export const CollectionProvider = ({ children }) => { }; }; - const removeCardsFromCollection = async (userId, collectionId, cardIds) => { - const endpoint = `/api/${userId}/collections/${collectionId}/removeCards`; - const method = 'POST'; - const payload = { cardIds }; - - try { - const response = await fetchWrapper(endpoint, method, payload); - return response; // The response contains the updated cards list - } catch (error) { - console.error('Error removing cards:', error); - throw error; - } - }; - const getUpdatedCollection = async ( collectionWithCards, // updated cards cardUpdate, // updated card @@ -446,28 +465,55 @@ export const CollectionProvider = ({ children }) => { ) => { const collectionId = collectionWithCards?._id || collectionData?._id; if (!collectionId) { - console.error('Collection ID is missing.'); + console.error('Collection ID is missing.', collectionId); return; } - const cardExists = collectionWithCards.cards.some( - (card) => card.id === cardUpdate.id + if (!userId) { + console.error('User ID is missing.', userId); + return; + } + const cardExists = collectionWithCards?.cards?.some( + (card) => card?.id === cardUpdate?.id ); - // Determine the method and endpoint based on operation and card existence - const method = - operation === 'remove' ? 'DELETE' : cardExists ? 'PUT' : 'POST'; - const endpointSuffix = - operation === 'remove' ? 'removeCards' : 'updateCards'; + let multipleOfSameCard = []; + let cardQuantity = 0; + if (cardExists) { + multipleOfSameCard = collectionWithCards?.cards?.filter( + (card) => card?.id === cardUpdate?.id + ); + cardQuantity = multipleOfSameCard[0]?.quantity; + } + console.log('MULTIPLE OF SAME CARD:', multipleOfSameCard); + console.log('CARD QUANTITY:', cardQuantity); + console.log('CARD EXISTS:', cardExists); + + let method; + if (operation === 'remove' && cardQuantity === 1) { + method = 'DELETE'; + } else if (cardExists) { + method = 'PUT'; + } else { + method = 'POST'; + } + + let endpointSuffix; + if (operation === 'remove' && cardQuantity === 1) { + endpointSuffix = 'removeCards'; + } else { + endpointSuffix = 'updateCards'; + } + const endpoint = createApiUrl( `${userId}/collections/${collectionId}/${endpointSuffix}` ); - console.log('CARDS BEFORE: ', collectionWithCards); const updatedCards = getUpdatedCards( collectionWithCards, cardUpdate, operation + // collectionId ); console.log('CARDS AFTER: ', updatedCards); @@ -478,22 +524,50 @@ export const CollectionProvider = ({ children }) => { // (total, card) => total + card.price * card.quantity, // 0 // ); - const updatedTotalQuantity = updatedCards.reduce( - (total, card) => total + card.quantity, + const updatedTotalQuantity = updatedCards?.reduce( + (total, card) => total + card?.quantity, 0 ); const newCollectionPriceHistoryObject = createPriceHistoryObject(updatedTotalPrice); let cardsResponse; - // if (operation === 'remove') { - // const cardIds = updatedCards.map((card) => card.id); - // cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); - // } else { - // const cardsPayload = { cards: updatedCards }; - // cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); - // } - const cardsPayload = { cards: updatedCards }; + let cardsPayload; + if (operation === 'remove') { + const allCardsWithIds = []; + for (const card of updatedCards) { + // const cardIds = updatedCards.map((card) => card.id); + // const cardObjIds = updatedCards.map((card) => card._id); + const cardIds = { + id: card?.id, + _id: card?._id, + }; + + allCardsWithIds?.push(cardIds); + } + const removeCard = allCardsWithIds?.find( + (idPair) => idPair?.id === cardUpdate?.id + ); + cardsPayload = { cardIds: removeCard }; + } else { + const allCardsWithIds = []; + for (const card of updatedCards) { + // const cardIds = updatedCards.map((card) => card.id); + // const cardObjIds = updatedCards.map((card) => card._id); + const cardIds = { + id: card.id, + _id: card._id, + }; + + allCardsWithIds?.push(cardIds); + } + const removeCard = allCardsWithIds?.find( + (idPair) => idPair?.id === cardUpdate?.id + ); + cardsPayload = { cards: updatedCards, cardIds: removeCard }; + } + console.log('CARDS PAYLOAD:', cardsPayload); + cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); const { cardMessage } = cardsResponse; @@ -513,10 +587,6 @@ export const CollectionProvider = ({ children }) => { const { chartMessage } = chartDataResponse; - // const updatedTotalPrice = calculateCollectionValue(selectedCollection); - // console.log('NEW VALUE:', newVal); - - // Prepare the updated collection data const updatedCollection = getUpdatedCollectionData( selectedCollection, updatedTotalPrice, @@ -526,7 +596,6 @@ export const CollectionProvider = ({ children }) => { // updatedCards ); - // Update the collection const collectionEndpoint = createApiUrl( `${userId}/collections/${collectionId}` ); @@ -536,28 +605,6 @@ export const CollectionProvider = ({ children }) => { const { collectionMessage } = collectionResponse; - console.log('********** [--------------------] **********'); - console.log('********** [CARDS] **********'); - console.log(`********** [${cardMessage}] **********`); - console.log(`********** [${cardsResponse.cards}] **********`); - console.log('********** [--------------------] **********'); - console.log('********** [CHARTS] **********'); - console.log(`********** [${chartMessage}] **********`); - console.log(`********** [${chartDataResponse.chartData}] **********`); - console.log('********** [--------------------] **********'); - console.log('********** [COLLECTION] **********'); - console.log(`********** [${collectionMessage}] **********`); - console.log(`********** [${collectionResponse.collectionData}] **********`); - console.log('********** [--------------------] **********'); - - // Restructure the collection object - // Optionally, update context or state with the new collection data - // updateCollectionData(collectionResponse.collectionData, 'allCollections'); - // updateCollectionData( - // collectionResponse.collectionData, - // 'selectedCollection' - // ); - // updateCollectionData(collectionResponse.collectionData, 'collectionData'); setTotalPrice(calculateCollectionValue(updatedCards)); const restructuredCollection = { ...collectionResponse.collectionData, @@ -566,7 +613,6 @@ export const CollectionProvider = ({ children }) => { }; console.log('RESTRUCTURED COLLECTION:', restructuredCollection); - // Return the updated collection data along with responses for cards and chart data return { restructuredCollection, }; @@ -581,7 +627,6 @@ export const CollectionProvider = ({ children }) => { description, }; - // Update in the state first setSelectedCollection(updatedCollection); setAllCollections((prevCollections) => prevCollections.map((collection) => @@ -618,7 +663,7 @@ export const CollectionProvider = ({ children }) => { const handleCardOperation = async ( card, operation, - selectedCollection, + collection, userId, allCollections ) => { @@ -627,33 +672,15 @@ export const CollectionProvider = ({ children }) => { return; } - // Check if selectedCollection is defined, if not set to first collection in allCollections - if (!selectedCollection || !selectedCollection._id) { - if (allCollections && allCollections.length > 0) { - selectedCollection = allCollections[0]; - } else { - console.error('Selected collection or allCollections is empty.'); - return; // Stop execution if no valid collection is available - } + if (!collection) { + console.error( + `Collection with ID ${collection._id} not found in allCollections.` + ); + return; } - const collectionId = selectedCollection._id; - - const updatedCards = getUpdatedCards( - selectedCollection, - card, - operation, - collectionId - ); - - const collectionWithCards = { - ...selectedCollection, - cards: updatedCards, - _id: selectedCollection._id, - }; - const updatedCollection = await getUpdatedCollection( - collectionWithCards, + collection, card, operation, userId @@ -661,9 +688,21 @@ export const CollectionProvider = ({ children }) => { if (updatedCollection) { console.log('UPDATED COLLECTION:', updatedCollection); - updateCollectionData(updatedCollection, 'allCollections'); - updateCollectionData(updatedCollection, 'selectedCollection'); - updateCollectionData(updatedCollection, 'collectionData'); + // Update the relevant collections in the state/context + updateCollectionData( + updatedCollection?.restructuredCollection, + 'allCollections' + ); + if (collection._id === selectedCollection?._id) { + updateCollectionData( + updatedCollection?.restructuredCollection, + 'selectedCollection' + ); + } + updateCollectionData( + updatedCollection?.restructuredCollection, + 'collectionData' + ); } else { console.error('Failed to update collection.'); } @@ -683,7 +722,6 @@ export const CollectionProvider = ({ children }) => { xys: xyData || [], openChooseCollectionDialog, calculateCollectionValue, - // setAllCardPrices, setTotalPrice, setXyData, setUpdatedPricesFromCombinedContext, @@ -694,25 +732,19 @@ export const CollectionProvider = ({ children }) => { getTotalCost: () => getTotalCost(selectedCollection), getCardQuantity: (cardId) => selectedCollection?.cards?.find((c) => c?.id === cardId)?.quantity || 0, - createUserCollection: (userId, newCollectionInfo, name, description) => - createUserCollection(userId, newCollectionInfo, name, description), + createUserCollection, removeCollection, addOneToCollection: (card) => handleCardOperation(card, 'add', selectedCollection, userId), removeOneFromCollection: (card) => handleCardOperation(card, 'remove', selectedCollection, userId), - updateOneFromCollection: (card) => handleCardOperation(card, 'update'), - updateCollection: (collectionWithCards, card, operation, userId) => - getUpdatedCollection(collectionWithCards, card, operation, userId), - updateCollectionState: (collection) => updateCollectionData(collection), - updateCollectionDetails: (updatedInfo, userId, collectionId) => - updateCollectionDetails(updatedInfo, userId, collectionId), - // updateCollectionDetails: (collection) => { - // console.log('UPDATING COLLECTION DETAILS:', collection); - // updateCollectionData(collection, 'allCollections'); - // updateCollectionData(collection, 'selectedCollection'); - // updateCollectionData(collection, 'collectionData'); - // } + updateOneFromCollection: (card) => + handleCardOperation(card, 'update', selectedCollection, userId), + updateCollection: (card, operation, collection) => + handleCardOperation(card, operation, collection, userId), + getUpdatedCollection, + updateCollectionState: updateCollectionData, + updateCollectionDetails, fetchAllCollectionsForUser: fetchAndSetCollections, fetchAllCollections: fetchAndSetCollections, setSelectedCollection, @@ -742,13 +774,13 @@ export const CollectionProvider = ({ children }) => { if (userId) fetchAndSetCollections(); }, [userId]); - useEffect(() => { - if (selectedCollection?.chartData) - setCurrentChartDataSets( - getCurrentChartDataSets(selectedCollection?.chartData) - ); - console.log('CURRENT CHART DATASETS:', currentChartDataSets); - }, [selectedCollection]); + // useEffect(() => { + // if (selectedCollection?.chartData) + // // setCurrentChartDataSets( + // // getCurrentChartDataSets(selectedCollection?.chartData) + // // ); + // console.log('CURRENT CHART DATASETS:', currentChartDataSets); + // }, [selectedCollection]); useEffect(() => { if (selectedCollection?.cards) { @@ -763,32 +795,12 @@ export const CollectionProvider = ({ children }) => { } }, [allCollections, fetchAndSetCollections]); - // setCurrentChartDataSets2( - // transformPriceHistoryToXY(selectedCollection?.chartData) - // ); - // console.log('CURRENT CHART DATASETS 2:', currentChartDataSets2); - // } - useEffect(() => { if (selectedCollection === null || selectedCollection === undefined) { setSelectedCollection(allCollections?.[0]); } }, [userId]); - // setTotalPrice(calculateTotalPrice(selectedCollection)); - // console.log('TOTAL PRICE:', totalPrice); - // }, [selectedCollection, setTotalPrice]); - - // useEffect(() => { - // const updatedSelectedCollection - // { - // ...selectedCollection, - - // } - - // getUpdatedCollection - // }, [setAllCardPrices, setTotalPrice]); - return ( {children} diff --git a/src/context/CollectionContext/checkServerForUpdates.jsx b/src/context/CollectionContext/checkServerForUpdates.jsx deleted file mode 100644 index b622890..0000000 --- a/src/context/CollectionContext/checkServerForUpdates.jsx +++ /dev/null @@ -1,66 +0,0 @@ -// Client-side JavaScript (e.g., in a React component or similar) -import axios from 'axios'; -import { useContext, useState } from 'react'; -import { CollectionContext } from './CollectionContext'; -import { useCombinedContext } from '../CombinedProvider'; -import { createApiUrl, fetchWrapper } from './collectionUtility'; -import { useCookies } from 'react-cookie'; - -async function checkServerForUpdates() { - const { socket } = useCombinedContext(); - const [updateResponse, setUpdateResponse] = useState(null); - try { - // Send an API request to check for updates - socket.emit('UPDATE_REQUEST_CHECK', { - message: 'Checking for updates...', - }); - - socket.on('UPDATE_CHECK_INITIAL_RESPONSE', (response) => { - console.log('UPDATE_CHECK_INITIAL_RESPONSE', response); - setUpdateResponse(response.message); - }); - - socket.on('UPDATES_RECEIVED', (response) => { - console.log('UPDATE_CHECK_RESPONSE', response); - if (response.hasUpdates) { - // If there are updates, call your client-side update function - updateCollectionClientSide(response.data); - } - setUpdateResponse(response.message); - }); - } catch (error) { - console.error('Error checking for updates:', error); - } -} -setInterval(checkServerForUpdates, 300000); // Check every 5 minutes - -const updateCollectionClientSide = async (response) => { - const { collection, setCollection, updateCollectionState } = - useContext(CollectionContext); - try { - updateCollectionState(response); - // Now, send an API request to save the updated collection data - await saveUpdatedCollectionData(response); - } catch (error) { - console.error('Error updating collection client-side:', error); - } -}; - -const saveUpdatedCollectionData = async (response) => { - const [cookies] = useCookies(['user']); - const userId = cookies.user?.id; - const collectionId = response._id; - try { - const collectionEndpoint = createApiUrl( - `${userId}/collections/${collectionId || ''}` - ); - const collectionResponse = await fetchWrapper(collectionEndpoint, 'PUT', { - response, - }); - - console.log('collectionResponse', collectionResponse); - console.log('Collection data saved successfully'); - } catch (error) { - console.error('Error saving updated collection data:', error); - } -}; diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx index 3b2858f..93ee1af 100644 --- a/src/context/CollectionContext/collectionUtility.jsx +++ b/src/context/CollectionContext/collectionUtility.jsx @@ -9,12 +9,20 @@ const initialCollectionState = { quantity: 0, // Initialize as 0 if not provided totalQuantity: 0, // Initialize as 0 if not provided previousDayTotalPrice: 0, // Initialize as 0 if not provided + lastSavedPrice: { + num: 0, + timestamp: '', + }, // Initialize as 0 if not provided + latestPrice: { + num: 0, + timestamp: '', + }, // Initialize as 0 if not provided dailyPriceChange: 0, // Initialize as 0 if not provided priceDifference: 0, // Initialize as 0 if not provided priceChange: 0, // Initialize as 0 if not provided allCardPrices: [], // Initialize as empty array if not provided cards: [], // Initialize as empty array if not provided - currentChartDataSets: [], // Initialize as empty array if not provided + currentChartDataSets2: [], // Initialize as empty array if not provided xys: [], // Use defaultXyData or initialize as empty if not provided chartData: { name: '', // Initialize as empty string if not provided @@ -586,44 +594,87 @@ const constructPayloadWithDifferences = ( return { payload, nonMatchingKeys, typeMismatchContent }; // Return both the payload, the list of non-matching keys, and type mismatch messages }; -function getCurrentChartDataSets(chartData) { - if (!chartData || !chartData.datasets) { - console.error('Invalid or missing chart data'); - return []; - } +// function getCurrentChartDataSets(chartData) { +// if (!chartData || !chartData?.allXYValues) { +// console.error('Invalid or missing chart data'); +// return []; +// } - const currentChartDataSets = []; - - // Iterate over each dataset - chartData.datasets.forEach((dataset) => { - // Check if dataset has 'data' array - if (dataset.data && dataset.data.length > 0) { - dataset.data.forEach((dataEntry) => { - // Check if dataEntry has 'xys' array - if (dataEntry.xys && dataEntry.xys.length > 0) { - // Add both 'data' and 'label' from each 'xys' entry - currentChartDataSets.push( - ...dataEntry.xys.map((xy) => ({ - ...xy.data, // Spread the 'data' object - label: xy.label, // Include the 'label' - })) - ); - } - }); - } - }); +// const currentChartDataSets = []; + +// // Iterate over each dataset +// chartData.allXYValues.forEach((val) => { +// // Check if dataset has 'data' array +// if (dataset.data && dataset.data.length > 0) { +// dataset.data.forEach((dataEntry) => { +// // Check if dataEntry has 'xys' array +// if (dataEntry.xys && dataEntry.xys.length > 0) { +// // Add both 'data' and 'label' from each 'xys' entry +// currentChartDataSets.push( +// ...dataEntry.xys.map((xy) => ({ +// ...xy.data, // Spread the 'data' object +// label: xy.label, // Include the 'label' +// })) +// ); +// } +// }); +// } +// }); + +// return currentChartDataSets; +// } - return currentChartDataSets; -} +// function getCurrentChartDataSets(chartData) { +// if (!chartData || !chartData.datasets) { +// console.error('Invalid or missing chart data'); +// return []; +// } + +// const currentChartDataSets = []; + +// // Iterate over each dataset +// chartData.datasets.forEach((dataset) => { +// // Check if dataset has 'data' array +// if (dataset.data && dataset.data.length > 0) { +// dataset.data.forEach((dataEntry) => { +// // Check if dataEntry has 'xys' array +// if (dataEntry.xys && dataEntry.xys.length > 0) { +// // Add both 'data' and 'label' from each 'xys' entry +// currentChartDataSets.push( +// ...dataEntry.xys.map((xy) => ({ +// ...xy.data, // Spread the 'data' object +// label: xy.label, // Include the 'label' +// })) +// ); +// } +// }); +// } +// }); + +// return currentChartDataSets; +// } const calculateCollectionValue = (cards) => { - if (!cards?.cards && !Array.isArray(cards) && !cards?.name) { + 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; @@ -823,7 +874,7 @@ module.exports = { createPayload, logError, constructPayloadWithDifferences, - getCurrentChartDataSets, + // getCurrentChartDataSets, getPriceChange, getUpdatedChartData, mergeCards, diff --git a/src/context/CollectionContext/copy.js b/src/context/CollectionContext/copy.js deleted file mode 100644 index cb6ff73..0000000 --- a/src/context/CollectionContext/copy.js +++ /dev/null @@ -1,926 +0,0 @@ -// import React, { -// createContext, -// useState, -// useEffect, -// useCallback, -// useMemo, -// useContext, -// } from 'react'; -// import { useCookies } from 'react-cookie'; -// import { -// initialCollectionState, -// fetchWrapper, -// removeDuplicateCollections, -// calculateAndUpdateTotalPrice, -// calculateTotalPrice, -// getTotalCost, -// getCardPrice, -// } from './exampleImport.js'; -// import { useCombinedContext } from '../CombinedProvider.jsx'; -// import { useUserContext } from '../UserContext/UserContext.js'; -// import moment from 'moment'; -// // import { useUserContext } from '../UserContext/UserContext.js'; -// // 1. Define a default context value -// const defaultContextValue = { -// allCollections: [], -// allCardPrices: [], -// xy: [], -// selectedCollection: {}, -// collectionData: initialCollectionState, -// totalCost: 0, -// openChooseCollectionDialog: false, -// updatedPricesFromCombinedContext: {}, -// setUpdatedPricesFromCombinedContext: () => {}, -// setOpenChooseCollectionDialog: () => {}, -// calculateTotalPrice: () => {}, -// getTotalCost: () => {}, -// createUserCollection: () => {}, -// removeCollection: () => {}, -// fetchAllCollectionsForUser: () => {}, -// setSelectedCollection: () => {}, -// setAllCollections: () => {}, -// addOneToCollection: () => {}, -// removeOneFromCollection: () => {}, -// }; - -// // 2. Replace null with the default value when creating the context -// export const CollectionContext = createContext(defaultContextValue); -// const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`; - -// const filterOutDuplicateYValues = (datasets) => { -// // console.log('DATASETS:', datasets); -// const seenYValues = new Set(); -// return datasets?.filter((data) => { -// const yValue = data?.y; -// if (seenYValues.has(yValue)) { -// return false; -// } -// seenYValues.add(yValue); -// return true; -// }); -// }; - -// const transformChartData = (chartData) => { -// let pointsArray = []; - -// if (Array.isArray(chartData?.datasets)) { -// chartData?.datasets?.forEach((dataset) => { -// dataset.data?.forEach((dataEntry) => { -// dataEntry.xys?.forEach((xyEntry) => { -// const { x, y } = xyEntry.data; -// if (x && y !== undefined) { -// pointsArray.push({ x, y }); -// } -// }); -// }); -// }); -// } else { -// console.error( -// 'Expected chartData.datasets to be an array, but got:', -// chartData -// ); -// } - -// return pointsArray; -// }; - -// function convertData(originalData) { -// let finalDataForChart = []; - -// const { datasets } = originalData; - -// if (Array.isArray(datasets) && datasets.length > 0) { -// datasets.forEach((dataset, index) => { -// // Loop over all datasets, not just the last one -// if (Array.isArray(dataset.data) && dataset.data.length > 0) { -// dataset.data.forEach((dataEntry) => { -// dataEntry.xys?.forEach((xyEntry) => { -// const { label, data } = xyEntry; -// // Assume that finalDataForChart has an array for each label -// finalDataForChart[label] = finalDataForChart[label] || []; - -// data.forEach(({ x, y }) => { -// if (x && y !== undefined) { -// finalDataForChart[label].push({ x, y }); -// } -// }); -// }); -// }); -// } -// }); -// } - -// // Convert the data into the format expected by Nivo -// const nivoData = Object.keys(finalDataForChart).map((label) => ({ -// id: label, -// data: finalDataForChart[label], -// })); - -// return { -// ...originalData, -// finalDataForChart: nivoData, // Replace this line to return Nivo-formatted data -// }; -// } - -// const isEmpty = (obj) => { -// return ( -// [Object, Array].includes((obj || {}).constructor) && -// !Object.entries(obj || {}).length -// ); -// }; -// const validateData = (data, eventName, functionName) => { -// const dataType = typeof data; -// console.log( -// `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` -// ); -// if (data === null || data === undefined) { -// console.warn( -// `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` -// ); -// return false; -// } -// if (isEmpty(data)) { -// console.error( -// `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` -// ); -// return false; -// } -// return true; -// }; - -// const handleCardAddition = (currentCards, cardToAdd) => { -// // Check if the card already exists in the currentCards array -// const cardToAddId = -// typeof cardToAdd.id === 'number' ? String(cardToAdd.id) : cardToAdd.id; - -// const matchingCard = currentCards.find((c) => c.id === cardToAddId); - -// if (matchingCard) { -// matchingCard.quantity++; -// return [...currentCards]; -// } else { -// return [...currentCards, { ...cardToAdd, id: cardToAddId, quantity: 1 }]; -// } -// }; - -// const handleCardRemoval = (currentCards, cardToRemove) => { -// // Convert the cardToRemove's id to a string if it's a number -// const cardToRemoveId = -// typeof cardToRemove.id === 'number' -// ? String(cardToRemove.id) -// : cardToRemove.id; - -// const matchingCard = currentCards.find((c) => c.id === cardToRemoveId); - -// if (!matchingCard) { -// console.error('Card not found in the collection.'); -// return [...currentCards]; -// } - -// if (matchingCard.quantity > 1) { -// matchingCard.quantity--; -// return [...currentCards]; -// } else { -// return currentCards.filter((card) => card.id !== cardToRemoveId); -// } -// }; -// const calculateTotalFromAllCardPrices = (allCardPrices) => { -// if (!Array.isArray(allCardPrices)) return 0; -// return allCardPrices.reduce( -// (total, price) => total + ensureNumber(price, 0), -// 0 -// ); -// }; -// const ensureNumber = (value, defaultValue = 0) => { -// let num = parseFloat(value); -// return isNaN(num) ? defaultValue : num; -// }; -// const HTTP_METHODS = { -// POST: 'POST', -// PUT: 'PUT', -// GET: 'GET', -// DELETE: 'DELETE', -// }; -// const handleError = (error) => { -// console.error(`An error occurred: ${error.message}`); -// }; -// const findCollectionIndex = useCallback( -// (collections, id) => -// collections?.findIndex((collection) => collection?._id === id) ?? -1, -// [] -// ); -// const apiRequest = async (userId, endpoint, method, data) => { -// try { -// const url = createApiUrl(endpoint); -// const response = await fetchWrapper(url, method, data); - -// if (!response || response.error) { -// throw new Error( -// response ? response.error.message : 'Failed to connect to the server.' -// ); -// } - -// return handleApiResponse(response, method); -// } catch (error) { -// console.error(`API Request failed: ${error.message}`); -// return null; -// } -// }; -// const createApiUrl = (path) => `${BASE_API_URL}/${path}`; -// const handleApiResponse = (response, method) => { -// // Handling POST requests -// if (method === 'POST' && response?.data?.newCollection) { -// return response.data.newCollection; -// } - -// // Handling PUT requests (updating a collection) -// if (method === 'PUT' && response.data?.data?.updatedCollection) { -// return response.data.data.updatedCollection; -// } - -// throw new Error('Unexpected response format'); -// }; -// const formatDate = () => moment().format('YYYY-MM-DD HH:mm'); -// // Utility function to filter unique X values -// const filterUniqueXValues = (values) => { -// const uniqueXValues = new Set(); -// return values.filter((entry) => { -// if (!uniqueXValues.has(entry.x)) { -// uniqueXValues.add(entry.x); -// return true; -// } -// return false; -// }); -// }; -// const getUniqueFilteredXYValues = (allXYValues) => { -// const uniqueXValues = new Set(); - -// return allXYValues -// .filter((entry) => entry.y !== 0) -// .filter((entry) => { -// if (!uniqueXValues.has(entry.x)) { -// uniqueXValues.add(entry.x); -// return true; -// } -// return false; -// }); -// }; -// const safeFetch = async (url, method, payload) => { -// try { -// const response = await fetchWrapper(url, method, payload); -// return handleApiResponse(response, method); -// } catch (error) { -// handleError(error); -// return null; -// } -// }; -// const fetchAndUpdateCollectionData = async ( -// endpoint, -// method, -// payload, -// setCollectionFn, -// setErrorFn -// ) => { -// try { -// const response = await fetchWrapper(endpoint, method, payload); -// const data = handleApiResponse(response, method); - -// if (data) { -// setCollectionFn(data); -// } else { -// throw new Error('Data is null'); -// } -// } catch (error) { -// setErrorFn(`Failed to process the request: ${error.message}`); -// } -// }; - -// const apiRequestWrapper = (userId, endpoint, method, data) => { -// return apiRequest(userId, `${userId}/collections${endpoint}`, method, data); -// }; - -// const updateCollectionData = (newData, updaterFn) => { -// updaterFn((prevData) => { -// const index = findCollectionIndex(prevData, newData._id); -// return index === -1 -// ? [...prevData, newData] -// : Object.assign([...prevData], { [index]: newData }); -// }); -// }; - -// const CollectionHandler = () => { -// // const { cardPrices } = useCombinedContext(); -// const [cookies] = useCookies(['user']); -// const { triggerCronJob } = useUserContext(); -// const [collectionData, setCollectionData] = useState(initialCollectionState); -// const [allCollections, setAllCollections] = useState([]); -// const [allCardPrices, setAllCardPrices] = useState([]); -// const [xyData, setXyData] = useState([ -// // { -// // label: '', -// // data: [], -// // }, -// ]); // New state to hold xy data -// // const [updatedPrices, setUpdatedPrices] = useState([]); -// const [ -// updatedPricesFromCombinedContext, -// setUpdatedPricesFromCombinedContext, -// ] = useState({}); -// const [selectedCollection, setSelectedCollection] = useState({}); -// const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = -// useState(false); -// const chartData = selectedCollection?.chartData || {}; -// // const datasets = chartData?.datasets || []; -// const userId = cookies.user?.id; -// const totalCost = useMemo( -// () => getTotalCost(selectedCollection), -// [selectedCollection] -// ); -// // [COLLECTION RETRIEVAL] -// const fetchCollections = useCallback( -// async (userId) => { -// fetchAndUpdateCollectionData( -// `${BASE_API_URL}/${userId}/collections`, -// HTTP_METHODS.GET, -// null, -// setAllCollections, -// handleError -// ); -// }, -// [setAllCollections, handleError] -// ); -// const useCollection = (userId) => { -// const [collections, setCollections] = useState(null); - -// const fetchAndSetCollections = useCallback(async () => { -// const data = await safeFetch( -// `${BASE_API_URL}/${userId}/collections`, -// HTTP_METHODS.GET -// ); -// if (data) setCollections(data.allCollections); -// }, [userId]); - -// return [collections, fetchAndSetCollections]; -// }; -// // ... Other Collection related methods - -// // [COLLECTION CREATION & DELETION] -// const useCollectionCrud = (userId) => { -// const [selectedCollection, setSelectedCollection] = useState(null); -// const [allCollections, setAllCollections] = useState([]); - -// const createUserCollection = useCallback( -// async (newCollectionInfo) => { -// const payload = { ...newCollectionInfo, userId, totalCost: 0, totalPrice: 0 }; -// const response = await safeFetch( -// createApiUrl(`${userId}/collections/newCollection/${userId}`), -// 'POST', -// payload -// ); -// if (response) performCollectionUpdate(response, 'create'); -// }, -// [userId] -// ); - -// const removeCollection = useCallback( -// async (collectionId) => { -// await safeFetch( -// createApiUrl(`${userId}/collections/${collectionId}`), -// 'DELETE' -// ); -// setAllCollections((prev) => prev.filter((item) => item._id !== collectionId)); -// }, -// [userId] -// ); -// const handleCollection = useCallback( -// async (method, payload, collectionId = '') => { -// const endpoint = collectionId -// ? `${userId}/collections/${collectionId}` -// : `${userId}/collections`; -// fetchAndUpdateCollectionData( -// endpoint, -// method, -// payload, -// setAllCollections, -// handleError -// ); -// }, -// [userId, setAllCollections, handleError] -// ); -// return [createUserCollection, removeCollection]; -// }; -// // [CARD ADDITION & REMOVAL] -// const addOrRemoveCard = useCallback( -// async (card, cardInfo, operation) => { -// // Your previous logic here. - -// const collectionId = selectedCollection._id || allCollections[0]._id; -// if (!collectionId) { -// // Handle error -// return; -// } - -// const updateInfo = getUpdateInfo(card, cardInfo, operation); // Assume this function is implemented -// const method = operation === 'create' ? 'POST' : 'PUT'; - -// fetchAndUpdateCollectionData( -// `${userId}/collections/${collectionId}`, -// method, -// updateInfo, -// setSelectedCollection, -// handleError -// ); -// }, -// [userId, setSelectedCollection, handleError] -// ); -// const useCardManager = (userId, selectedCollection, allCollections) => { -// const addOrRemoveCard = useCallback( -// async (card, operation) => { -// // ... (your existing logic here, try to use helper methods) -// await performCollectionUpdate(updatedCollection, 'update'); -// }, -// [userId, selectedCollection, allCollections] -// ); - -// return [addOrRemoveCard]; -// }; - -// // [COLLECTION DATA UPDATE] -// const performCollectionUpdate = useCallback( -// async (updateInfo, operation) => { -// const method = operation === 'create' ? 'POST' : 'PUT'; -// const endpoint = updateInfo._id ? `/${updateInfo._id}` : ''; -// const updatedCollection = await apiRequestWrapper( -// userId, -// endpoint, -// method, -// updateInfo -// ); -// if (updatedCollection) { -// updateCollectionData(updatedCollection, setAllCollections); -// updateCollectionData(updatedCollection, setSelectedCollection); -// } -// }, -// [userId, setAllCollections, setSelectedCollection] -// ); -// return ( -// // Your JSX components here -// ); -// }; -// // [COLLECTION RETRIEVAL] -// // const fetchCollections = useCallback( -// // async (userId) => { -// // fetchAndUpdateCollectionData( -// // `${BASE_API_URL}/${userId}/collections`, -// // HTTP_METHODS.GET, -// // null, -// // setAllCollections, -// // handleError -// // ); -// // }, -// // [setAllCollections, handleError] -// // ); -// // const setCollections = useCallback((collections) => { -// // try { -// // if (!collections || !Array.isArray(collections)) -// // throw new Error('Invalid collections array'); - -// // // Other logic for setting collections -// // } catch (error) { -// // handleError(error); -// // } -// // }, []); -// // const fetchAndSetCollections = useCallback(async () => { -// // try { -// // const collections = await fetchCollections(userId); -// // if (collections) setCollections(collections); -// // } catch (error) { -// // handleError(error); -// // } -// // }, [userId, fetchCollections, setCollections]); -// // // [COLLECTION CREATION & DELETION] -// // const createUserCollection = async ( -// // userId, -// // newCollectionInfo, -// // name, -// // description -// // ) => { -// // if (!userId) { -// // console.error('User ID is undefined.'); -// // return; -// // } -// // if ( -// // !validateData( -// // newCollectionInfo, -// // 'Create User Collection', -// // 'createUserCollection' -// // ) -// // ) { -// // console.error('Validation failed for collection data.'); -// // return; -// // } - -// // const payload = { -// // name: name || newCollectionInfo.name, -// // description: description || newCollectionInfo.description, -// // userId: userId || newCollectionInfo.userId, -// // totalCost: 0, -// // totalPrice: 0, -// // quantity: 0, -// // totalQuantity: 0, -// // xys: xyData || [], -// // allCardPrices: [], -// // cards: [], -// // chartData: {}, -// // }; - -// // try { -// // const url = createApiUrl(`${userId}/collections/newCollection/${userId}`); -// // const response = await fetchWrapper(url, 'POST', payload); - -// // if (!response) { -// // console.error('Failed to connect to the server.'); -// // return; -// // } - -// // if (response.error) { -// // console.error( -// // `Failed to create a new collection: ${response.error.message}` -// // ); -// // return; -// // } - -// // const savedCollection = handleApiResponse(response, 'POST'); -// // await performCollectionUpdate(payload, 'create'); - -// // // updateCollectionData(savedCollection, 'allCollections'); -// // // updateCollectionData(savedCollection, 'collectionData'); -// // } catch (error) { -// // console.error(`Failed to create a new collection: ${error.message}`); -// // } -// // }; -// // const removeCollection = async (collection) => { -// // if (!collection._id) { -// // console.error('Collection ID is undefined.'); -// // return; -// // } - -// // try { -// // const url = createApiUrl(`${userId}/collections/${collection._id}`); -// // const response = await fetchWrapper(url, 'DELETE'); - -// // if (response.error) { -// // console.error('Failed to delete the collection:', response.error); -// // return; -// // } - -// // setAllCollections((prev) => -// // prev.filter((item) => item._id !== collection._id) -// // ); - -// // if (selectedCollection._id === collection._id) { -// // setSelectedCollection(initialCollectionState); -// // setCollectionData(initialCollectionState); -// // } -// // } catch (error) { -// // console.error(`Failed to delete the collection: ${error.message}`); -// // } -// // }; -// // // const handleCollection = useCallback( -// // // async (method, payload, collectionId = '') => { -// // // const endpoint = collectionId -// // // ? `${userId}/collections/${collectionId}` -// // // : `${userId}/collections`; -// // // fetchAndUpdateCollectionData( -// // // endpoint, -// // // method, -// // // payload, -// // // setAllCollections, -// // // handleError -// // // ); -// // // }, -// // // [userId, setAllCollections, handleError] -// // // ); -// // // [CARD ADDITION & REMOVAL] -// // const getUpdatedCards = (activeCollection, card, operation) => { -// // const handler = -// // operation === 'add' ? handleCardAddition : handleCardRemoval; -// // const updatedCards = handler(activeCollection?.cards, card); - -// // return updatedCards.map((card) => { -// // const cardPrice = card.card_prices?.[0]?.tcgplayer_price; -// // const computedPrice = cardPrice * card.quantity; - -// // const newDataset = { x: formatDate(), y: computedPrice }; -// // card.chart_datasets = filterUniqueXValues([ -// // ...(card?.chart_datasets || []), -// // newDataset, -// // ]); - -// // card.price = cardPrice; -// // card.totalPrice = computedPrice; -// // return card; -// // }); -// // }; -// // const getNewChartData = (activeCollection, updatedPrice, newDataSet) => { -// // const combinedXYValues = [ -// // ...(selectedCollection?.chartData?.datasets?.flatMap( -// // (dataset) => dataset.data -// // ) || []), -// // newDataSet.data[0].xy, -// // ]; - -// // const filteredXYValues = getUniqueFilteredXYValues(combinedXYValues); - -// // return { -// // name: `Chart for Collection: ${activeCollection?.name}`, -// // userId: userId, -// // updatedPrice: updatedPrice, -// // xys: xyData || [], -// // datasets: [ -// // ...(selectedCollection?.chartData?.datasets || []), -// // newDataSet, -// // ], -// // allXYValues: filteredXYValues, -// // }; -// // }; -// // // const addOrRemoveCard = useCallback( -// // // async (card, cardInfo, operation) => { -// // // // Your previous logic here. - -// // // const collectionId = selectedCollection._id || allCollections[0]._id; -// // // if (!collectionId) { -// // // // Handle error -// // // return; -// // // } - -// // // const updateInfo = getUpdateInfo(card, cardInfo, operation); // Assume this function is implemented -// // // const method = operation === 'create' ? 'POST' : 'PUT'; - -// // // fetchAndUpdateCollectionData( -// // // `${userId}/collections/${collectionId}`, -// // // method, -// // // updateInfo, -// // // setSelectedCollection, -// // // handleError -// // // ); -// // // }, -// // // [userId, setSelectedCollection, handleError] -// // // ); -// // // // [COLLECTION DATA UPDATE] -// // // const performCollectionUpdate = useCallback( -// // // async (updateInfo, operation) => { -// // // const method = operation === 'create' ? 'POST' : 'PUT'; -// // // const endpoint = updateInfo._id ? `/${updateInfo._id}` : ''; -// // // const updatedCollection = await apiRequestWrapper( -// // // userId, -// // // endpoint, -// // // method, -// // // updateInfo -// // // ); -// // // if (updatedCollection) { -// // // updateCollectionData(updatedCollection, setAllCollections); -// // // updateCollectionData(updatedCollection, setSelectedCollection); -// // // } -// // // }, -// // // [userId, setAllCollections, setSelectedCollection] -// // // ); -// // const updateCollection = (newData, collectionType, updaterFn) => { -// // switch (collectionType) { -// // case 'allCollections': -// // return updaterFn((prevCollections = []) => { -// // const existingIndex = findCollectionIndex( -// // prevCollections, -// // newData?._id -// // ); -// // return existingIndex === -1 -// // ? [...prevCollections, newData] -// // : Object.assign([...prevCollections], { [existingIndex]: newData }); -// // }); -// // case 'selectedCollection': -// // case 'collectionData': -// // return updaterFn(newData); -// // default: -// // break; -// // } -// // }; -// // const updateActiveCollection = useCallback( -// // async (collectionData, existingChartData = {}) => { -// // const isCreatingNew = !collectionData?._id; -// // const isNewCollectionEndpoint = -// // collectionData?.endpoint === 'newCollection'; // Add this line to check if it's the newcollection endpoint -// // const endpoint = isCreatingNew -// // ? createApiUrl(`${userId}/collections`) -// // : createApiUrl(`${userId}/collections/${collectionData._id}`); -// // let method = isCreatingNew ? 'POST' : 'PUT'; // Default setting -// // console.log(`Debug: Fetching ${method} ${endpoint}`); // Debugging log - -// // // Restrict to only POST for the 'newcollection' endpoint -// // if (!isCreatingNew && isNewCollectionEndpoint) { -// // method = 'POST'; -// // } -// // try { -// // const response = await fetchWrapper(endpoint, method, collectionData); -// // const updatedCollection = handleApiResponse(response, method); - -// // if (!isCreatingNew && !updatedCollection) { -// // throw new Error('Failed to update the existing collection'); -// // } -// // const newChartData = { -// // ...updatedCollection.chartData, -// // xys: [ -// // ...(existingChartData.xys || []), // Spread existing xys data -// // { -// // label: `Update Number ${ -// // updatedCollection?.chartData?.datasets?.length + 1 || 1 -// // }`, -// // data: [ -// // ...(existingChartData.data || []), // Spread existing data -// // { -// // x: moment().format('YYYY-MM-DD HH:mm'), -// // y: updatedCollection.totalPrice, -// // }, -// // ], -// // }, -// // ], -// // datasets: [ -// // ...(updatedCollection.chartData?.datasets || []), -// // { -// // data: [ -// // { -// // xys: [ -// // { -// // label: `Update Number ${ -// // updatedCollection?.chartData?.datasets?.length + 1 || 1 -// // }`, -// // data: [ -// // { -// // x: moment().format('YYYY-MM-DD HH:mm'), -// // y: updatedCollection.totalPrice, -// // }, -// // ], -// // }, -// // ], -// // additionalPriceData: { -// // priceChanged: false, -// // initialPrice: -// // updatedCollection.chartData?.updatedPrice || 0, -// // updatedPrice: updatedCollection.totalPrice, -// // priceDifference: 0, -// // priceChange: 0, -// // }, -// // }, -// // ], -// // }, -// // ], -// // }; -// // updatedCollection.chartData = newChartData; - -// // const convertedData = convertData(newChartData); -// // updatedCollection.xys = convertedData; -// // // setXyData(convertedData.finalDataForChart); -// // xyData.push(...convertedData.finalDataForChart); // Spread to add to existing array -// // updateCollectionData(updatedCollection, 'selectedCollection'); -// // updateCollectionData(updatedCollection, 'allCollections'); -// // } catch (error) { -// // console.error(`Failed to update the collection: ${error.message}`); -// // } -// // }, -// // [userId, updateCollectionData] -// // ); -// // console.log( -// // '<----------$$$$$$$$$CONVERTED DATA FOR CHART$$$$$$$$$---------->', -// // xyData -// // ); -// // useEffect(() => { -// // // Check if the prices are updated or new cards are added -// // const updatedPricesArray = -// // updatedPricesFromCombinedContext?.updatedPrices || []; - -// // if (!Array.isArray(updatedPricesArray)) { -// // return; // Exit the useEffect early if not an array -// // } - -// useEffect(() => { -// const updatedPricesArray = Object.keys( -// updatedPricesFromCombinedContext || {} -// ).map((cardId) => updatedPricesFromCombinedContext[cardId]); - -// console.log( -// '[1][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][UPDATED PRICES]==========>', -// updatedPricesArray -// ); - -// const updatedCardPrices = []; - -// updatedPricesArray.forEach((card) => { -// const currentCardPrice = selectedCollection?.cards[card?.id]?.price; - -// // Check if this is the special tagged card -// if (card._tag === 'updated') { -// console.log('Found the special card:', card); -// } - -// if (card?.updatedPrice !== currentCardPrice) { -// updatedCardPrices.push(card); -// console.log( -// '[2][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][CARD]==========>', -// card -// ); -// console.log( -// 'CARD FROM SELECTED COLLECTIONS:', -// selectedCollection.cards[card.id] -// ); -// } else { -// console.log('Price has not been updated for card with ID:', card.id); -// } -// }); - -// if (updatedCardPrices.length > 0) { -// updatedCardPrices.forEach((card) => { -// addOrRemoveCard(card, { updated: true }, 'update'); -// }); -// } -// }, [updatedPricesFromCombinedContext]); - -// const contextValue = useMemo( -// () => ({ -// // DATA -// allCollections, -// selectedCollection, -// collectionData, -// totalCost, - -// allCardPrices: selectedCollection?.allCardPrices || [], -// xys: xyData || [], -// openChooseCollectionDialog, -// updatedPricesFromCombinedContext, -// setUpdatedPricesFromCombinedContext: (updatedPrices) => { -// // This is the function that will be passed to the combined context to update the prices -// setUpdatedPricesFromCombinedContext(updatedPrices); -// }, -// setOpenChooseCollectionDialog, -// // FUNCTIONS -// calculateTotalPrice: () => getCardPrice(selectedCollection), -// getTotalCost: () => getTotalCost(selectedCollection), -// // FUNCTIONS -// createUserCollection: (userId, newCollectionInfo) => -// createUserCollection( -// userId, -// newCollectionInfo, -// newCollectionInfo.name, -// newCollectionInfo.description -// ), -// removeCollection: (collection) => removeCollection(collection), -// fetchAllCollectionsForUser: fetchAndSetCollections, -// setSelectedCollection: updateActiveCollection, -// setAllCollections: (collections) => setAllCollections(collections), -// addOneToCollection: (card, cardInfo) => -// addOrRemoveCard(card, cardInfo, 'add'), -// removeOneFromCollection: (card) => addOrRemoveCard(card, null, 'remove'), -// }), -// [allCollections, selectedCollection, totalCost] -// ); - -// useEffect(() => { -// console.log('COLLECTION CONTEXT: ', { -// contextValue, -// }); -// }, [contextValue, updatedPricesFromCombinedContext]); -// // Assuming updatedPrices is passed as a prop or state - -// useEffect(() => { -// if (selectedCollection && totalCost) { -// // Trigger the cron job whenever the selectedCollection changes -// triggerCronJob(); -// } -// }, [selectedCollection, triggerCronJob, totalCost]); - -// useEffect(() => { -// console.log('Total Cost has been updated:', totalCost); -// }, [totalCost]); - -// useEffect(() => { -// if (userId) fetchAndSetCollections(); -// }, [userId]); - -// return ( -// -// {children} -// -// ); -// }; - -// // useCollectionStore.js -// // import { useContext } from 'react'; -// // import { CollectionContext } from '../CollectionContext/CollectionContext'; - -// export const useCollectionStore = () => { -// const context = useContext(CollectionContext); -// if (!context) { -// throw new Error( -// 'useCollectionStore must be used within a CollectionProvider' -// ); -// } -// return context; -// }; diff --git a/src/context/CollectionContext/exampleImport.js b/src/context/CollectionContext/exampleImport.js deleted file mode 100644 index 68aebb7..0000000 --- a/src/context/CollectionContext/exampleImport.js +++ /dev/null @@ -1,123 +0,0 @@ -// // Initial collection state -// export const initialCollectionState = { -// _id: '', -// cards: [], -// quantity: 0, -// totalPrice: 0, -// }; - -// // Fetch wrapper function -// // export const fetchWrapper = async (url, method, body = null) => { -// // try { -// // const options = { -// // method, -// // headers: { 'Content-Type': 'application/json' }, -// // ...(body && { body: JSON.stringify(body) }), -// // }; -// // const response = await fetch(url, options); -// // // console.log('RESPONSE:', response); -// // if (!response.ok) { -// // const message = `HTTP error! status: ${response.status}`; -// // console.error(message); -// // throw new Error(message); -// // } -// // return await response.json(); -// // } catch (error) { -// // console.error('Fetch Error:', error); -// // throw error; -// // // if (!response.ok) { -// // // throw new Error(`HTTP error! status: ${response.status}`); -// // // } -// // // return await response.json(); -// // } -// // }; - -// // Remove duplicate collections -// // export const removeDuplicateCollections = (collections) => { -// // const uniqueCollections = {}; -// // collections?.forEach((collection) => { -// // uniqueCollections[collection._id] = collection; -// // }); -// // return Object.values(uniqueCollections); -// // }; - -// // Calculate and update total price -// export const calculateAndUpdateTotalPrice = (collection) => { -// if (!collection || !collection.cards) return 0; -// return collection.cards.reduce( -// (total, card) => total + card.price * card.quantity, -// 0 -// ); -// }; - -// // Format card data -// export const formatCardData = (card) => ({ -// id: card.id, -// ...Object.fromEntries( -// [ -// 'name', -// 'type', -// 'frameType', -// 'description', -// 'card_images', -// 'archetype', -// 'atk', -// 'def', -// 'level', -// 'race', -// 'attribute', -// 'quantity', -// ].map((key) => [key, card[key] || null]) -// ), -// }); - -// // Get card quantity from a collection -// export const getCardQuantity = (collectionId, allCollections) => { -// const collection = allCollections?.find((item) => item._id === collectionId); -// if (!collection) return 0; -// return collection.cards.reduce((acc, card) => acc + card.quantity, 0); -// }; - -// // Calculate total cost of the selected collection -// // export const getTotalCost = (selectedCollection) => { -// // if (!selectedCollection?.cards) return 0; -// // return selectedCollection.cards.reduce((total, card) => { -// // const price = card.card_prices?.[0]?.tcgplayer_price; -// // return price ? total + parseFloat(price) : total; -// // }, 0); -// // }; -// export const getTotalCost = (selectedCollection) => { -// if (!selectedCollection || !Array.isArray(selectedCollection.cards)) return 0; - -// return selectedCollection.cards.reduce((total, card) => { -// const cardPrice = -// (card.card_prices && card.card_prices[0]?.tcgplayer_price) || 0; -// return total + cardPrice * card.quantity; -// }, 0); -// }; - -// export const getCardPrice = (collection) => -// console.log('CARD:', collection) || -// parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); - -// // Function to calculate total price of a collection -// export const calculateTotalPrice = (collection) => { -// // Assuming collection is an object where each key-value pair is cardId-price -// return Object.allCardPrices(collection).reduce; -// }; -// // const getCollectionQuantity = useCallback(() => { -// // return ( -// // selectedCollection?.cards?.reduce( -// // (acc, card) => acc + card.quantity, -// // 0 -// // ) || 0 -// // ); -// // }, [selectedCollection]); - -// // getCardPrice(selectedCollection); -// // console.log( -// // 'SELECTED COLLECTION CARD PRICE:', -// // getCardPrice( -// // selectedCollection?.cards?.find((card) => card.id === collectionData.id) -// // ) -// // ); diff --git a/src/context/CollectionContext/fml.jsx b/src/context/CollectionContext/fml.jsx deleted file mode 100644 index 439993d..0000000 --- a/src/context/CollectionContext/fml.jsx +++ /dev/null @@ -1,1389 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ - -import React, { - createContext, - useState, - useEffect, - useCallback, - useMemo, - useContext, - useRef, -} from 'react'; -import { useCookies } from 'react-cookie'; -import { - filterOutDuplicateYValues, - handleCardAddition, - handleCardRemoval, - getUniqueFilteredXYValues, - calculateTotalFromAllCardPrices, - ensureNumber, - findCollectionIndex, - createApiUrl, - handleApiResponse, - BASE_API_URL, - fetchWrapper, - removeDuplicateCollections, - getTotalCost, - initialCollectionState, - getCardPrice, - defaultContextValue, - validateUserIdAndData, - createPayload, - logError, - determineHttpMethod, -} from './collectionUtility.jsx'; -import { useCombinedContext } from '../CombinedProvider.jsx'; -import { useUserContext } from '../UserContext/UserContext.js'; -import moment from 'moment'; -import CustomLogger from '../CutstomLogger.jsx'; -import { - createNewDataSet, - getCollectionId, - // updateCardInCollection, -} from './cardHelpers.jsx'; - -export const CollectionContext = createContext(defaultContextValue); - -const constructPayloadWithDifferences = (existingData, newData) => { - const payload = {}; - let logContent = - '[constructPayloadWithDifferences] -----> Differences found in collection data:\n'; - - Object.keys(newData).forEach((key) => { - if (newData[key] !== existingData[key]) { - payload[key] = newData[key]; - - logContent += ` - Field "${key}":\n`; - logContent += ` Old Value: ${JSON.stringify(existingData[key])}\n`; - logContent += ` New Value: ${JSON.stringify(newData[key])}\n`; - } - }); - - if (Object.keys(payload).length > 0) { - console.log('Payload with differences:', payload); - } else { - console.log('No differences found between existing and new data.'); - } - - return payload; -}; - -const updateCardInCollection = (cards, cardToUpdate) => { - // Validate that cards is an array - if (!Array.isArray(cards)) { - throw new TypeError('The first argument must be an array of cards.'); - } - - // Validate that cardToUpdate is an object - if (typeof cardToUpdate !== 'object' || cardToUpdate === null) { - throw new TypeError('The card to update must be an object.'); - } - - // Validate that cardToUpdate has an id property - if (!('id' in cardToUpdate)) { - throw new Error('The card to update must have an "id" property.'); - } - - try { - // Attempt to update the card within the collection - const updatedCards = cards.map((card) => - card.id === cardToUpdate.id ? { ...card, ...cardToUpdate } : card - ); - return updatedCards; - } catch (error) { - // Handle any other errors that occur during the map operation - throw new Error(`Failed to update card in collection: ${error.message}`); - } -}; - -export const CollectionProvider = ({ children }) => { - // const { cardPrices } = useCombinedContext(); - const [cookies] = useCookies(['user']); - const [collectionData, setCollectionData] = useState(initialCollectionState); - const [collection, setCollection] = useState({ - cards: [], - allCardPrices: [], - }); - - const [officialCollectionDatasets, setOfficialCollectionDatasets] = useState([ - {}, - ]); - const [allCollections, setAllCollections] = useState([]); - const [allCardPrices, setAllCardPrices] = useState([]); - const [totalPrice, setTotalPrice] = useState(0); - const [xyData, setXyData] = useState([]); - const [ - updatedPricesFromCombinedContext, - setUpdatedPricesFromCombinedContext, - ] = useState({}); - // const [selectedCollection, setSelectedCollection] = useState({}); - const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = - useState(false); - const userId = cookies.user?.id; - const totalCost = useMemo( - () => getTotalCost(selectedCollection), - [selectedCollection] - ); - const [selectedCollection, setSelectedCollection] = useState( - initialCollectionState - ); - const lastFetchedTime = useRef(null); - const calculateTotalPrice = (collection) => - collection.cards.reduce( - (total, card) => total + (card.price || 0) * (card.quantity || 0), - 0 - ); - - // Consolidated state setters for collection - const setCollectionState = (newState) => { - setSelectedCollection(newState); - setAllCollections((prev) => - prev.map((c) => (c._id === newState._id ? newState : c)) - ); - }; - - const createUpdateInfo = ( - updatedCards, - updatedPrice, - cardInfo, - userId, - selectedCollection, - collectionId, - newDataSet, - xyData - ) => { - // const { updateCollectionChartData } = useCollectionStore(); - return { - ...cardInfo, - name: selectedCollection?.name, - description: selectedCollection?.description, - cards: updatedCards, - userId: userId, - totalCost: updatedPrice, - totalPrice: updatedPrice, - xys: xyData, - quantity: updatedCards.length, - totalQuantity: updatedCards, - // chartData: updateCollectionChartData( - // updatedPrice, - // selectedCollection, - // newDataSet - // ), - allCardPrices: updatedCards?.flatMap((card) => - Array(card.quantity).fill(card.card_prices?.[0]?.tcgplayer_price) - ), - _id: collectionId, - }; - }; - - const saveCollectionToApi = async (collection, isUpdate = false) => { - const method = isUpdate ? 'PUT' : 'POST'; - const endpoint = isUpdate ? `/${collection._id}` : ''; - const url = `/api/users/collections${endpoint}`; - - try { - const response = await fetchWrapper(url, method, collection); - console.log( - `Collection ${isUpdate ? 'updated' : 'created'}:`, - response.data - ); - return response.data; // Return the updated data from the server - } catch (error) { - console.error( - `Failed to ${isUpdate ? 'update' : 'create'} collection:`, - error - ); - } - }; - - const createUserCollection = async ( - userId, - newCollectionInfo, - name, - description - ) => { - if (!userId) { - console.warn('Invalid userId to createUserCollection.'); - return; - } - if (!name) { - console.warn('Invalid name to createUserCollection.'); - return; - } - const actionDescription = 'create a new collection'; - if (!validateUserIdAndData(userId, newCollectionInfo, actionDescription)) - return; - - const payload = createPayload( - { name, description, userId }, - newCollectionInfo - ); - // const url = createApiUrl(`${userId}/collections`); - // console.log('URL:', url); - try { - // const response = await fetchWrapper(url, 'POST', payload); - const response = await saveCollectionToApi(payload, false); // Pass false to indicate it's not an update - - if (response?.error) { - console.error('Failed to create the collection:', response.error); - return; - } - if (response?.data) { - console.log('RESPONSE:', response); - setAllCollections((prev) => [...prev, response.data]); - setSelectedCollection(response.data); - setCollectionData(response.data); - } - return response; - } catch (error) { - logError('createUserCollection', { actionDescription, error }); - } - }; - - const removeCollection = async (collection) => { - if (!collection._id) { - console.error('Collection ID is undefined.'); - return; - } - - try { - const url = createApiUrl(`${userId}/collections`); - const response = await fetchWrapper(url, 'DELETE'); - - if (response.error) { - console.error('Failed to delete the collection:', response.error); - return; - } - - setAllCollections((prev) => - prev.filter((item) => item._id !== collection._id) - ); - - if (selectedCollection._id === collection._id) { - setSelectedCollection(initialCollectionState); - setCollectionData(initialCollectionState); - } - } catch (error) { - console.error(`Failed to delete the collection: ${error.message}`); - } - }; - - const fetchCollections = useCallback(async (userId) => { - if (!userId) { - console.warn('userId is not set, aborting fetchCollections.'); - return null; - } - const url = createApiUrl(`${userId}/collections`); - console.log('URL:', url); - try { - console.log(`Debug: ${'GET'} ${url}`); - const response = await fetchWrapper(url, 'GET'); - console.log('[GET RESPONSE]:', response); - const collections = response?.data; - console.log('Fetched collections:', collections); - return collections; - } catch (error) { - console.error('Error fetching collections:', error); - logError('fetchCollections', 'fetch collections', error); - return null; - } - }, []); - - const setCollections = useCallback((collections) => { - if (!collections || !Array.isArray(collections)) { - console.warn('Invalid collections array:', collections); - return; - } - - const uniqueCollections = removeDuplicateCollections(collections); - const validCollections = uniqueCollections.filter(Boolean); - - validCollections.forEach((collection) => { - collection.totalPrice = calculateTotalFromAllCardPrices( - collection.allCardPrices - ); - }); - - setAllCollections(validCollections); - - setCollectionData( - validCollections.length === 0 - ? initialCollectionState - : validCollections[0] - ); - setSelectedCollection( - validCollections.length === 0 - ? initialCollectionState - : validCollections[0] - ); - }, []); - - const fetchAndSetCollections = useCallback(async () => { - // Throttle the fetch calls - const currentTime = Date.now(); - const fetchDelay = 60000; // 1 minute - if ( - lastFetchedTime.current && - currentTime - lastFetchedTime.current < fetchDelay - ) - return; - lastFetchedTime.current = currentTime; - - try { - const response = await fetchWrapper( - createApiUrl(`collections/${userId}`), - 'GET' - ); - - console.log('FETCHED COLLECTIONS:', response); - setAllCollections(response.data || []); - } catch (error) { - console.error(`Failed to fetch collections: ${error}`); - } - }, [userId]); - // Function to update chart datasets and avoid duplicate entries - - // Function to calculate the total price from all card prices - const calculateTotalFromCardPrices = (cards) => - cards.reduce((total, card) => total + card.price * card.quantity, 0); - - // Function to update the card details - const updateCardDetails = (card, newPrice) => { - const priceHasChanged = - !card.priceHistory.length || - card.priceHistory.slice(-1)[0].num !== newPrice; - return { - ...card, - price: newPrice, - totalPrice: newPrice * card.quantity, - latestPrice: createPriceHistoryEntry(newPrice), - lastSavedPrice: card.latestPrice || createPriceHistoryEntry(newPrice), - chart_datasets: updateChartDatasets( - card, - newPrice * card.quantity, - new Date().toISOString() - ), - quantity: card.quantity, - priceHistory: priceHasChanged - ? [...card.priceHistory, createPriceHistoryEntry(newPrice)] - : card.priceHistory, - }; - }; - - // Function to add a card to the selected collection - // Add card to the selected collection - const createPriceHistoryEntry = (price) => ({ - num: price, - timestamp: new Date().toISOString(), - }); - - const updateChartDatasets = (card, computedPrice) => { - const newDataset = { x: new Date().toISOString(), y: computedPrice }; - const datasets = card.chart_datasets || []; - // Assuming a utility function is in place to filter out duplicates - return [...datasets, newDataset]; // For simplicity, this example does not filter out duplicates - }; - - const updateCardPriceDetails = (card, newPrice) => { - const priceChanged = - !card.priceHistory.length || - card.priceHistory.slice(-1)[0].num !== newPrice; - return { - ...card, - price: newPrice, - totalPrice: newPrice * (card.quantity || 1), // Default to 1 if quantity is not set - latestPrice: createPriceHistoryEntry(newPrice), - lastSavedPrice: priceChanged ? card.latestPrice : card.lastSavedPrice, // Update lastSavedPrice only if price changed - priceHistory: priceChanged - ? [...card.priceHistory, createPriceHistoryEntry(newPrice)] - : card.priceHistory, - chart_datasets: updateChartDatasets( - card, - newPrice * (card.quantity || 1) - ), - }; - }; - // const addOrRemoveCard = useCallback( -// async (card, cardInfo, operation) => { -// const collectionId = getCollectionId(selectedCollection, allCollections); -// if (!collectionId) { -// console.error('No valid collection selected.'); -// setOpenChooseCollectionDialog(true); -// return; -// } - -// let updatedCards = -// operation === 'update' -// ? updateCardInCollection(selectedCollection.cards, card) -// : getUpdatedCards(selectedCollection, card, operation); - -// const updatedPrice = calculateTotalFromAllCardPrices(updatedCards); -// const accumulatedTotal = selectedCollection.totalPrice + updatedPrice; - -// const newXYValue = { -// label: `Update - ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: accumulatedTotal, -// }; - -// // Update the chart data with the new accumulated total -// const updatedChartData = { -// ...selectedCollection.chartData, -// allXYValues: [...selectedCollection.chartData.allXYValues, newXYValue], -// }; - -// const updateInfo = createUpdateInfo( -// updatedCards, -// accumulatedTotal, -// cardInfo, -// userId, -// selectedCollection, -// collectionId, -// updatedChartData, -// xyData -// ); -// console.log('[UPDATE INFO]:', updateInfo); -// console.log('[ACCUMULATED]:', accumulatedTotal); -// const updatedCollection = { -// ...selectedCollection, -// ...updateInfo, -// chartData: updatedChartData, -// // totalPrice: accumulatedTotal, -// collectionPriceHistory: [ -// ...(selectedCollection.collectionPriceHistory || []), -// createPriceHistoryObject(accumulatedTotal), -// ], -// }; - -// console.log('[COLLECTION DATA][B4UPDATE]:', updatedCollection); -// await updateActiveCollection(updatedCollection, updateInfo.chartData); -// updateCollectionData(updatedCollection, 'selectedCollection'); -// updateCollectionData(updatedCollection, 'collectionData'); -// }, -// [ -// selectedCollection, -// allCollections, -// userId, -// openChooseCollectionDialog, -// handleCardAddition, -// handleCardRemoval, -// updateCollectionData, -// setOpenChooseCollectionDialog, -// ] -// ); - - // Main functions - const addOrUpdateCardInCollection = (collection, card, newPrice) => { - // Find the card in the collection or create a new one - const existingCardIndex = collection.cards.findIndex( - (c) => c.id === card.id - ); - const updatedCard = updateCardPriceDetails(card, newPrice); - - let updatedCards = [...collection.cards]; - if (existingCardIndex >= 0) { - updatedCards[existingCardIndex] = updatedCard; // Update existing card - } else { - updatedCards = [...updatedCards, updatedCard]; // Add new card - } - - const updatedTotalPrice = updatedCards.reduce( - (acc, card) => acc + card.totalPrice, - 0 - ); - const updatedAllCardPrices = updatedCards.map((card) => card.price); - - return { - ...collection, - cards: updatedCards, - totalPrice: updatedTotalPrice, - allCardPrices: updatedAllCardPrices, - }; - }; - - // Function to save the collection to the backend - // const saveCollectionToApi = async (collection, method) => { - // try { - // const url = `/api/users/collections/${ - // method === 'POST' ? '' : collection._id - // }`; - // const response = await fetchWrapper(url, method, collection); - // console.log( - // `Collection ${method === 'POST' ? 'created' : 'updated'}:`, - // response.data - // ); - // } catch (error) { - // console.error( - // `Failed to ${method === 'POST' ? 'create' : 'update'} collection:`, - // error - // ); - // } - // }; - - const contextValue = useMemo( - () => ({ - allCollections, - selectedCollection, - collectionData, - officialCollectionDatasets, - totalCost, - totalPrice, - // processAndUpdateCardPrices: (cardPrices, newCardPricesData) => - // processAndUpdateCardPrices(cardPrices, newCardPricesData), - allCardPrices: selectedCollection?.allCardPrices || [], - xys: xyData || [], - openChooseCollectionDialog, - // collectionData, - // createUserCollection, - // removeCollection, - updatedPricesFromCombinedContext, - // addCard: (card) => addOrUpdateCard(card, 'add'), - // updateCardPrices: (cardUpdates) => - // cardUpdates.forEach((card) => addOrUpdateCard(card, 'update')), - setAllCardPrices: (cardPrices) => setAllCardPrices(cardPrices), - // updateCardPrices: (priceUpdates) => updateCardPrices(priceUpdates), - addCardToCollection: (card) => addOrUpdateCardInCollection(card), - setXyData: (newXyData) => setXyData(newXyData), - // updateCollection: (collection) => updateCollection(collection), - updateCardCollection: (collection, cardData, operation) => - updateCardInCollection(collection, cardData, operation), - setOfficialCollectionDatasets, - setUpdatedPricesFromCombinedContext: (updatedPrices) => { - setUpdatedPricesFromCombinedContext(updatedPrices); - }, - setOpenChooseCollectionDialog, - calculateTotalPrice: () => getCardPrice(selectedCollection), - getTotalCost: () => getTotalCost(selectedCollection), - getCardQuantity: (cardId) => { - const card = selectedCollection?.cards?.find((c) => c?.id === cardId); - return card?.quantity || 0; - }, - createUserCollection: (userId, newCollectionInfo) => - createUserCollection( - userId, - newCollectionInfo, - newCollectionInfo.name, - newCollectionInfo.description - ), - - removeCollection: (collection) => removeCollection(collection), - fetchAllCollectionsForUser: fetchAndSetCollections, - // addCardToCollection: (card) => addCardToCollection(card), - setSelectedCollection: (collectionId) => - setSelectedCollection(collectionId), - setAllCollections: (collections) => setAllCollections(collections), - addOneToCollection: (card) => addOrUpdateCardInCollection(card), - // removeOneFromCollection: (card) => addOrRemoveCard(card, null, 'remove'), - }), - [allCollections, selectedCollection, totalCost, totalPrice, xyData] - ); - - useEffect(() => { - // Log context values - console.log('CONTEXT:', contextValue); - console.log('TOTAL COST:', totalCost); - console.log('TOTAL PRICE:', totalPrice); - console.log('SELECTED COLLECTION:', selectedCollection); - console.log('ALL COLLECTIONS:', allCollections); - console.log('COLLECTION DATA:', collectionData); - console.log('OFFICIAL COLLECTION DATASETS:', officialCollectionDatasets); - console.log( - 'UPDATED PRICES FROM CONTEXT:', - updatedPricesFromCombinedContext - ); - console.log('ALL CARD PRICES:', selectedCollection?.allCardPrices); - console.log('XY DATA:', xyData); - }, [ - contextValue, - totalPrice, - selectedCollection, - updatedPricesFromCombinedContext, - xyData, - ]); - useEffect(() => { - console.log('UPDATED COLLECTION DATA POST SERVER:', collectionData); - }, [collectionData]); - - useEffect(() => { - if (userId) { - fetchAndSetCollections(); - } - }, [userId]); - - return ( - - {children} - - ); -}; - -export const useCollectionStore = () => { - const context = useContext(CollectionContext); - if (!context) { - throw new Error( - 'useCollectionStore must be used within a CollectionProvider' - ); - } - return context; -}; -// const updateActiveCollection = useCallback( -// async (collectionData) => { -// if (!collectionData) throw new Error('No collection data provided.'); - -// CustomLogger({ 'COLLECTION DATA': collectionData }); -// const isCreatingNew = !collectionData?._id; -// const actionDescription = isCreatingNew -// ? 'create a new collection' -// : 'update the existing collection'; -// const method = determineHttpMethod( -// isCreatingNew, -// collectionData?.endpoint -// ); -// CustomLogger({ 'METHOD:': method }); -// try { -// if (isCreatingNew) { -// console.log( -// `Skipping fetch call to ${method} since it's a new collection` -// ); -// } else { -// const endpoint = createApiUrl( -// `${userId}/collections/${collectionData?._id}` -// ); -// console.log('ENDPOINT:', endpoint); - -// const payload = constructPayloadWithDifferences( -// initialCollectionState, -// collectionData -// ); -// console.log('PAYLOAD:', collectionData); -// console.log(`Debug: ${method} ${endpoint}`); -// const response = await fetchWrapper(endpoint, method, payload); -// console.log('[UPDATE RESPONSE][RAW]:', response); -// const updatedCollection = response?.data; -// console.log('[UPDATE RESPONSE][DES]:', updatedCollection); - -// if (!updatedCollection) -// throw new Error('No collection returned from server.'); -// updateCollectionChartData(updatedCollection, existingChartData); -// // updateChartDataForCollection(updatedCollection, existingChartData); -// updateCollectionData(updatedCollection, 'selectedCollection'); -// updateCollectionData(updatedCollection, 'collectionData'); -// updateCollectionData(updatedCollection, 'allCollections'); -// } -// } catch (error) { -// console.error( -// `Failed to update collection for user ${userId} with collectionId ${collectionData._id}:`, -// error -// ); -// logError('updateActiveCollection', actionDescription, error); -// console.error(`Failed to ${actionDescription}: ${error.message}`); -// } -// }, -// [userId, updateCollectionData] -// ); -// Adds or updates a card in the collection - -// const handleUpdatePrices = useCallback( -// (priceUpdates) => { -// const timestamp = new Date().toISOString(); - -// setSelectedCollection((prevCollection) => { -// const updatedCards = prevCollection.cards.map((card) => { -// const update = priceUpdates.find((u) => u.id === card.id); -// if (update) { -// return { -// ...card, -// price: update.newPrice, -// latestPrice: { num: update.newPrice, timestamp }, -// lastSavedPrice: card.latestPrice, -// priceHistory: -// card.latestPrice.num !== update.newPrice -// ? [...card.priceHistory, { num: update.newPrice, timestamp }] -// : card.priceHistory, -// chart_datasets: updateChartDatasets( -// card, -// update.newPrice * card.quantity, -// timestamp -// ), -// }; -// } -// return card; -// }); - -// const newTotalPrice = calculateTotalFromCardPrices(updatedCards); - -// // Save the updated collection to the backend -// updateCardPrices({ -// ...prevCollection, -// cards: updatedCards, -// totalPrice: newTotalPrice, -// }); - -// return { -// ...prevCollection, -// cards: updatedCards, -// totalPrice: newTotalPrice, -// }; -// }); -// }, -// [updateCardPrices] -// ); - -// Transform the card data to fit the collection item structure -// const transformCardToCollectionItem = (card) => { -// return { -// id: card.id, -// name: card.name, -// price: card.price, -// quantity: 1, // Default to 1 for a new card, adjust as needed -// ... any other card properties needed -// }; -// }; -// const updateCardPriceHistory = (card, newPrice) => { -// if (card.priceHistory.slice(-1)[0]?.num !== newPrice) { -// return [...card.priceHistory, createPriceHistoryEntry(newPrice)]; -// } -// return card.priceHistory; -// }; -// // Refactored getUpdatedCards function -// const getUpdatedCards = (activeCollection, card, operation) => { -// const handleOperation = -// operation === 'add' ? handleCardAddition : handleCardRemoval; -// const cardsToUpdate = handleOperation(activeCollection?.cards, card); - -// return cardsToUpdate.map(updateCardDetails); -// }; - -// const updateCollectionData = useCallback( -// (newData, collectionType) => { -// switch (collectionType) { -// case 'allCollections': -// setAllCollections((prevCollections) => { -// const indexToUpdate = prevCollections.findIndex( -// (collection) => collection._id === newData._id -// ); -// if (indexToUpdate === -1) { -// return [...prevCollections, newData]; -// } else { -// const updatedCollections = [...prevCollections]; -// updatedCollections[indexToUpdate] = newData; -// return updatedCollections; -// } -// }); -// break; -// case 'selectedCollection': -// setSelectedCollection(newData); -// break; -// case 'collectionData': -// setCollectionData(newData); -// break; -// default: -// console.warn('Unknown collection type for update:', collectionType); -// } -// }, -// [setAllCollections, setSelectedCollection, setCollectionData] -// ); - -// // // Function to handle the update of card price and price history -// // const updateCardDetails = (card) => { -// // const cardPrice = card.card_prices?.[0]?.tcgplayer_price; -// // const computedPrice = cardPrice * card.quantity; - -// // card.chart_datasets = updateChartDatasets(card, computedPrice); -// // card.price = cardPrice; -// // card.totalPrice = computedPrice; - -// // const lastEntry = card.priceHistory?.slice(-1)?.[0]; -// // if (!lastEntry || lastEntry.num !== cardPrice) { -// // card.priceHistory = [ -// // ...(card.priceHistory || []), -// // createPriceHistoryObject(cardPrice), -// // ]; -// // } - -// // return card; -// // }; - -// // Update the allCardPrices array based on the new card's price -// const updateAllCardPrices = (card) => { -// return [...selectedCollection.allCardPrices, card.price]; -// }; -// // Update the collection's cards array with the new card -// const updatePriceHistory = (card, newPrice) => { -// const lastPriceEntry = card.priceHistory?.slice(-1)?.[0]; -// return lastPriceEntry && lastPriceEntry.num !== newPrice -// ? [ -// ...card.priceHistory, -// { num: newPrice, timestamp: new Date().toISOString() }, -// ] -// : card.priceHistory; -// }; - -// Only add a new entry if the price has changed -// Refactored addOrRemoveCard function -// const addOrRemoveCard = useCallback( -// async (card, cardInfo, operation) => { -// const collectionId = selectedCollection._id; -// if (!collectionId) { -// console.error('No valid collection selected.'); -// setOpenChooseCollectionDialog(true); -// return; -// } - -// const updatedCards = -// operation === 'update' -// ? updateCardInCollection(selectedCollection.cards, card) -// : getUpdatedCards(selectedCollection, card, operation); - -// const updateInfo = createUpdateInfo(selectedCollection, updatedCards); -// const updatedCollection = await updateActiveCollection( -// selectedCollection._id, -// updateInfo -// ); - -// updateCollectionData(updatedCollection, 'selectedCollection'); -// }, -// [selectedCollection, updateCollectionData, setOpenChooseCollectionDialog] -// ); - -// Function to update the active collection on the backend -// const updateActiveCollection = async (collectionId, updateInfo) => { -// const url = createApiUrl(`${userId}/collections/${collectionId._id}`); - -// try { -// const response = await fetchWrapper(url, 'PUT', updateInfo); -// if (!response.data) -// throw new Error('No collection returned from server.'); -// return response.data; -// } catch (error) { -// console.error(`Failed to update collection: ${error}`); -// throw error; -// } -// }; - -// const getUpdatedCards = (activeCollection, card, operation) => { -// const cardsToUpdate = -// operation === 'add' -// ? handleCardAddition(activeCollection?.cards, card) -// : handleCardRemoval(activeCollection?.cards, card); - -// return cardsToUpdate.map((card) => { -// const cardPrice = card.card_prices?.[0]?.tcgplayer_price; -// const computedPrice = cardPrice * card.quantity; -// console.log('COMPUTED PRICE:', computedPrice); - -// // Update chart_datasets if necessary -// const allDatasets = [ -// ...(card?.chart_datasets || []), -// { x: moment().format('YYYY-MM-DD HH:mm'), y: computedPrice }, -// ]; -// card.chart_datasets = filterOutDuplicateYValues(allDatasets); - -// // Set card's price and total price -// card.price = cardPrice; -// card.totalPrice = computedPrice; - -// // Create a new price history object -// const newPriceHistoryObject = createPriceHistoryObject(cardPrice); - -// // Update priceHistory only if the last entry's num is different from the new price -// if ( -// !card.priceHistory || -// card.priceHistory.length === 0 || -// card.priceHistory[card.priceHistory.length - 1].num !== cardPrice -// ) { -// card.priceHistory = [ -// ...(card.priceHistory || []), -// newPriceHistoryObject, -// ]; -// } -// card.tag = card.tag || 'monitored'; -// return card; -// }); -// }; - -// const createPriceHistoryObject = (price) => ({ -// timestamp: new Date().toISOString(), -// num: price, -// }); - -// // The main function for adding or removing a card -// const addOrRemoveCard = useCallback( -// async (card, cardInfo, operation) => { -// const collectionId = getCollectionId(selectedCollection, allCollections); -// if (!collectionId) { -// console.error('No valid collection selected.'); -// setOpenChooseCollectionDialog(true); -// return; -// } - -// let updatedCards = -// operation === 'update' -// ? updateCardInCollection(selectedCollection.cards, card) -// : getUpdatedCards(selectedCollection, card, operation); - -// const updatedPrice = calculateTotalFromAllCardPrices(updatedCards); -// const accumulatedTotal = selectedCollection.totalPrice + updatedPrice; - -// const newXYValue = { -// label: `Update - ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: accumulatedTotal, -// }; - -// // Update the chart data with the new accumulated total -// const updatedChartData = { -// ...selectedCollection.chartData, -// allXYValues: [...selectedCollection.chartData.allXYValues, newXYValue], -// }; - -// const updateInfo = createUpdateInfo( -// updatedCards, -// accumulatedTotal, -// cardInfo, -// userId, -// selectedCollection, -// collectionId, -// updatedChartData, -// xyData -// ); -// console.log('[UPDATE INFO]:', updateInfo); -// console.log('[ACCUMULATED]:', accumulatedTotal); -// const updatedCollection = { -// ...selectedCollection, -// ...updateInfo, -// chartData: updatedChartData, -// // totalPrice: accumulatedTotal, -// collectionPriceHistory: [ -// ...(selectedCollection.collectionPriceHistory || []), -// createPriceHistoryObject(accumulatedTotal), -// ], -// }; - -// console.log('[COLLECTION DATA][B4UPDATE]:', updatedCollection); -// await updateActiveCollection(updatedCollection, updateInfo.chartData); -// updateCollectionData(updatedCollection, 'selectedCollection'); -// updateCollectionData(updatedCollection, 'collectionData'); -// }, -// [ -// selectedCollection, -// allCollections, -// userId, -// openChooseCollectionDialog, -// handleCardAddition, -// handleCardRemoval, -// updateCollectionData, -// setOpenChooseCollectionDialog, -// ] -// ); - -// const updateCardCollection = async ( -// selectedCollection, -// priceData, -// operation -// ) => { -// if (operation !== 'update') { -// console.warn('Invalid operation:', operation); -// return; -// } - -// const updatedCards = priceData.data.data; -// let accumulatedTotal = selectedCollection.totalPrice || 0; -// let newXYValues = [...selectedCollection.chartData.allXYValues]; - -// updatedCards.forEach((card) => { -// // Only add to priceHistory if the num value is different from the last entry -// const lastPriceHistoryNum = card.priceHistory?.slice(-1)[0]?.num; -// if (card.latestPrice?.num !== lastPriceHistoryNum) { -// const newPriceHistoryEntry = { -// num: card.latestPrice?.num || 0, -// timestamp: new Date().toISOString(), -// }; -// card.priceHistory = [ -// ...(card.priceHistory || []), -// newPriceHistoryEntry, -// ]; -// } - -// card.lastSavedPrice = { -// num: card.lastSavedPrice?.num || card.latestPrice?.num || 0, -// timestamp: card.lastSavedPrice?.timestamp || new Date().toISOString(), -// }; - -// card.latestPrice = { -// num: card.latestPrice?.num || 0, -// timestamp: new Date().toISOString(), -// }; - -// card.tag = card.tag || 'monitored'; - -// // Calculate the new total price -// accumulatedTotal += card.latestPrice.num * card.quantity; - -// // Update the chart data -// newXYValues.push({ -// label: `Update - ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: accumulatedTotal, -// }); -// }); - -// // Update the chart data with accumulated values -// const updatedChartData = { -// ...selectedCollection.chartData, -// allXYValues: newXYValues, -// }; - -// const updatedCollectionWithChartData = { -// ...selectedCollection, -// cards: updatedCards, -// totalPrice: accumulatedTotal, -// chartData: updatedChartData, -// }; - -// await updateActiveCollection(updatedCollectionWithChartData); -// return updatedCollectionWithChartData; -// }; - -// const updateCollectionChartData = ( -// collection, -// updatedPrice, -// updatedChartData -// ) => { -// const chartData = collection.chartData || { -// name: `Chart for Collection: ${collection.name}`, -// userId: collection.userId, -// datasets: [], -// allXYValues: [], -// xys: [], -// }; - -// const newXYValue = { -// label: `Update - ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: updatedPrice, -// }; - -// // Update the allXYValues array with the new data point -// const allXYValues = [...collection.chartData.allXYValues, newXYValue]; - -// const uniqueFilteredXYValues = getUniqueFilteredXYValues(allXYValues); - -// return { -// ...collection, -// chartData: { -// ...collection.chartData, -// allXYValues: uniqueFilteredXYValues, -// }, -// totalPrice: updatedPrice, -// }; -// }; - -// Create a new XY value for the current update -// const newXYValue = { -// label: `Update ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: updatedPrice, -// additionalPriceData: { -// priceChanged: updatedPrice !== collection.totalPrice, -// initialPrice: collection.totalPrice, -// updatedPrice: updatedPrice, -// priceDifference: updatedPrice - collection.totalPrice, -// priceChange: parseFloat( -// ((updatedPrice - collection.totalPrice) / collection.totalPrice) * 100 -// ).toFixed(2), -// }, -// }; - -// Update datasets if newDataSet is provided -// if (newDataSet && Array.isArray(newDataSet.data)) { -// newDataSet.data.forEach((dataset) => { -// if (dataset.xys && Array.isArray(dataset.xys)) { -// chartData.datasets.push(dataset); -// } -// }); -// } -// if (newDataSet && Array.isArray(newDataSet.data)) { -// newDataSet.data.forEach((dataset) => { -// if (dataset.xys && Array.isArray(dataset.xys)) { -// chartData.datasets.push(...dataset.xys); -// } -// }); -// } -// Return the updated collection with the new chartData -// const updateCardCollection = async ( -// selectedCollection, -// priceData, -// operation -// ) => { -// const priceArray = priceData.data.data; -// let updatedCards = selectedCollection.cards.map((card) => { -// const matchingNewCard = priceArray.find( -// (newCard) => newCard.id === card.id -// ); -// if (matchingNewCard) { -// const { latestPrice, priceHistory, name, quantity, tag, _id } = -// matchingNewCard; -// return { ...card, latestPrice, priceHistory, name, quantity, tag, _id }; -// } -// return card; -// }); - -// let newTotalPrice = updatedCards.reduce( -// (total, card) => total + (card.latestPrice?.num * card.quantity || 0), -// 0 -// ); - -// let newAllCardPrices = updatedCards.flatMap((card) => -// Array(card.quantity).fill(card.latestPrice?.num) -// ); - -// const newDataSet = { -// data: [ -// { -// xys: [ -// { -// label: `Update ${new Date().toISOString()}`, -// x: new Date().toISOString(), -// y: newTotalPrice, -// }, -// ], -// }, -// ], -// }; - -// const updatedCollectionWithChartData = updateCollectionChartData( -// selectedCollection, -// newTotalPrice, -// newDataSet -// ); - -// await updateActiveCollection(updatedCollectionWithChartData); - -// return updatedCollectionWithChartData; -// }; - -// function updateChartDataForCollection(collection, chartData) { -// if (!collection.chartData) { -// collection.chartData = { -// name: `Chart for Collection: ${collection.name}`, -// userId: collection.userId, -// datasets: [], -// allXYValues: [], -// }; -// } - -// if (chartData) { -// collection.chartData = { ...collection.chartData, ...chartData }; -// } - -// const newXYSData = getUniqueFilteredXYValues( -// collection.chartData.allXYValues -// ); -// const timestamp = new Date(); -// const newPrice = collection.totalPrice; -// const previousPrice = collection.previousDayTotalPrice || newPrice; -// const priceDifference = newPrice - previousPrice; -// const priceChange = previousPrice -// ? (priceDifference / previousPrice) * 100 -// : 0; - -// collection.chartData.allXYValues.push({ -// label: `Update ${timestamp.toISOString()}`, -// x: timestamp, -// y: newPrice, -// }); - -// const newDatasetEntry = { -// name: collection.name, -// data: newXYSData.map((xy) => ({ -// ...xy, -// additionalPriceData: { -// priceChanged: priceDifference !== 0, -// initialPrice: previousPrice, -// updatedPrice: newPrice, -// priceDifference: priceDifference, -// priceChange: parseFloat(priceChange.toFixed(2)), -// }, -// })), -// }; - -// collection.chartData.datasets.push(newDatasetEntry); - -// return collection; -// } -// useEffect(() => { -// if (!selectedCollection || typeof totalPrice !== 'number') return; - -// const updateNumber = -// selectedCollection.chartData?.datasets?.length + 1 || 1; -// const newDataSet = createNewDataSet(updateNumber, totalPrice); - -// const updatedChartData = updateCollectionChartData( -// selectedCollection, -// totalPrice, -// newDataSet -// ); - -// const updatedCollection = { -// ...selectedCollection, -// chartData: updatedChartData, -// totalPrice, -// allCardPrices, -// }; - -// updateActiveCollection(updatedCollection); -// }, [totalPrice, selectedCollection, allCardPrices]); - -// const createNewDataSet = (updateNumber, totalPrice) => ({ -// data: [ -// { -// xys: [ -// { -// label: `Update Number ${updateNumber}`, -// data: { x: new Date().toISOString(), y: totalPrice }, -// }, -// ], -// additionalPriceData: {}, // Additional data can be added here -// }, -// ], -// }); - -// Additional functions and context setup as necessary - -// const updateActiveCollection = useCallback( -// async (collectionData, existingChartData = {}) => { -// if (!collectionData) throw new Error('No collection data provided.'); - -// const isCreatingNew = !collectionData?._id; -// const actionDescription = isCreatingNew -// ? 'create a new collection' -// : 'update the existing collection'; -// const method = determineHttpMethod( -// isCreatingNew, -// collectionData?.endpoint -// ); - -// try { -// if (isCreatingNew) { -// console.log( -// `Skipping fetch call to ${method} since it's a new collection` -// ); -// updateChartDataForCollection(collectionData, existingChartData); -// } else { -// const endpoint = createApiUrl( -// `${userId}/collections/${collectionData._id}` -// ); -// console.log('ENDPOINT:', endpoint); - -// // Calculate the difference between the existing chart data and the new collection data -// const payload = constructPayloadWithDifferences( -// existingChartData, -// collectionData -// ); - -// console.log(`Debug: ${method} ${endpoint}`); -// const response = await fetchWrapper(endpoint, method, payload); -// console.log('RESPONSE:', response); -// const updatedCollection = response?.data; -// console.log('[COLLECTION DATA][A4UPDATE]:', updatedCollection); - -// if (!updatedCollection) -// throw new Error('No collection returned from server.'); - -// updateChartDataForCollection(updatedCollection, existingChartData); -// updateCollectionData(updatedCollection, 'selectedCollection'); -// updateCollectionData(updatedCollection, 'collectionData'); -// updateCollectionData(updatedCollection, 'allCollections'); -// } -// } catch (error) { -// console.error( -// `Failed to update collection for user ${userId} with collectionId ${collectionData._id}:`, -// error -// ); -// logError('updateActiveCollection', actionDescription, error); -// console.error(`Failed to ${actionDescription}: ${error.message}`); -// } -// }, -// [userId, updateCollectionData] -// ); - -// // Helper function to construct payload with only the differences -// function constructPayloadWithDifferences(existingData, newData) { -// const payload = {}; -// Object.keys(newData).forEach((key) => { -// if (newData[key] !== existingData[key]) { -// payload[key] = newData[key]; -// } -// }); -// return payload; -// } - -// function updateChartDataForCollection(collection, chartData) { -// if (!collection.chartData) { -// collection.chartData = { -// // Initialize with defaults if not present -// name: `Chart for Collection: ${collection.name}`, -// userId: collection.userId, -// datasets: [], -// allXYValues: [], -// }; -// } - -// // Merge in any additional chartData that was passed in -// if (chartData) { -// collection.chartData = { ...collection.chartData, ...chartData }; -// } - -// // Get new x and y values -// const newXYSData = getUniqueFilteredXYValues( -// collection.chartData.allXYValues -// ); - -// // Calculate new and previous price -// const timestamp = new Date(); -// const newPrice = collection.totalPrice; // Fixed the code to get the correct value -// const previousPrice = collection.previousDayTotalPrice || newPrice; // Assuming previousDayTotalPrice is a property -// const priceDifference = newPrice - previousPrice; -// const priceChange = previousPrice -// ? (priceDifference / previousPrice) * 100 -// : 0; - -// // Update the allXYValues field with the new data point -// collection.chartData.allXYValues.push({ -// label: `Update ${new Date().toISOString()}`, -// x: new Date(), -// y: newPrice, -// }); - -// const newDatasetEntry = { -// name: collection.name, // or some other way to name the dataset -// data: [ -// { -// xys: [ -// // newXYSData.map((xy) => ({ -// { -// label: `Update ${timestamp.toISOString()}`, -// data: { x: timestamp, y: newPrice }, -// }, -// // })), -// ], -// additionalPriceData: [ -// { -// priceChanged: priceDifference !== 0, -// initialPrice: previousPrice, -// updatedPrice: newPrice, -// priceDifference: priceDifference, -// priceChange: parseFloat(priceChange.toFixed(2)), -// }, -// ], -// }, -// ], -// }; - -// // Push the new Dataset to the datasets array -// collection.chartData.datasets.push(newDatasetEntry); - -// // Return the updated collection object -// return collection; -// } diff --git a/src/context/CollectionContext/fmljsx b/src/context/CollectionContext/fmljsx deleted file mode 100644 index 1587fa4..0000000 --- a/src/context/CollectionContext/fmljsx +++ /dev/null @@ -1 +0,0 @@ -isMountedisMountedisMountedisMountedisMounted \ No newline at end of file diff --git a/src/context/CollectionContext/index.js b/src/context/CollectionContext/index.js deleted file mode 100644 index b622760..0000000 --- a/src/context/CollectionContext/index.js +++ /dev/null @@ -1,453 +0,0 @@ -// import React, { -// createContext, -// useState, -// useEffect, -// useCallback, -// useContext, -// useMemo, -// } from 'react'; -// import { useCookies } from 'react-cookie'; -// import { fetchWrapper, removeDuplicateCollections } from '../Helpers'; -// import { -// initialCollectionState, -// useUserId, -// useFetchAndSetCollections, -// useAddOrRemoveCard, -// useMakeContextValue, -// calculateAndUpdateTotalPrice, -// } from './SomeHelper'; - -// export const CollectionContext = createContext(undefined); - -// // Custom Hook to manage a collection -// const useCollection = (initialCollection = {}) => { -// const [collection, setCollection] = useState(initialCollection); -// const updateCollection = useCallback( -// (newData) => setCollection((prev) => ({ ...prev, ...newData })), -// [] -// ); -// const totalPrice = useMemo( -// () => calculateAndUpdateTotalPrice(collection), -// [collection] -// ); - -// return { collection, updateCollection, totalPrice }; -// }; - -// export const CollectionProvider = ({ children }) => { -// // const [cookies] = useCookies(['userCookie']); -// // const [collectionData, setCollectionData] = useState(initCollectionState()); -// // const [allCollections, setAllCollections] = useState([]); -// // const [allCardPrices, setAllCardPrices] = useState([]); -// // const [prevTotalQuantity, setPrevTotalQuantity] = useState(0); -// // const [selectedCollection, setSelectedCollection] = useState( -// // initCollectionState() -// // ); -// // const userId = cookies.userCookie?.id; -// const [collectionData, setCollectionData] = useState(initialCollectionState); -// const { collection, updateCollection } = useCollection( -// initialCollectionState -// ); -// const [allCollections, setAllCollections] = useState([]); -// const [allCardPrices, setAllCardPrices] = useState([]); -// const [prevTotalQuantity, setPrevTotalQuantity] = useState(0); -// const [selectedCollection, setSelectedCollection] = useState( -// initialCollectionState -// ); - -// const userId = useUserId(); - -// const fetchAndSetCollections = useFetchAndSetCollections( -// userId, -// setAllCollections, -// setSelectedCollection, -// setCollectionData -// ); - -// const addOrRemoveCard = useAddOrRemoveCard( -// userId, -// setSelectedCollection, -// setAllCollections, -// setAllCardPrices, -// allCollections, -// allCardPrices -// ); - -// const totalCost = useMemo( -// () => calculateTotalPrice(selectedCollection?.cards), -// [selectedCollection] -// ); - -// const contextValue = useMakeContextValue( -// collectionData, -// allCollections, -// allCardPrices, -// selectedCollection, -// totalCost, -// setSelectedCollection, -// fetchAndSetCollections, -// addOrRemoveCard, -// userId -// ); - -// const { collection: collectionData, updateCollection: updateCollectionData } = -// useCollection(initialCollectionData); -// const { -// collection: selectedCollection, -// updateCollection: updateSelectedCollection, -// } = useCollection(initialSelectedCollection); - -// const fetchCollectionsForUser = useCallback(async () => { -// try { -// const url = `${process.env.REACT_APP_SERVER}/api/users/${userId}/collections`; -// return await fetchWrapper(url, 'GET'); -// } catch (error) { -// console.error(`Failed to fetch collections for user: ${error.message}`); -// return null; -// } -// }, [userId]); - -// const updateCollectionData = useCallback((newData, updaterFn) => { -// updaterFn((prevCollections) => { -// const existingIndex = prevCollections -// ? prevCollections.findIndex( -// (collection) => collection._id === newData._id -// ) -// : -1; - -// // Ensure that prevCollections is an array or initialize it as an empty array -// if (!Array.isArray(prevCollections)) { -// prevCollections = []; -// } - -// console.log('existingIndex:', existingIndex); -// console.log('prevCollections:', prevCollections); - -// if (existingIndex === -1) return [...prevCollections, newData]; -// const updatedCollections = [...prevCollections]; -// updatedCollections[existingIndex] = newData; -// return updatedCollections; -// }); -// }, []); - -// const fetchAndSetCollections = useCallback(async () => { -// try { -// const userCollections = await fetchCollectionsForUser(); -// if (userCollections && userCollections.length > 0) { -// const uniqueCollections = removeDuplicateCollections(userCollections); - -// // Initialize totalPrice for each collection based on its cards -// uniqueCollections.forEach((collection) => { -// collection.totalPrice = calculateAndUpdateTotalPrice(collection); -// }); - -// setAllCollections(uniqueCollections); -// setCollectionData(uniqueCollections[0]); -// setSelectedCollection(uniqueCollections[0]); -// } -// } catch (error) { -// console.error(`Failed to fetch collections: ${error.message}`); -// } -// }, [fetchCollectionsForUser]); - -// const formatCardData = (card) => ({ -// id: card.id, -// ...Object.fromEntries( -// [ -// 'name', -// 'type', -// 'frameType', -// 'description', -// 'card_images', -// 'archetype', -// 'atk', -// 'def', -// 'level', -// 'race', -// 'attribute', -// 'quantity', -// ].map((key) => [key, card[key] || null]) -// ), -// }); - -// const createUserCollection = async ( -// userId, -// name, -// description, -// newCollectionInfo -// ) => { -// try { -// const url = `${process.env.REACT_APP_SERVER}/api/users/${userId}/collections/newCollection/${userId}`; - -// const data = await fetchWrapper(url, 'POST', { -// name, -// description, -// userId, -// totalPrice: 0, -// allCardPrices: [], -// cards: [], // Initialize with an empty array of cards -// }); - -// console.log('NEW COLLECTION DATA:', data.savedCollection); -// // Add the new collection to allCollections -// setAllCollections((prevCollections) => [...prevCollections, data]); - -// setCollectionData(data); -// setSelectedCollection(data); -// } catch (error) { -// console.error(`Failed to create a new collection: ${error.message}`); -// } -// }; - -// const getCardQuantity = (collectionId) => { -// const collection = allCollections?.find( -// (item) => item._id === collectionId -// ); -// if (!collection) return 0; -// return collection.cards.reduce((acc, card) => acc + card.quantity, 0); -// }; - -// const addOrRemoveCard = useCallback( -// async (card, cardInfo, isAdding) => { -// if ( -// !selectedCollection._id && -// (!allCollections[0] || !allCollections[0]._id) -// ) { -// console.error('No valid collection to add or remove a card.'); -// return; -// } -// const activeCollection = selectedCollection._id -// ? selectedCollection -// : allCollections[0]; - -// // GETS THE PRICE OF THE CARD -// let cardPrice = 0; - -// if ( -// card.card_prices && -// card.card_prices.length > 0 && -// card.card_prices[0].tcgplayer_price -// ) { -// cardPrice = parseFloat(card.card_prices[0].tcgplayer_price); -// } - -// // Create a copy of the current state -// const currentCards = [...(activeCollection?.cards || [])]; -// let currentTotalPrice = activeCollection.totalPrice || 0; -// // Find the card index in the current state -// const cardIndex = currentCards.findIndex((item) => item.id === card.id); -// if (isAdding) { -// setAllCardPrices([...allCardPrices, { cardId: card.id, cardPrice }]); -// if (cardIndex !== -1) { -// currentCards[cardIndex].quantity += 1; -// currentCards[cardIndex].price += cardPrice; // Add card price -// } else { -// currentCards.push({ ...card, quantity: 1, price: cardPrice }); -// } -// currentTotalPrice += cardPrice; -// } else { -// setAllCardPrices((prevCardValues) => -// prevCardValues.filter((val) => val !== cardPrice) -// ); - -// if (cardIndex !== -1) { -// currentCards[cardIndex].quantity -= 1; -// currentCards[cardIndex].price -= cardPrice; // Subtract card price -// if (currentCards[cardIndex].quantity <= 0) { -// currentCards.splice(cardIndex, 1); -// } -// currentTotalPrice -= cardPrice; -// } -// } -// currentTotalPrice = calculateAndUpdateTotalPrice({ -// ...activeCollection, -// cards: currentCards, -// }); - -// if (cardInfo) { -// console.log('COLLECTION NAME CHANGE:', cardInfo.name); -// console.log('COLLECTION DESC CHANGE:', cardInfo.description); - -// activeCollection.name = cardInfo.name; -// activeCollection.description = cardInfo.description; -// } - -// // Update the collection's totalPrice -// activeCollection.totalPrice = currentTotalPrice; - -// console.log('activeCollection:', activeCollection); -// console.log('currentTotalPrice:', currentTotalPrice); -// const collectionId = activeCollection._id; -// console.log('currentCards:', currentCards); -// console.log('collectionId:', collectionId); -// try { -// // Replace this URL with your server's URL and route for updating the collection -// const url = `${process.env.REACT_APP_SERVER}/api/users/${userId}/collections/${collectionId}`; - -// // Send a PUT request to the server to update the collection -// const updatedCollection = await fetchWrapper(url, 'PUT', { -// cards: currentCards, -// name: cardInfo.name, -// description: cardInfo.description, -// quantity: getCardQuantity(collectionId), -// userId: userId, -// totalPrice: currentTotalPrice, -// }); - -// // Update the state based on the server's response -// setSelectedCollection({ -// ...updatedCollection, -// totalPrice: currentTotalPrice, -// name: cardInfo.name, -// description: cardInfo.description, -// }); -// updateCollectionData( -// { -// ...updatedCollection, -// totalPrice: currentTotalPrice, -// name: cardInfo.name, -// description: cardInfo.description, -// }, -// setAllCollections -// ); -// } catch (error) { -// // If the PUT request fails, log the error and revert to the previous state -// console.error(`Failed to update the collection: ${error.message}`); -// } -// }, -// [ -// selectedCollection, -// allCollections, -// userId, -// updateCollectionData, -// allCardPrices, -// ] -// ); - -// const totalCost = selectedCollection?.cards -// ? selectedCollection.cards.reduce((total, card) => { -// if ( -// card.card_prices && -// card.card_prices[0] && -// card.card_prices[0].tcgplayer_price -// ) { -// return total + parseFloat(card.card_prices[0].tcgplayer_price); -// } -// return total; -// }, 0) -// : 0; - -// // const getCardQuantity: (collectionId) => { - -// // const collection = allCollections?.find( -// // (item) => item._id === collectionId -// // ); -// // if (!collection) return 0; -// // return collection.cards.reduce((acc, card) => acc + card.quantity, 0); -// // }, - -// const contextValue = useMemo( -// () => ({ -// collectionData, -// allCollections, -// allCardPrices, -// selectedCollection, -// totalPrice: calculateAndUpdateTotalPrice(selectedCollection), -// setSelectedCollection, -// fetchAllCollectionsForUser: fetchAndSetCollections, // fetchAndSetCollections should be defined -// addOneToCollection: (card, cardInfo) => -// addOrRemoveCard(card, cardInfo, true), // addOrRemoveCard should be defined -// removeOneFromCollection: (card) => addOrRemoveCard(card, null, false), -// createUserCollection: (name, description, newCollectionInfo) => -// createUserCollection(userId, name, description, newCollectionInfo), // createUserCollection should be defined -// getCardQuantity, -// getTotalCost: (collectionId) => { -// const collection = allCollections?.find( -// (item) => item._id === collectionId -// ); -// if (!collection) return 0; -// return collection.cards.reduce( -// (acc, card) => acc + card.price * card.quantity, -// 0 -// ); -// }, -// getCollectionCardDetails: (collectionId) => { -// const collection = allCollections?.find( -// (item) => item._id === collectionId -// ); -// if (!collection || !collection.cards) return [0, []]; - -// const totalQuantity = collection.cards.reduce( -// (acc, card) => acc + card.quantity, -// 0 -// ); -// const cardDetails = collection.cards.map((card) => ({ -// name: card.name, -// quantity: card.quantity, -// })); - -// return [totalQuantity, cardDetails]; -// }, -// }), -// [collectionData, allCollections, selectedCollection, allCardPrices] -// ); - -// useEffect(() => { -// console.log('collectionData has been updated:', collectionData); -// }, [collectionData]); - -// useEffect(() => { -// if (allCollections.length > 0) { -// const firstCollection = allCollections[0]; -// const totalQuantity = firstCollection.cards.reduce( -// (acc, card) => acc + card.quantity, -// 0 -// ); - -// // Only log when a new card has been added -// if (totalQuantity > prevTotalQuantity) { -// const cardDetails = firstCollection.cards.map((card) => ({ -// name: card.name, -// quantity: card.quantity, -// })); -// console.log( -// 'A new card has been added. Updated totals:', -// totalQuantity, -// cardDetails -// ); -// } - -// // Update the previous total quantity -// setPrevTotalQuantity(totalQuantity); -// } -// }, [allCollections]); - -// useEffect(() => { -// console.log('COLLECTIONCONTEXT:', contextValue); -// if (userId) fetchAndSetCollections(); -// }, [fetchAndSetCollections, userId]); - -// return ( -// -// {children} -// -// ); -// }; - -// export const useCollectionStore = () => { -// const context = useContext(CollectionContext); -// if (!context) { -// throw new Error( -// 'useCollectionStore must be used within a CollectionProvider' -// ); -// } -// return context; -// }; - -// // Initial states -// const initialCollectionState = { -// _id: '', -// cards: [], -// quantity: 0, -// totalPrice: 0, -// }; diff --git a/src/context/CollectionContext/validateCollection.jsx b/src/context/CollectionContext/validateCollection.jsx deleted file mode 100644 index 4e1b10c..0000000 --- a/src/context/CollectionContext/validateCollection.jsx +++ /dev/null @@ -1,68 +0,0 @@ -function validateCollection(collection) { - const warnings = []; - - // Utility to check if a value matches the type - function checkType(value, type, fieldName) { - if (typeof value !== type) { - warnings.push( - `Warning: Field "${fieldName}" should be of type "${type}".` - ); - } - } - - if ( - !collection.userId || - !mongoose.Types.ObjectId.isValid(collection.userId) - ) { - warnings.push('Warning: "userId" is missing or invalid.'); - } - if (typeof collection.name !== 'string') { - warnings.push('Warning: "name" is missing or not a string.'); - } - // ... continue with other fields - - // For nested objects like priceEntrySchema, you would have to check each field - if (collection.latestPrice) { - if (typeof collection.latestPrice.num !== 'number') { - warnings.push('Warning: "latestPrice.num" should be a number.'); - } - if (!(collection.latestPrice.timestamp instanceof Date)) { - warnings.push('Warning: "latestPrice.timestamp" should be a Date.'); - } - } - - // For arrays, you would check each element - if (Array.isArray(collection.priceHistory)) { - collection.priceHistory.forEach((entry, index) => { - if (typeof entry.num !== 'number') { - warnings.push( - `Warning: "priceHistory[${index}].num" should be a number.` - ); - } - if (!(entry.timestamp instanceof Date)) { - warnings.push( - `Warning: "priceHistory[${index}].timestamp" should be a Date.` - ); - } - }); - } - - // ... repeat for each field and nested object/array as needed - - if (warnings.length > 0) { - console.warn('Validation warnings:', warnings.join('\n')); - } else { - console.log('No validation warnings. The object is valid.'); - } - - return warnings.length === 0; -} - -// Example usage: -const collectionToValidate = { - userId: '507f191e810c19729de860ea', // This should be a valid ObjectId - name: 'My Collection', - // ... other fields -}; - -validateCollection(collectionToValidate); diff --git a/src/context/ColorModeProvider.jsx b/src/context/ColorModeProvider.jsx index d5b1a19..9084db9 100644 --- a/src/context/ColorModeProvider.jsx +++ b/src/context/ColorModeProvider.jsx @@ -1,12 +1,12 @@ import { useState, useMemo, createContext, useEffect } from 'react'; import { createTheme } from '@mui/material/styles'; import { useCookies } from 'react-cookie'; -import { themeSettings } from '../themeSettings'; +import { themeSettings } from '../assets/themeSettings'; export const ColorModeContext = createContext({ mode: 'dark', colorMode: {}, - theme: themeSettings('dark'), // default theme is light mode theme + theme: createTheme(themeSettings('dark')), // Default theme is dark mode // eslint-disable-next-line @typescript-eslint/no-empty-function toggleColorMode: () => {}, @@ -21,18 +21,17 @@ export const ColorModeProvider = ({ children }) => { useEffect(() => { // Set the cookie whenever the mode changes setCookie('colorMode', mode, { path: '/' }); - }, [mode]); + }, [mode, setCookie]); const colorMode = useMemo( () => ({ toggleColorMode: () => { - const newMode = mode === 'light' ? 'dark' : 'light'; + const newMode = mode === 'dark' ? 'dark' : 'light'; setMode(newMode); setCookie('colorMode', newMode); // also set the cookie here for immediate effect - // Cookies.set('colorMode', newMode, { expires: 365 }); // also set the cookie here for immediate effect }, }), - [mode] + [mode, setCookie] ); const theme = useMemo(() => createTheme(themeSettings(mode)), [mode]); diff --git a/src/context/CombinedProvider.jsx b/src/context/CombinedProvider.jsx index f7fa7d1..56fea34 100644 --- a/src/context/CombinedProvider.jsx +++ b/src/context/CombinedProvider.jsx @@ -9,18 +9,16 @@ import React, { import { useCookies } from 'react-cookie'; import { CollectionContext } from './CollectionContext/CollectionContext'; import { useSocketContext } from './SocketProvider'; -import CustomLogger from './CutstomLogger'; - +import { validateData } from './Helpers'; export const CombinedContext = createContext(); const initialState = { allData: {}, data: {}, messageTest: {}, - // chartData: {}, + userData: {}, existingChartData: {}, collectionData: {}, - // currentChartData: {}, allCollectionsUpdated: {}, simData: {}, allCollectionData: {}, @@ -33,7 +31,6 @@ const initialState = { dailyPriceChange: 0, priceDifference: 0, allCardPrices: {}, - handleCardPricesUpdated: {}, retrievedListOfMonitoredCards: {}, listOfMonitoredCards: {}, listOfSimulatedCards: {}, @@ -43,147 +40,19 @@ const initialState = { 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 - // allCollectionsUpdated: {}, - // cardStats: {}, - // deckData: {}, - // updatedChartData: {}, - // allUpdatedPrices: {}, - // allItemTypeData: {}, - // allItemData: {}, - // allItemData2: {}, - // cardStatsArray: {}, -}; -const filterDuplicatePrices = (data) => { - const seen = {}; - - return data?.runs?.filter((run) => { - if ( - !run?.valuesUpdated || - !run?.valuesUpdated?.updatedPrices || - Object.keys(run?.valuesUpdated?.updatedPrices)?.length === 0 - ) { - // Remove the item if there's no updatedPrices or it's empty - return false; - } - - let isDuplicate = false; - for (const id in run?.valuesUpdated?.updatedPrices) { - const item = run?.valuesUpdated?.updatedPrices[id]; - const key = `${id}-${item?.previousPrice}-${item?.updatedPrice}`; - - if (seen[key]) { - isDuplicate = true; - break; - } - seen[key] = true; - } - - return !isDuplicate; - }); -}; - -// function processCardPrices(cardPrices, selectedCollection) { -// if (!cardPrices || !cardPrices.data || !Array.isArray(cardPrices.data.data)) { -// console.error('Invalid cardPrices data structure.'); -// return; -// } - -// console.log('Card prices retrieved:', cardPrices); -// const priceArray = []; -// let totalPrice = 0; - -// cardPrices.data.data.forEach((card) => { -// const { latestPrice, quantity } = card; - -// if (!latestPrice || !quantity) { -// console.error(`Missing price or quantity for card ID: ${card.id}`); -// return; -// } - -// for (let i = 0; i < quantity; i++) { -// priceArray.push(latestPrice.num); -// totalPrice += latestPrice.num; -// } -// }); - -// const filteredCards = cardPrices.data.data.filter((card) => { -// const cardIds = selectedCollection?.cards?.map((card) => card.id); -// return cardIds?.includes(card.id); -// }); - -// console.log('Price Array:', priceArray); -// console.log('Total Price:', totalPrice.toFixed(2)); -// // console.log('Filtered Cards:', filteredCards); - -// // Save priceArray and totalPrice as needed -// // For example, you might want to set them to your application's state - -// return { priceArray, totalPrice: totalPrice.toFixed(2) }; -// } - -const isEmpty = (obj) => { - return ( - [Object, Array].includes((obj || {}).constructor) && - !Object.entries(obj || {}).length - ); -}; - -const validateData = (data, eventName, functionName) => { - const dataType = typeof data || data.data || data.data.data || data.message; - console.log( - '----------------------------------------------------------------------------------------------------' - ); - console.log( - `| [SUCCESS] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}] |` - ); - console.log( - '----------------------------------------------------------------------------------------------------' - ); - if (data === null || data === undefined) { - console.log( - '----------------------------------------------------------------------------------------------------' - ); - console.warn( - `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` - ); - console.log( - '----------------------------------------------------------------------------------------------------' - ); - return false; - } - if (isEmpty(data) && isEmpty(data?.data) && isEmpty(data?.data?.data)) { - console.log( - '----------------------------------------------------------------------------------------------------' - ); - console.error( - `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` - ); - console.log( - '----------------------------------------------------------------------------------------------------' - ); - return false; - } - return true; }; export const CombinedProvider = ({ children }) => { - const { user } = useCookies(['user']); + const [cookies] = useCookies(['user']); + // const { user } = useCookies(['user']); const [state, setState] = useState(initialState); - const userId = user?.userId; + const user = cookies.user; + // console.log('USER ID:', user.id); const { selectedCollection, allCollections, getNewTotalPrice, - updateOneFromCollection, - updateCollection, - processAndUpdateCardPrices, - setAllCardPrices, - setTotalPrice, - setAllCollections, - updatedPricesFromCombinedContext, - officialCollectionDatasets, - setOfficialCollectionDatasets, - setUpdatedPricesFromCombinedContext, + getUpdatedCollection, } = useContext(CollectionContext); const socket = useSocketContext(); @@ -209,6 +78,7 @@ export const CombinedProvider = ({ children }) => { const setDataFunctions = { data: createStateUpdaterFunction('chartData'), + userData: createStateUpdaterFunction('userData'), messageTest: createStateUpdaterFunction('messageTest'), finalUpdateData: createStateUpdaterFunction('finalUpdateData'), chartData: createStateUpdaterFunction('chartData'), @@ -258,23 +128,12 @@ export const CombinedProvider = ({ children }) => { useEffect(() => { if (state.eventsTriggered) { console.log(`Handling event: ${state.eventsTriggered.eventName}`); - // Additional logic to handle the event } }, [state.eventsTriggered]); // ----------- XXX ----------- const generateListOfMonitoredCards = (allCollections) => { if (!allCollections) return []; - // // Ensure cardPrices is an array - // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; - - // // Flatten all cards from all collections, including collection ID - // const cardsWithCollectionId = allCollections.flatMap((collection) => - // collection?.cards?.map((card) => ({ - // ...card, - // collectionId: collection._id, - // })) - // ); const cardsWithCollectionId = allCollections.flatMap((collection) => collection?.cards?.map((card) => ({ ...card, @@ -282,18 +141,26 @@ export const CombinedProvider = ({ children }) => { })) ); - 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); + const originalCard = cardsWithCollectionId.find( + (card) => card?.id === id + ); return { ...originalCard, - priceHistory: originalCard.priceHistory || [], + priceHistory: originalCard?.priceHistory || [], }; }); }; const updateCardPricesInList = (listOfMonitoredCards, cardPrices) => { + // if (!data || !Array.isArray(data)) { + // console.error('Data is undefined or not an array'); + // return; // or handle the error appropriately + // } return listOfMonitoredCards.map((originalCard) => { const updatedCardInfo = cardPrices.find((price) => price.id === originalCard.id) || {}; @@ -303,16 +170,17 @@ export const CombinedProvider = ({ children }) => { return { ...originalCard, ...updatedCardInfo, - quantity: originalCard.quantity, - price: updatedCardInfo.latestPrice?.num || originalCard.price, - lastSavedPrice: - updatedCardInfo.lastSavedPrice || - updatedCardInfo.priceHistory[ - updatedCardInfo.priceHistory.length - 1 - ], + 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, + updatedCardInfo?.latestPrice, ], }; } @@ -325,283 +193,14 @@ export const CombinedProvider = ({ children }) => { [allCollections] ); - // const listOfMonitoredCards = useMemo(() => { - // if (!allCollections) return []; - - // const cardPrices = Array.isArray(state.cardPrices) - // ? state.cardPrices - // : [state.cardPrices]; - - // const cardsWithCollectionId = allCollections.flatMap((collection) => - // collection?.cards?.map((card) => ({ - // ...card, - // collectionId: collection._id, - // })) - // ); - - // const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card.id)); - - // return Array.from(uniqueCardIds).map((id) => { - // const originalCard = cardsWithCollectionId.find((card) => card.id === id); - // const updatedCardInfo = cardPrices.find((price) => price.id === id) || {}; - - // const latestPrice = - // updatedCardInfo.latestPrice || originalCard.latestPrice; - // const lastSavedPrice = - // updatedCardInfo.lastSavedPrice || originalCard.lastSavedPrice; - - // return { - // ...originalCard, - // ...updatedCardInfo, - // latestPrice, - // lastSavedPrice, - // price: latestPrice?.num || originalCard.price, - // priceHistory: updatedCardInfo.priceHistory || originalCard.priceHistory, - // }; - // }); - // }, [allCollections, state.cardPrices]); - - // const updateCardPricesState = (currentCardPrices, updatedCardsList) => { - // // Ensure both currentCardPrices and updatedCardsList are arrays - // if (!Array.isArray(currentCardPrices)) currentCardPrices = []; - // if (!Array.isArray(updatedCardsList)) updatedCardsList = []; - - // // Create a map for easy lookup of current card prices by ID - // const currentCardPricesMap = new Map( - // currentCardPrices.map((card) => [card.id, card]) - // ); - - // // Update the card prices with new data from updatedCardsList - // return updatedCardsList.map((updatedCard) => { - // const currentCardPrice = currentCardPricesMap.get(updatedCard.id) || {}; - - // return { - // ...currentCardPrice, - // latestPrice: updatedCard.latestPrice || currentCardPrice.latestPrice, - // lastSavedPrice: - // updatedCard.lastSavedPrice || currentCardPrice.lastSavedPrice, - // price: updatedCard.latestPrice?.num || currentCardPrice.price, - // priceHistory: updatedCard.priceHistory || currentCardPrice.priceHistory, - // }; - // }); - // }; - const emitUpdatedCards = (socket, updatedCards) => { socket.emit('UPDATED_MONITORED_CARDS', updatedCards); }; - // Usage - // Now you can set the state with newCardPrices - - // const listOfMonitoredCards = useMemo(() => { - // if (!allCollections) return []; - - // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; - - // // Flatten all cards from all collections with additional collection ID - // const cardsWithCollectionId = allCollections.flatMap((collection) => - // collection?.cards?.map((card) => ({ - // ...card, - // collectionId: collection._id, - // })) - // ); - - // // Reduce the cards to a map, merging cards with the same ID - // const mergedCardsMap = cardsWithCollectionId.reduce((acc, card) => { - // const updatedCardInfo = cardPrices.find((price) => price.id === card.id) || {}; - // const existingCard = acc.get(card.id) || {}; - - // const mergedCard = { - // ...existingCard, - // ...card, - // ...updatedCardInfo, - // latestPrice: updatedCardInfo.latestPrice || card.latestPrice || existingCard.latestPrice, - // lastSavedPrice: updatedCardInfo.lastSavedPrice || card.lastSavedPrice || existingCard.lastSavedPrice, - // price: updatedCardInfo.latestPrice?.num || card.price || existingCard.price, - // }; - - // acc.set(card.id, mergedCard); - // return acc; - // }, new Map()); - - // // Convert the map values to an array - // return Array.from(mergedCardsMap.values()); - // }, [allCollections, state.cardPrices]); - - // const listOfMonitoredCards = useMemo(() => { - // if (!allCollections) return []; - - // // Ensure cardPrices is an array - // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; - - // // Flatten all cards from all collections, including collection ID - // const cardsWithCollectionId = allCollections.flatMap((collection) => - // collection?.cards?.map((card) => ({ - // ...card, - // collectionId: collection._id, - // })) - // ); - - // // Create a unique set of card IDs - // const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card.id)); - - // // Map over unique card IDs to find corresponding card and update with new prices if available - // return cardsWithCollectionId.map((card) => { - // const updatedCardInfo = - // cardPrices.find((price) => price.id === card.id) || {}; - - // return { - // ...card, - // latestPrice: updatedCardInfo?.latestPrice || card?.latestPrice, - // lastSavedPrice: updatedCardInfo?.lastSavedPrice || card?.lastSavedPrice, - // price: updatedCardInfo?.latestPrice?.num || card.price, // Assuming you want to update the price field as well - // _id: updatedCardInfo?._id || card?._id, - // id: updatedCardInfo?.id || card?.id, - // collectionId: updatedCardInfo?.collectionId || card?.collectionId, - // tag: updatedCardInfo?.tag || card?.tag, - // name: updatedCardInfo?.name || card?.name, - // quantity: updatedCardInfo?.quantity || card?.quantity, - // priceHistory: updatedCardInfo?.priceHistory || card?.priceHistory, - // // __v: updatedCardInfo?.__v || originalCard.__v, - // // _id: card?._id, - // // id: card?.id, - // // collectionId: card?.collectionId, // Include collection ID in the returned object - // // tag: 'monitored', - // // name: card?.name, - // // quantity: card?.quantity, - // // price: card?.price, - // // latestPrice: { - // // num: updatedLatestPrice, - // // timestamp: card?.latestPrice?.timestamp - // // ? card?.latestPrice?.timestamp - // // : new Date().toISOString(), - // // }, - // // lastSavedPrice: { - // // num: updatedLastSavedPrice, - // // timestamp: card?.lastSavedPrice?.timestamp - // // ? card?.lastSavedPrice?.timestamp - // // : new Date().toISOString(), - // // }, - // // priceHistory: card?.priceHistory, - // // __v: card?.__v, - // }; - // }); - // }, [allCollections, state.cardPrices]); - - // ----------- SOCKET EVENT HANDLERS ----------- - - // const safeEmit = useCallback( - // (event, data) => { - // try { - // if (!validateData(data, event, 'safeEmit')) { - // throw new Error(`Invalid data emitted for event: ${event}`); - // } - // if (socket) { - // socket.emit(event, data); - // console.log(`[Info] Emitted event: ${event}`); - // } else { - // console.warn('Socket is not connected. Cannot emit event:', event); - // } - // } catch (error) { - // console.error(`[Error] Failed to emit event: ${event}`, error); - // setDataFunctions.error({ - // message: error.message, - // source: 'safeEmit', - // }); - // } - // }, - // [socket] - // ); - - // const safeOn = useCallback( - // (event, handler) => { - // const wrapper = (data) => { - // try { - // if (!validateData(data, event, handler.name)) { - // throw new Error(`Invalid data received for event: ${event}`); - // } // Add this line to validate the data received - // // console.log(`[Info] Handling event: ${event}`); - // handler(data); - // } catch (error) { - // console.error(`[Error] Failed to handle event: ${event}`, error); - // setDataFunctions.error({ message: error.message, source: event }); - // } - // }; - - // socket.on(event, wrapper); // Add this line to register the event listener - - // return () => { - // socket.off(event, wrapper); // Add this line to unregister the event listener when the component unmounts - // }; - // }, - // [socket] - // ); - - // const mergeUpdates = (currentArray, updates) => { - // const updatedArray = [...currentArray]; - // updates.forEach((update) => { - // const index = updatedArray.findIndex((item) => item.id === update.id); - // if (index !== -1) { - // updatedArray[index] = { ...updatedArray[index], ...update }; - // } else { - // updatedArray.push(update); - // } - // }); - // return updatedArray; - // }; - - // const handleStatusUpdateCharts = (newData) => { - // const { updates } = newData.data; - // console.log('[STATUS_UPDATE_CHARTS] Data:', updates); - // const updatedList = mergeUpdates(state.listOfSimulatedCards, updates); - // setDataFunctions.listOfSimulatedCards(updatedList); - // }; const handleStatusUpdateCharts = async (newData) => { console.log('[STATUS_UPDATE_CHARTS] Data:', newData); - console.log('Card prices retrieved:', newData); - // const processedPrices = processCardPrices(newData, selectedCollection); - console.log('Card prices updated:', newData); - - // Filter out cards which are not in the selected collection - // const filteredCards = newData.data.data.filter((card) => { - // const cardIds = selectedCollection?.cards?.map((card) => card.id); - // return cardIds?.includes(card.id); - // }); - - // Merge the selectedCollection cards with the filteredCards by adding the latestPrice, lastSavedPrice and priceHistory - // selectedCollection should receive: price: latestPrice.num, priceHistory: [...priceHistory, latestPrice.num], lastSavedPrice: latestPrice - // monitoredCards should then be updated with the new values of quantity from the card data in selectedCollection - // const updatedAllCardPrices = processedPrices.priceArray; - // const updatedTotalPrice = processedPrices.totalPrice; - // const filteredCards = processedPrices.filteredCards; - // console.log('********** [--------------] **********'); - // console.log('********** [FILTERED CARDS] **********', filteredCards); - - // console.log('********** [--------------] **********'); - // console.log('********** [UPDATED PRICES] **********', updatedAllCardPrices); - - // console.log('********** [--------------] **********'); - // console.log('********** [UPDATED TOTAL] **********', updatedTotalPrice); - - // console.log('********** [--------------] **********'); - - // Start with the current collection's state let updatedCollection = { ...selectedCollection }; - // setTotalPrice(updatedTotalPrice); - // setAllCardPrices(updatedAllCardPrices); - // console.log('Updated collection in combined:', updatedCollection); - // Iterate over the array of processed prices - // for (const card of filteredCards) { - // updatedCollection = await updateOneFromCollection(card); - // } - - // return updatedCollection; - // const updatedCollection = await updateCollection({ - // ...selectedCollection, - // cards: filteredCards, - // }); - console.log('Updated collection in combined:', updatedCollection); return updatedCollection; }; @@ -628,7 +227,10 @@ export const CombinedProvider = ({ children }) => { const handleStatusUpdateCron = (newData) => { const { message, data } = newData; - console.log('[STATUS_UPDATE_CRON]', message, data); + // console.log('[STATUS_UPDATE_CRON]', message, data); + if (!Array.isArray(data) || !data.data || data.data.length === 0) { + return null; + } setDataFunctions.data(data); }; @@ -664,19 +266,10 @@ export const CombinedProvider = ({ children }) => { setDataFunctions.collectionData(collectionData); }; - // const handleExistingChartData = (chartData) => { - // console.log('Existing chart data:', chartData); - // setDataFunctions.existingChartData(chartData); - // }; - - // const handleChartDatasetsUpdated = (chartUpdate) => { - // console.log('Chart datasets updated:', chartUpdate); - // setDataFunctions.currentChartData(chartUpdate); - // }; - - const handleCardPricesUpdated = (priceData) => { + const handleCardPricesUpdated = async (priceData) => { console.log('Card prices retrieved:', priceData); - // Update listOfMonitoredCards based on the updated card prices + const updatedCardPrices = priceData.data.data; + const userId = user?.id; const currentListOfMonitoredCards = generateListOfMonitoredCards(allCollections); console.log( @@ -685,24 +278,6 @@ export const CombinedProvider = ({ children }) => { )}] | `, currentListOfMonitoredCards ); - const updatedCardPrices = priceData.data.data; - // updatedCardPrices.forEach( - // ( - // card // Update the price of each card in the listOfMonitoredCards - // ) => { - // card.price = currentListOfMonitoredCards.price || card.price; - // } - // ); - - setDataFunctions.cardPrices(updatedCardPrices); - // console.log( - // `[updatedCardPrices: $${getNewTotalPrice(updatedCardPrices)}] | `, - // updatedCardPrices - // ); - // console.log( - // `[state.cardPrices.data: $${getNewTotalPrice(state.cardPrices.data)}] | `, - // state.cardPrices.data - // ); const updatedListOfMonitoredCards = updateCardPricesInList( currentListOfMonitoredCards, updatedCardPrices @@ -714,22 +289,39 @@ export const CombinedProvider = ({ children }) => { updatedListOfMonitoredCards ); - // Now update the listOfMonitoredCards in your state - setDataFunctions.allCardPrices(updatedListOfMonitoredCards); - const updatedCollectionResult = updateCollection( - selectedCollection, - null, // since we're not updating a specific card - 'update', // Assuming 'update' is the operation when prices change - userId + // Update the selectedCollection with new card prices + const updatedSelectedCollectionCards = selectedCollection.cards.map( + (card) => { + const updatedCardPrice = updatedListOfMonitoredCards.find( + (updatedCard) => updatedCard.id === card.id + ); + return updatedCardPrice ? { ...card, ...updatedCardPrice } : card; + } ); - if (updatedCollectionResult) { - // Do something with the updated collection - console.log('Updated Collection:', updatedCollectionResult); - // Update your state/context or perform additional actions + const updatedCollection = { + ...selectedCollection, + cards: updatedSelectedCollectionCards, + }; + + try { + const updatedCollectionResult = await getUpdatedCollection( + updatedCollection, + null, // No specific card to update + 'update', // Operation type + userId + ); + + if (updatedCollectionResult) { + console.log('Updated Collection:', updatedCollectionResult); + // setDataFunctions.collectionData(updatedCollectionResult); + setDataFunctions.listOfSimulatedCards(updatedCollectionResult); + } + } catch (error) { + console.error('Failed to update collection:', error); } - // Additional processing if required - // ... + + setDataFunctions.allCardPrices(updatedListOfMonitoredCards); }; const handleNoPricesChanged = () => { @@ -809,7 +401,7 @@ export const CombinedProvider = ({ children }) => { socket.on(event, handler); }); - validateData(eventHandlers, 'eventHandlers', 'useEffect'); + // validateData(eventHandlers, 'eventHandlers', 'useEffect'); return () => { eventHandlers.forEach((_, event) => { socket.off(event); @@ -828,49 +420,20 @@ export const CombinedProvider = ({ children }) => { ); return; } - socket.emit('REQUEST_EXISTING_COLLECTION_DATA', { + socket?.emit('REQUEST_EXISTING_COLLECTION_DATA', { userId, data: selectedCollection, }); }, - chart: (userId, selectedCollection) => { - if (!userId) - return console.error('Missing userId for chart data request.'); - if (!selectedCollection) - return console.error( - 'Missing selectedCollection for chart data request.' - ); - if (selectedCollection.chartData === undefined || null) { - if (selectedCollection.chartData === undefined) - console.log('chartData is undefined'); - if (selectedCollection.chartData === null) - console.log('chartData is null'); - return console.error( - 'The selected collections chart data is missing, null or undefined.' - ); - } - - console.log( - 'Attempting to retrieve chart data', - selectedCollection?.chartData - ); - const chartData = selectedCollection?.chartData || {}; - socket.emit('REQUEST_EXISTING_CHART_DATA', { - data: { - userId, - data: chartData, - }, - }); - }, }, sendAction: { message: (message) => { if (!message) return console.error('Message content is missing.'); - socket.emit('MESSAGE_FROM_CLIENT', { message, data: message }); + 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 }); + socket?.emit('REQUEST_CRON_STOP', { userId }); }, checkAndUpdateCardPrices: ( userId, @@ -887,7 +450,7 @@ export const CombinedProvider = ({ children }) => { listOfMonitoredCards ); const selectedList = listOfMonitoredCards; - socket.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { + socket?.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { userId, data: { selectedList, @@ -905,9 +468,6 @@ export const CombinedProvider = ({ children }) => { if (!listOfMonitoredCards) return console.log('Missing retrievedListOfMonitoredCards.'); if (!allCollections) return console.log('Missing allCollections.'); - // if (!cardsWithChangedPrice) - // return console.log('Missing cardsWithChangedPrice.'); - const selectedList = listOfMonitoredCards; socket.emit('REQUEST_PRICES_ACTIVATE_CRON', { userId, @@ -935,78 +495,32 @@ export const CombinedProvider = ({ children }) => { const confirm = (message) => window.confirm(message); - useEffect(() => { - if (userId && selectedCollection) { - handleSocketInteraction.requestData.collection( - userId, - selectedCollection - ); - handleSocketInteraction.requestData.chart(userId, selectedCollection); - handleSocketInteraction.sendAction.message('Hello from client!'); - // handleSocketInteraction.sendAction.updateCollection(); - // handleSocketInteraction.sendAction.updateChart(); - // handleSocketInteraction.sendAction.checkAndUpdateCardPrices( - // userId, - // listOfMonitoredCards - // // retrieveListOfMonitoredCards() - // ); - } - }, [userId, selectedCollection, socket]); useEffect(() => { // Update the collectionData state when selectedCollection changes setDataFunctions.collectionData(selectedCollection); }, [selectedCollection]); - // useEffect(() => { - // if (state.allCardPrices) { - // console.log('ALL PRICE DATA', state.allCardPrices); - // // const oldTotal = getNewTotalPrice(state.cardPrices); - // const oldTotal2 = getNewTotalPrice(listOfMonitoredCards); - - // console.log('OLD TOTAL', oldTotal2); - // if ( - // JSON.stringify(state.allCardPrices) !== JSON.stringify(state.cardPrices) - // ) { - // console.log('SETTING SELECTED COLLECTION'); - // const newTotal = getNewTotalPrice(state.allCardPrices); - // console.log('NEW TOTAL COMBINED', newTotal); - // setAllCollections(state.collectionData); - // } - // } - // }, [state.allCardPrices]); - useEffect(() => { if (allCollections) { - // console.log('allCollections', allCollections); - // console.log('listOfMonitoredCards', listOfMonitoredCards); - - console.log('ALLL', allCollections); + // console.log('ALLL', allCollections); if ( JSON.stringify(allCollections) !== JSON.stringify(state.allCollectionData) ) { setDataFunctions.allCollectionData(allCollections); } - - // setDataFunctions.retrievedListOfMonitoredCards( - // retrieveListOfMonitoredCards() - // ); } }, [allCollections]); + useEffect(() => { + if (user) { + // console.log('userId', user.userId); + setDataFunctions.userData(user); + } + }, [user]); const logError = (message) => console.error(message); // ----------- CONTEXT VALUE ----------- - // useEffect(() => { - // if (listOfMonitoredCards.length === 0) - // console.log('listOfMonitoredCards', listOfMonitoredCards); - - // setDataFunctions.listOfMonitoredCards({ listOfMonitoredCards }); - // // handleSocketInteraction.sendAction.checkAndUpdateCardPrices( - // // userId, - // // listOfMonitoredCards - // // ); - // }, [listOfMonitoredCards, allCollections]); const value = useMemo( () => ({ diff --git a/src/context/CronJobContext/CronJobContext.jsx b/src/context/CronJobContext/CronJobContext.jsx new file mode 100644 index 0000000..d28b60e --- /dev/null +++ b/src/context/CronJobContext/CronJobContext.jsx @@ -0,0 +1,52 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { useUserContext } from '../UserContext/UserContext'; +import { useCollectionStore } from '../CollectionContext/CollectionContext'; +import { useCombinedContext } from '../CombinedProvider'; + +const CronJobContext = createContext(); + +export const useCronJobContext = () => useContext(CronJobContext); + +export const CronJobProvider = ({ children }) => { + const { user } = useUserContext(); + const { allCollections } = useCollectionStore(); + const { handleSendAllCardsInCollections } = useCombinedContext(); + const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState( + new Date().getTime() + ); + + useEffect(() => { + const handleTriggerCronJob = () => { + const currentTime = new Date().getTime(); + const timeDifference = currentTime - lastCronJobTriggerTime; + + // Define your cron job logic here + // Example: Trigger an action if a certain time has elapsed + if (timeDifference >= 60000) { + // 60 seconds + setLastCronJobTriggerTime(currentTime); + if (user && user.userID) { + console.log('Triggering cron job actions'); + // Call your functions here + handleSendAllCardsInCollections(user.userID); + } + } + }; + + const interval = setInterval(handleTriggerCronJob, 1000); // Check every second + return () => clearInterval(interval); + }, [ + lastCronJobTriggerTime, + user, + allCollections, + handleSendAllCardsInCollections, + ]); + + return ( + + {children} + + ); +}; diff --git a/src/context/CutstomLogger.jsx b/src/context/CutstomLogger.jsx deleted file mode 100644 index fb746c0..0000000 --- a/src/context/CutstomLogger.jsx +++ /dev/null @@ -1,23 +0,0 @@ -let logCounter = 0; - -function CustomLogger(data) { - logCounter += 1; - const dataType = Array.isArray(data) ? 'Array' : typeof data; - let formattedData = ''; - - if (dataType === 'object') { - formattedData = JSON.stringify(data, null, 2); - } else if (dataType === 'Array') { - formattedData = data - .map((item) => JSON.stringify(item, null, 2)) - .join('\n'); - } else { - formattedData = data.toString(); - } - - console.log(`[LOG ${logCounter}] --- [Type: ${dataType}] -----------------`); - console.log(formattedData); - console.log(`[END OF LOG ${logCounter}] ---------------------------------`); -} - -export default CustomLogger; diff --git a/src/context/DeckContext/new.js b/src/context/DeckContext/new.js deleted file mode 100644 index 59d5dfc..0000000 --- a/src/context/DeckContext/new.js +++ /dev/null @@ -1,645 +0,0 @@ - // const FetchAllDeck = ({ setDeckData, deckData, setDeckOptions }) => { - // useEffect(() => { - - // return null; - // }; - - // const fetchUserDeck = useCallback( - // async (userId) => { - // setLoading(true); - // setError(null); - // try { - // const response = await fetch( - // `${process.env.REACT_APP_SERVER}/api/decks/userDeck/${userId}` - // ); - - // if (!response.ok) { - // if (response.status === 404) { - // return createUserDeck(userId); - // } else if (response.status === 500) { - // setError('An unexpected error occurred. Please try again later.'); - // } else { - // throw new Error(`HTTP error! status: ${response.status}`); - // } - // } else { - // const data = await response.json(); - // console.log('DECKLIST EXISTS:', data); - // setCookie('deck', Array.isArray(data.deck) ? data.deck : [], { - // path: '/', - // }); - // setDeckDataAndCookie( - // 'deck', - // Array.isArray(data.deck) ? data.deck : [], - // { - // path: '/', - // } - // ); - // setLoading(false); - // return data; - // } - - // // if (data && data.deck) { - // // // ...existing code to update deckData - // // // New code to update allDecks - // // const updatedAllDecks = allDecks.map((deck) => { - // // if (deck.creatorId === userId) { - // // return { ...deck, decks: data }; - // // } - // // return deck; - // // }); - // // setAllDecks(updatedAllDecks); - // // } - // } catch (error) { - // setError(error.message); - // setLoading(false); - // } - // }, - // [setCookie] - // ); - - // useEffect(() => { - // if (userId && typeof userId === 'string') { - // fetchUserDeck(userId).then((data) => { - // if (data && data.deck) { - // setDeckDataAndCookie(data); - // } else { - // console.error('Deck data was not retrieved for user', userId); - // } - // }); - // } - // }, [userId, fetchUserDeck]); - - import React, { - createContext, - useState, - useEffect, - useCallback, - useContext, - } from 'react'; - import { useCookies } from 'react-cookie'; - import { useCardStore } from '../CardContext/CardStore'; - - export const DeckContext = createContext(null); - - const apiBase = `${process.env.REACT_APP_SERVER}/api/decks`; - - const fetchWrapper = async (url, method, body = null) => { - const options = { - method, - headers: { 'Content-Type': 'application/json' }, - ...(body && { body: JSON.stringify(body) }), - }; - const response = await fetch(url, options); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - return response.json(); - }; - - const removeDuplicateDecks = (decks) => { - const uniqueDecks = {}; - decks.forEach((deck) => (uniqueDecks[deck._id] = deck)); - return Object.values(uniqueDecks); - }; - - export const DeckProvider = ({ children }) => { - const { getCardData } = useCardStore(); - const [cookies, setCookie] = useCookies(['userCookie']); - const [deckData, setDeckData] = useState({}); - const [allDecks, setAllDecks] = useState([]); - const [selectedDeck, setSelectedDeck] = useState({}); - const userId = cookies.userCookie?.id; - - // Helper Functions - const setDeckDataAndCookie = useCallback( - (newDeckData) => { - setDeckData(newDeckData); - setCookie('deckData', newDeckData, { path: '/' }); - }, - [setCookie] - ); - - const filterUserDecks = useCallback( - (decks) => decks.filter((deck) => deck.userId === userId), - [userId] - ); - - const fetchAndSetDecks = useCallback(async () => { - const url = `${apiBase}/users/${userId}/decks`; - const fetchedDecks = await fetchWrapper(url, 'GET'); - const userDecks = filterUserDecks(fetchedDecks); - const uniqueDecks = removeDuplicateDecks(userDecks); - - setAllDecks((prevDecks) => - removeDuplicateDecks([...prevDecks, ...uniqueDecks]) - ); - setDeckDataAndCookie(uniqueDecks[0]); - }, [userId, setAllDecks, setDeckDataAndCookie, filterUserDecks]); - - useEffect(() => { - if (userId) fetchAndSetDecks(); - }, [userId, fetchAndSetDecks]); - - const fetchAllDecksForUser = useCallback( - async (userId) => { - // Fetch the initial set of decks - const initialDecks = await fetchAndSetDecks( - apiBase2, - userId, - setAllDecks, - setDeckDataAndCookie - ); - if (initialDecks === null) { - const shouldCreateDeck = window.confirm( - 'No decks found. Would you like to create a new one?' - ); - if (shouldCreateDeck) { - const deckName = prompt('Enter the deck name:'); - const deckDescription = prompt('Enter the deck description:'); - await createUserDeck(userId, { - name: deckName, - description: deckDescription, - }); - } - return; - } - - const fetchCount = initialDecks?.length || 0; - for (let i = 0; i < fetchCount; i++) { - await new Promise((res) => setTimeout(res, 1000)); - await fetchAndSetDecks( - apiBase1, - userId, - setAllDecks, - setDeckDataAndCookie - ); - } - }, - [setAllDecks, setDeckDataAndCookie] - ); - - const updateDeck = (newDeckData) => { - setDeckDataAndCookie(newDeckData); - setDeckData(newDeckData); - }; - - const updateAndSyncDeck = async (newDeckData) => { - updateDeck(newDeckData); - - setAllDecks((prevDecks) => { - const newAllDecks = prevDecks.map((deck) => - deck._id === newDeckData._id ? newDeckData : deck - ); - return prevDecks.some((deck) => deck._id === newDeckData._id) - ? newAllDecks - : [...newAllDecks, newDeckData]; - }); - - try { - const url = `${process.env.REACT_APP_SERVER}/api/users/${userId}/decks`; - await fetchWrapper( - url, - 'PUT', - formatDeckData(newDeckData._id, newDeckData.cards) - ); - } catch (error) { - console.error(`Failed to update deck in backend: ${error.message}`); - } - }; - - const formatDeckData = (deckId, updatedDeck) => { - return { - _id: deckId, // Changed "deckId" to "_id" to match the structure - userId: userId, - name: deckData.name, // assuming you have the name in your state - description: deckData.description, // assuming you have the description in your state - cards: updatedDeck.map((card) => formatCardData(card)), - }; - }; - - const formatCardData = (card) => ({ - id: card.id, - ...Object.fromEntries( - [ - 'name', - 'type', - 'frameType', - 'description', - 'card_images', - 'archetype', - 'atk', - 'def', - 'level', - 'race', - 'attribute', - 'quantity', - ].map((key) => [key, card[key] || null]) - ), - }); - - const addOrRemoveCard = async (card, isAdding) => { - try { - setDeckData((prevState) => { - const cardIndex = prevState.cards.findIndex( - (item) => item.id === card.id - ); - let newCards = [...prevState.cards]; - - if (cardIndex !== -1) { - newCards[cardIndex].quantity += isAdding ? 1 : -1; - if (newCards[cardIndex].quantity <= 0) { - newCards.splice(cardIndex, 1); - } - } else if (isAdding) { - newCards.push({ ...formatCardData(card), quantity: 1 }); - } - - const newDeckData = { ...prevState, cards: newCards }; - updateAndSyncDeck(newDeckData); - return newDeckData; - }); - } catch (error) { - console.error(`Failed to modify the deck: ${error.message}`); - } - }; - - const addOneToDeck = (card) => addOrRemoveCard(card, true); - const removeOneFromDeck = (cardId) => addOrRemoveCard({ id: cardId }, false); - - const createUserDeck = async (userId, newDeckInfo) => { - try { - const url = `${apiBase1}/newDeck/${userId}`; - const initialDeck = newDeckInfo.initialCard - ? [formatCardData(newDeckInfo.initialCard)] - : []; - const data = await fetchWrapper(url, 'POST', { - ...newDeckInfo, - cards: initialDeck, - userId, - }); - updateDeck(data); - } catch (error) { - console.error(`Failed to create a new deck: ${error.message}`); - } - }; - - // To get the quantity of a specific card in the deck - const getCardQuantity = (cardId) => { - // if (Array.isArray(deckData)) { - const card = deckData.cards?.find((item) => item.id === cardId); - // } - - return card?.quantity || 0; - }; - - const getTotalCost = () => - deckData.cards.reduce((acc, card) => acc + (card.cost || 0), 0); - - const value = { - deckData, - allDecks, - getCardQuantity, - addOneToDeck, - removeOneFromDeck, - getTotalCost, // Added - deleteFromDeck, - updateAndSyncDeck, // Added - selectedDeck, // Added - setSelectedDeck, // Added - fetchAllDecksForUser, - createUserDeck, - }; - - useEffect(() => { - console.log('DECKCONTEXT:', value); - const userId = cookies.userCookie?.id; - if (userId) { - fetchAllDecksForUser(userId); - } - }, [fetchAllDecksForUser, cookies.userCookie]); - - return {children}; - }; - - export const useDeckStore = () => { - const context = useContext(DeckContext); - if (!context) - throw new Error('useDeckStore must be used within a DeckProvider'); - return context; - }; - - // import React, { - // createContext, - // useState, - // useEffect, - // useCallback, - // useContext, - // } from 'react'; - // import { useCookies } from 'react-cookie'; - // import { useCardStore } from '../CardContext/CardStore'; - - // export const DeckContext = createContext(null); - - // const apiBase = `${process.env.REACT_APP_SERVER}/api/decks`; - - // const fetchWrapper = async (url, method, body = null) => { - // const options = { - // method, - // headers: { 'Content-Type': 'application/json' }, - // ...(body && { body: JSON.stringify(body) }), - // }; - // const response = await fetch(url, options); - // if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - // return response.json(); - // }; - - // const removeDuplicateDecks = (decks) => { - // const uniqueDecks = {}; - // decks.forEach((deck) => (uniqueDecks[deck._id] = deck)); - // return Object.values(uniqueDecks); - // }; - - // export const DeckProvider = ({ children }) => { - // const { getCardData } = useCardStore(); - // const [cookies, setCookie] = useCookies(['userCookie']); - // const [deckData, setDeckData] = useState({}); - // const [allDecks, setAllDecks] = useState([]); - // const [selectedDeck, setSelectedDeck] = useState({}); - // const userId = cookies.userCookie?.id; - - // // Helper Functions - // const setDeckDataAndCookie = useCallback( - // (newDeckData) => { - // setDeckData(newDeckData); - // setCookie('deckData', newDeckData, { path: '/' }); - // }, - // [setCookie] - // ); - - // const filterUserDecks = useCallback( - // (decks) => decks.filter((deck) => deck.userId === userId), - // [userId] - // ); - - // const fetchAndSetDecks = useCallback(async () => { - // const url = `${apiBase}/users/${userId}/decks`; - // const fetchedDecks = await fetchWrapper(url, 'GET'); - // const userDecks = filterUserDecks(fetchedDecks); - // const uniqueDecks = removeDuplicateDecks(userDecks); - - // setAllDecks((prevDecks) => - // removeDuplicateDecks([...prevDecks, ...uniqueDecks]) - // ); - // setDeckDataAndCookie(uniqueDecks[0]); - // }, [userId, setAllDecks, setDeckDataAndCookie, filterUserDecks]); - - // useEffect(() => { - // if (userId) fetchAndSetDecks(); - // }, [userId, fetchAndSetDecks]); - - // const fetchAllDecksForUser = useCallback( - // async (userId) => { - // // Fetch the initial set of decks - // const initialDecks = await fetchAndSetDecks( - // apiBase2, - // userId, - // setAllDecks, - // setDeckDataAndCookie - // ); - // if (initialDecks === null) { - // const shouldCreateDeck = window.confirm( - // 'No decks found. Would you like to create a new one?' - // ); - // if (shouldCreateDeck) { - // const deckName = prompt('Enter the deck name:'); - // const deckDescription = prompt('Enter the deck description:'); - // await createUserDeck(userId, { - // name: deckName, - // description: deckDescription, - // }); - // } - // return; - // } - - // const fetchCount = initialDecks?.length || 0; - // for (let i = 0; i < fetchCount; i++) { - // await new Promise((res) => setTimeout(res, 1000)); - // await fetchAndSetDecks( - // apiBase1, - // userId, - // setAllDecks, - // setDeckDataAndCookie - // ); - // } - // }, - // [setAllDecks, setDeckDataAndCookie] - // ); - - // const updateAndSyncDeck = async (newDeckData) => { - // // Update the deck locally - // setDeckDataAndCookie(newDeckData); // Using the function to also set the cookie - - // // Update in the global allDecks state - // const existingDeck = allDecks.find((deck) => deck._id === newDeckData._id); - // if (existingDeck) { - // const newAllDecks = allDecks.map((deck) => - // deck._id === newDeckData._id ? newDeckData : deck - // ); - // setAllDecks(newAllDecks); - // } else { - // setAllDecks((prevDecks) => [...prevDecks, newDeckData]); - // } - - // // Sync with the backend - // try { - // // Corrected the way to include `deckId` in the URL - // const url = `${process.env.REACT_APP_SERVER}/api/users/${userId}/decks`; - // await fetchWrapper( - // url, - // 'PUT', - // formatDeckData(newDeckData._id, newDeckData.cards) - // ); - // } catch (error) { - // console.error(`Failed to update deck in backend: ${error.message}`); - // } - // }; - - // // To formatdeckId - // const formatDeckData = (deckId, updatedDeck) => { - // return { - // _id: deckId, // Changed "deckId" to "_id" to match the structure - // userId: userId, - // name: deckData.name, // assuming you have the name in your state - // description: deckData.description, // assuming you have the description in your state - // cards: updatedDeck.map((card) => formatCardData(card)), - // }; - // }; - - // // To format card data before sending to backend - // const formatCardData = (card) => { - // return { - // id: card.id, - // name: card.name || null, - // type: card.type || null, - // frameType: card.frameType || null, - // description: card.description || null, - // card_images: card.card_images || null, - // archetype: card.archetype || null, - // atk: card.atk || null, - // def: card.def || null, - // level: card.level || null, - // race: card.race || null, - // attribute: card.attribute || null, - // quantity: card.quantity || null, - // }; - // }; - - // // To get the quantity of a specific card in the deck - // const getCardQuantity = (cardId) => { - // // if (Array.isArray(deckData)) { - // const card = deckData.cards?.find((item) => item.id === cardId); - // // } - - // return card?.quantity || 0; - // }; - - // // To add one card to the deck - // // To add one card to the deck - // const addOneToDeck = async (card) => { - // try { - // setDeckData((prevState) => { - // if (!Array.isArray(prevState.cards)) { - // console.error('deckData.cards is not an array.'); - // return prevState; - // } - - // const existingCard = prevState.cards.find( - // (item) => item.id === card.id - // ); - // let newCards; - - // if (existingCard) { - // existingCard.quantity += 1; - // newCards = [...prevState.cards]; - // } else { - // newCards = [ - // ...prevState.cards, - // { - // ...formatCardData(card), - // quantity: 1, - // }, - // ]; - // } - - // const newDeckData = { ...prevState, cards: newCards }; - // // const newDeckData = { ...deckData }; // Take a copy of existing deckData - // updateAndSyncDeck(newDeckData); - - // // Update the cookie - // setCookie('deckData', newDeckData, { path: '/' }); - - // return newDeckData; - // }); - - // setAllDecks((prevState) => { - // const updatedAllDecks = prevState.map((deck) => { - // if (deck._id === deckData._id) { - // return { ...deck, cards: deckData.cards }; // Assuming deckData.cards is updated - // } - // return deck; - // }); - // return updatedAllDecks; - // }); - // } catch (error) { - // console.error(`Failed to add a card to the deck: ${error.message}`); - // } - // }; - - // const createUserDeck = async (userId, newDeckInfo) => { - // try { - // const { initialCard, ...rest } = newDeckInfo; - // const url = `${apiBase1}/newDeck/${userId}`; // Modified this line to use apiBase - - // const initialDeck = initialCard ? [formatCardData(initialCard)] : []; - - // const data = await fetchWrapper(url, 'POST', { - // ...rest, - // cards: initialDeck, - // userId, - // }); - - // setDeckData(data); - // updateAndSyncDeck(data); - // } catch (error) { - // console.error(`Failed to create a new deck: ${error.message}`); - // } - // }; - - // const removeOneFromDeck = async (cardId) => { - // try { - // const newDeckData = { ...deckData }; // Take a copy of existing deckData - // const cardToRemove = newDeckData.cards?.find( - // (item) => item.id === cardId - // ); - // if (!cardToRemove) return; - // cardToRemove.quantity -= 1; - - // if (cardToRemove.quantity === 0) { - // newDeckData.cards = newDeckData.cards.filter( - // (item) => item.id !== cardId - // ); - // } - - // // Update and sync the deck - // await updateAndSyncDeck(newDeckData); - // } catch (error) { - // console.error(`Failed to remove a card from the deck: ${error.message}`); - // } - // }; - - // // To delete a card entirely from the deck - // const deleteFromDeck = async (cardId) => { - // try { - // const newDeck = deckData.cards.filter((item) => item.id !== cardId); - // const updatedDeckData = await updateAndSyncDeck(deckData._id, newDeck); - // setDeckData(updatedDeckData); - // } catch (error) { - // console.error(`Failed to delete a card from the deck: ${error.message}`); - // } - // }; - - // const getTotalCost = () => { - // return deckData.cards.reduce((acc, card) => acc + (card.cost || 0), 0); - // }; - - // const value = { - // deckData, - // allDecks, - // getCardQuantity, - // addOneToDeck, - // removeOneFromDeck, - // getTotalCost, // Added - // deleteFromDeck, - // updateAndSyncDeck, // Added - // selectedDeck, // Added - // setSelectedDeck, // Added - // fetchAllDecksForUser, - // createUserDeck, - // }; - - // useEffect(() => { - // console.log('DECKCONTEXT:', value); - // const userId = cookies.userCookie?.id; - // if (userId) { - // fetchAllDecksForUser(userId); - // } - // }, [fetchAllDecksForUser, cookies.userCookie]); - - // return {children}; - // }; - - // export const useDeckStore = () => { - // const context = useContext(DeckContext); - // if (!context) - // throw new Error('useDeckStore must be used within a DeckProvider'); - // return context; - // }; - \ No newline at end of file diff --git a/src/context/Helpers.jsx b/src/context/Helpers.jsx index 35365bd..e71198e 100644 --- a/src/context/Helpers.jsx +++ b/src/context/Helpers.jsx @@ -5,7 +5,10 @@ import { useCookies } from 'react-cookie'; export const fetchWrapper = async (url, method, body = null) => { const options = { method, - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, }; if (body) { @@ -24,26 +27,6 @@ export const fetchWrapper = async (url, method, body = null) => { } }; -// const fetchWrapper = async (url, method, body = null) => { -// const options = { -// method, -// headers: { 'Content-Type': 'application/json' }, -// ...(body && { body: JSON.stringify(body) }), -// }; -// const response = await fetch(url, options); -// if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); -// return await response.json(); -// }; - -// const removeDuplicateCollections = (collections) => { -// const uniqueCollections = {}; -// collections.forEach((collection) => { -// uniqueCollections[collection._id] = collection; -// }); -// return Object.values(uniqueCollections); -// }; - -// Function to remove duplicate collections export const removeDuplicateCollections = (collections) => { const seen = new Set(); return collections.filter((collection) => { @@ -87,3 +70,48 @@ export const useUserId = () => { return userId; }; + +const isEmpty = (obj) => { + return ( + [Object, Array].includes((obj || {}).constructor) && + !Object.entries(obj || {}).length + ); +}; + +export const validateData = (data, eventName, functionName) => { + const dataType = typeof data || data.data || data.data.data || data.message; + console.log( + '----------------------------------------------------------------------------------------------------' + ); + console.log( + `| [SUCCESS] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}] |` + ); + console.log( + '----------------------------------------------------------------------------------------------------' + ); + if (data === null || data === undefined) { + console.log( + '----------------------------------------------------------------------------------------------------' + ); + console.warn( + `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` + ); + console.log( + '----------------------------------------------------------------------------------------------------' + ); + return false; + } + if (isEmpty(data) && isEmpty(data?.data) && isEmpty(data?.data?.data)) { + console.log( + '----------------------------------------------------------------------------------------------------' + ); + console.error( + `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` + ); + console.log( + '----------------------------------------------------------------------------------------------------' + ); + return false; + } + return true; +}; diff --git a/src/context/ModalContext/ModalContext.jsx b/src/context/ModalContext/ModalContext.jsx index 67debe6..1657960 100644 --- a/src/context/ModalContext/ModalContext.jsx +++ b/src/context/ModalContext/ModalContext.jsx @@ -4,21 +4,27 @@ export const ModalContext = createContext(); export const ModalProvider = ({ children }) => { const [modalContent, setModalContent] = useState(null); - const [isOpen, setIsOpen] = useState(false); + const [isModalOpen, setModalOpen] = useState(false); - const showModal = (content) => { - setModalContent(content); - setIsOpen(true); + const openModalWithCard = (card) => { + setModalContent(card); + setModalOpen(true); }; - const hideModal = () => { + const closeModal = () => { setModalContent(null); - setIsOpen(false); + setModalOpen(false); }; return ( {children} diff --git a/src/context/PopoverContext/PopoverContext.jsx b/src/context/PopoverContext/PopoverContext.jsx new file mode 100644 index 0000000..0204cd9 --- /dev/null +++ b/src/context/PopoverContext/PopoverContext.jsx @@ -0,0 +1,26 @@ +import React, { createContext, useState } from 'react'; + +export const PopoverContext = createContext({ + hoveredCard: null, + isPopoverOpen: false, + // eslint-disable-next-line @typescript-eslint/no-empty-function + setHoveredCard: () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + setIsPopoverOpen: () => {}, +}); + +export const PopoverProvider = ({ children }) => { + const [hoveredCard, setHoveredCard] = useState(null); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const value = { + hoveredCard, + isPopoverOpen, + setHoveredCard, + setIsPopoverOpen, + }; + + return ( + {children} + ); +}; diff --git a/src/context/SocketManager.jsx b/src/context/SocketManager.jsx deleted file mode 100644 index 625efc8..0000000 --- a/src/context/SocketManager.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useEffect } from 'react'; - -export const useSocketManager = (socket, eventHandlers) => { - useEffect(() => { - eventHandlers.forEach(({ event, handler }) => { - socket.on(event, handler); - }); - - return () => { - eventHandlers.forEach(({ event, handler }) => { - socket.off(event, handler); - }); - socket.disconnect(); - }; - }, [socket, eventHandlers]); -}; diff --git a/src/context/SocketProvider.jsx b/src/context/SocketProvider.jsx index 5a62a1f..f3d0953 100644 --- a/src/context/SocketProvider.jsx +++ b/src/context/SocketProvider.jsx @@ -9,10 +9,6 @@ import io from 'socket.io-client'; const SocketContext = createContext(); -// export const useSocket = () => { -// return useContext(SocketContext); -// }; - export const SocketProvider = ({ children }) => { const [socket, setSocket] = useState(null); diff --git a/src/context/UserContext/UserContext.js b/src/context/UserContext/UserContext.js index 148159b..2b462c4 100644 --- a/src/context/UserContext/UserContext.js +++ b/src/context/UserContext/UserContext.js @@ -13,12 +13,9 @@ const UserContext = createContext(); export const UserProvider = ({ children }) => { const [cookies, setCookie] = useCookies(['userCookie']); - // const [user, setUser] = useState(null); const [isCronJobTriggered, setIsCronJobTriggered] = useState(false); const [allCollections, setAllCollections] = useState([]); - const { user, setUser } = useAuthContext(); // Use the useAuthContext hook - // const { fetchCollections, fetchAllCollectionsForUser } = useCollectionStore(); const triggerCronJob = async () => { // Add your code here @@ -30,31 +27,21 @@ export const UserProvider = ({ children }) => { if (userID) { const updatedUser = { userID, username }; setUser(updatedUser); - // setAuthUser(updatedUser); // Update the user in AuthContext as well } }, [cookies]); const updateUser = (userData) => { setUser(userData); - // setAuthUser(userData); // Update the user in AuthContext setCookie('userCookie', userData, { path: '/' }); console.log('User Data Sent to Server and Cookie Updated:', userData); }; - // const fetchAndSetCollections = useCallback(async () => { - // const collections = fetchAllCollectionsForUser(user.id); - // if (collections) { - // setAllCollections(collections); // This is saved in the context - // } - // }, [user.id, fetchCollections]); - return ( { - const [isContextLoading, setIsContextLoading] = useState(true); - const [directedResponses, setDirectedResponses] = useState([]); // Initialize as an empty array - // const fetchWrapper = async (url, method, body = null) => { - // try { - // const options = { - // method, - // headers: { 'Content-Type': 'application/json' }, - // ...(body && { body: JSON.stringify(body) }), - // }; - // const response = await fetch(url, options); - // if (!response.ok) { - // throw new Error(`HTTP error! status: ${response.status}`); - // } - // return await response.json(); - // } catch (error) { - // if (error.message === 'Failed to fetch') { - // console.error('Network error: ', error); - // throw new Error( - // 'Network error. Please check your connection and try again.' - // ); - // } - // throw error; - // } - // }; - - const fetchDirectedResponses = async () => { - // let isMounted = true; // Added this flag - - try { - setIsContextLoading(true); - const response = await axios.get(`${BASE_API_URL}/directedResponses`); - const data = response.data; - - // if (isMounted) { - // Check if component is still mounted - Array.isArray(data) - ? setDirectedResponses(data) - : setDirectedResponses([]); - // } - } catch (error) { - // if (isMounted) { - // Check if component is still mounted - console.error('Error:', error); - setDirectedResponses([]); - // } - } finally { - // if (isMounted) { - // Check if component is still mounted - setIsContextLoading(false); - // } - } - }; - - // useEffect(() => { - // let isMounted = true; // Added this flag - - // if (isMounted && isContextLoading) { - // // console.log('Loading...'); - // } else if (isMounted && !isContextLoading) { - // // console.log('Finished Loading'); - // } - - // return () => { - // isMounted = false; // Cleanup - // }; - // }, [isContextLoading]); + const [isLoading, setIsLoading] = useState(true); const contextValue = { - isLoading: isContextLoading, - setIsContextLoading, - fetchDirectedResponses, - directedResponses: directedResponses, + isLoading, + setIsLoading, }; useEffect(() => { - if (isContextLoading) { + if (isLoading) { console.log('Loading...'); } else { console.log('Finished Loading', contextValue); } - }, [isContextLoading]); + }, [setIsLoading]); useEffect(() => { console.log('UTILITY CONTEXT VALUE:', contextValue); diff --git a/src/context/cleanUp/ApiServiceProvider.jsx b/src/context/cleanUp/ApiServiceProvider.jsx deleted file mode 100644 index 4105ddc..0000000 --- a/src/context/cleanUp/ApiServiceProvider.jsx +++ /dev/null @@ -1,139 +0,0 @@ -// // import { useCallback, useMemo } from 'react'; -// // import axios from 'axios'; -// // import { useCookies } from 'react-cookie'; -// // import { useCombinedContext } from './CombinedProvider'; -// // import useSocket from './SocketProvider'; - -// // export const useApiServiceProvider = () => { -// // const BASE_API_URL_CHARTS = `${process.env.REACT_APP_SERVER}/other/chart-data`; -// // const { userCookie } = useCookies(['userCookie']); -// // const { userId } = userCookie || {}; -// // const { setState, setChartData, setIsCronJobTriggered } = -// // useCombinedContext(); -// // const socket = useSocket(); - -// // const fetchData = useCallback(async () => { -// // if (!userId) return; - -// // setState((prevState) => ({ ...prevState, isLoading: true, error: null })); - -// // try { -// // const response = await axios.get( -// // `${BASE_API_URL_CHARTS}/charts/${userId}` -// // ); -// // setChartData(response?.data); -// // } catch (error) { -// // console.error('Error fetching updated data:', error); -// // setState((prevState) => ({ ...prevState, error: 'Error fetching data' })); -// // } finally { -// // setState((prevState) => ({ ...prevState, isLoading: false })); -// // } -// // }, [userId, setChartData, setState]); // Added `setState` to dependencies - -// // const updateServerData = useCallback( -// // async (updatedData) => { -// // if (!userId) return; - -// // const uniqueData = useMemo( -// // () => -// // Array.from( -// // new Set( -// // (updatedData || []) -// // .flatMap((obj) => obj.data || []) -// // .map(JSON.stringify) -// // ) -// // ).map(JSON.parse), -// // [updatedData] -// // ); - -// // try { -// // const chartId = updatedData?._id || 'all'; -// // const name = updatedData?.name || 'all'; - -// // const response = await axios.post( -// // `${BASE_API_URL_CHARTS}/charts/${userId}/${chartId}/updateChart`, -// // { userId, chartId, name, datasets: uniqueData } -// // ); - -// // socket.emit('RECEIVE_C2S_CHART_UPDATE', { -// // userId, -// // chartId, -// // name, -// // datasets: uniqueData, -// // }); - -// // setState((prevState) => ({ ...prevState, chartData: uniqueData })); -// // setIsCronJobTriggered(true); -// // } catch (error) { -// // console.error('Error updating server data:', error); -// // } -// // }, -// // [userId, setIsCronJobTriggered, setState, BASE_API_URL_CHARTS, socket] -// // ); - -// // return { fetchData, updateServerData }; -// // }; -// import React, { createContext, useContext, useCallback } from 'react'; -// import axios from 'axios'; - -// export const ApiContext = createContext(); - -// export const ApiServiceProvider = ({ children }) => { -// const BASE_API_URL = `${process.env.REACT_APP_SERVER}/other/cron`; - -// const getRequest = useCallback(async (endpoint) => { -// try { -// const response = await axios.get(`${BASE_API_URL}/${endpoint}`); -// return response.data; -// } catch (error) { -// console.error(`GET Error: ${error}`); -// throw error; -// } -// }, []); - -// const postRequest = useCallback(async (endpoint, data) => { -// try { -// const response = await axios.post(`${BASE_API_URL}/${endpoint}`, data); -// return response.data; -// } catch (error) { -// console.error(`POST Error: ${error}`); -// throw error; -// } -// }, []); - -// const putRequest = useCallback(async (endpoint, data) => { -// try { -// const response = await axios.put(`${BASE_API_URL}/${endpoint}`, data); -// return response.data; -// } catch (error) { -// console.error(`PUT Error: ${error}`); -// throw error; -// } -// }, []); - -// const deleteRequest = useCallback(async (endpoint) => { -// try { -// const response = await axios.delete(`${BASE_API_URL}/${endpoint}`); -// return response.data; -// } catch (error) { -// console.error(`DELETE Error: ${error}`); -// throw error; -// } -// }, []); - -// return ( -// -// {children} -// -// ); -// }; - -// export const useApiService = () => { -// const context = useContext(ApiContext); -// if (!context) { -// throw new Error('useApiService must be used within an ApiServiceProvider'); -// } -// return context; -// }; diff --git a/src/context/cleanUp/CombinedFetcher.jsx b/src/context/cleanUp/CombinedFetcher.jsx deleted file mode 100644 index 0cc21ae..0000000 --- a/src/context/cleanUp/CombinedFetcher.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import axios from 'axios'; -const BASE_API_URL_CRON = `${process.env.REACT_APP_SERVER}/other/cron`; -const BASE_API_URL_CHARTS = `${process.env.REACT_APP_SERVER}/other/chart-data`; - -export const fetchFromAPI = async (endpoint) => { - try { - return await axios.get(`${BASE_API_URL_CRON}/${endpoint}`); - } catch (error) { - console.error(`Error in request to ${endpoint}:`, error); - throw error; - } -}; - -export const fetchDataForUser = async (userId) => { - try { - const response = await axios.get(`${BASE_API_URL_CHARTS}/charts/${userId}`); - return response?.data; - } catch (error) { - console.error('Error fetching updated data:', error); - throw error; - } -}; - -export const updateServerDataForUser = async (userId, updatedData, io) => { - try { - const chartId = updatedData?._id || 'all'; - const name = updatedData?.name || 'all'; - const uniqueData = Array.from( - new Set( - (updatedData || []).flatMap((obj) => obj.data || []).map(JSON.stringify) - ) - ).map(JSON.parse); - - await axios.post( - `${BASE_API_URL_CHARTS}/charts/${userId}/${chartId}/updateChart`, - { userId, chartId, name, datasets: uniqueData } - ); - - io.emit('RECEIVE_C2S_CHART_UPDATE', { - userId, - chartId, - name, - datasets: uniqueData, - }); - return uniqueData; - } catch (error) { - console.error('Error updating server data:', error); - throw error; - } -}; diff --git a/src/context/cleanUp/SocketActions.jsx b/src/context/cleanUp/SocketActions.jsx deleted file mode 100644 index 60d8a7f..0000000 --- a/src/context/cleanUp/SocketActions.jsx +++ /dev/null @@ -1,171 +0,0 @@ -// import React, { -// createContext, -// useContext, -// useCallback, -// useEffect, -// useMemo, -// useState, -// } from 'react'; -// import { useSocket } from '../SocketProvider'; - -// const SocketActionsContext = createContext(); - -// export const useSocketActions = () => { -// return useContext(SocketActionsContext); -// }; -// const initialState = { -// chartData: {}, -// isLoading: false, -// data: {}, -// cronTriggerTimestamps: [], -// collectionData: [], -// deckData: [], -// allData: [], -// allUpdatedPrices: [], -// allItemTypeData: {}, -// error: null, -// prices: { -// totalCard: 0, -// updated: 0, -// allUpdated: [], -// totalDeck: 0, -// totalCollection: 0, -// }, -// cronData: {}, // Added cronData to initialState as it is referred in your code -// 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 SocketActionsProvider = ({ children }) => { -// const socket = useSocket(); -// const [receivedMessage, setReceivedMessage] = useState(null); -// // const [chartData, setChartData] = useState(); -// const [collectionData, setCollectionData] = useState(); - -// const [state, setState] = useState(initialState); -// /** -// * Custom hook to manage socket events -// // * @param {Socket} socket - the socket.io client socket -// // * @param {Array} events - array of event objects containing `event` and `handler` properties -// */ -// // const updateStateProperty = useCallback((property, value) => { -// // setState((prevState) => ({ ...prevState, [property]: value })); -// // }, []); -// // const setChartData = useCallback((newChartData) => { -// // setState((prevState) => ({ ...prevState, chartData: newChartData })); -// // }, []); -// // const setCollectionData = useCallback((newCollectionData) => { -// // setState((prevState) => ({ -// // ...prevState, -// // collectionData: newCollectionData, -// // })); -// // }, []); -// const sendMessage = useCallback( -// (message) => { -// if (socket) { -// console.log('Sending message:', message); -// const data = message; -// // socket.emit('connection'); -// socket.emit('MESSAGE_FROM_CLIENT', data); -// } else { -// console.error('Socket is not connected!'); -// } -// }, -// [socket] -// ); - -// const receiveMessage = useCallback((message) => { -// console.log('Received message:', message); -// setReceivedMessage(message); -// // handleHelloReply; -// // You might perform additional actions upon receiving a message here. -// }, []); - -// // const handleRequestCollectionData = useCallback(() => { -// // if (socket) socket.emit('EXISTING_COLLECTION_DATA', {}); -// // }, [socket]); - -// // const handleExistingCollectionData = (data) => { -// // console.log('Received existing collection data:', data.data); -// // setCollectionData(data.data); -// // }; - -// // Attach the event listener -// // socket.on('SEND_S2C_EXISTING_COLLECTION', handleExistingCollectionData); -// // const handleExistingChartData = useCallback( -// // (existingChartData) => { -// // setChartData(existingChartData); -// // }, -// // [setChartData] -// // ); - -// // const handleUpdateChartData = useCallback((data) => { -// // setState((prevState) => ({ -// // ...prevState, -// // chartData: [...prevState.chartData, ...data], -// // })); -// // }, []); - -// const handleUpdateCollectionData = useCallback((updatedCollectionData) => { -// setState((prevState) => ({ -// ...prevState, -// collectionData: updatedCollectionData, -// })); -// }, []); - -// // const handleDataUpdate = useCallback((dataUpdate) => { -// // console.log('HANDLING DATA UPDATE:', dataUpdate); -// // const { userId, chartId, name, datasets } = dataUpdate; -// // socket.emit('SEND_C2S_CHART_UPDATE', { -// // data: { userId, chartId, name, datasets }, -// // }); -// // }, []); - -// // const handleS2CChartUpdate = useCallback((s2cUpdate) => { -// // console.log('S2C UPDATE:', s2cUpdate); -// // socket.emit('RECEIVE_C2C_CHART_UPDATE', s2cUpdate); -// // }, []); - -// // const handleNewChartData = useCallback((newData) => { -// // setState((prevState) => ({ -// // ...prevState, -// // chartData: [...prevState.chartData, ...newData], -// // })); -// // }, []); - -// // useEffect(() => { -// // if (!socket) return; - -// // const handleNewChartData = (newData) => { -// // setState((prevState) => ({ -// // ...prevState, -// // chartData: [...prevState.chartData, ...newData], -// // })); -// // }; - -// // socket.on('NEW_CHART_DATA', handleNewChartData); - -// // return () => { -// // socket.off('NEW_CHART_DATA', handleNewChartData); -// // }; -// // }, [socket]); - -// return ( -// -// {children} -// -// ); -// }; diff --git a/src/context/cleanUp/SocketHandler.js b/src/context/cleanUp/SocketHandler.js deleted file mode 100644 index 6f9431f..0000000 --- a/src/context/cleanUp/SocketHandler.js +++ /dev/null @@ -1,115 +0,0 @@ -// import React, { createContext, useCallback, useEffect, useState } from 'react'; -// import io from 'socket.io-client'; - -// const SocketContext = createContext(); - -// const SocketHandler = ({ children }) => { -// const { userCookie } = useCookies(['userCookie']); -// const [state, setState] = useState(initialState); -// const userId = userCookie?.id; - -// // const { fetchData, updateServerData } = useApiServiceProvider(); -// const updateStateProperty = useCallback((property, value) => { -// setState((prevState) => ({ ...prevState, [property]: value })); -// }, []); -// const setChartData = useCallback((newChartData) => { -// setState((prevState) => ({ ...prevState, chartData: newChartData })); -// }, []); -// const setCollectionData = useCallback((newCollectionData) => { -// setState((prevState) => ({ -// ...prevState, -// collectionData: newCollectionData, -// })); -// }, []); -// const handleExistingCollectionData = useCallback( -// (existingCollectionData) => { -// setCollectionData(existingCollectionData); -// }, -// [setChartData] -// ); - -// const handleExistingChartData = useCallback( -// (existingChartData) => { -// setChartData(existingChartData); -// }, -// io.emit('SEND_C2S_CHART_UPDATE', { -// data: { data: existingChartData }, -// }), -// [setChartData] -// ); - -// const handleUpdateChartData = useCallback((data) => { -// setState((prevState) => ({ -// ...prevState, -// chartData: [...prevState.chartData, ...data], -// })); -// }, []); - -// const handleUpdateCollectionData = useCallback((updatedCollectionData) => { -// setState((prevState) => ({ -// ...prevState, -// collectionData: updatedCollectionData, -// })); -// }, []); - -// const handleDataUpdate = useCallback((dataUpdate) => { -// console.log('HANDLING DATA UPDATE:', dataUpdate); -// const { userId, chartId, name, datasets } = dataUpdate; - -// io.emit('SEND_C2S_CHART_UPDATE', { -// data: { userId, chartId, name, datasets }, -// }); -// }, []); - -// const handleReturnValue = useCallback((returnValue) => { -// // Logic to handle 'returnvalue' event -// // Example: Maybe update a state or log to console -// }, []); - -// const handleAllItemsUpdated = useCallback((updatedItems) => { -// state.setAllItems(updatedItems); -// // Additional logic as needed -// }, []); - -// const handleUpdateError = useCallback((errorInfo) => { -// // Logic to handle errors during the update -// // E.g., show a notification to the user -// }, []); - -// const handleS2CChartUpdate = useCallback((s2cUpdate) => { -// console.log('S2C UPDATE:', s2cUpdate); -// io.emit('RECEIVE_C2C_CHART_UPDATE', (s2cUpdate) => { -// console.log('S2C UPDATE:', s2cUpdate); -// }); -// }, []); -// }; - -// const value = { -// handleExistingCollectionData, -// handleExistingChartData, -// handleUpdateChartData, -// handleUpdateCollectionData, -// handleDataUpdate, -// handleReturnValue, -// handleAllItemsUpdated, -// handleUpdateError, -// handleS2CChartUpdate -// }; - -// return ( -// -// {children} -// -// ); -// }; - -// // 3. Export the provider and a custom hook to access the context -// export const useSocket = () => { -// const context = useContext(SocketContext); -// if (!context) { -// throw new Error("useSocket must be used within a SocketProvider"); -// } -// return context; -// }; - -// export { SocketProvider }; diff --git a/src/context/cleanUp/useSocketEvent.js b/src/context/cleanUp/useSocketEvent.js deleted file mode 100644 index 6bc4cb3..0000000 --- a/src/context/cleanUp/useSocketEvent.js +++ /dev/null @@ -1,16 +0,0 @@ -// useSocketEvent.js -import { useEffect } from 'react'; - -const useSocketEvent = (socket, event, handler) => { - useEffect(() => { - if (!socket) return; - - socket.on(event, handler); - - return () => { - socket.off(event, handler); - }; - }, [socket, event, handler]); -}; - -export default useSocketEvent; diff --git a/src/context/cleanUp/useUpdateChartData.jsx b/src/context/cleanUp/useUpdateChartData.jsx deleted file mode 100644 index 9e1d67c..0000000 --- a/src/context/cleanUp/useUpdateChartData.jsx +++ /dev/null @@ -1,72 +0,0 @@ -// import { useCallback, useEffect } from 'react'; -// import moment from 'moment'; -// import { useCollectionStore } from './hooks/collection'; -// import { useCombinedContext } from './CombinedProvider'; - -// const transformChartData = (chartData) => { -// let pointsArray = []; - -// if (Array.isArray(chartData?.datasets)) { -// chartData?.datasets.forEach((dataset) => { -// dataset.data?.forEach((dataEntry) => { -// const { x, y } = dataEntry.xy; -// if (x && y !== undefined) { -// pointsArray.push(dataEntry.xy); -// } -// }); -// }); -// } else { -// console.error( -// 'Expected chartData.datasets to be an array, but got:', -// chartData -// ); -// } - -// return pointsArray; -// }; -// const useUpdateChartData = () => { -// const { selectedCollection } = useCollectionStore() || {}; -// const { -// updateServerData, -// isCronJobTriggered, -// setIsCronJobTriggered, -// chartData, -// } = useCombinedContext() || {}; - -// const transformedData = transformChartData(selectedCollection?.chartData); - -// // const createDataset = (label, priceData) => ({ -// // name: label, -// // color: 'blue', -// // data: priceData?.map(({ x, y }) => ({ x, y })), -// // }); - -// // const newDataPoint = useCallback(() => { -// // return { -// // x: moment().format('YYYY-MM-DD HH:mm'), -// // y: totalCost ? parseFloat(totalCost).toFixed(2) : null, -// // }; -// // }, [totalCost]); - -// // const updateChart = useCallback(() => { -// // // let updatedDatasets = []; -// // const updatedDatasets = selectedCollection?.chartData?.datasets || []; -// // if (updatedDatasets.length) { -// // updateServerData(updatedDatasets); -// // } -// // // if (updatedDatasets.length) { -// // // updateServerData(updatedDatasets); -// // // } -// // }, []); - -// useEffect(() => { -// if (isCronJobTriggered) { -// // updateChart(); -// setIsCronJobTriggered(false); -// } -// }, [isCronJobTriggered, setIsCronJobTriggered]); -// console.log('transformedData', transformedData); -// return { datasets: transformedData }; -// }; - -// export default useUpdateChartData; diff --git a/src/context/hooks/collection.jsx b/src/context/hooks/collection.jsx deleted file mode 100644 index 9e1fc10..0000000 --- a/src/context/hooks/collection.jsx +++ /dev/null @@ -1,13 +0,0 @@ -// // useCollectionStore.js -// import { useContext } from 'react'; -// import { CollectionContext } from '../CollectionContext/CollectionContext'; - -// export const useCollectionStore = () => { -// const context = useContext(CollectionContext); -// if (!context) { -// throw new Error( -// 'useCollectionStore must be used within a CollectionProvider' -// ); -// } -// return context; -// }; diff --git a/src/context/hooks/useAppContext.jsx b/src/context/hooks/useAppContext.jsx index 125e6b7..dde49a6 100644 --- a/src/context/hooks/useAppContext.jsx +++ b/src/context/hooks/useAppContext.jsx @@ -1,16 +1,16 @@ // customHooks/useAppContext.js import { useContext } from 'react'; -import { AppContext } from '../AppContextProvider'; +import { AppContext } from '../AppContext/AppContextProvider'; const useAppContext = () => { const context = useContext(AppContext); if (!context) { console.error("The component isn't wrapped with AppContextProvider"); - return [null, false]; + return {}; // return an empty object } - return [context, true]; + return context; // return context as an object }; export default useAppContext; diff --git a/src/context/hooks/useUpdateContext.jsx b/src/context/hooks/useUpdateContext.jsx new file mode 100644 index 0000000..0ebc00c --- /dev/null +++ b/src/context/hooks/useUpdateContext.jsx @@ -0,0 +1,29 @@ +// import { useEffect } from 'react'; +// import { useLocation } from 'react-router-dom'; +// import useAppContext from './useAppContext'; + +// const useUpdateAppContext = () => { +// const location = useLocation(); +// const { setContext } = useAppContext(); // Destructuring from object + +// useEffect(() => { +// switch (location.pathname) { +// case '/deckbuilder': +// setContext('Deck'); +// break; +// case '/collection': +// setContext('Collection'); +// break; +// case '/cart': +// setContext('Cart'); +// break; +// default: +// setContext('Home'); +// break; +// } +// }, [location, setContext]); + +// return null; +// }; + +// export default useUpdateAppContext; diff --git a/src/components/headings/navigation/useWindowSize.jsx b/src/context/hooks/useWindowSize.jsx similarity index 100% rename from src/components/headings/navigation/useWindowSize.jsx rename to src/context/hooks/useWindowSize.jsx diff --git a/src/index.js b/src/index.js index 6d584fe..e6a9146 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import GlobalStyles from './assets/GlobalStyles'; +import ErrorBoundary from './context/ErrorBoundary'; import AuthProvider from './context/Auth/authContext'; import { CartProvider } from './context/CartContext/CartContext'; import { DeckProvider } from './context/DeckContext/DeckContext'; @@ -11,61 +12,58 @@ import { ModalProvider } from './context/ModalContext/ModalContext'; import { UserProvider } from './context/UserContext/UserContext'; import { CombinedProvider } from './context/CombinedProvider'; import { ThemeProvider } from '@mui/styles'; -import { useMode } from './context/hooks/colormode'; import { ColorModeProvider } from './context/ColorModeProvider'; -// import { ApiServiceProvider } from './context/cleanUp/ApiServiceProvider'; -import ErrorBoundary from './context/ErrorBoundary'; import { SocketProvider } from './context/SocketProvider'; import { SidebarProvider } from './context/SideBarProvider'; import { ChartProvider } from './context/ChartContext/ChartContext'; import { UtilityProvider } from './context/UtilityContext/UtilityContext'; -import { AppContextProvider } from './context/AppContextProvider'; -import { createTheme } from '@mui/material'; +import { AppContextProvider } from './context/AppContext/AppContextProvider'; +import { useMode } from './context/hooks/colormode'; +import { PopoverProvider } from './context/PopoverContext/PopoverContext'; +import { CronJobProvider } from './context/CronJobContext/CronJobContext'; +import { CardImagesProvider } from './context/CardImagesContext/CardImagesContext'; const root = document.getElementById('root'); function Main() { const { theme } = useMode(); - // const darkTheme = createTheme({ - // palette: { - // mode: 'dark', - // }, - // }); - // const theme = darkTheme; + return ( - {/* */} - {/* */} - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - {/* */} - {/* */} diff --git a/src/pages/CartPage.js b/src/pages/CartPage.js index 1b40e9e..85a85ec 100644 --- a/src/pages/CartPage.js +++ b/src/pages/CartPage.js @@ -7,15 +7,17 @@ import { Container, } from '@mui/material'; import { useCartStore } from '../context/CartContext/CartContext'; -import LoadingIndicator from '../components/indicators/LoadingIndicator'; -import ErrorIndicator from '../components/indicators/ErrorIndicator'; -import CartContentContainer from '../containers/CartContentContainer'; -import CustomerFormContainer from '../containers/CustomerFormContainer'; +import LoadingIndicator from '../components/reusable/indicators/LoadingIndicator'; +import ErrorIndicator from '../components/reusable/indicators/ErrorIndicator'; +import CartContentContainer from '../containers/cartPageContainers/CartContentContainer'; +import CustomerFormContainer from '../containers/cartPageContainers/CustomerFormContainer'; +import useUpdateAppContext from '../context/hooks/useUpdateContext'; const CartPage = () => { - const [cookies] = useCookies(['userCookie']); - const user = cookies.userCookie; + const [cookies] = useCookies(['user']); + const user = cookies.user; const userId = user?.id; + // useUpdateAppContext(); // This will set the context to 'Deck' when this page is rendered const { cartData, diff --git a/src/pages/CollectionPage.js b/src/pages/CollectionPage.js index 6e427ec..cc4aac6 100644 --- a/src/pages/CollectionPage.js +++ b/src/pages/CollectionPage.js @@ -1,20 +1,24 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { useCookies } from 'react-cookie'; -import LoadingIndicator from '../components/indicators/LoadingIndicator'; -import ErrorIndicator from '../components/indicators/ErrorIndicator'; +import LoadingIndicator from '../components/reusable/indicators/LoadingIndicator'; +import ErrorIndicator from '../components/reusable/indicators/ErrorIndicator'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import CollectionBanner from './pageStyles/CollectionBanner'; import CardPortfolio from '../components/collection/CardPortfolio'; import Subheader from '../components/reusable/Subheader'; import { useCollectionStore } from '../context/CollectionContext/CollectionContext'; +import { ModalContext } from '../context/ModalContext/ModalContext'; +import GenericCardModal from '../components/modals/GenericCardModal'; +import { CollectionBanner } from './pageStyles/StyledComponents'; +import useUpdateAppContext from '../context/hooks/useUpdateContext'; // Hero section with customizable props const HeroCenter = ({ decorative, title, subtitle }) => ( { // Destructuring the first element directly from the useCookies hook for cleaner code - const [{ userCookie }] = useCookies(['userCookie']); + const [{ user }] = useCookies(['user']); const { allCollections, selectedCollection, loading, error } = useCollectionStore(); - - const userId = userCookie?.id; + const { openModalWithCard, closeModal, isModalOpen, modalContent } = + useContext(ModalContext); + const userId = user?.id; + // useUpdateAppContext(); // This will set the context to 'Deck' when this page is rendered // Handling loading and error states upfront for better user experience if (loading) return ; if (error) return ; return ( - - - - - + + + + + + + + + + {' '} + {isModalOpen && ( + + )} + ); }; diff --git a/src/pages/DeckBuilderPage.js b/src/pages/DeckBuilderPage.js index 43629d5..4b9cdb1 100644 --- a/src/pages/DeckBuilderPage.js +++ b/src/pages/DeckBuilderPage.js @@ -1,20 +1,67 @@ import React, { useEffect, useState, useContext } from 'react'; -import DeckBuilderBanner from './pageStyles/DeckBuilderBanner'; -import DeckBuilderTitle from './pageStyles/DeckBuilderTitle'; import { useCookies } from 'react-cookie'; import { DeckContext } from '../context/DeckContext/DeckContext'; -import LoadingIndicator from '../components/indicators/LoadingIndicator'; -import ErrorIndicator from '../components/indicators/ErrorIndicator'; +import LoadingIndicator from '../components/reusable/indicators/LoadingIndicator'; +import ErrorIndicator from '../components/reusable/indicators/ErrorIndicator'; import DeckBuilderContainer from '../containers/deckBuilderPageContainers/DeckBuilderContainer'; -import { Grid } from '@mui/material'; +import { Box, Grid, Typography } from '@mui/material'; +import { ModalContext } from '../context/ModalContext/ModalContext'; +import GenericCardModal from '../components/modals/GenericCardModal'; +import HeaderTitle from '../components/reusable/HeaderTitle'; +import { useMode } from '../context/hooks/colormode'; +import { DeckBuilderBanner } from './pageStyles/StyledComponents'; +import useUpdateAppContext from '../context/hooks/useUpdateContext'; + +const HeroCenter2 = ({ title, subtitle }) => ( + + + {title} + + + {subtitle} + + +); + +HeroCenter2.defaultProps = { + title: 'Welcome to Deck Builder', + subtitle: 'Craft, refine, and explore your deck strategies in one place.', +}; const DeckBuilderPage = () => { const [userDecks, setUserDecks] = useState([]); - const { userCookie } = useCookies(['userCookie'])[0]; + const { user } = useCookies(['user'])[0]; + const { theme } = useMode(); const { fetchAllDecksForUser, allDecks, loading, error } = useContext(DeckContext); - const userId = userCookie?.id; + const { openModalWithCard, closeModal, isModalOpen, modalContent } = + useContext(ModalContext); + const userId = user?.id; + // useUpdateAppContext(); // This will set the context to 'Deck' when this page is rendered + console.log('userid', userId); useEffect(() => { fetchAllDecksForUser().catch((err) => console.error('Failed to get all decks:', err) @@ -25,7 +72,6 @@ const DeckBuilderPage = () => { if (allDecks && userId) { const filteredDecks = allDecks.filter((deck) => deck.userId === userId); setUserDecks(filteredDecks); - // console.log('(DECK PAGE) -- (USERDECKS):', userDecks); } }, [allDecks, userId]); @@ -33,14 +79,23 @@ const DeckBuilderPage = () => { if (error) return ; return ( - - Deck Builder - - - + + + + + + + - - + + {isModalOpen && ( + + )} + ); }; diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js index 8affe01..6fcc033 100644 --- a/src/pages/HomePage.js +++ b/src/pages/HomePage.js @@ -1,42 +1,32 @@ import React, { useContext, useEffect, useRef } from 'react'; import { Carousel } from 'react-responsive-carousel'; import 'react-responsive-carousel/lib/styles/carousel.min.css'; -import { Container, Typography, Box, Stack, Button } from '@mui/material'; -import { makeStyles, useTheme } from '@mui/styles'; -import { ColorModeContext } from '../context/ColorModeProvider'; +import { + Container, + Typography, + Box, + Stack, + Button, + CssBaseline, + CardActions, + CardContent, + CardHeader, + Card, + Grid, + Paper, +} from '@mui/material'; import { useMode } from '../context/hooks/colormode'; -import HeaderTitle from '../components/reusable/HeaderTitle'; -import useStyles from './styles'; -// import Hero from './pageStyles/Hero'; - -const carouselImages = [ - { image: '/images/yugioh.jpeg', caption: 'Yu-Gi-Oh!' }, - { image: '/images/pokemon.jpeg', caption: 'Pokemon' }, - { - image: '/images/magic-the-gathering.jpeg', - caption: 'Magic: The Gathering', - }, -]; - -const HomeBanner = ({ children }) => { - return ( - - `linear-gradient(to right, ${theme.palette.primary.light}, ${theme.palette.secondary.main})`, - Height: '100vh', - padding: 4, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }} - > - {children} - - ); -}; - +import useStyles from './pageStyles/styles'; +import DeckOfCardsIcon from '../components/icons/DeckOfCardsIcon'; +import { ModalContext } from '../context/ModalContext/ModalContext'; +import GenericCardModal from '../components/modals/GenericCardModal'; +import { + TertiaryContainer, + SecondaryContainer, +} from './pageStyles/StyledComponents'; +import LoadingIndicator2 from '../components/reusable/indicators/LoadingIndicator2'; +import LoadingCardAnimation from '../assets/animations/LoadingCardAnimation'; +import pages from './pages.json'; const CarouselImage = ({ image, caption }) => { return (
@@ -49,7 +39,7 @@ const CarouselImage = ({ image, caption }) => { sx={{ position: 'absolute', bottom: 0, - backgroundColor: (theme) => theme.palette.secondary.main, + // backgroundColor: (theme) => theme.palette.secondary.main, color: (theme) => theme.palette.secondary.contrastText || 'common.white', width: '100%', @@ -66,111 +56,194 @@ const CarouselImage = ({ image, caption }) => { ); }; -const CarouselContainer = ({ isMounted }) => { - // if (!isMounted.current) { - // return; - // } - const classes = useStyles(); - return ( - - {carouselImages?.map(({ image, caption }, index) => ( - - ))} - - ); -}; - const HomePage = () => { - const classes = useStyles(); - const theme = useTheme(); - const isMounted = useRef(true); - - // useEffect(() => { - // return () => { - // isMounted.current = false; - // }; - // }, []); + // const theme = useTheme(); + const { theme, setMode } = useMode(); + const classes = useStyles(theme); + const { openModalWithCard, closeModal, isModalOpen, modalContent } = + useContext(ModalContext); + const { initialState, carouselImages, tiers } = pages; return ( - - - {/* */} + // + + + + // `linear-gradient(to right, ${theme.palette.success.light}, ${theme.palette.success.dark})`, + background: theme.palette.primary.light, + p: 8, + m: 4, + borderRadius: 2, }} > - - - Album layout - - + Album layout + + + Something short and leading about the collection below—its contents, + the creator, etc. Make it short and sweet, but not too short so + folks don't simply skip over it entirely. + + + - - - + Secondary action + + + {/* */} - - {carouselImages.map(({ image, caption }, index) => ( - + + + + {carouselImages.map(({ image, caption }, index) => ( + + ))} + + + + + + + + {tiers.map((tier) => ( + + + {' '} + : null} + subheaderTypographyProps={{ + align: 'center', + }} + sx={{ + background: theme.palette.success.main, + // backgroundColor: (theme) => + // theme.palette.mode === 'light' + // ? theme.palette.grey[200] + // : theme.palette.grey[700], + }} + /> + + + + ${tier.price} + + + /mo + + +
    + {tier.description.map((line) => ( + + {line} + + ))} +
+
+ + + +
+
))} -
+ -
+ {isModalOpen && ( + + )} + + // ); }; diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 0e641f2..a2e8ee0 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -23,35 +23,13 @@ import { TypographyStyled, IconButtonStyled, ButtonStyled, -} from './StyledComponents'; + ButtonsContainer, + CustomButton, + ProfileFormContainer, +} from './pageStyles/StyledComponents'; import ProfileForm from '../components/forms/ProfileForm'; import { useCollectionStore } from '../context/CollectionContext/CollectionContext'; -const CustomButton = styled(Button)({ - margin: '10px', // Add margin around each button - width: '100%', // Make all buttons take full width of their parent - padding: '10px 0', // Add vertical padding - fontSize: '16px', // Set a consistent font size - // Any other styling you want to apply to all buttons -}); - -const ButtonsContainer = styled(Box)({ - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: '20px', - margin: '10px 0', - // Add additional styles as needed -}); - -const ProfileFormContainer = styled(Box)({ - marginTop: '20px', - padding: '20px', - boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)', // Example shadow - borderRadius: '8px', - // Add additional styles as needed -}); - const ProfilePage = () => { const { selectedCollection } = useCollectionStore(); const { user, updateUser } = useUserContext(); @@ -75,7 +53,8 @@ const ProfilePage = () => { const openSnackbar = (message) => { setSnackbarData({ open: true, message }); }; - const userId = user?.id; + // const userId = user?.id; + const userId = cookies.user?.id; console.log('USER ID:', userId); // console.log('USER:', user); const handleSaveChanges = useCallback( diff --git a/src/pages/Splash.css b/src/pages/Splash.css deleted file mode 100644 index efba8b5..0000000 --- a/src/pages/Splash.css +++ /dev/null @@ -1,113 +0,0 @@ -:root { - --calendar-width: 200px; - --calendar-height: 100px; -} - -/* Combine styles for splash_wrapper */ -.splash_wrapper { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100vw; - height: 100vh; - background: linear-gradient( - 45deg, - #305270, - #284b60, - #20444f, - #173d3e, - #0e362e, - #052f1d - ); - background-size: 600% 600%; - animation: Gradient 16s ease infinite; -} - -.logo_container { - margin-bottom: 2rem; -} - -.loading_text { - font-size: 1.5rem; - font-weight: bold; - text-transform: uppercase; - color: #284b60; /* Using the Adjusted Indigo dye from your pentenary color theme */ - letter-spacing: 1.5px; -} - -@keyframes Gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } -} - -/* splash calendar animation */ - -.github_calendar_container { - position: relative; - display: flex; - align-items: center; - justify-content: center; - margin-top: 60px; /* More space from the animation */ - padding: 20px; - /* width: 200px; - height: 100px; */ - width: var(--calendar-width); - height: var(--calendar-height); -} - -/* - .rotating-border { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: -1; /* Place it below the GitHub calendar -} -*/ - -.rotating-border rect { - width: var(--calendar-width); - height: var(--calendar-height); - stroke-dasharray: 1000; - stroke-dashoffset: 1000; - animation: - rotateBorder 9s infinite linear, - dashOffset 9s infinite linear; -} - -.calendar-box { - position: relative; - z-index: 1; /* Place it above the rotating border */ -} - -@keyframes rotateBorder { - 0% { - /* stroke: #4db0a8; */ - stroke: red; - } - 50% { - stroke: #052f1d; - } - 100% { - /* stroke: #4db0a8; */ - stroke: red; - } -} - -@keyframes dashOffset { - 0% { - stroke-dashoffset: 1000; - } - 100% { - stroke-dashoffset: 0; - } -} diff --git a/src/pages/Splash.js b/src/pages/Splash.js deleted file mode 100644 index 5a4ed53..0000000 --- a/src/pages/Splash.js +++ /dev/null @@ -1,242 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import './Splash.css'; -// import { motion } from 'framer-motion'; -// import { theme } from '../../assets/theme'; -// import Particle from '../utils/Particle'; -// import logo from '../../assets/logo.png'; // Import the logo -// import GitHubCalendar from 'react-github-calendar'; -// import { Box } from '@chakra-ui/react'; -import CardDeckAnimation from './CardDeckAnimation'; - -// const logoVariants = { -// hidden: { opacity: 0 }, -// visible: { -// opacity: 1, -// rotate: [0, 360], // Add rotation animation -// transition: { -// duration: 4, -// repeat: Infinity, -// ease: 'linear', -// }, -// }, -// exit: { opacity: 0, transition: { duration: 0.5 } }, -// }; - -// const ellipsisVariants = { -// hidden: { opacity: 0 }, -// visible: { -// opacity: [0, 1, 1, 1, 0], // This makes the ellipsis fade in and out -// x: ['0%', '5%', '10%', '15%', '20%'], // Move the ellipsis to the right -// transition: { -// duration: 2, -// repeat: Infinity, -// repeatType: 'loop', -// }, -// }, -// exit: { opacity: 0, transition: { duration: 0.5 } }, -// }; - -// const textVariants = { -// hidden: { opacity: 0 }, -// visible: { -// opacity: 1, -// color: [ -// theme.colors.quaternary[400], -// theme.colors.quaternary[500], -// theme.colors.quaternary[600], -// theme.colors.quaternary[700], -// theme.colors.quaternary[800], -// theme.colors.quaternary[900], -// ], -// textShadow: ['0px 0px', '5px 5px', '3px 3px', '0px 0px'], -// transition: { -// duration: 6, // Increased due to more colors -// repeat: Infinity, -// repeatType: 'loop', -// }, -// }, -// exit: { opacity: 0, transition: { duration: 0.5 } }, -// }; - -// function AnimatedSplash() { -// const [calendarSize, setCalendarSize] = useState({ width: 0, height: 0 }); -// const calendarRef = useRef(null); -// // const rect = document.getElementById('myElement').getBoundingClientRect(); - -// // Declare minimum dimensions here -// const minWidth = window.innerWidth * 0.65; -// const minHeight = 100; - -// useEffect(() => { -// if (calendarRef.current) { -// // Check if the ref is assigned to a DOM element -// // const rect = calendarRef.current.getBoundingClientRect(); - -// const breakpoints = { -// xs: 20 * 16, -// sm: 28 * 16, -// md: 36 * 16, -// lg: 45 * 16, -// xl: 60 * 16, -// }; - -// const adjustSize = () => { -// const width = window.innerWidth; -// let newWidth, newHeight; -// if (width <= breakpoints.xs) { -// newWidth = '40px'; -// newHeight = '20px'; -// } else if (width <= breakpoints.sm) { -// newWidth = '50px'; -// newHeight = '25px'; -// } else if (width <= breakpoints.md) { -// newWidth = '60px'; -// newHeight = '30px'; -// } else if (width <= breakpoints.lg) { -// newWidth = '70px'; -// newHeight = '35px'; -// } else { -// newWidth = '200px'; -// newHeight = '40px'; -// } -// document.documentElement.style.setProperty( -// '--calendar-width', -// newWidth -// ); -// document.documentElement.style.setProperty( -// '--calendar-height', -// newHeight -// ); -// }; -// adjustSize(); -// window.addEventListener('resize', adjustSize); - -// if (calendarRef.current) { -// const rect = calendarRef.current.getBoundingClientRect(); -// setCalendarSize({ -// width: Math.max(rect.width + 15, minWidth), -// height: Math.max(rect.height + 15, minHeight), -// }); -// } -// console.log('Updated calendar size:', calendarSize); - -// return () => { -// window.removeEventListener('resize', adjustSize); -// }; -// } -// }, [calendarSize]); - -// return ( -// -// -//

-// ReedVogt.com -//

-//
- -// {Particle && } -// -//
-// -// Loading -// -// -// . -// . -// . -// -//
- -//
-// -// -// -// -// -// -//
-//
-// ); -// } - -function Splash() { - const [redirected, setRedirected] = useState(false); - const navigate = useNavigate(); - - useEffect(() => { - const id = setTimeout(() => setRedirected(true), 10000); - return () => clearTimeout(id); - }, []); - - useEffect(() => { - if (redirected) { - navigate('/'); - } - }, [redirected, navigate]); - - return redirected ? null : ; - // return redirected ? null : ; -} - -export default Splash; diff --git a/src/pages/StorePage.js b/src/pages/StorePage.js index 81e8ddb..f4b27e4 100644 --- a/src/pages/StorePage.js +++ b/src/pages/StorePage.js @@ -1,31 +1,13 @@ import React, { useState, useContext, useEffect } from 'react'; -import styled from 'styled-components'; import { Grid } from '@mui/material'; import { useCookies } from 'react-cookie'; import SearchBar from '../components/search/SearchBar'; import ProductGrid from '../components/grids/storeSearchResultsGrid/ProductGrid'; import { useCardStore } from '../context/CardContext/CardStore'; import { useCartStore } from '../context/CartContext/CartContext'; -import LoadingIndicator from '../components/indicators/LoadingIndicator'; -import ErrorIndicator from '../components/indicators/ErrorIndicator'; - -const StoreBanner = styled.div` - display: flex; - max-width: 100%; - justify-content: center; - margin: 0 auto; - flex-direction: column; - align-items: center; - padding: 20px; - background-color: #f7f7f7; -`; - -const StoreTitle = styled.h2` - color: #333; - font-size: 1.5rem; - text-align: center; - margin-bottom: 20px; -`; +import LoadingIndicator from '../components/reusable/indicators/LoadingIndicator'; +import ErrorIndicator from '../components/reusable/indicators/ErrorIndicator'; +import { StoreBanner, StoreTitle } from './pageStyles/StyledComponents'; const SearchContainer = () => { return ( diff --git a/src/pages/StyledComponents.jsx b/src/pages/StyledComponents.jsx deleted file mode 100644 index 54975ef..0000000 --- a/src/pages/StyledComponents.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import styled from '@emotion/styled'; -import { Avatar, Box, Button, IconButton, Typography } from '@mui/material'; -export const AvatarStyled = styled(Avatar)({ - width: 60, - height: 60, - marginBottom: 15, -}); - -export const TypographyStyled = styled(Typography)({ - marginBottom: 15, -}); - -export const IconButtonStyled = styled(IconButton)({ - marginBottom: 20, -}); - -export const DataBoxStyled = styled(Box)({ - margin: '10px 0', - padding: '10px', - border: '1px solid #ddd', - borderRadius: '8px', - textAlign: 'center', - width: '100%', -}); - -export const ButtonStyled = styled(Button)({ - margin: '15px 0', - padding: '10px', - color: '#fff', - backgroundColor: '#3f51b5', - '&:hover': { - backgroundColor: '#303f9f', - }, -}); - -export const DataTextStyled = styled(Typography)({ - margin: '5px 0', - fontSize: '0.9rem', -}); - -export const DataTextBoldStyled = styled(Typography)({ - margin: '5px 0', - fontSize: '0.9rem', - fontWeight: 'bold', -}); diff --git a/src/pages/pageStyles/CollectionBanner.jsx b/src/pages/pageStyles/CollectionBanner.jsx deleted file mode 100644 index ee3dd21..0000000 --- a/src/pages/pageStyles/CollectionBanner.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import styled from 'styled-components'; - -const CollectionBanner = styled.div` - display: flex; - max-width: 100vw; - ${'' /* min-width: '100vw'; */} - justify-content: center; - margin: 0 auto; - flex-direction: column; - align-items: center; - padding: 20px; - background-color: #f7f7f7; -`; - -export default CollectionBanner; diff --git a/src/pages/pageStyles/CollectionTitle.jsx b/src/pages/pageStyles/CollectionTitle.jsx deleted file mode 100644 index 394e73c..0000000 --- a/src/pages/pageStyles/CollectionTitle.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import styled from 'styled-components'; - -const CollectionTitle = styled.h2` - color: #333; - font-size: 1.5rem; - text-align: center; - margin-bottom: 20px; -`; - -export default CollectionTitle; diff --git a/src/pages/pageStyles/DeckBuilderBanner.jsx b/src/pages/pageStyles/DeckBuilderBanner.jsx deleted file mode 100644 index 4abda8b..0000000 --- a/src/pages/pageStyles/DeckBuilderBanner.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import styled from 'styled-components'; - -const DeckBuilderBanner = styled.div` - display: flex; - flex-direction: column; - align-items: center; - background-color: #f9f9f9; - width: 100%; - max-width: 1600px; - margin: auto; -`; - -export default DeckBuilderBanner; diff --git a/src/pages/pageStyles/DeckBuilderTitle.jsx b/src/pages/pageStyles/DeckBuilderTitle.jsx deleted file mode 100644 index 0f5c25d..0000000 --- a/src/pages/pageStyles/DeckBuilderTitle.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import styled from 'styled-components'; - -const DeckBuilderTitle = styled.h2` - color: #333; - font-size: 1.5rem; - text-align: center; - margin-bottom: 20px; -`; - -export default DeckBuilderTitle; diff --git a/src/pages/pageStyles/Hero.jsx b/src/pages/pageStyles/Hero.jsx deleted file mode 100644 index 45ca420..0000000 --- a/src/pages/pageStyles/Hero.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { Box, Container, Typography } from '@mui/material'; -import { makeStyles } from '@mui/styles'; -import { Canvas } from '@react-three/fiber'; -import Cube from './Cube'; - -const useStyles = makeStyles((theme) => ({ - bannerBox: { - backgroundImage: `linear-gradient(to right, ${theme.palette.primary.light}, ${theme.palette.primary.main})`, - minHeight: '100vh', - padding: theme.spacing(4), - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, -})); - -const Hero = () => { - const classes = useStyles(); - return ( - - - - {/* eslint-disable-next-line react/no-unknown-property */} - - - - - - Buy Amazing Cards - - - - ); -}; - -export default Hero; diff --git a/src/pages/pageStyles/StyledComponents.jsx b/src/pages/pageStyles/StyledComponents.jsx new file mode 100644 index 0000000..8b3013e --- /dev/null +++ b/src/pages/pageStyles/StyledComponents.jsx @@ -0,0 +1,218 @@ +import { Avatar, Box, Button, IconButton, Typography } from '@mui/material'; +import { styled } from '@mui/styles'; + +// export const AppContainer = styled.div` +export const AppContainer = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + height: '100vh', +})); +export const MainContainer = styled('div')(({ theme }) => ({ + background: '#222', // Dark background + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + maxHeight: '100%', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, +})); +export const MainContainer2 = styled('div')(({ theme }) => ({ + background: '#333', // Dark background + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + maxHeight: '100%', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, +})); +export const MainContainer3 = styled('div')(({ theme }) => ({ + background: '#444', // Dark background + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + maxHeight: '100%', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, +})); +export const SecondaryContainer = styled('div')(({ theme }) => ({ + background: '#b7ebde', + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + maxHeight: '100%', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, +})); +export const TertiaryContainer = styled('div')(({ theme }) => ({ + padding: 3, + borderRadius: 2, + background: theme.palette.success.main, + boxShadow: theme.shadows[10], + mb: 4, +})); +export const MainPaperContainer = styled('div')(({ theme }) => ({ + elevation: 3, + borderRadius: 2, + margin: 0, + marginBottom: theme.spacing(2), + background: theme.palette.background.main, + width: '100%', + padding: theme.spacing(2), +})); + +export const SecondaryPaperContainer = styled('div')(({ theme }) => ({ + elevation: 3, + borderRadius: 2, + margin: 0, + marginBottom: theme.spacing(2), + background: '#70d8bd', + width: '100%', + padding: theme.spacing(2), +})); + +export const DeckBuilderBanner = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + backgroundColor: theme.palette.background.paper, + width: '100%', + maxWidth: '1600px', + margin: 'auto', + padding: theme.spacing(6, 2), + boxShadow: theme.shadows[4], + textAlign: 'center', +})); +// export const DeckBuilderBanner = styled(Box)(({ theme }) => ({ +// backgroundColor: theme.palette.background.paper, +// padding: theme.spacing(6, 2), +// boxShadow: theme.shadows[4], +// textAlign: 'center', +// })); +// export const DeckBuilderBanner = styled.div` +// display: flex; +// flex-direction: column; +// align-items: center; +// background-color: #f9f9f9; +// width: 100%; +// max-width: 1600px; +// margin: auto; +// `; +export const AvatarStyled = styled(Avatar)({ + width: 60, + height: 60, + marginBottom: 15, +}); + +export const TypographyStyled = styled(Typography)({ + marginBottom: 15, +}); + +export const IconButtonStyled = styled(IconButton)({ + marginBottom: 20, +}); + +export const DataBoxStyled = styled(Box)({ + margin: '10px 0', + padding: '10px', + border: '1px solid #ddd', + borderRadius: '8px', + textAlign: 'center', + width: '100%', +}); + +export const ButtonStyled = styled(Button)({ + margin: '15px 0', + padding: '10px', + color: '#fff', + backgroundColor: '#3f51b5', + '&:hover': { + backgroundColor: '#303f9f', + }, +}); + +export const DataTextStyled = styled(Typography)({ + margin: '5px 0', + fontSize: '0.9rem', +}); + +export const DataTextBoldStyled = styled(Typography)({ + margin: '5px 0', + fontSize: '0.9rem', + fontWeight: 'bold', +}); + +export const DeckBuilderTitle = styled(Typography)(({ theme }) => ({ + color: '#333', + fontSize: '1.5rem', + textAlign: 'center', + marginBottom: theme.spacing(2.5), +})); + +export const CollectionTitle = styled(Typography)(({ theme }) => ({ + color: '#333', + fontSize: '1.5rem', + textAlign: 'center', + marginBottom: theme.spacing(2.5), +})); + +export const CollectionBanner = styled(Box)(({ theme }) => ({ + display: 'flex', + justifyContent: 'center', + margin: '0 auto', + flexDirection: 'column', + alignItems: 'center', + padding: theme.spacing(2.5), + backgroundColor: '#f7f7f7', +})); + +export const StoreBanner = styled(Box)(({ theme }) => ({ + display: 'flex', + maxWidth: '100%', + justifyContent: 'center', + margin: '0 auto', + flexDirection: 'column', + alignItems: 'center', + padding: theme.spacing(2.5), + backgroundColor: '#f7f7f7', +})); + +export const StoreTitle = styled(Typography)(({ theme }) => ({ + color: '#333', + fontSize: '1.5rem', + textAlign: 'center', + marginBottom: theme.spacing(2.5), +})); + +export const CustomButton = styled(Button)({ + margin: '10px', + width: '100%', + padding: '10px 0', + fontSize: '16px', +}); + +export const ButtonsContainer = styled(Box)({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '20px', + margin: '10px 0', +}); + +export const ProfileFormContainer = styled(Box)({ + marginTop: '20px', + padding: '20px', + boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)', + borderRadius: '8px', +}); diff --git a/src/pages/styles.jsx b/src/pages/pageStyles/styles.jsx similarity index 67% rename from src/pages/styles.jsx rename to src/pages/pageStyles/styles.jsx index 330a361..91c27ca 100644 --- a/src/pages/styles.jsx +++ b/src/pages/pageStyles/styles.jsx @@ -1,6 +1,52 @@ import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles((theme) => ({ + mainContainer: { + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + background: '#222', // Dark background + // background: theme.palette.primary.light, + maxHeight: '100%', // Set the container height to 25vh + width: '100%', // Set the container width to 100% + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, + }, + secondaryContainer: { + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + // background: '#222', // Dark background + background: '#b7ebde', + // background: theme.palette.primary.light, + maxHeight: '100%', // Set the container height to 25vh + width: '100%', // Set the container width to 100% + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, + }, + mainPaperContainer: { + elevation: 3, + borderRadius: 2, + margin: 0, + marginBottom: theme.spacing(2), + background: theme.palette.background.main, + width: '100%', // Set the container width to 100% + padding: theme.spacing(2), + }, + secondaryPaperContainer: { + elevation: 3, + borderRadius: 2, + margin: 0, + marginBottom: theme.spacing(2), + // background: theme.palette.background.main, + background: '#70d8bd', + width: '100%', // Set the container width to 100% + padding: theme.spacing(2), + }, // return { banner: { backgroundImage: `linear-gradient(to right, ${theme.palette.primary.light}, ${theme.palette.secondary.main})`, @@ -110,6 +156,17 @@ const useStyles = makeStyles((theme) => ({ paddingBottom: '30px', fontSize: '18px', }, + cardHoverEffect: { + '&:hover': { + transform: 'scale(1.05)', // Enlarge card on hover + transition: 'transform 0.3s ease-in-out', // Smooth transition for the hover effect + }, + }, + // carouselContainer: { + // '& .carousel .slide': { + // backgroundColor: (theme) => theme.palette.background.paper, + // }, + // }, title: { paddingBottom: '15px', }, diff --git a/src/pages/pages.json b/src/pages/pages.json new file mode 100644 index 0000000..eedc150 --- /dev/null +++ b/src/pages/pages.json @@ -0,0 +1,49 @@ +{ + "carouselImages": [ + { "image": "/images/yugioh.jpeg", "caption": "Yu-Gi-Oh!" }, + { "image": "/images/pokemon.jpeg", "caption": "Pokemon" }, + { + "image": "/images/magic-the-gathering.jpeg", + "caption": "Magic: The Gathering" + } + ], + "tiers": [ + { + "title": "Deck Builder", + "price": "0", + "description": [ + "10 users included", + "2 GB of storage", + "Help center access", + "Email support" + ], + "buttonText": "Sign up for free", + "buttonVariant": "outlined" + }, + { + "title": "Store", + "subheader": "Most popular", + "price": "15", + "description": [ + "20 users included", + "10 GB of storage", + "Help center access", + "Priority email support" + ], + "buttonText": "Get started", + "buttonVariant": "contained" + }, + { + "title": "Collection Tracker", + "price": "30", + "description": [ + "50 users included", + "30 GB of storage", + "Help center access", + "Phone & email support" + ], + "buttonText": "Contact us", + "buttonVariant": "outlined" + } + ] +} diff --git a/src/setupProxy.js b/src/setupProxy.js new file mode 100644 index 0000000..9f0c65b --- /dev/null +++ b/src/setupProxy.js @@ -0,0 +1,18 @@ +// // eslint-disable-next-line @typescript-eslint/no-var-requires +// const { createProxyMiddleware } = require('http-proxy-middleware'); + +// module.exports = function (app) { +// app.use( +// '/api', +// createProxyMiddleware({ +// target: 'https://images.ygoprodeck.com', +// headers: { +// // 'Access-Control-Allow-Origin': '*', +// crossorigin: 'anonymous', +// // 'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE,PATCH,OPTIONS', +// }, +// changeOrigin: true, +// pathRewrite: { '^/api': '' }, +// }) +// ); +// }; From 317a493656e4aa61d065e5298d559d2dbe8338ef Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Fri, 24 Nov 2023 14:55:38 -0800 Subject: [PATCH 10/10] add --- .github/workflows/main.yml | 37 ++++ src/App.js | 24 ++- src/assets/themeSettings.jsx | 2 + .../topButton/TopButton.css | 0 .../buttons => assets}/topButton/TopButton.js | 0 .../actionButtons/CardActionButtons.jsx | 181 +++++++++++------- .../actionButtons/GenericActionButtons.jsx | 56 +++++- src/components/buttons/other/SearchButton.js | 9 +- src/components/cards/GenericCard.jsx | 40 +++- src/components/chart/LinearChart.js | 2 +- src/components/chart/PortfolioChart.jsx | 2 +- src/components/cleanUp/AppWrapper.jsx | 13 -- .../cleanUp/CalculateCollectionStatistics.jsx | 57 ------ src/components/cleanUp/CardModal.js | 123 ------------ src/components/cleanUp/CartActionButtons.js | 36 ---- .../cleanUp/CollectionActionButtons.jsx | 73 ------- src/components/cleanUp/CollectionDialog.jsx | 61 ------ .../cleanUp/CollectionEditPanel.jsx | 89 --------- src/components/cleanUp/CollectionForm.jsx | 63 ------ src/components/cleanUp/CustomPopover.jsx | 69 ------- src/components/cleanUp/DeckActionButtons.js | 65 ------- src/components/cleanUp/DeckCard.js | 118 ------------ src/components/cleanUp/DeckCardDialog.jsx | 16 -- src/components/cleanUp/DeckCardDialogue.js | 96 ---------- src/components/cleanUp/DeckCardModal.js | 55 ------ src/components/cleanUp/DeckCountDisplay.jsx | 12 -- src/components/cleanUp/DeckDialog.jsx | 57 ------ src/components/cleanUp/Hero.jsx | 49 ----- src/components/cleanUp/PortfolioCard.jsx | 118 ------------ src/components/cleanUp/PracticeScraperPage.js | 48 ----- src/components/cleanUp/ProductCard.js | 98 ---------- src/components/cleanUp/ScrapeDisplay.js | 26 --- src/components/cleanUp/ScrapeSearch.js | 44 ----- src/components/cleanUp/ScraperContainer.js | 36 ---- .../cleanUp/SelectCollectionDialog.jsx | 133 ------------- src/components/cleanUp/UpdateChartData.jsx | 72 ------- src/components/cleanUp/button/Button.css | 43 ----- src/components/cleanUp/button/Button.js | 35 ---- src/components/cleanUp/theme.jsx | 0 src/components/collection/example-data.json | 64 ------- src/components/forms/AddCardForm.jsx | 74 +++---- src/components/forms/SearchForm.jsx | 123 ++++++------ .../customerCheckoutForm/CartActions.jsx | 2 +- .../customerCheckoutForm/CustomerForm.js | 40 ++-- src/components/grids/DeckDisplay.js | 88 ++++----- src/components/grids/StoreItem.jsx | 11 ++ .../grids/deckBuilderGrids/DeckButtonList.js | 2 + .../searchResultGrids/DeckSearchCardGrid.jsx | 35 ++-- .../storeSearchResultsGrid/ProductGrid.js | 58 ++++-- src/components/headings/PortfolioHeader.jsx | 24 --- src/components/icons/DeckOfCardsIcon.jsx | 8 - .../{ => cardModal}/GenericCardModal.jsx | 21 +- .../modals/stripeModal/StripeCheckoutModal.js | 87 ++++----- src/components/other/DeckEditPanel.js | 99 ---------- .../other/InputComponents/CardNameInput.js | 21 +- .../CollectionStatisticsSelector.jsx | 2 +- .../other/InputComponents/CustomSelector.js | 49 +++-- .../other/InputComponents/DeckEditPanel.js | 109 +++++++++++ .../other/{ => dataDisplay}/CartSummary.js | 0 .../other/{ => dataDisplay}/CartTotal.jsx | 0 .../CollectionValueTracker.jsx | 2 +- .../{ => dataDisplay}/ProgressCircle.jsx | 0 .../other/{ => dataDisplay}/StatBox.jsx | 0 .../{ => dataDisplay}/StatisticsArea.jsx | 0 .../{ => dataDisplay}/TopCardsDisplay.jsx | 42 ++-- .../other/{ => dataDisplay}/UserStats.jsx | 6 +- .../ChartErrorBoundary.jsx | 0 src/components/reusable/HeaderTitle.jsx | 55 +++--- .../{chart => reusable}/chartUtils.jsx | 124 ------------ .../{ => reusable}/icons/CartIcon.jsx | 0 .../{ => reusable}/icons/CollectionIcon.jsx | 0 .../reusable/icons/DeckOfCardsIcon.jsx | 29 +++ .../{ => reusable}/icons/TestingIcon.jsx | 0 .../reusable/indicators/LoadingIndicator2.js | 22 +-- src/components/search/DeckSearch.js | 14 +- src/components/search/PortfolioCardSearch.jsx | 27 --- src/components/search/SearchBar.js | 132 ++++++++----- .../cartPageContainers}/AddressForm.jsx | 0 .../cartPageContainers/CartContent.js | 2 +- .../cartPageContainers}/Checkout.jsx | 0 .../cartPageContainers}/PaymentForm.jsx | 0 .../cartPageContainers}/Review.jsx | 0 .../PortfolioChartContainer.jsx | 2 +- .../PortfolioContent.jsx | 107 +++++------ .../CardImagesContext/CardImagesContext.jsx | 3 +- src/context/CartContext/CartContext.js | 135 +++++++------ .../CollectionContext/CollectionContext.jsx | 52 +---- .../CollectionContext/collectionUtility.jsx | 52 +++++ src/context/DeckContext/DeckContext.js | 45 +++-- src/index.js | 55 +++--- src/pages/CartPage.js | 1 - src/pages/CollectionPage.js | 10 +- src/pages/DeckBuilderPage.js | 10 +- src/pages/HomePage.js | 50 ++--- src/pages/ProfilePage.js | 2 +- src/pages/StorePage.js | 97 +++++++++- src/pages/pages.json | 33 ++-- src/setupProxy.js | 18 -- 98 files changed, 1263 insertions(+), 2839 deletions(-) create mode 100644 .github/workflows/main.yml rename src/{components/buttons => assets}/topButton/TopButton.css (100%) rename src/{components/buttons => assets}/topButton/TopButton.js (100%) delete mode 100644 src/components/cleanUp/AppWrapper.jsx delete mode 100644 src/components/cleanUp/CalculateCollectionStatistics.jsx delete mode 100644 src/components/cleanUp/CardModal.js delete mode 100644 src/components/cleanUp/CartActionButtons.js delete mode 100644 src/components/cleanUp/CollectionActionButtons.jsx delete mode 100644 src/components/cleanUp/CollectionDialog.jsx delete mode 100644 src/components/cleanUp/CollectionEditPanel.jsx delete mode 100644 src/components/cleanUp/CollectionForm.jsx delete mode 100644 src/components/cleanUp/CustomPopover.jsx delete mode 100644 src/components/cleanUp/DeckActionButtons.js delete mode 100644 src/components/cleanUp/DeckCard.js delete mode 100644 src/components/cleanUp/DeckCardDialog.jsx delete mode 100644 src/components/cleanUp/DeckCardDialogue.js delete mode 100644 src/components/cleanUp/DeckCardModal.js delete mode 100644 src/components/cleanUp/DeckCountDisplay.jsx delete mode 100644 src/components/cleanUp/DeckDialog.jsx delete mode 100644 src/components/cleanUp/Hero.jsx delete mode 100644 src/components/cleanUp/PortfolioCard.jsx delete mode 100644 src/components/cleanUp/PracticeScraperPage.js delete mode 100644 src/components/cleanUp/ProductCard.js delete mode 100644 src/components/cleanUp/ScrapeDisplay.js delete mode 100644 src/components/cleanUp/ScrapeSearch.js delete mode 100644 src/components/cleanUp/ScraperContainer.js delete mode 100644 src/components/cleanUp/SelectCollectionDialog.jsx delete mode 100644 src/components/cleanUp/UpdateChartData.jsx delete mode 100644 src/components/cleanUp/button/Button.css delete mode 100644 src/components/cleanUp/button/Button.js delete mode 100644 src/components/cleanUp/theme.jsx delete mode 100644 src/components/collection/example-data.json create mode 100644 src/components/grids/StoreItem.jsx delete mode 100644 src/components/headings/PortfolioHeader.jsx delete mode 100644 src/components/icons/DeckOfCardsIcon.jsx rename src/components/modals/{ => cardModal}/GenericCardModal.jsx (79%) delete mode 100644 src/components/other/DeckEditPanel.js create mode 100644 src/components/other/InputComponents/DeckEditPanel.js rename src/components/other/{ => dataDisplay}/CartSummary.js (100%) rename src/components/other/{ => dataDisplay}/CartTotal.jsx (100%) rename src/components/other/{ => dataDisplay}/CollectionValueTracker.jsx (98%) rename src/components/other/{ => dataDisplay}/ProgressCircle.jsx (100%) rename src/components/other/{ => dataDisplay}/StatBox.jsx (100%) rename src/components/other/{ => dataDisplay}/StatisticsArea.jsx (100%) rename src/components/other/{ => dataDisplay}/TopCardsDisplay.jsx (80%) rename src/components/other/{ => dataDisplay}/UserStats.jsx (76%) rename src/components/{chart => reusable}/ChartErrorBoundary.jsx (100%) rename src/components/{chart => reusable}/chartUtils.jsx (55%) rename src/components/{ => reusable}/icons/CartIcon.jsx (100%) rename src/components/{ => reusable}/icons/CollectionIcon.jsx (100%) create mode 100644 src/components/reusable/icons/DeckOfCardsIcon.jsx rename src/components/{ => reusable}/icons/TestingIcon.jsx (100%) delete mode 100644 src/components/search/PortfolioCardSearch.jsx rename src/{components/other => containers/cartPageContainers}/AddressForm.jsx (100%) rename src/{components/other => containers/cartPageContainers}/Checkout.jsx (100%) rename src/{components/other => containers/cartPageContainers}/PaymentForm.jsx (100%) rename src/{components/other => containers/cartPageContainers}/Review.jsx (100%) delete mode 100644 src/setupProxy.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6a3095a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '18.16.0' + + - name: Install dependencies + run: npm install + + - name: Run tests + run: npm test + + - name: Build + run: npm run build + + - name: Install Netlify CLI + run: npm install netlify-cli -g + + - name: Deploy to Netlify + run: npx netlify deploy --dir=src --prod + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} diff --git a/src/App.js b/src/App.js index e56bc47..2f136d0 100644 --- a/src/App.js +++ b/src/App.js @@ -26,6 +26,8 @@ import { useUtilityContext } from './context/UtilityContext/UtilityContext'; import { AppContainer } from './pages/pageStyles/StyledComponents'; import { useCardImages } from './context/CardImagesContext/CardImagesContext'; import { useCookies } from 'react-cookie'; +import { useDeckStore } from './context/DeckContext/DeckContext'; +import { useCartStore } from './context/CartContext/CartContext'; const App = () => { const [cookies] = useCookies(['user']); @@ -34,6 +36,8 @@ const App = () => { // const { setContext } = useAppContext(); // Assuming useAppContext provides setContext const { fetchAllCollectionsForUser, selectedCollection } = useCollectionStore(); + const { allDecks, fetchAllDecksForUser, selectedDeck } = useDeckStore(); + const { fetchUserCart, cartData } = useCartStore(); const { isLoading, setIsLoading } = useUtilityContext(); // const { getRandomCardImages } = useCardImages(); // Add this line @@ -44,7 +48,7 @@ const App = () => { useEffect(() => { if (user) { - fetchAllCollectionsForUser(user.id) + fetchAllCollectionsForUser() .then(() => { setIsLoading(false); }) @@ -54,6 +58,20 @@ const App = () => { }); } }, [user, fetchAllCollectionsForUser, setIsLoading, selectedCollection]); + // useEffect(() => { + // if (user) { + // fetchAllDecksForUser(user?.id).catch((err) => + // console.error('Failed to get all decks:', err) + // ); + // } + // }, [fetchAllDecksForUser]); + // useEffect(() => { + // if (user) { + // fetchUserCart(user?.id).catch((err) => + // console.error('Failed to get cart:', err) + // ); + // } + // }, [fetchUserCart]); // Handle initial loading state useEffect(() => { @@ -79,7 +97,7 @@ const App = () => { {isLoading ? ( ) : ( - +
@@ -127,7 +145,7 @@ const App = () => {
- + )} ); diff --git a/src/assets/themeSettings.jsx b/src/assets/themeSettings.jsx index 23cf87f..e3c7751 100644 --- a/src/assets/themeSettings.jsx +++ b/src/assets/themeSettings.jsx @@ -62,6 +62,7 @@ export const themeSettings = (mode) => { error: { main: colors.redAccent[500], dark: colors.redAccent[700], + contrastText: '#fff', }, warning: { main: colors.redAccent[500], @@ -70,6 +71,7 @@ export const themeSettings = (mode) => { light: colors.greenAccent[100], main: colors.greenAccent[500], dark: colors.greenAccent[200], + contrastText: '#fff', }, info: { main: colors.blueAccent[500], diff --git a/src/components/buttons/topButton/TopButton.css b/src/assets/topButton/TopButton.css similarity index 100% rename from src/components/buttons/topButton/TopButton.css rename to src/assets/topButton/TopButton.css diff --git a/src/components/buttons/topButton/TopButton.js b/src/assets/topButton/TopButton.js similarity index 100% rename from src/components/buttons/topButton/TopButton.js rename to src/assets/topButton/TopButton.js diff --git a/src/components/buttons/actionButtons/CardActionButtons.jsx b/src/components/buttons/actionButtons/CardActionButtons.jsx index ea451a0..1e1314f 100644 --- a/src/components/buttons/actionButtons/CardActionButtons.jsx +++ b/src/components/buttons/actionButtons/CardActionButtons.jsx @@ -1,5 +1,13 @@ import React, { useCallback } from 'react'; -import { Box, Button, Grid } from '@mui/material'; +import { + Box, + Button, + Divider, + Grid, + IconButton, + Typography, + useMediaQuery, +} from '@mui/material'; import { useStyles } from '../buttonStyles'; import useAppContext from '../../../context/hooks/useAppContext'; import { useMode } from '../../../context/hooks/colormode'; @@ -7,41 +15,68 @@ import { useCollectionStore } from '../../../context/CollectionContext/Collectio import Logger from '../../grids/collectionGrids/Logger'; import { useDeckStore } from '../../../context/DeckContext/DeckContext'; import { useCartStore } from '../../../context/CartContext/CartContext'; +import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; +import DeleteIcon from '@material-ui/icons/Delete'; const cardOtherLogger = new Logger([ 'Action', 'Card Name', 'Quantity', 'Total Price', ]); -const CardActionButtons = ({ card, context, closeModal }) => { +const CardActionButtons = ({ + card, + context, + closeModal, + modifiedContextProps, +}) => { const { theme } = useMode(); // Using the theme hook const classes = useStyles(); - const { contextProps, isContextAvailable } = useAppContext(context); // Changed to object destructuring - const { addOneToCollection, removeOneFromCollection } = useCollectionStore(); - const { addToDeck, removeFromDeck } = useDeckStore(); - const { addToCart, removeFromCart } = useCartStore(); - // const { totalQuantity } = context[contextProps?.totalQuantity]; - // const { totalQuantity } = useCollectionStore(); - // if (!isContextAvailable || !contextProps) { - // console.error(`The component isn't wrapped with the ${context}Provider`); - // return Context not available; - // } + // const { contextProps, isContextAvailable } = useAppContext(context); + const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg')); + const { addOneToCollection, removeOneFromCollection } = useCollectionStore(); + const { addOneToDeck, removeOneFromDeck } = useDeckStore(); + const { addOneToCart, removeOneFromCart } = useCartStore(); const styles = { box: { display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', + flexGrow: 1, + width: '100%', // Using theme spacing padding: theme.spacing(2), // Using theme spacing backgroundColor: theme.palette.background.paper, // Using theme background color }, + grid: { + // display: 'flex', + flexGrow: 1, + // flexDirection: 'column', + width: '100%', // Using theme spacing + }, + grid2: { + display: 'flex', + flexGrow: 1, + flexDirection: 'row', + width: '100%', // Using theme spacing + justifyContent: 'space-between', + }, button: { margin: theme.spacing(1), // Using theme spacing color: theme.palette.background.primary, // Using theme text color backgroundColor: theme.palette.success.main, // Using theme background color }, + removeButton: { + margin: theme.spacing(1), // Using theme spacing + color: theme.palette.error.contrastText, // Using theme text color + backgroundColor: theme.palette.error.main, // Using theme background color + }, + addButton: { + margin: theme.spacing(1), // Using theme spacing + color: theme.palette.success.contrastText, // Using theme text color + backgroundColor: theme.palette.success.main, // Using theme background color + }, gridItem: { textAlign: 'center', }, @@ -49,35 +84,6 @@ const CardActionButtons = ({ card, context, closeModal }) => { const ADD = 'add'; const REMOVE_ONE = 'removeOne'; const REMOVE_ALL = 'removeAll'; - // const performAction = useCallback( - // (action, card) => { - // console.log( - // `action --> ${action}`, - // `payload --> ${card}`, - // `context --> ${context}` - // ); - // try { - // if (context === 'Collection') { - // if (action === ADD) { - // addOneToCollection(card, card.id); - // cardOtherLogger.logCardAction('Add Card', card); - // } else if (action === REMOVE_ONE) { - // removeOneFromCollection(card, card.id); - // cardOtherLogger.logCardAction('Remove Card', card); - // } - // } - // // Additional context handling can be implemented here - // } catch (error) { - // console.error( - // `Error performing action '${action}' with payload`, - // card, - // error - // ); - // } - // }, - // [context, addOneToCollection, removeOneFromCollection, cardOtherLogger] - // ); - const performAction = useCallback( (action, card) => { try { @@ -91,16 +97,16 @@ const CardActionButtons = ({ card, context, closeModal }) => { break; case 'Deck': if (action === 'add') { - addToDeck(card, card.id); + addOneToDeck(card); } else if (action === 'removeOne') { - removeFromDeck(card, card.id); + removeOneFromDeck(card); } break; case 'Cart': if (action === 'add') { - addToCart(card, card.id); + addOneToCart(card); } else if (action === 'removeOne') { - removeFromCart(card, card.id); + removeOneFromCart(card); } break; default: @@ -118,16 +124,16 @@ const CardActionButtons = ({ card, context, closeModal }) => { [ addOneToCollection, removeOneFromCollection, - addToDeck, - removeFromDeck, - addToCart, - removeFromCart, + addOneToDeck, + removeOneFromDeck, + addOneToCart, + removeOneFromCart, context, ] ); const handleAddClick = () => { - console.log('handleAddClick', card); + console.log('handleAddClick', card, context); performAction(ADD, card); closeModal?.(); }; @@ -144,24 +150,69 @@ const CardActionButtons = ({ card, context, closeModal }) => { return ( - - - {`In ${context}: `} {card.quantity} - - - - - - + {!isLargeScreen && ( + <> + + + + {`In ${context}: `} + + + {card.quantity} + + + + + + + + + + + + + + + + + + + + + + + )} + diff --git a/src/components/buttons/actionButtons/GenericActionButtons.jsx b/src/components/buttons/actionButtons/GenericActionButtons.jsx index 820ecc4..cf898e9 100644 --- a/src/components/buttons/actionButtons/GenericActionButtons.jsx +++ b/src/components/buttons/actionButtons/GenericActionButtons.jsx @@ -28,9 +28,11 @@ const GenericActionButtons = ({ card, context }) => { { export default GenericActionButtons; +// import React, { useContext } from 'react'; +// import CardActionButtons from './CardActionButtons'; +// import useAppContext from '../../../context/hooks/useAppContext'; +// import { ModalContext } from '../../../context/ModalContext/ModalContext'; +// import { Box } from '@mui/material'; + +// const GenericActionButtons = ({ card, context }) => { +// const contextProps = useAppContext(); // Assuming useAppContext returns the context object +// const { closeModal, isModalOpen, setModalOpen } = useContext(ModalContext); + +// if (!contextProps) { +// return ( +// Provider not found for {context} +// ); +// } + +// let modifiedContextProps = contextProps[context]; + +// if (!modifiedContextProps) { +// return ( +// +// Invalid context provided: {context} +// +// ); +// } + +// return ( +// +// setModalOpen(false)} +// open={isModalOpen} +// /> +// +// ); +// }; + +// export default GenericActionButtons; + // import React, { useContext, useEffect } from 'react'; // import { makeStyles } from '@mui/styles'; // import GenericCardModal from '../../modals/GenericCardModal'; diff --git a/src/components/buttons/other/SearchButton.js b/src/components/buttons/other/SearchButton.js index a82ec7c..f32054d 100644 --- a/src/components/buttons/other/SearchButton.js +++ b/src/components/buttons/other/SearchButton.js @@ -1,9 +1,11 @@ import React from 'react'; import { Button, Grid } from '@mui/material'; import { useCardStore } from '../../../context/CardContext/CardStore'; +import { useMode } from '../../../context/hooks/colormode'; -const SearchButton = ({ searchParams }) => { - const { handleRequest } = useCardStore(); +const SearchButton = ({ searchParams, handleSubmit }) => { + // const { handleRequest } = useCardStore(); + const { theme } = useMode(); return ( @@ -11,7 +13,8 @@ const SearchButton = ({ searchParams }) => { fullWidth variant="contained" color="primary" - onClick={() => handleRequest(searchParams)} + sx={{ mt: 1, mb: 1, background: theme.palette.primary.main }} + onClick={() => handleSubmit()} // Corrected onClick > Search diff --git a/src/components/cards/GenericCard.jsx b/src/components/cards/GenericCard.jsx index 18b2292..8c6ceea 100644 --- a/src/components/cards/GenericCard.jsx +++ b/src/components/cards/GenericCard.jsx @@ -5,6 +5,8 @@ import { CardActions, Typography, styled, + useMediaQuery, + Container, } from '@mui/material'; import CardMediaSection from '../media/CardMediaSection'; import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons'; @@ -12,6 +14,7 @@ import placeholderImage from '../../assets/images/placeholder.jpeg'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; import { ModalContext } from '../../context/ModalContext/ModalContext'; import { PopoverContext } from '../../context/PopoverContext/PopoverContext'; +import { useMode } from '../../context/hooks/colormode'; const AspectRatioBox = styled('div')(({ theme }) => ({ width: '100%', // Full width of the parent container @@ -35,7 +38,10 @@ const StyledCard = styled(Card)(({ theme }) => ({ })); const GenericCard = React.forwardRef((props, ref) => { + const { theme } = useMode(); const { card, context, setClickedCard } = props; + // const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg')); + const { selectedCollection } = useCollectionStore(); const { openModalWithCard, setModalOpen } = useContext(ModalContext); const { setHoveredCard, setIsPopoverOpen, hoveredCard } = @@ -58,9 +64,34 @@ const GenericCard = React.forwardRef((props, ref) => { setIsPopoverOpen(hoveredCard === card); }, [hoveredCard, card, setIsPopoverOpen]); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md')); + const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg')); + const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; const price = `Price: ${card?.card_prices?.[0]?.tcgplayer_price || 'N/A'}`; + const renderCardContent = () => { + // Adjust content based on the screen size + let variant = 'body2'; + if (isSmallScreen) { + // variant = 'caption'; + variant = 'h6'; + } else if (isMediumScreen) { + variant = 'body2'; + } else if (isLargeScreen) { + variant = 'body2'; + } + + return ( + + {card?.name} + + {price} + + + ); + }; return ( @@ -75,13 +106,8 @@ const GenericCard = React.forwardRef((props, ref) => { /> - - {card?.name} - - {price} - - - + {renderCardContent()} + diff --git a/src/components/chart/LinearChart.js b/src/components/chart/LinearChart.js index d4c4be3..3e6ab33 100644 --- a/src/components/chart/LinearChart.js +++ b/src/components/chart/LinearChart.js @@ -2,7 +2,7 @@ import React, { useState, useMemo, useCallback } from 'react'; import { ResponsiveLine } from '@nivo/line'; import { Typography, Box, Tooltip, useTheme } from '@mui/material'; import { styled } from '@mui/material/styles'; -import ChartErrorBoundary from './ChartErrorBoundary'; +import ChartErrorBoundary from '../reusable/ChartErrorBoundary'; import { tokens } from '../../assets/tokens'; import { useMode } from '../../context/hooks/colormode'; diff --git a/src/components/chart/PortfolioChart.jsx b/src/components/chart/PortfolioChart.jsx index ccaa994..8c25bd6 100644 --- a/src/components/chart/PortfolioChart.jsx +++ b/src/components/chart/PortfolioChart.jsx @@ -10,7 +10,7 @@ import { convertDataForNivo2, getUniqueValidData, groupAndAverageData, -} from './chartUtils'; +} from '../reusable/chartUtils'; const ChartPaper = styled(Paper)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, diff --git a/src/components/cleanUp/AppWrapper.jsx b/src/components/cleanUp/AppWrapper.jsx deleted file mode 100644 index e017a16..0000000 --- a/src/components/cleanUp/AppWrapper.jsx +++ /dev/null @@ -1,13 +0,0 @@ -// import React from 'react'; -// import { BrowserRouter as Router } from 'react-router-dom'; -// import App from './App'; - -// const AppWrapper = () => { -// return ( -// -// -// -// ); -// }; - -// export default AppWrapper; diff --git a/src/components/cleanUp/CalculateCollectionStatistics.jsx b/src/components/cleanUp/CalculateCollectionStatistics.jsx deleted file mode 100644 index fe3693a..0000000 --- a/src/components/cleanUp/CalculateCollectionStatistics.jsx +++ /dev/null @@ -1,57 +0,0 @@ -// import React, { useMemo } from 'react'; -// import { MenuItem, Select, Typography } from '@mui/material'; - -// // Helper function to calculate statistics -// const calculateStatistics = (data, timeRange) => { -// // Filter the data according to the timeRange -// const filteredData = data.filter( -// (item) => new Date(item.x).getTime() >= Date.now() - timeRange -// ); - -// const prices = filteredData.map((d) => d.y); -// const high = Math.max(...prices); -// const low = Math.min(...prices); -// const percentChange = -// ((prices[prices.length - 1] - prices[0]) / prices[0]) * 100; -// const average = prices.reduce((a, b) => a + b, 0) / prices.length; - -// return { high, low, percentChange, average }; -// }; - -// const StatisticsSelector = ({ data, timeRange }) => { -// const [selectedStat, setSelectedStat] = React.useState(''); - -// // Compute the statistics when the data or timeRange changes -// const stats = useMemo( -// () => calculateStatistics(data, timeRange), -// [data, timeRange] -// ); - -// // Handle selection change -// const handleChange = (event) => { -// setSelectedStat(event.target.value); -// }; - -// return ( -// <> -// -// -// {selectedStat === 'high' && `High: $${stats.high}`} -// {selectedStat === 'low' && `Low: $${stats.low}`} -// {selectedStat === 'percentChange' && -// `Change: ${stats.percentChange.toFixed(2)}%`} -// {selectedStat === 'average' && `24hr Avg: $${stats.average.toFixed(2)}`} -// -// -// ); -// }; - -// export default StatisticsSelector; diff --git a/src/components/cleanUp/CardModal.js b/src/components/cleanUp/CardModal.js deleted file mode 100644 index 68fc78e..0000000 --- a/src/components/cleanUp/CardModal.js +++ /dev/null @@ -1,123 +0,0 @@ -// import React, { useContext } from 'react'; -// import { -// Box, -// Button, -// CardMedia, -// Dialog, -// DialogContent, -// DialogTitle, -// Grid, -// Typography, -// } from '@mui/material'; -// import { -// FaDragon, -// FaLevelUpAlt, -// FaRegCopy, -// FaRegLightbulb, -// FaShieldAlt, -// FaVenusMars, -// } from 'react-icons/fa'; -// import { makeStyles } from '@mui/styles'; -// import placeholderImage from '../../../assets/placeholder.jpeg'; -// import { -// CartContext, -// useCartStore, -// } from '../../../context/CartContext/CartContext'; -// import CardDetailsContainer from './CardDetailsContainer'; -// import CollectionActionButtons from '../../buttons/CollectionActionButtons'; -// import GenericActionButtons from '../../buttons/GenericActionButtons'; -// import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; -// import CollectionDialog from '../../dialogs/CollectionDialog'; - -// const useStyles = (context) => -// makeStyles((theme) => ({ -// actionButtons: { -// display: 'flex', -// justifyContent: 'center', -// alignItems: 'center', -// gap: '1.5rem', -// }, -// media: { -// objectFit: 'cover', -// borderRadius: '4px', -// boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', -// }, -// details: { -// display: 'flex', -// alignItems: 'center', -// gap: '1.5rem', -// marginBottom: '1.5rem', -// }, -// dialogTitle: { -// fontSize: '1.5rem', -// fontWeight: 600, -// color: theme.palette.primary.dark, -// }, -// dialogContent: { -// padding: '2rem', -// }, -// })); - -// const CardModal = ({ isOpen, onClose, card, context }) => { -// const classes = useStyles(); -// const { getCardQuantity } = useContext(CartContext); -// const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; -// const productQuantity = getCardQuantity(card?.id); - -// return ( -// -// {card?.name} -// -// -// -// -// -// -// {/* -// */} -// -// -// -// -// -// -// -// ); -// }; - -// export default CardModal; diff --git a/src/components/cleanUp/CartActionButtons.js b/src/components/cleanUp/CartActionButtons.js deleted file mode 100644 index 44e7f1d..0000000 --- a/src/components/cleanUp/CartActionButtons.js +++ /dev/null @@ -1,36 +0,0 @@ -// // src/components/CartActionButtons.js -// import React from 'react'; -// import { useCartStore } from '../../context/CartContext/CartContext'; -// import { makeStyles } from '@mui/styles'; -// import CardActionButtons from './CardActionButtons'; // Import the reusable component -// import CardCountDisplay from './CardCountDisplay'; - -// const useStyles = makeStyles({ -// root: {}, -// }); - -// const CartActionButtons = ({ card, productQuantity }) => { -// const classes = useStyles(); -// const { addOneToCart, deleteFromCart, removeOneFromCart } = useCartStore(); - -// return ( -//
-// - -// -//
-// ); -// }; - -// export default CartActionButtons; diff --git a/src/components/cleanUp/CollectionActionButtons.jsx b/src/components/cleanUp/CollectionActionButtons.jsx deleted file mode 100644 index a5ddde6..0000000 --- a/src/components/cleanUp/CollectionActionButtons.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useState } from 'react'; -import { makeStyles } from '@mui/styles'; -import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; // Assuming you have a separate context for Collections -import CardActionButtons from './CardActionButtons'; -import CollectionDialog from './CollectionDialog'; // Assuming you have a separate dialog for Collections -import CardCountDisplay from '../other/dataDisplay/CardCountDisplay'; -import { useCartStore } from '../../context/CartContext/CartContext'; - -const useStyles = makeStyles({ - root: { - height: '100%', - display: 'flex', - flexDirection: 'column', - }, - button: { - flex: 1, - height: '100%', - padding: '4px', - }, - fullWidthButton: { - width: '100%', - padding: '4px', - }, -}); - -const CollectionActionButtons = ({ card }) => { - const classes = useStyles(); - const { - addOneToCollection, - removeOneFromCollection, - removeAllFromCollection, - createUserCollection, - collectionCardQuantity, - } = useCollectionStore(); - - const { getCardQuantity } = useCartStore(); - const [openDialog, setOpenDialog] = useState(false); - - const handleDialogClose = (newCollectionInfo) => { - createUserCollection(newCollectionInfo); - addOneToCollection(card); - setOpenDialog(false); - }; - - // let collectionCardQuantity = getCardQuantity(card?.id); - console.log('CARD', card); - console.log('getCardQuantity', getCardQuantity(card?.id)); - return ( -
- - - - -
- ); -}; - -export default CollectionActionButtons; - -// will having collectionActionButtons and cartActionButtons in the same component cause errors: diff --git a/src/components/cleanUp/CollectionDialog.jsx b/src/components/cleanUp/CollectionDialog.jsx deleted file mode 100644 index 107d6ba..0000000 --- a/src/components/cleanUp/CollectionDialog.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useState } from 'react'; -import { - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - TextField, - Button, -} from '@mui/material'; - -const CollectionDialog = ({ open, onClose }) => { - const [newCollectionInfo, setNewCollectionInfo] = useState({ - name: '', - description: '', - }); - - const handleInputChange = (e) => { - const { name, value } = e.target; - setNewCollectionInfo((prev) => ({ ...prev, [name]: value })); - }; - - return ( - onClose(null)}> - Create a New Collection - - - You don't have an existing collection. Would you like to create - one? - - - - - - - - - - ); -}; - -export default CollectionDialog; diff --git a/src/components/cleanUp/CollectionEditPanel.jsx b/src/components/cleanUp/CollectionEditPanel.jsx deleted file mode 100644 index 01df715..0000000 --- a/src/components/cleanUp/CollectionEditPanel.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { makeStyles } from '@mui/styles'; -import { Button, TextField, Paper, Typography } from '@mui/material'; - -const useStyles = makeStyles((theme) => ({ - root: { - padding: theme.spacing(4), - borderRadius: '12px', - backgroundColor: theme.palette.grey[100], - boxShadow: '0px 8px 16px 0px rgba(0,0,0,0.2)', - }, - header: { - paddingBottom: theme.spacing(2), - borderBottom: `2px solid ${theme.palette.secondary.main}`, - }, - textField: { - marginTop: theme.spacing(3), - '& .MuiOutlinedInput-root': { - '&:hover fieldset': { - borderColor: theme.palette.secondary.main, - }, - '&.Mui-focused fieldset': { - borderColor: theme.palette.secondary.dark, - }, - }, - }, - saveButton: { - marginTop: theme.spacing(3), - borderRadius: '24px', - textTransform: 'none', - fontSize: 18, - padding: theme.spacing(1, 4), - }, -})); - -const CollectionEditPanel = ({ - collection, - onSave, - isNew, - name, - setName, - setDescription, - description, -}) => { - const classes = useStyles(); - - const handleSave = () => { - onSave({ - ...collection, - name, - description, - }); - }; - - return ( - - - {isNew ? 'Create Collection' : 'Edit Collection'} - - setName(e.target.value)} - /> - setDescription(e.target.value)} - /> - - - ); -}; - -export default CollectionEditPanel; diff --git a/src/components/cleanUp/CollectionForm.jsx b/src/components/cleanUp/CollectionForm.jsx deleted file mode 100644 index 8f9f420..0000000 --- a/src/components/cleanUp/CollectionForm.jsx +++ /dev/null @@ -1,63 +0,0 @@ -// CollectionForm.js - -import React, { useState } from 'react'; -import { Dialog, DialogContent, Button, TextField } from '@mui/material'; -import { makeStyles } from '@mui/styles'; - -const useStyles = makeStyles((theme) => ({ - form: { - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), - }, - buttonContainer: { - display: 'flex', - justifyContent: 'flex-end', - marginTop: theme.spacing(2), - }, -})); - -const CollectionForm = ({ isDialogOpen, closeDialog, onSave, userId }) => { - const classes = useStyles(); - const [name, setName] = useState(''); - const [description, setDescription] = useState(''); - - const handleSave = () => { - const newCollectionInfo = { - name, - description, - }; - onSave(userId, newCollectionInfo); - closeDialog(); - }; - - return ( - - -
- setName(e.target.value)} - variant="outlined" - /> - setDescription(e.target.value)} - variant="outlined" - multiline - rows={4} - /> -
- -
- -
-
- ); -}; - -export default CollectionForm; diff --git a/src/components/cleanUp/CustomPopover.jsx b/src/components/cleanUp/CustomPopover.jsx deleted file mode 100644 index 01f888d..0000000 --- a/src/components/cleanUp/CustomPopover.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; -import Popover from '@mui/material/Popover'; -import CardToolTip from '../cards/CardToolTip'; -import { makeStyles } from '@mui/styles'; -import GenericCard from '../cards/GenericCard'; - -const useStyles = makeStyles((theme) => ({ - paper: { - padding: theme.spacing(1), - }, -})); - -const CustomPopover = ({ - card, - children, - isHovering, - cardRef, - anchorEl, - open, - setAnchorEl, - // handleMouseEnter, - // handleMouseLeave, - handlePopoverClose, -}) => { - const classes = useStyles(); - - const id = open ? 'mouse-over-popover' : undefined; - // console.log('card:', card); - // console.log('cardRef:', cardRef); - // console.log('anchorEl:', anchorEl); - // console.log('open:', open); - // console.log('handlePopoverClose:', handlePopoverClose); - return ( -
- {children} - {/* - - */} - {/*
- -
*/} -
- ); -}; - -CustomPopover.propTypes = { - card: PropTypes.object.isRequired, - children: PropTypes.node.isRequired, -}; - -export default CustomPopover; diff --git a/src/components/cleanUp/DeckActionButtons.js b/src/components/cleanUp/DeckActionButtons.js deleted file mode 100644 index 8a66b54..0000000 --- a/src/components/cleanUp/DeckActionButtons.js +++ /dev/null @@ -1,65 +0,0 @@ -// import React, { useState } from 'react'; -// import { makeStyles } from '@mui/styles'; -// import { useDeckStore } from '../../context/DeckContext/DeckContext'; -// import CardActionButtons from './CardActionButtons'; -// import DeckDialog from '../cleanUp/DeckDialog'; -// import DeckCountDisplay from './DeckCountDisplay'; -// import CardCountDisplay from './CardCountDisplay'; - -// const useStyles = makeStyles({ -// root: { -// height: '100%', -// display: 'flex', -// flexDirection: 'column', -// }, -// button: { -// flex: 1, -// height: '100%', -// padding: '4px', -// }, -// fullWidthButton: { -// width: '100%', -// padding: '4px', -// }, -// }); - -// const DeckActionButtons = ({ card }) => { -// const classes = useStyles(); -// const { -// addOneToDeck, -// removeOneFromDeck, -// removeAllFromDeck, -// createUserDeck, -// deckCardQuantity, -// } = useDeckStore(); -// const [openDialog, setOpenDialog] = useState(false); - -// const handleDialogClose = (newDeckInfo) => { -// createUserDeck(newDeckInfo); -// addOneToDeck(card); -// setOpenDialog(false); -// }; - -// return ( -//
-// -// -// -//
-// ); -// }; - -// export default DeckActionButtons; diff --git a/src/components/cleanUp/DeckCard.js b/src/components/cleanUp/DeckCard.js deleted file mode 100644 index 071b888..0000000 --- a/src/components/cleanUp/DeckCard.js +++ /dev/null @@ -1,118 +0,0 @@ -// import React, { useState, useEffect, useRef } from 'react'; -// import DeckCardMedia from '../media/DeckCardMedia'; -// import CardToolTip from './CardToolTip'; -// import DeckCardDialog from '../dialogs/DeckCardDialog'; -// import placeholderImage from '../../assets/placeholder.jpeg'; -// import './deckcard.css'; // Assuming you have styles here -// import { makeStyles } from '@mui/styles'; -// import CardMediaSection from '../media/CardMediaSection'; - -// const useStyles = makeStyles((theme) => ({ -// card: { -// position: 'relative', // Add this -// display: 'flex', -// flexDirection: 'column', -// height: '100%', -// width: '100%', -// flexGrow: 1, -// }, -// media: { -// width: '100%', -// objectFit: 'contain', -// }, -// content: { -// flex: '1 1 auto', -// overflow: 'hidden', -// // padding: theme.spacing(1), -// }, -// text: { -// textOverflow: 'ellipsis', -// overflow: 'hidden', -// whiteSpace: 'nowrap', -// }, -// actionButtons: { -// backgroundColor: '#f5f5f5', -// // padding: theme.spacing(1), -// margin: theme.spacing(1, 0), -// borderRadius: '4px', -// overflow: 'auto', -// }, -// dialog: { -// position: 'absolute', // Add this -// top: 0, -// right: 0, -// zIndex: 1000, // High z-index value -// }, -// })); - -// const DeckCard = ({ card, cardInfo }) => { -// const [isDeckModalOpen, setDeckModalOpen] = useState(false); -// const [isHovering, setHovering] = useState(false); -// const [hasLoggedCard, setHasLoggedCard] = useState(false); -// const [imgLoadError, setImgLoadError] = useState(false); // For error handling - -// const tooltipRef = useRef(null); -// const cardRef = useRef(null); -// const classes = useStyles(); - -// const imgUrl = -// !imgLoadError && (card?.card_images?.[0]?.image_url || placeholderImage); - -// const openDeckModal = () => setDeckModalOpen(true); -// const closeDeckModal = () => setDeckModalOpen(false); - -// useEffect(() => { -// if (process.env.NODE_ENV !== 'production') { -// // Log only in development mode -// if (!hasLoggedCard) { -// console.log('CARD:', card); -// setHasLoggedCard(true); -// } -// } - -// if (isHovering && tooltipRef.current && cardRef.current) { -// const cardRect = cardRef.current.getBoundingClientRect(); -// tooltipRef.current.style.top = `${cardRect.top}px`; -// tooltipRef.current.style.left = `${cardRect.right}px`; -// } -// }, [isHovering, card, hasLoggedCard]); - -// // This useEffect is for debugging, remove in production -// useEffect(() => { -// if (process.env.NODE_ENV !== 'production') { -// console.log('isHovering:', isHovering); -// console.log('isDeckModalOpen:', isDeckModalOpen); -// } -// }, [isHovering, isDeckModalOpen]); - -// return ( -// <> -// -// -// -// -// ); -// }; - -// export default DeckCard; diff --git a/src/components/cleanUp/DeckCardDialog.jsx b/src/components/cleanUp/DeckCardDialog.jsx deleted file mode 100644 index 8f8cdfa..0000000 --- a/src/components/cleanUp/DeckCardDialog.jsx +++ /dev/null @@ -1,16 +0,0 @@ -// import React from 'react'; -// import GenericCardModal from '../modals/GenericCardModal'; - -// const DeckCardDialog = ({ isOpen, onClose, card, cardInfo, context }) => { -// return ( -// -// ); -// }; - -// export default DeckCardDialog; diff --git a/src/components/cleanUp/DeckCardDialogue.js b/src/components/cleanUp/DeckCardDialogue.js deleted file mode 100644 index d0cb703..0000000 --- a/src/components/cleanUp/DeckCardDialogue.js +++ /dev/null @@ -1,96 +0,0 @@ -// import React from 'react'; -// import Button from '@mui/material/Button'; -// import { Box } from '@mui/system'; -// import { Popover, Typography } from '@mui/material'; -// import { makeStyles } from '@mui/styles'; -// import DeckCardModal from '../modals/DeckCardModal'; - -// const useStyles = makeStyles((theme) => ({ -// card: { -// display: 'flex', -// flexDirection: 'column', -// flexGrow: 1, -// overflow: 'hidden', -// }, -// media: { -// objectFit: 'contain', -// }, -// content: { -// flexGrow: 1, -// display: 'flex', -// flexDirection: 'row', -// // padding: theme.spacing(0.5), // reduce padding -// }, -// reducedBox: { -// flexGrow: 1, -// display: 'flex', -// flexDirection: 'row', -// padding: theme.spacing(0.5), // reduce padding -// }, -// button: { -// flexGrow: 1, -// fontSize: '0.7rem', // reduce font size -// padding: theme.spacing(0.5), // reduce padding -// }, -// text: { -// fontSize: '0.8rem', // reduce font size -// whiteSpace: 'nowrap', -// overflow: 'hidden', -// textOverflow: 'ellipsis', -// }, -// popover: { -// pointerEvents: 'none', -// }, -// paper: { -// padding: theme.spacing(1), -// }, -// })); - -// const DeckCardDialogue = ({ -// anchorEl, -// isOpen, -// onClose, -// card, -// openCardModal, -// closeCardModal, -// isCardModalOpen, -// }) => { -// const classes = useStyles(); - -// return ( -// -// {card?.name} -// -// -// -// -// -// ); -// }; - -// export default DeckCardDialogue; diff --git a/src/components/cleanUp/DeckCardModal.js b/src/components/cleanUp/DeckCardModal.js deleted file mode 100644 index 17db76a..0000000 --- a/src/components/cleanUp/DeckCardModal.js +++ /dev/null @@ -1,55 +0,0 @@ -// import React, { useContext } from 'react'; -// import { Dialog, DialogContent, DialogTitle, Grid } from '@mui/material'; -// import { DeckContext } from '../../context/DeckContext/DeckContext'; -// import CardMediaSection from '../media/CardMediaSection'; -// import CardDetailsContainer from './cardModal/CardDetailsContainer'; -// import DeckActionButtons from '../cleanUp/DeckActionButtons'; -// import { makeStyles } from '@mui/styles'; -// import GenericActionButtons from '../buttons/GenericActionButtons'; - -// const useStyles = (context) => -// makeStyles({ -// dialogTitle: { -// backgroundColor: '#0066cc', -// color: 'white', -// padding: '16px', -// fontWeight: 'bold', -// }, -// }); - -// const DeckCardModal = ({ isOpen, onClose, card, userDecks, context }) => { -// const classes = useStyles(); -// if (!card) return null; // or some other placeholder - -// const { getCardQuantity } = useContext(DeckContext); -// const productQuantity = getCardQuantity(card?.id); - -// const handleClose = (event, reason) => { -// if (reason === 'backdropClick') { -// onClose(); -// } -// }; - -// return ( -// -// {card?.name} -// -// -// -// -// -// -// -// -// -// {/* */} -// -// -// -// ); -// }; - -// export default DeckCardModal; diff --git a/src/components/cleanUp/DeckCountDisplay.jsx b/src/components/cleanUp/DeckCountDisplay.jsx deleted file mode 100644 index f1224b8..0000000 --- a/src/components/cleanUp/DeckCountDisplay.jsx +++ /dev/null @@ -1,12 +0,0 @@ -// import React from 'react'; -// import { Grid } from '@mui/material'; - -// const DeckCountDisplay = ({ quantity }) => ( -// -// -// In Deck: {quantity} -// -// -// ); - -// export default DeckCountDisplay; diff --git a/src/components/cleanUp/DeckDialog.jsx b/src/components/cleanUp/DeckDialog.jsx deleted file mode 100644 index a9dcfbe..0000000 --- a/src/components/cleanUp/DeckDialog.jsx +++ /dev/null @@ -1,57 +0,0 @@ -// import React, { useState } from 'react'; -// import { -// Dialog, -// DialogActions, -// DialogContent, -// DialogContentText, -// DialogTitle, -// TextField, -// Button, -// } from '@mui/material'; - -// const DeckDialog = ({ open, onClose }) => { -// const [newDeckInfo, setNewDeckInfo] = useState({ name: '', description: '' }); - -// const handleInputChange = (e) => { -// const { name, value } = e.target; -// setNewDeckInfo((prev) => ({ ...prev, [name]: value })); -// }; - -// return ( -// onClose(null)}> -// Create a New Deck -// -// -// You don't have an existing deck. Would you like to create one? -// -// -// -// -// -// -// -// -// -// ); -// }; - -// export default DeckDialog; diff --git a/src/components/cleanUp/Hero.jsx b/src/components/cleanUp/Hero.jsx deleted file mode 100644 index 7bc8866..0000000 --- a/src/components/cleanUp/Hero.jsx +++ /dev/null @@ -1,49 +0,0 @@ -// import React from 'react'; -// import { Box, Container, Typography } from '@mui/material'; -// import { makeStyles } from '@mui/styles'; -// import { Canvas } from '@react-three/fiber'; -// import Cube from '../../assets/animations/Cube'; - -// const useStyles = makeStyles((theme) => ({ -// bannerBox: { -// backgroundImage: `linear-gradient(to right, ${theme.palette.primary.light}, ${theme.palette.primary.main})`, -// minHeight: '100vh', -// padding: theme.spacing(4), -// display: 'flex', -// alignItems: 'center', -// justifyContent: 'center', -// }, -// })); - -// const Hero = () => { -// const classes = useStyles(); -// return ( -// -// -// -// {/* eslint-disable-next-line react/no-unknown-property */} -// -// -// -// -// -// Buy Amazing Cards -// -// -// -// ); -// }; - -// export default Hero; diff --git a/src/components/cleanUp/PortfolioCard.jsx b/src/components/cleanUp/PortfolioCard.jsx deleted file mode 100644 index ce16ac2..0000000 --- a/src/components/cleanUp/PortfolioCard.jsx +++ /dev/null @@ -1,118 +0,0 @@ -// import React, { useState, useEffect, useRef } from 'react'; -// import DeckCardMedia from '../media/DeckCardMedia'; -// import CardToolTip from './CardToolTip'; -// import DeckCardDialog from './DeckCardDialog'; -// import placeholderImage from '../../assets/placeholder.jpeg'; -// import './deckcard.css'; // Assuming you have styles here -// import { makeStyles } from '@mui/styles'; - -// const useStyles = makeStyles((theme) => ({ -// card: { -// position: 'relative', // Add this -// display: 'flex', -// flexDirection: 'column', -// height: '100%', -// width: '100%', -// flexGrow: 1, -// }, -// media: { -// width: '100%', -// objectFit: 'contain', -// }, -// content: { -// flex: '1 1 auto', -// overflow: 'hidden', -// // padding: theme.spacing(1), -// }, -// text: { -// textOverflow: 'ellipsis', -// overflow: 'hidden', -// whiteSpace: 'nowrap', -// }, -// actionButtons: { -// backgroundColor: '#f5f5f5', -// // padding: theme.spacing(1), -// margin: theme.spacing(1, 0), -// borderRadius: '4px', -// overflow: 'auto', -// }, -// dialog: { -// position: 'absolute', // Add this -// top: 0, -// right: 0, -// zIndex: 1000, // High z-index value -// }, -// })); - -// const PortfolioCard = ({ card, cardInfo }) => { -// const [isDeckModalOpen, setDeckModalOpen] = useState(false); -// const [isHovering, setHovering] = useState(false); -// const [hasLoggedCard, setHasLoggedCard] = useState(false); -// const [imgLoadError, setImgLoadError] = useState(false); // For error handling - -// const tooltipRef = useRef(null); -// const cardRef = useRef(null); - -// const imgUrl = -// !imgLoadError && (card?.card_images?.[0]?.image_url || placeholderImage); - -// const openDeckModal = () => setDeckModalOpen(true); -// const closeDeckModal = () => setDeckModalOpen(false); - -// useEffect(() => { -// if (process.env.NODE_ENV !== 'production') { -// // Log only in development mode -// if (!hasLoggedCard) { -// console.log('CARD:', card); -// setHasLoggedCard(true); -// } -// } - -// if (isHovering && tooltipRef.current && cardRef.current) { -// const cardRect = cardRef.current.getBoundingClientRect(); -// tooltipRef.current.style.top = `${cardRect.top}px`; -// tooltipRef.current.style.left = `${cardRect.right}px`; -// } -// }, [isHovering, card, hasLoggedCard]); - -// // This useEffect is for debugging, remove in production -// useEffect(() => { -// if (process.env.NODE_ENV !== 'production') { -// console.log('isHovering:', isHovering); -// console.log('isDeckModalOpen:', isDeckModalOpen); -// } -// }, [isHovering, isDeckModalOpen]); - -// const handleImgError = () => { -// setImgLoadError(true); -// }; - -// return ( -// <> -// -// -// -// -// ); -// }; - -// export default PortfolioCard; diff --git a/src/components/cleanUp/PracticeScraperPage.js b/src/components/cleanUp/PracticeScraperPage.js deleted file mode 100644 index 06ad132..0000000 --- a/src/components/cleanUp/PracticeScraperPage.js +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useContext, useEffect } from 'react'; -import ScraperContainer from './ScraperContainer'; -import { BeatLoader } from 'react-spinners'; -import { useCookies } from 'react-cookie'; -import { ScraperContext } from '../../context/ScraperContext/ScraperContext'; - -const ErrorIndicator = ({ error }) => { - return
Error: {error}
; -}; - -const PracticeScraperPage = () => { - const [cookies] = useCookies(['userCookie']); - const { fetchExistingScrapeData, loading, error } = - useContext(ScraperContext); - - // const cardName = cookies.cardName; - // console.log('cardName:', cardName); - - useEffect(() => { - fetchExistingScrapeData() - .then((data) => { - console.log('Successfully fetched scrape data:', data); - // Optionally, display a success message to the user. - // This would require you to manage a 'message' state. - // setMessage('Successfully fetched scrape data.'); - }) - .catch((err) => { - console.error('Failed to get card scrape data', err); - }); - }, [fetchExistingScrapeData]); - - if (loading) { - return ; - } - - if (error) { - return ; - } - - return ( -
-

Web Scraper Page

- -
- ); -}; - -export default PracticeScraperPage; diff --git a/src/components/cleanUp/ProductCard.js b/src/components/cleanUp/ProductCard.js deleted file mode 100644 index b23a052..0000000 --- a/src/components/cleanUp/ProductCard.js +++ /dev/null @@ -1,98 +0,0 @@ -// import { Card, Button, CardContent, CardActions } from '@mui/material'; -// import { makeStyles } from '@mui/styles'; -// import { useContext, useState } from 'react'; -// import CardMedia from '@mui/material/CardMedia'; -// import Typography from '@mui/material/Typography'; -// import placeholderImage from '../../assets/placeholder.jpeg'; -// import CardModal from '../modals/cardModal/CardModal'; -// import CartActionButtons from '../buttons/CartActionButtons'; -// import { useCartStore } from '../../context/CartContext/CartContext'; - -// const useStyles = makeStyles({ -// card: { -// display: 'flex', -// flexDirection: 'column', -// maxWidth: '100%', -// minHeight: 200, -// margin: '16px', -// padding: '16px', -// }, -// media: { -// flex: 0.75, // 3/4 of the card -// }, -// content: { -// flex: 0.25, // 1/4 of the card -// }, -// button: { -// margin: '0 8px', -// }, -// largerButton: { -// margin: '0 8px', -// padding: '10px', -// fontSize: '20px', -// }, -// actionButtons: { -// // Custom styles for CartActionButtons in ProductCard go here -// backgroundColor: '#f5f5f5', -// padding: '10px', -// margin: '10px 0', -// borderRadius: '4px', -// }, -// }); - -// const ProductCard = ({ card, page }) => { -// const { cartData, getCardQuantity } = useCartStore(); -// const classes = useStyles(); - -// const { id, name } = card; -// const [isCardModalOpen, setCardModalOpen] = useState(false); -// const openCardModal = () => setCardModalOpen(true); -// const closeCardModal = () => setCardModalOpen(false); -// const productQuantity = getCardQuantity(card?.id); -// console.log('productQuantity', productQuantity); -// const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; - -// return ( -// -// -//
-// -// -// {name} -// -// -// Price: {card?.card_prices?.[0]?.tcgplayer_price} -// -// -// Quantity:{' '} -// {productQuantity.quantityOfSameId > 0 -// ? productQuantity.quantityOfSameId -// : 'Not in cart'} -// -// -// -// -// -// -// -//
-//
-// ); -// }; - -// export default ProductCard; diff --git a/src/components/cleanUp/ScrapeDisplay.js b/src/components/cleanUp/ScrapeDisplay.js deleted file mode 100644 index caadcaa..0000000 --- a/src/components/cleanUp/ScrapeDisplay.js +++ /dev/null @@ -1,26 +0,0 @@ -// src/components/other/ScrapeDisplay.js -import React, { useState, useEffect } from 'react'; - -const ScrapeDisplay = () => { - const [results, setResults] = useState([]); // You'd probably be fetching this from an API or context. - - // Simulating getting some results for demonstration. - useEffect(() => { - // Fetch results from an API or context here. - const dummyResults = ['Result 1', 'Result 2', 'Result 3']; // Placeholder data - setResults(dummyResults); - }, []); - - return ( -
-

Scraping Results:

-
    - {results.map((result, index) => ( -
  • {result}
  • - ))} -
-
- ); -}; - -export default ScrapeDisplay; diff --git a/src/components/cleanUp/ScrapeSearch.js b/src/components/cleanUp/ScrapeSearch.js deleted file mode 100644 index f654f2c..0000000 --- a/src/components/cleanUp/ScrapeSearch.js +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useState } from 'react'; -import { Button, TextField } from '@mui/material'; -import axios from 'axios'; - -const ScrapeSearch = ({ onCardNameChange }) => { - const [cardName, setCardName] = useState(''); - - const handleScrape = async () => { - const finalCardName = cardName || 'Dark Magician'; - try { - onCardNameChange(finalCardName); - - const response = await axios.get( - `${ - process.env.REACT_APP_SERVER - }/api/scrape?cardName=${encodeURIComponent(finalCardName)}`, - { - withCredentials: true, - } - ); - - alert(response.data); - } catch (error) { - alert('Error while scraping: ' + error.message); - } - }; - - return ( -
-

Initiate Scraping

- setCardName(e.target.value)} - /> - -
- ); -}; - -export default ScrapeSearch; diff --git a/src/components/cleanUp/ScraperContainer.js b/src/components/cleanUp/ScraperContainer.js deleted file mode 100644 index 5d3fd3b..0000000 --- a/src/components/cleanUp/ScraperContainer.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { makeStyles } from '@mui/styles'; -import { Grid } from '@mui/material'; -import ScrapeDisplay from './ScrapeDisplay'; -import ScrapeSearch from './ScrapeSearch'; -import { useCookies } from 'react-cookie'; - -const useStyles = makeStyles((theme) => ({ - root: { - flexGrow: 1, - padding: theme.spacing(2), - }, -})); - -const ScraperContainer = () => { - const classes = useStyles(); - const [cookies, setCookie] = useCookies(['userCookie']); - console.log('cookies:', cookies); - const handleCardNameChange = (newCardName) => { - // Set the new card name as a cookie - setCookie('cardName', newCardName, { path: '/' }); - }; - - return ( - - - - - - - - - ); -}; - -export default ScraperContainer; diff --git a/src/components/cleanUp/SelectCollectionDialog.jsx b/src/components/cleanUp/SelectCollectionDialog.jsx deleted file mode 100644 index ed5732f..0000000 --- a/src/components/cleanUp/SelectCollectionDialog.jsx +++ /dev/null @@ -1,133 +0,0 @@ -// import React from 'react'; -// import PropTypes from 'prop-types'; -// import { -// Dialog, -// DialogContent, -// Button, -// TextField, -// Typography, -// } from '@mui/material'; -// import { useCookies } from 'react-cookie'; -// import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; - -// const SelectCollectionDialog = ({ -// isDialogOpen, -// closeDialog, -// onSave, -// isNew, -// // userId, -// editedName, -// setEditedName, -// editedDescription, -// setEditedDescription, -// }) => { -// const { -// createUserCollection, -// addOneToCollection, -// removeCollection, -// selectedCollection, -// } = useCollectionStore(); -// const [cookies] = useCookies(['user']); -// const userId = cookies.user?.id; - -// const handleSave = () => { -// const newCollectionInfo = { -// name: editedName, -// description: editedDescription, -// userId: userId, -// }; - -// if (isNew) { -// createUserCollection( -// userId, -// newCollectionInfo, -// editedName, -// editedDescription, -// userId -// ); -// } else if (editedName && editedDescription) { -// addOneToCollection(newCollectionInfo); -// } else { -// console.error('No card to add to the collection'); -// } - -// onSave(newCollectionInfo); -// closeDialog(); -// }; - -// const handleRemove = (e) => { -// e.preventDefault(); - -// removeCollection(selectedCollection); -// closeDialog(); -// }; - -// return ( -// -// -// -// {isNew -// ? 'Fill in the data below to create a new collection' -// : 'Edit your collection'} -// -//
-// setEditedName(e.target.value)} -// variant="outlined" -// fullWidth -// margin="normal" -// /> -//
-//
-// setEditedDescription(e.target.value)} -// variant="outlined" -// multiline -// rows={4} -// fullWidth -// margin="normal" -// /> -//
-//
-// -// {!isNew && ( -// -// )} -//
-//
-//
-// ); -// }; - -// SelectCollectionDialog.propTypes = { -// isDialogOpen: PropTypes.bool.isRequired, -// closeDialog: PropTypes.func.isRequired, -// onSave: PropTypes.func.isRequired, -// isNew: PropTypes.bool, -// userId: PropTypes.string, -// editedName: PropTypes.string, -// setEditedName: PropTypes.func.isRequired, -// editedDescription: PropTypes.string, -// setEditedDescription: PropTypes.func.isRequired, -// }; - -// export default SelectCollectionDialog; diff --git a/src/components/cleanUp/UpdateChartData.jsx b/src/components/cleanUp/UpdateChartData.jsx deleted file mode 100644 index 9744a17..0000000 --- a/src/components/cleanUp/UpdateChartData.jsx +++ /dev/null @@ -1,72 +0,0 @@ -// import { useCallback, useEffect } from 'react'; -// import moment from 'moment'; -// import { useCollectionStore } from '../../context/hooks/collection'; -// import { useCombinedContext } from '../../context/CombinedProvider'; - -// const transformChartData = (chartData) => { -// let pointsArray = []; - -// if (Array.isArray(chartData)) { -// chartData?.forEach((item) => { -// item.datasets.forEach((dataset) => { -// dataset.data.forEach((data) => { -// pointsArray.push(...data.points); -// }); -// }); -// }); -// } else { -// console.error('Expected chartData to be an array, but got:', chartData); -// } - -// return pointsArray; -// }; - -// const useUpdateChartData = () => { -// const { totalCost } = useCollectionStore() || {}; -// const { -// updateServerData, -// isCronJobTriggered, -// setIsCronJobTriggered, -// chartData, -// } = useCombinedContext() || {}; - -// const transformedData = transformChartData(chartData); - -// const createDataset = (label, priceData) => ({ -// name: label, -// color: 'blue', -// data: priceData?.map(({ x, y }) => ({ x, y })), -// }); - -// const newDataPoint = useCallback(() => { -// return { -// x: moment().format('YYYY-MM-DD HH:mm'), -// y: totalCost ? parseFloat(totalCost).toFixed(2) : null, -// }; -// }, [totalCost]); - -// const updateChart = useCallback(() => { -// let updatedDatasets = []; - -// if (transformedData.length && totalCost != null) { -// updatedDatasets.push( -// createDataset('totalPrice', [...transformedData, newDataPoint()]) -// ); -// } - -// if (updatedDatasets.length) { -// updateServerData(updatedDatasets); -// } -// }, [transformedData, newDataPoint, updateServerData, totalCost]); - -// useEffect(() => { -// if (isCronJobTriggered) { -// updateChart(); -// setIsCronJobTriggered(false); -// } -// }, [isCronJobTriggered, updateChart, setIsCronJobTriggered]); - -// return { datasets: transformedData }; -// }; - -// export default useUpdateChartData; diff --git a/src/components/cleanUp/button/Button.css b/src/components/cleanUp/button/Button.css deleted file mode 100644 index 2245749..0000000 --- a/src/components/cleanUp/button/Button.css +++ /dev/null @@ -1,43 +0,0 @@ -.main-button { - background-color: #55198b; - border: solid 1px #55198b; - color: white; - font-weight: 500; - line-height: 1.1; - width: max-content; - padding: 13px 22px; - margin-right: 0px; - /* text-transform: uppercase; */ - border-radius: 6px; - text-align: center; - text-decoration: none; - font-family: 'Google Sans Regular'; - display: block; - margin-top: 20px; - font-size: 18px; - cursor: pointer; - letter-spacing: 2px; -} -.main-button:hover { - /* background-color: #ffffff; */ - /* color: black; */ - transition: ease-in 0.3s; -} -.project-button { - display: flex; - justify-content: center; - margin-top: 20px; -} -/* Media Query */ -@media (max-width: 768px) { - .main-button { - font-size: 15px; - padding: 12px 18px; - margin-right: 0px; - } -} -@media (max-width: 320px) { - .main-button { - font-size: 12px; - } -} diff --git a/src/components/cleanUp/button/Button.js b/src/components/cleanUp/button/Button.js deleted file mode 100644 index da3b188..0000000 --- a/src/components/cleanUp/button/Button.js +++ /dev/null @@ -1,35 +0,0 @@ -// import React from 'react'; -// import './Button.css'; - -// const onMouseEnter = (event, color, bgColor) => { -// const el = event.target; -// el.style.color = color; -// el.style.backgroundColor = bgColor; -// }; - -// const onMouseOut = (event, color, bgColor) => { -// const el = event.target; -// el.style.color = color; -// el.style.backgroundColor = bgColor; -// }; - -// export default function Button({ text, className, href, newTab, theme }) { -// return ( -// -// ); -// } diff --git a/src/components/cleanUp/theme.jsx b/src/components/cleanUp/theme.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/collection/example-data.json b/src/components/collection/example-data.json deleted file mode 100644 index 601be24..0000000 --- a/src/components/collection/example-data.json +++ /dev/null @@ -1,64 +0,0 @@ -[ - { - "id": "totalPrice", - "color": "hsl(351, 70%, 50%)", - "data": [ - { - "x": "1:00", - "y": 2170 - }, - { - "x": "1:30", - "y": 2176 - }, - { - "x": "2:00", - "y": 2200 - }, - { - "x": "2:30", - "y": 2150 - }, - { - "x": "3:00", - "y": 2250 - }, - { - "x": "3:30", - "y": 2300 - }, - { - "x": "4:00", - "y": 2400 - }, - { - "x": "4:30", - "y": 2450 - }, - { - "x": "5:00", - "y": 2500 - }, - { - "x": "5:30", - "y": 2550 - }, - { - "x": "6:00", - "y": 2600 - }, - { - "x": "6:30", - "y": 2650 - }, - { - "x": "7:00", - "y": 2700 - }, - { - "x": "7:30", - "y": 2750 - } - ] - } -] \ No newline at end of file diff --git a/src/components/forms/AddCardForm.jsx b/src/components/forms/AddCardForm.jsx index b52ecf7..0f47efb 100644 --- a/src/components/forms/AddCardForm.jsx +++ b/src/components/forms/AddCardForm.jsx @@ -1,39 +1,39 @@ -import React from 'react'; -import { Box, Input, Button } from '@mui/material'; +// import React from 'react'; +// import { Box, Input, Button } from '@mui/material'; -const AddCardForm = ({ - newCard, - setNewCard, - newCardPrice, - setNewCardPrice, - newCardCondition, - setNewCardCondition, // New prop for card condition - addCard, -}) => { - return ( - - setNewCard(e.target.value)} - /> - setNewCardPrice(e.target.value)} - mt={2} - /> - setNewCardCondition(e.target.value)} // New state setter function - mt={2} - /> - - - ); -}; +// const AddCardForm = ({ +// newCard, +// setNewCard, +// newCardPrice, +// setNewCardPrice, +// newCardCondition, +// setNewCardCondition, // New prop for card condition +// addCard, +// }) => { +// return ( +// +// setNewCard(e.target.value)} +// /> +// setNewCardPrice(e.target.value)} +// mt={2} +// /> +// setNewCardCondition(e.target.value)} // New state setter function +// mt={2} +// /> +// +// +// ); +// }; -export default AddCardForm; +// export default AddCardForm; diff --git a/src/components/forms/SearchForm.jsx b/src/components/forms/SearchForm.jsx index 358bc91..c992696 100644 --- a/src/components/forms/SearchForm.jsx +++ b/src/components/forms/SearchForm.jsx @@ -3,68 +3,77 @@ import { TextField, Button, Paper } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { useMode } from '../../context/hooks/colormode'; -const useStyles = makeStyles((theme) => ({ - formContainer: { - width: '100%', - padding: theme.spacing(3), - borderRadius: theme.shape.borderRadius, - backgroundColor: theme.palette.background.default, - boxShadow: theme.shadows[3], // Use the theme's predefined shadows - maxWidth: 400, - margin: 'auto', - }, - form: { - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), - padding: theme.spacing(2), - }, - textField: { - '& label.Mui-focused': { - color: theme.palette.primary.main, - }, - '& .MuiOutlinedInput-root': { - '&:hover fieldset': { - borderColor: theme.palette.primary.light, - }, - '&.Mui-focused fieldset': { - borderColor: theme.palette.primary.main, - }, - }, - }, - submitButton: { - backgroundColor: theme.palette.success.main, - color: theme.palette.getContrastText(theme.palette.success.main), - '&:hover': { - backgroundColor: theme.palette.success.dark, - }, - }, -})); - const SearchForm = ({ searchTerm, handleChange, handleSubmit }) => { const { theme } = useMode(); - const classes = useStyles(theme); return ( - -
- - - + + + +
); }; diff --git a/src/components/forms/customerCheckoutForm/CartActions.jsx b/src/components/forms/customerCheckoutForm/CartActions.jsx index 3fab6cb..65ba644 100644 --- a/src/components/forms/customerCheckoutForm/CartActions.jsx +++ b/src/components/forms/customerCheckoutForm/CartActions.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { Box } from '@mui/material'; -import CartSummary from '../../other/CartSummary'; +import CartSummary from '../../other/dataDisplay/CartSummary'; import OrderSubmitButton from '../../buttons/other/OrderSubmitButton'; const CartActions = ({ quantity, getTotalCost, handleModalOpen }) => { diff --git a/src/components/forms/customerCheckoutForm/CustomerForm.js b/src/components/forms/customerCheckoutForm/CustomerForm.js index 854c76e..7be8582 100644 --- a/src/components/forms/customerCheckoutForm/CustomerForm.js +++ b/src/components/forms/customerCheckoutForm/CustomerForm.js @@ -1,5 +1,5 @@ import React, { useState, useCallback, useContext } from 'react'; -import { Box, Container, Typography } from '@mui/material'; +import { Box, Container, Typography, Grid } from '@mui/material'; import { useCartStore } from '../../../context/CartContext/CartContext'; import CustomerInfoFields from './CustomerInfoFields'; import CartActions from './CartActions'; @@ -8,7 +8,6 @@ import { ModalContext } from '../../../context/ModalContext/ModalContext'; const CustomerForm = () => { const { getTotalCost, cartData } = useCartStore(); - // const [isModalOpen, setIsModalOpen] = useState(false); const { openModalWithCard, closeModal, @@ -16,10 +15,13 @@ const CustomerForm = () => { setModalOpen, modalContent, } = useContext(ModalContext); - // const handleModalOpen = useCallback(() => setIsModalOpen(true), []); - // const handleModalClose = useCallback(() => setIsModalOpen(false), []); - const handleModalOpen = useCallback(() => setModalOpen(true), []); - const handleModalClose = useCallback(() => setModalOpen(false), []); + const { cartCardQuantity } = useCartStore(); + + const handleModalOpen = useCallback(() => setModalOpen(true), [setModalOpen]); + const handleModalClose = useCallback( + () => setModalOpen(false), + [setModalOpen] + ); const onToken = useCallback( (token) => { console.log(token); @@ -28,7 +30,8 @@ const CustomerForm = () => { [handleModalClose] ); - console.log('CUSTOMER FORM QUANTITY FROM CARTDATA', cartData.quantity); + console.log('CUSTOMER FORM QUANTITY FROM CARTDATA', cartCardQuantity); + return ( @@ -39,14 +42,21 @@ const CustomerForm = () => { Customer Info
- - - - + + + + + + {/* This space can be used for additional components if needed */} + + + + +
({ - root: { - padding: theme.spacing(3), - backgroundColor: theme.palette.background.default, - border: `1px solid ${theme.palette.divider}`, - borderRadius: theme.shape.borderRadius, - margin: 'auto', // Centering the form - }, - paper: { - padding: theme.spacing(2), - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[4], - backgroundColor: theme.palette.background.paper, - }, - button: { - margin: theme.spacing(1), - backgroundColor: theme.palette.primary.main, - color: theme.palette.primary.contrastText, - '&:hover': { - backgroundColor: theme.palette.primary.dark, - }, - }, - noCardsText: { - marginTop: theme.spacing(2), - textAlign: 'center', - color: theme.palette.text.secondary, - fontStyle: 'italic', - }, - title: { - fontWeight: 'bold', - color: theme.palette.text.primary, - marginBottom: theme.spacing(2), - }, - // Other styles... -})); +import DeckEditPanel from '../other/InputComponents/DeckEditPanel'; +import { useMode } from '../../context/hooks/colormode'; const DeckDisplay = ({ userDecks = [] }) => { - const classes = useStyles(); + const { theme } = useMode(); const { setSelectedDeck, selectedDeck, updateAndSyncDeck } = useContext(DeckContext); const [selectedCards, setSelectedCards] = useState([]); @@ -61,14 +25,43 @@ const DeckDisplay = ({ userDecks = [] }) => { }; return ( - - - + + + Your Decks @@ -87,7 +80,14 @@ const DeckDisplay = ({ userDecks = [] }) => { {selectedCards.length > 0 ? ( ) : ( - + No cards to display )} diff --git a/src/components/grids/StoreItem.jsx b/src/components/grids/StoreItem.jsx new file mode 100644 index 0000000..862668e --- /dev/null +++ b/src/components/grids/StoreItem.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import GenericCard from '../cards/GenericCard'; + +const StoreItem = ({ card, page, index, context }) => ( + + + +); + +export default StoreItem; diff --git a/src/components/grids/deckBuilderGrids/DeckButtonList.js b/src/components/grids/deckBuilderGrids/DeckButtonList.js index 2aeea2f..02a29c1 100644 --- a/src/components/grids/deckBuilderGrids/DeckButtonList.js +++ b/src/components/grids/deckBuilderGrids/DeckButtonList.js @@ -9,6 +9,8 @@ const DeckButtonList = ({ userDecks, handleSelectDeck }) => { const { selectedDeck } = useContext(DeckContext); // console.log('SELECTED DECK:', selectedDeck.name); // console.log('userDecks', userDecks); + console.log('userDecks', userDecks); + console.log('selectedDeck', selectedDeck); return ( {userDecks?.map((deck) => ( diff --git a/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx b/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx index 08cfcfa..4cf85d7 100644 --- a/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx +++ b/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx @@ -1,22 +1,33 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { Box, Grid, useMediaQuery } from '@mui/material'; -// import LoadingIndicator from '../../reusable/indicators/LoadingIndicator'; +import { Box, Container, Grid, useMediaQuery } from '@mui/material'; import GenericCard from '../../cards/GenericCard'; import { useMode } from '../../../context/hooks/colormode'; import { styled } from '@mui/system'; -// import LoadingCardAnimation from '../../../assets/animations/LoadingCardAnimation'; -import LoadingIndicator2 from '../../reusable/indicators/LoadingIndicator2'; +import LoadingIndicator from '../../reusable/indicators/LoadingIndicator'; + const StyledGrid = styled(Grid)(({ theme }) => ({ - margin: theme.spacing(1), + // margin: theme.spacing(1), [theme.breakpoints.down('sm')]: { margin: theme.spacing(0.5), }, + [theme.breakpoints.up('md')]: { + margin: theme.spacing(1.5), + }, + [theme.breakpoints.up('lg')]: { + margin: theme.spacing(2), + }, })); const StyledGridItem = styled(Grid)(({ theme }) => ({ - padding: theme.spacing(2), + // padding: theme.spacing(2), [theme.breakpoints.down('sm')]: { - padding: theme.spacing(1), + // padding: theme.spacing(1), + }, + [theme.breakpoints.up('md')]: { + // padding: theme.spacing(2), + }, + [theme.breakpoints.up('lg')]: { + // padding: theme.spacing(2), }, })); @@ -35,19 +46,15 @@ const DeckSearchCardGrid = ({ cards, userDecks }) => { [cards] ); - // if (isLoading) { - // return ; - // } - if (isLoading) { - return ; + return ; } return ( {Array.from(uniqueCards).map((card) => ( - - + + ))} diff --git a/src/components/grids/storeSearchResultsGrid/ProductGrid.js b/src/components/grids/storeSearchResultsGrid/ProductGrid.js index a88701f..2e2ab0d 100644 --- a/src/components/grids/storeSearchResultsGrid/ProductGrid.js +++ b/src/components/grids/storeSearchResultsGrid/ProductGrid.js @@ -1,26 +1,13 @@ import React, { useMemo } from 'react'; import { Grid } from '@mui/material'; -import { makeStyles } from '@mui/styles'; import GenericCard from '../../cards/GenericCard'; import { useCardStore } from '../../../context/CardContext/CardStore'; - -const useStyles = makeStyles((theme) => ({ - productGrid: { - maxWidth: '90%', - justifyContent: 'center', - margin: '0 auto', - }, - productGridItem: { - padding: theme.spacing(2), - height: 'auto', // Set a specific value if required - width: '100%', - }, -})); +import StoreItem from '../StoreItem'; +import { useCartStore } from '../../../context/CartContext/CartContext'; const ProductGrid = () => { const { searchData } = useCardStore(); - - const classes = useStyles(); + const { addOneToCart, removeOneFromCart } = useCartStore(); const isCardDataValid = searchData && Array.isArray(searchData); @@ -29,8 +16,28 @@ const ProductGrid = () => { [searchData] ); + const handleModifyItemInCart = async (cardId, operation) => { + try { + if (operation === 'add') { + addOneToCart(cardId); + } else if (operation === 'remove') { + removeOneFromCart(cardId); + } + } catch (error) { + console.error('Failed to adjust quantity in cart: ', error); + } + }; + return ( - + {isCardDataValid && limitedCardsToRender.map((card, index) => ( { md={4} lg={3} key={`${card.id}-${index}`} - className={classes.productGridItem} + sx={{ + padding: (theme) => theme.spacing(2), + height: 'auto', // Set a specific value if required + width: '100%', + }} > - + {/* */} + handleAddToCart(cardId)} + onQuantityChange={(cardId, operation) => + handleModifyItemInCart(cardId, operation) + } + /> ))} diff --git a/src/components/headings/PortfolioHeader.jsx b/src/components/headings/PortfolioHeader.jsx deleted file mode 100644 index e68f267..0000000 --- a/src/components/headings/PortfolioHeader.jsx +++ /dev/null @@ -1,24 +0,0 @@ -// components/PortfolioHeader.js - -import React from 'react'; -import { AlertTitle, Box, Typography } from '@mui/material'; -import Alert from '@mui/material/Alert'; -import HeaderTitle from '../reusable/HeaderTitle'; -import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; - -export const PortfolioHeader = ({ error }) => { - const { allCollections, selectedCollection } = useCollectionStore(); - return ( - - {/* */} - {error && ( - - Error - This is an error alert — {error} - - )} - - ); -}; - -export default PortfolioHeader; diff --git a/src/components/icons/DeckOfCardsIcon.jsx b/src/components/icons/DeckOfCardsIcon.jsx deleted file mode 100644 index f9fab42..0000000 --- a/src/components/icons/DeckOfCardsIcon.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import DeckIcon from '@mui/icons-material/Deck'; // Import the deck icon - -const DeckOfCardsIcon = () => { - return ; -}; - -export default DeckOfCardsIcon; diff --git a/src/components/modals/GenericCardModal.jsx b/src/components/modals/cardModal/GenericCardModal.jsx similarity index 79% rename from src/components/modals/GenericCardModal.jsx rename to src/components/modals/cardModal/GenericCardModal.jsx index 5aecff4..4161d78 100644 --- a/src/components/modals/GenericCardModal.jsx +++ b/src/components/modals/cardModal/GenericCardModal.jsx @@ -6,12 +6,12 @@ import { Snackbar, Alert, } from '@mui/material'; -import { useStyles } from './modalStyles'; -import useAppContext from '../../context/hooks/useAppContext'; -import useSnackbar from '../../context/hooks/useSnackBar'; -import CardMediaAndDetails from '../media/CardMediaAndDetails'; -import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons'; -import { ModalContext } from '../../context/ModalContext/ModalContext'; +import { useStyles } from '../modalStyles'; +import useAppContext from '../../../context/hooks/useAppContext'; +import useSnackbar from '../../../context/hooks/useSnackBar'; +import CardMediaAndDetails from '../../media/CardMediaAndDetails'; +import GenericActionButtons from '../../buttons/actionButtons/GenericActionButtons'; +import { ModalContext } from '../../../context/ModalContext/ModalContext'; const GenericCardModal = ({ open, card, context }) => { const classes = useStyles(); @@ -22,15 +22,6 @@ const GenericCardModal = ({ open, card, context }) => { const { openModalWithCard, closeModal, isModalOpen, modalContent } = useContext(ModalContext); - // if (!isContextAvailable) { - // handleSnackbar( - // `The component isn't wrapped with the ${context}Provider`, - // 'error' - // ); - // console.error(`The component isn't wrapped with the ${context}Provider`); - // return null; // Consider rendering an error boundary or user-friendly error message instead. - // } - const requiresDoubleButtons = context === 'Deck' || context === 'Collection'; useEffect(() => { diff --git a/src/components/modals/stripeModal/StripeCheckoutModal.js b/src/components/modals/stripeModal/StripeCheckoutModal.js index 12cba06..8189dc6 100644 --- a/src/components/modals/stripeModal/StripeCheckoutModal.js +++ b/src/components/modals/stripeModal/StripeCheckoutModal.js @@ -1,72 +1,69 @@ import React from 'react'; -import { Modal, Backdrop, Fade, Box, Typography } from '@mui/material'; +import { Modal, Fade, Box, Typography, Backdrop } from '@mui/material'; import { loadStripe } from '@stripe/stripe-js'; import { Elements } from '@stripe/react-stripe-js'; import StripeForm from '../../forms/customerCheckoutForm/StripeForm'; -// Make sure to call `loadStripe` outside of a component’s render to avoid recreating the `Stripe` object on every render. -// console.log('Stripe key: ', process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY); const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY); const StripeCheckoutModal = ({ open, onClose, onToken, purchases, total }) => { - // Rest of your component code... return ( - - - - Your Purchases - - {purchases.map((purchase, index) => ( - - {purchase.name} - - ${purchase.card_prices[0].tcgplayer_price} - - - ))} + theme.zIndex.drawer + 1 }} // Ensures the backdrop is above other elements but below the modal + > + - Total: - ${total} + + Your Purchases + + {purchases.map((purchase, index) => ( + + {purchase.name} + + ${purchase.card_prices[0].tcgplayer_price} + + + ))} + + Total: + ${total} + + + + - - - - - + + ); }; diff --git a/src/components/other/DeckEditPanel.js b/src/components/other/DeckEditPanel.js deleted file mode 100644 index dd79ce3..0000000 --- a/src/components/other/DeckEditPanel.js +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useState } from 'react'; -import { makeStyles } from '@mui/styles'; -import { Button, TextField, Paper, Typography } from '@mui/material'; -import { useMode } from '../../context/hooks/colormode'; - -const useStyles = makeStyles((theme) => ({ - root: { - padding: theme.spacing(4), - margin: theme.spacing(2), - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[3], // Utilize theme's predefined shadows - backgroundColor: theme.palette.background.paper, // Use theme's paper background color - }, - title: { - marginBottom: theme.spacing(3), - fontWeight: theme.typography.fontWeightBold, // Use theme's font weight for bold text - fontSize: theme.typography.h6.fontSize, // Align font size with h6 variant - color: theme.palette.text.primary, // Use theme's primary text color - }, - textField: { - marginBottom: theme.spacing(3), - '& .MuiOutlinedInput-root': { - '&:hover fieldset': { - borderColor: theme.palette.secondary.light, // Use secondary color for hover state - }, - '&.Mui-focused fieldset': { - borderColor: theme.palette.secondary.main, // Use secondary main color for focus - }, - }, - '& .MuiInputBase-input': { - fontSize: theme.typography.subtitle1.fontSize, // Align with subtitle1 font size - }, - }, - saveButton: { - boxShadow: theme.shadows[1], // Use a softer shadow from theme - textTransform: 'none', - fontSize: theme.typography.button.fontSize, // Align with button font size - padding: theme.spacing(1, 4), - lineHeight: theme.typography.button.lineHeight, // Align with button line height - backgroundColor: theme.palette.secondary.main, // Use secondary color for button - color: theme.palette.secondary.contrastText, // Contrast text for readability - '&:hover': { - backgroundColor: theme.palette.secondary.dark, // Darken on hover - boxShadow: theme.shadows[2], // Slightly elevate shadow on hover - }, - }, -})); - -const DeckEditPanel = ({ selectedDeck, onSave }) => { - const { theme } = useMode(); - const classes = useStyles(theme); - const [name, setName] = useState(selectedDeck?.name || ''); - const [description, setDescription] = useState( - selectedDeck?.description || '' - ); - - const handleSave = () => { - onSave({ - ...selectedDeck, - name, - description, - }); - }; - - return ( - - - Edit Deck - - setName(e.target.value)} - /> - setDescription(e.target.value)} - /> - - - ); -}; - -export default DeckEditPanel; diff --git a/src/components/other/InputComponents/CardNameInput.js b/src/components/other/InputComponents/CardNameInput.js index 3be6fd7..d8b8365 100644 --- a/src/components/other/InputComponents/CardNameInput.js +++ b/src/components/other/InputComponents/CardNameInput.js @@ -2,24 +2,23 @@ import React from 'react'; import { Input } from '@mui/material'; import { useCardStore } from '../../../context/CardContext/CardStore'; -const CardNameInput = ({ value, setValue }) => { +const CardNameInput = ({ value, setValue, handleChange }) => { const { handleRequest } = useCardStore(); - const handleKeyDown = (event) => { - if (event.key === 'Enter') { - handleRequest(); // Use handleRequest from context - } - }; - - const handleChange = (event) => { - setValue(event.target.value); - }; + // const handleChange = (event) => { + // setValue(event.target.value); + // }; return ( { + if (event.key === 'Enter') { + handleRequest(value); + } + }} + value={value} /> ); }; diff --git a/src/components/other/InputComponents/CollectionStatisticsSelector.jsx b/src/components/other/InputComponents/CollectionStatisticsSelector.jsx index 549730d..c55a545 100644 --- a/src/components/other/InputComponents/CollectionStatisticsSelector.jsx +++ b/src/components/other/InputComponents/CollectionStatisticsSelector.jsx @@ -8,7 +8,7 @@ import { CardContent, Card, } from '@mui/material'; -import { getFilteredData2 } from '../../chart/chartUtils'; +import { getFilteredData2 } from '../../reusable/chartUtils'; import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; function calculatePriceChanges(data) { diff --git a/src/components/other/InputComponents/CustomSelector.js b/src/components/other/InputComponents/CustomSelector.js index d3978e5..2724fa2 100644 --- a/src/components/other/InputComponents/CustomSelector.js +++ b/src/components/other/InputComponents/CustomSelector.js @@ -1,26 +1,39 @@ import React from 'react'; import { Grid, FormControl, InputLabel, Select, MenuItem } from '@mui/material'; +import { useMode } from '../../../context/hooks/colormode'; -const CustomSelector = ({ label, name, value, setValue, values }) => { - const handleChange = (event) => { - setValue( - event.target.value.toLowerCase() === 'unset' ? '' : event.target.value - ); - }; +const CustomSelector = ({ label, name, value, handleChange, values }) => { + // const handleChange = (event) => { + // setValue( + // event.target.value.toLowerCase() === 'unset' ? '' : event.target.value + // ); + // }; + const defaultValue = value || 'Unset'; + + if (!values) { + return
Values not provided
; + } + + if (!handleChange) { + return
handleChange not provided
; + } + + if (!defaultValue) { + return
Value not provided
; + } + const { theme } = useMode(); return ( - - - {label} - - - + + {label} + + ); }; diff --git a/src/components/other/InputComponents/DeckEditPanel.js b/src/components/other/InputComponents/DeckEditPanel.js new file mode 100644 index 0000000..cf5f991 --- /dev/null +++ b/src/components/other/InputComponents/DeckEditPanel.js @@ -0,0 +1,109 @@ +import React, { useState } from 'react'; +import { Button, TextField, Paper, Typography } from '@mui/material'; +import { useMode } from '../../../context/hooks/colormode'; + +const DeckEditPanel = ({ selectedDeck, onSave }) => { + const { theme } = useMode(); + const [name, setName] = useState(selectedDeck?.name || ''); + const [description, setDescription] = useState( + selectedDeck?.description || '' + ); + + const handleSave = () => { + onSave({ + ...selectedDeck, + name, + description, + }); + }; + + return ( + + + Edit Deck + + setName(e.target.value)} + /> + setDescription(e.target.value)} + /> + + + ); +}; + +export default DeckEditPanel; diff --git a/src/components/other/CartSummary.js b/src/components/other/dataDisplay/CartSummary.js similarity index 100% rename from src/components/other/CartSummary.js rename to src/components/other/dataDisplay/CartSummary.js diff --git a/src/components/other/CartTotal.jsx b/src/components/other/dataDisplay/CartTotal.jsx similarity index 100% rename from src/components/other/CartTotal.jsx rename to src/components/other/dataDisplay/CartTotal.jsx diff --git a/src/components/other/CollectionValueTracker.jsx b/src/components/other/dataDisplay/CollectionValueTracker.jsx similarity index 98% rename from src/components/other/CollectionValueTracker.jsx rename to src/components/other/dataDisplay/CollectionValueTracker.jsx index 73ddfcd..54a534f 100644 --- a/src/components/other/CollectionValueTracker.jsx +++ b/src/components/other/dataDisplay/CollectionValueTracker.jsx @@ -11,7 +11,7 @@ import { } from '@mui/material'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; -import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; import { styled } from '@mui/styles'; import { GridExpandMoreIcon } from '@mui/x-data-grid'; import ChangeHistoryIcon from '@mui/icons-material/ChangeHistory'; diff --git a/src/components/other/ProgressCircle.jsx b/src/components/other/dataDisplay/ProgressCircle.jsx similarity index 100% rename from src/components/other/ProgressCircle.jsx rename to src/components/other/dataDisplay/ProgressCircle.jsx diff --git a/src/components/other/StatBox.jsx b/src/components/other/dataDisplay/StatBox.jsx similarity index 100% rename from src/components/other/StatBox.jsx rename to src/components/other/dataDisplay/StatBox.jsx diff --git a/src/components/other/StatisticsArea.jsx b/src/components/other/dataDisplay/StatisticsArea.jsx similarity index 100% rename from src/components/other/StatisticsArea.jsx rename to src/components/other/dataDisplay/StatisticsArea.jsx diff --git a/src/components/other/TopCardsDisplay.jsx b/src/components/other/dataDisplay/TopCardsDisplay.jsx similarity index 80% rename from src/components/other/TopCardsDisplay.jsx rename to src/components/other/dataDisplay/TopCardsDisplay.jsx index 1642159..be8a8da 100644 --- a/src/components/other/TopCardsDisplay.jsx +++ b/src/components/other/dataDisplay/TopCardsDisplay.jsx @@ -13,36 +13,22 @@ import { useTheme, } from '@mui/material'; import { KeyboardArrowLeft, KeyboardArrowRight } from '@mui/icons-material'; -import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -import { useMode } from '../../context/hooks/colormode'; +import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; +import { useMode } from '../../../context/hooks/colormode'; import { makeStyles } from '@mui/styles'; -import { ModalContext } from '../../context/ModalContext/ModalContext'; -import GenericCard from '../cards/GenericCard'; - +import { ModalContext } from '../../../context/ModalContext/ModalContext'; +import GenericCard from '../../cards/GenericCard'; +import { + MainContainer2, + MainContainer, +} from '../../../pages/pageStyles/StyledComponents'; const useStyles = makeStyles((theme) => ({ - cardContainer: { - display: 'flex', - justifyContent: 'center', // Center the card horizontally - alignItems: 'center', // Center the card vertically - padding: theme.spacing(2), - width: '100%', // Use full width of the container - }, stepper: { // backgroundColor: theme.palette.success.main, - backgroundColor: theme.palette.primary.light, + background: theme.palette.primary.light, color: 'white', marginTop: 'auto', }, - paperContainer: { - padding: '10px', - backgroundColor: '#555', - width: '100%', // Use full width of the container - // height: '40vh', // Set the container height to 25vh - color: theme.palette.text.primary, - // padding: theme.spacing(2), - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[3], - }, })); const CarouselCard = ({ card }) => { @@ -58,7 +44,7 @@ const CarouselCard = ({ card }) => { openModalWithCard(card); }; return ( - + { {card.description} - + Price: ${card?.latestPrice?.num ?? 'N/A'} {/* Additional statistics */} - + ); }; @@ -139,9 +125,9 @@ const TopCardsDisplay = () => { }} > {top5Cards.map((card, index) => ( - + - + ))} { const { allDecks } = useDeckStore(); diff --git a/src/components/chart/ChartErrorBoundary.jsx b/src/components/reusable/ChartErrorBoundary.jsx similarity index 100% rename from src/components/chart/ChartErrorBoundary.jsx rename to src/components/reusable/ChartErrorBoundary.jsx diff --git a/src/components/reusable/HeaderTitle.jsx b/src/components/reusable/HeaderTitle.jsx index 9d0559a..b70c0da 100644 --- a/src/components/reusable/HeaderTitle.jsx +++ b/src/components/reusable/HeaderTitle.jsx @@ -4,12 +4,12 @@ import React from 'react'; const HeaderTitle = ({ title, size = 'extraSmall', location = 'left' }) => { const sizes = { - huge: '2.5rem', - large: '1.75rem', - medium: '1.5rem', - small: '1.25rem', - tiny: '1rem', - extraSmall: '0.75rem', + huge: { fontSize: '2.5rem', lineHeight: '3rem' }, + large: { fontSize: '1.75rem', lineHeight: '2.25rem' }, + medium: { fontSize: '1.5rem', lineHeight: '2rem' }, + small: { fontSize: '1.25rem', lineHeight: '1.75rem' }, + tiny: { fontSize: '1rem', lineHeight: '1.5rem' }, + extraSmall: { fontSize: '0.75rem', lineHeight: '1.25rem' }, }; const textAlign = { @@ -18,24 +18,33 @@ const HeaderTitle = ({ title, size = 'extraSmall', location = 'left' }) => { right: 'right', }; + const containerStyles = { + backgroundColor: (theme) => theme.palette.background.default, + padding: 2, + transition: 'background-color 0.3s ease', + '&:hover': { + backgroundColor: (theme) => theme.palette.background.paper, + }, + }; + + const typographyStyles = { + fontSize: sizes[size].fontSize, + lineHeight: sizes[size].lineHeight, + textAlign: textAlign[location], + fontWeight: 700, + letterSpacing: '0.05em', + margin: '0 auto', + color: (theme) => theme.palette.text.primary, + textShadow: '1px 1px 2px rgba(0, 0, 0, 0.2)', + transition: 'color 0.3s ease', + '&:hover': { + color: (theme) => theme.palette.secondary.main, + }, + }; + return ( - theme.palette.background.default, - padding: 2, - }} - > - theme.palette.text.primary, - }} - > + + {title} diff --git a/src/components/chart/chartUtils.jsx b/src/components/reusable/chartUtils.jsx similarity index 55% rename from src/components/chart/chartUtils.jsx rename to src/components/reusable/chartUtils.jsx index a9ee8b2..474420f 100644 --- a/src/components/chart/chartUtils.jsx +++ b/src/components/reusable/chartUtils.jsx @@ -1,14 +1,3 @@ -// export const getUniqueValidData = (allXYValues) => { -// if (!Array.isArray(allXYValues)) { -// console.error('Invalid input: allXYValues should be an array'); -// return []; -// } - -import { Box, Typography } from '@mui/material'; -import { useTheme } from '@mui/styles'; -// import { useCallback, useMemo, useState } from 'react'; -import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; - export const getUniqueValidData = (currentChartData) => { if (!Array.isArray(currentChartData)) { console.error('Invalid input: currentChartData should be an array'); @@ -50,62 +39,6 @@ export const getUniqueValidData = (currentChartData) => { })); }; -// export const groupAndAverageData = (data, threshold = 600000) => { -// // 10 minutes in milliseconds -// if (!data || data.length === 0) return { dates: [], times: [], values: [] }; - -// const clusters = []; -// let currentCluster = [data[0]]; - -// for (let i = 1; i < data.length; i++) { -// const prevTime = new Date(data[i - 1].x).getTime(); -// const currentTime = new Date(data[i].x).getTime(); -// if (currentTime - prevTime <= threshold) { -// currentCluster.push(data[i]); -// } else { -// console.log('Current Cluster:', currentCluster); // Log the current cluster -// clusters.push(currentCluster); -// currentCluster = [data[i]]; -// } -// } -// clusters.push(currentCluster); // Push the last cluster -// console.log('All Clusters:', clusters); // Log all clusters - -// // For each cluster, find the middlemost x-value and average y-values -// let dates = []; -// let times = []; -// let values = []; - -// clusters.forEach((cluster) => { -// const middleIndex = Math.floor(cluster.length / 2); -// const middleDatum = cluster[middleIndex]; -// const date = new Date(middleDatum.x); -// const avgY = -// cluster.reduce((sum, point) => sum + point.y, 0) / cluster.length; - -// const hours = date.getHours(); -// const minutes = date.getMinutes(); -// const AMPM = hours >= 12 ? 'PM' : 'AM'; -// const adjustedHours = hours % 12 || 12; // Converts "0" to "12" - -// dates.push( -// `${date.getFullYear()}-${String(date.getMonth() + 1).padStart( -// 2, -// '0' -// )}-${String(date.getDate()).padStart(2, '0')}` -// ); -// times.push( -// `${String(adjustedHours).padStart(2, '0')}:${String(minutes).padStart( -// 2, -// '0' -// )} ${AMPM}` -// ); -// values.push(avgY); -// }); - -// return { dates, times, values }; -// }; - export const groupAndAverageData = (data, threshold = 600000) => { if (!data || data.length === 0) return []; @@ -150,7 +83,6 @@ export const groupAndAverageData = (data, threshold = 600000) => { }; }); - // console.log('Processed clusters: ', clusters); return clusters; }; @@ -185,62 +117,6 @@ export const getTickValues = (timeRange) => { return mapping[timeRange] || 'every day'; // Default to 'every week' if no match }; -// export const useMovingAverage = (data, numberOfPricePoints) => { -// return useMemo(() => { -// if (!Array.isArray(data)) { -// return []; -// } -// console.log('[1][Data]----------> [', data + ']'); -// console.log( -// '[2][NUMBER OF POINTS]----------> [', -// numberOfPricePoints + ']' -// ); - -// return data.map((row, index, total) => { -// const start = Math.max(0, index - numberOfPricePoints); -// const end = index; -// const subset = total.slice(start, end + 1); -// const sum = subset.reduce((a, b) => a + b.y, 0); -// return { -// // x: String(row.x), -// x: row.x, -// y: sum / subset.length || 0, -// }; -// }); -// }, [data, numberOfPricePoints]); -// }; - -// export const convertDataForNivo = ({ dates, times, values }) => { -// if ( -// !dates || -// !times || -// !values || -// dates.length !== times.length || -// dates.length !== values.length -// ) { -// console.error('Invalid or mismatched data arrays provided'); -// return []; -// } - -// // Assuming that dates, times, and values arrays are of equal length -// const nivoData = dates.map((date, index) => { -// const dateTime = new Date(`${date} ${times[index]}`); -// // Nivo chart requires the date to be either a Date object or an ISO date string -// return { -// x: dateTime.toISOString(), -// y: values[index], -// }; -// }); - -// Wrapping the data for a single series, you can add more series similarly -// return [ -// { -// id: 'Averaged Data', -// data: nivoData, -// }, -// ]; -// }; - export const convertDataForNivo2 = (rawData2) => { if (!Array.isArray(rawData2) || rawData2?.length === 0) { console.error('Invalid or empty rawData provided', rawData2); diff --git a/src/components/icons/CartIcon.jsx b/src/components/reusable/icons/CartIcon.jsx similarity index 100% rename from src/components/icons/CartIcon.jsx rename to src/components/reusable/icons/CartIcon.jsx diff --git a/src/components/icons/CollectionIcon.jsx b/src/components/reusable/icons/CollectionIcon.jsx similarity index 100% rename from src/components/icons/CollectionIcon.jsx rename to src/components/reusable/icons/CollectionIcon.jsx diff --git a/src/components/reusable/icons/DeckOfCardsIcon.jsx b/src/components/reusable/icons/DeckOfCardsIcon.jsx new file mode 100644 index 0000000..32afe04 --- /dev/null +++ b/src/components/reusable/icons/DeckOfCardsIcon.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { IconButton } from '@mui/material'; +import deckIcon1 from '../../../assets/deckIcon2.png'; + +const DeckOfCardsIcon = () => { + return ( + + deck icon + + ); +}; + +export default DeckOfCardsIcon; diff --git a/src/components/icons/TestingIcon.jsx b/src/components/reusable/icons/TestingIcon.jsx similarity index 100% rename from src/components/icons/TestingIcon.jsx rename to src/components/reusable/icons/TestingIcon.jsx diff --git a/src/components/reusable/indicators/LoadingIndicator2.js b/src/components/reusable/indicators/LoadingIndicator2.js index b1e75b1..150584d 100644 --- a/src/components/reusable/indicators/LoadingIndicator2.js +++ b/src/components/reusable/indicators/LoadingIndicator2.js @@ -1,13 +1,13 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import LoadingCardAnimation from '../../../assets/animations/LoadingCardAnimation'; +// import * as React from 'react'; +// import Box from '@mui/material/Box'; +// import LoadingCardAnimation from '../../../assets/animations/LoadingCardAnimation'; -const LoadingIndicator2 = () => { - return ( - - - - ); -}; +// const LoadingIndicator2 = () => { +// return ( +// +// +// +// ); +// }; -export default LoadingIndicator2; +// export default LoadingIndicator2; diff --git a/src/components/search/DeckSearch.js b/src/components/search/DeckSearch.js index 6f58b6b..b024742 100644 --- a/src/components/search/DeckSearch.js +++ b/src/components/search/DeckSearch.js @@ -28,11 +28,21 @@ const DeckSearch = ({ userDecks }) => { const itemsPerPage = 36; const start = (page - 1) * itemsPerPage; const end = start + itemsPerPage; - const currentDeckSearchData = deckSearchData.slice(start, end); + const currentDeckSearchData = deckSearchData?.slice(start, end); return ( - + ( - - - setSearchParams((prevState) => ({ - ...prevState, - name: newValue, - })) - } - handleRequest={() => handleRequest(searchParams)} - /> - - -); - -export default PortfolioCardSearch; diff --git a/src/components/search/SearchBar.js b/src/components/search/SearchBar.js index 2de0086..072669a 100644 --- a/src/components/search/SearchBar.js +++ b/src/components/search/SearchBar.js @@ -1,81 +1,109 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Grid, Box, Typography, Container, Paper } from '@mui/material'; import { useCardStore } from '../../context/CardContext/CardStore'; import SearchButton from '../buttons/other/SearchButton'; import CardNameInput from '../other/InputComponents/CardNameInput'; import CustomSelector from '../other/InputComponents/CustomSelector'; -import { useCombinedContext } from '../../context/CombinedProvider'; -import { makeStyles } from '@mui/styles'; import search from './search.json'; -const useStyles = makeStyles((theme) => ({ - searchContainer: { - padding: theme.spacing(3), - borderRadius: theme.shape.borderRadius, - backgroundColor: theme.palette.background.paper, - boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.12)', - margin: theme.spacing(2, 'auto'), - width: '100%', - maxWidth: 'md', - }, - title: { - marginBottom: theme.spacing(3), - fontWeight: 600, - color: theme.palette.primary.main, - }, - searchGrid: { - gap: theme.spacing(2), - }, - searchButtonContainer: { - display: 'flex', - width: '100%', - justifyContent: 'center', - marginTop: theme.spacing(2), - }, -})); +import { useMode } from '../../context/hooks/colormode'; const SearchBar = () => { - const { searchParams, setSearchParams } = useCombinedContext(); - const { handleRequest } = useCardStore(); - const classes = useStyles(); - const { initialState, filters } = search; // Destructure the data from JSON + const { theme } = useMode(); + const { initialState, filters } = search; + const [searchParams, setSearchParams] = useState({ + name: '', + type: '', + attribute: '', + race: '', + }); + const { handleRequest } = useCardStore(); + // const handleChange = (name, newValue) => { + // setSearchParams((prev) => ({ ...prev, [name]: newValue })); + // }; + const handleChange = (name, newValue) => { + setSearchParams((prev) => ({ ...prev, [name]: newValue })); + }; + // Correct handleSubmit to use handleRequest with searchParams + // const handleSubmit = (event) => { + // event.preventDefault(); + // handleRequest(searchParams); + // }; + const handleSubmit = () => { + handleRequest(searchParams); + }; return ( - + - + Search Cards - + - setSearchParams((prevState) => ({ - ...prevState, - name: newValue, - })) - } - handleRequest={() => handleRequest(searchParams)} + value={searchParams.name} + handleChange={(event) => handleChange('name', event.target.value)} /> {filters?.map((filter) => ( - + - setSearchParams((prevState) => ({ - ...prevState, - [filter?.name]: newValue, - })) + value={searchParams?.[filter?.label]} + handleChange={(event) => + handleChange(filter.label, event.target.value) } + // handleChange={(event) => + // handleChange(filter.name, event.target.value) + // } + // setValue={(newValue) => + // handleChange({ target: { value: newValue } }) + // } + // handleChange={handleChange} + // setValue={(newValue) => + // setSearchParams((prevState) => ({ + // ...prevState, + // [filter?.name]: newValue, + // })) + // } + setSearchParams={setSearchParams} values={filter?.values} /> ))} - - + + diff --git a/src/components/other/AddressForm.jsx b/src/containers/cartPageContainers/AddressForm.jsx similarity index 100% rename from src/components/other/AddressForm.jsx rename to src/containers/cartPageContainers/AddressForm.jsx diff --git a/src/containers/cartPageContainers/CartContent.js b/src/containers/cartPageContainers/CartContent.js index 533bf48..c95db93 100644 --- a/src/containers/cartPageContainers/CartContent.js +++ b/src/containers/cartPageContainers/CartContent.js @@ -3,7 +3,7 @@ import { Typography } from '@mui/material'; import { useCartStore } from '../../context/CartContext/CartContext'; import CartContainer from './CartContainer'; import CartItem from '../../components/grids/CartItem'; -import CartTotal from '../../components/other/CartTotal'; +import CartTotal from '../../components/other/dataDisplay/CartTotal'; const CartContent = () => { const { cartData, getTotalCost } = useCartStore(); diff --git a/src/components/other/Checkout.jsx b/src/containers/cartPageContainers/Checkout.jsx similarity index 100% rename from src/components/other/Checkout.jsx rename to src/containers/cartPageContainers/Checkout.jsx diff --git a/src/components/other/PaymentForm.jsx b/src/containers/cartPageContainers/PaymentForm.jsx similarity index 100% rename from src/components/other/PaymentForm.jsx rename to src/containers/cartPageContainers/PaymentForm.jsx diff --git a/src/components/other/Review.jsx b/src/containers/cartPageContainers/Review.jsx similarity index 100% rename from src/components/other/Review.jsx rename to src/containers/cartPageContainers/Review.jsx diff --git a/src/containers/collectionPageContainers/PortfolioChartContainer.jsx b/src/containers/collectionPageContainers/PortfolioChartContainer.jsx index f5f3fe1..f3cf490 100644 --- a/src/containers/collectionPageContainers/PortfolioChartContainer.jsx +++ b/src/containers/collectionPageContainers/PortfolioChartContainer.jsx @@ -6,7 +6,7 @@ import CollectionStatisticsSelector, { calculateStatistics, } from '../../components/other/InputComponents/CollectionStatisticsSelector'; import UpdateStatusBox2 from '../../components/other/InputComponents/UpdateStatusBox2'; -import TopCardsDisplay from '../../components/other/TopCardsDisplay'; +import TopCardsDisplay from '../../components/other/dataDisplay/TopCardsDisplay'; import { useChartContext } from '../../context/ChartContext/ChartContext'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; import { useCombinedContext } from '../../context/CombinedProvider'; diff --git a/src/containers/collectionPageContainers/PortfolioContent.jsx b/src/containers/collectionPageContainers/PortfolioContent.jsx index 692a4aa..2a7d5d2 100644 --- a/src/containers/collectionPageContainers/PortfolioContent.jsx +++ b/src/containers/collectionPageContainers/PortfolioContent.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { Box, Container, Grid, Paper, useTheme } from '@mui/material'; -import PortfolioHeader from '../../components/headings/PortfolioHeader'; import PortfolioListContainer from './PortfolioListContainer'; import PortfolioChartContainer from './PortfolioChartContainer'; import HeaderTitle from '../../components/reusable/HeaderTitle'; @@ -12,78 +11,68 @@ const PortfolioContent = ({ error, selectedCards, removeCard }) => { const { selectedCollection } = useCollectionStore(); return ( - - + + + - - - - - - - - - - + + + + - - +
+
); }; diff --git a/src/context/CardImagesContext/CardImagesContext.jsx b/src/context/CardImagesContext/CardImagesContext.jsx index d6cad47..2f67d6f 100644 --- a/src/context/CardImagesContext/CardImagesContext.jsx +++ b/src/context/CardImagesContext/CardImagesContext.jsx @@ -36,7 +36,8 @@ export const CardImagesProvider = ({ children }) => { const [randomCardImage, setRandomCardImage] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const BASE_API_URL = 'http://localhost:3001/api/card-image'; + // const BASE_API_URL = 'http://localhost:3001/api/card-image'; + const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/card-image`; const downloadCardImages = async () => { setIsLoading(true); diff --git a/src/context/CartContext/CartContext.js b/src/context/CartContext/CartContext.js index 7653cc8..23b3748 100644 --- a/src/context/CartContext/CartContext.js +++ b/src/context/CartContext/CartContext.js @@ -34,8 +34,8 @@ export const CartProvider = ({ children }) => { quantity: 0, // Total quantity of items totalPrice: 0, // Total price of items }); - const [cookies, setCookie] = useCookies(['userCookie', 'cart']); - const userId = cookies.userCookie?.id; + const [cookies, setCookie] = useCookies(['user', 'cart']); + const userId = cookies?.user?.id; const { getCardData } = useCardStore(); const isMounted = useRef(true); @@ -45,7 +45,6 @@ export const CartProvider = ({ children }) => { }; }, []); - //Utility functions const fetchFromServer = async (url, options = {}) => { const response = await fetch( `${process.env.REACT_APP_SERVER}${url}`, @@ -56,36 +55,49 @@ export const CartProvider = ({ children }) => { } return await response.json(); }; - const updateCart = async (cartId, updatedCart) => { - const formattedCartData = updatedCart.map((item) => ({ - ...item, - quantity: item.quantity, - })); + if (!cartId) return; + const formattedCartData = { + cartItems: updatedCart.map((item) => ({ + ...item, + id: item.id, // Make sure 'id' is a number + quantity: item.quantity, + })), + userId: userId, + }; const data = await fetchFromServer(`/api/carts/${cartId}/update`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formattedCartData), }); + // if (data && data.cart) { + // setCartDataAndCookie(data.cart); + // } setCartDataAndCookie(data); + return data; }; - const createUserCart = useCallback( async (userId) => { - const newCartData = await fetchFromServer( - `/api/carts/${userId}/newCart`, - { method: 'POST' } - ); - const newCartItems = Array.isArray(newCartData.cart) - ? newCartData.cart - : []; - setCookie('cart', newCartItems, { - // Changed this line - path: '/', - secure: true, - sameSite: 'none', - }); - return newCartItems; + try { + const newCartData = await fetchFromServer( + '/api/carts/createEmptyCart', + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userId }), + } + ); + // setCookie('cart', newCartData, { + // // Changed this line + // path: '/', + // secure: true, + // sameSite: 'none', + // }); + setCartDataAndCookie(newCartData); + return newCartData; + } catch (error) { + console.error('Error creating cart:', error); + } }, [setCookie] ); @@ -94,46 +106,24 @@ export const CartProvider = ({ children }) => { async (userId) => { try { const data = await fetchFromServer(`/api/carts/userCart/${userId}`); - setCookie('cart', data.cart || [], { - path: '/', - secure: true, - sameSite: 'none', - }); + // setCookie('cart', data.cart || [], { + // path: '/', + // secure: true, + // sameSite: 'none', + // }); + setCartDataAndCookie(data); return data; } catch (error) { if (error.message === 'HTTP error! status: 404') { - return createUserCart(userId); + await createUserCart(userId); } else { - console.error(error.message); + console.error('Error fetching user cart:', error); } } }, - [createUserCart, setCookie] + [createUserCart] ); - useEffect(() => { - if (userId && typeof userId === 'string') { - // console.log('Fetching user cart'); - fetchUserCart(userId) - .then((data) => { - if (data && data.cart) { - setCartDataAndCookie(data); - } - }) - .catch((error) => console.log('Error fetching user cart:', error)); - } - }, [userId, fetchUserCart]); - - const setCartDataAndCookie = (newCartData) => { - setCartData(newCartData); - setCookie('cart', newCartData.cart, { - // Changed this line - path: '/', - secure: true, - sameSite: 'none', - }); - }; - const getCardQuantity = (cardId) => { let totalItems = 0; let quantityOfSameId = 0; @@ -148,7 +138,8 @@ export const CartProvider = ({ children }) => { const addOneToCart = async (cardInfo) => { if (!cartData._id) return; - const { quantityOfSameId } = getCardQuantity(cardInfo.id); + console.log('Adding one to cart', cardInfo); + const { quantityOfSameId, totalItems } = getCardQuantity(cardInfo.id); if (quantityOfSameId >= 3) return; let updatedCart = cartData.cart.map((item) => item.id === cardInfo.id ? { ...item, quantity: item.quantity + 1 } : item @@ -157,6 +148,7 @@ export const CartProvider = ({ children }) => { updatedCart = [...updatedCart, { ...cardInfo, quantity: 1 }]; } const updatedCartData = await updateCart(cartData._id, updatedCart); + console.log('UPDATED CART DATA:', updatedCartData); if (updatedCartData) setCartData(updatedCartData); }; @@ -180,6 +172,17 @@ export const CartProvider = ({ children }) => { if (updatedCartData) setCartData(updatedCartData); }; + const setCartDataAndCookie = (newCartData) => { + if (newCartData && Array.isArray(newCartData.cart)) { + setCartData(newCartData); + setCookie('cart', newCartData.cart, { + path: '/', + secure: true, + sameSite: 'none', + }); + } + }; + useEffect(() => { if (userId && typeof userId === 'string' && isMounted.current) { fetchUserCart(userId) @@ -190,7 +193,22 @@ export const CartProvider = ({ children }) => { }) .catch((error) => console.log('Error fetching user cart:', error)); } + return () => { + isMounted.current = false; + }; }, [userId, fetchUserCart]); + // useEffect(() => { + // if (userId && typeof userId === 'string') { + // // console.log('Fetching user cart'); + // fetchUserCart(userId) + // .then((data) => { + // if (data && data.cart) { + // setCartDataAndCookie(data); + // } + // }) + // .catch((error) => console.log('Error fetching user cart:', error)); + // } + // }, [userId, fetchUserCart]); const getTotalCost = useMemo( () => @@ -210,6 +228,7 @@ export const CartProvider = ({ children }) => { 0 ); + console.log('TOTAL QUANTITY:', totalQuantity); if ( cartData.quantity !== totalQuantity || cartData.totalPrice !== getTotalCost @@ -242,8 +261,8 @@ export const CartProvider = ({ children }) => { (acc, card) => acc + card.card_prices[0].tcgplayer_price * card.quantity, 0 ), - addOneToCart, - removeOneFromCart, + addOneToCart: addOneToCart, + removeOneFromCart: removeOneFromCart, deleteFromCart, getTotalCost, fetchUserCart, @@ -254,7 +273,7 @@ export const CartProvider = ({ children }) => { console.log('CART CONTEXT: ', { cartData, // getTotalCost, - // // getCardQuantity, + // getCardQuantity, // fetchUserCart, // addOneToCart, // removeOneFromCart, diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index f964a05..d4bf44b 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -54,42 +54,6 @@ function filterUniqueDataPoints(dataArray) { return Array.from(uniqueRecords.values()); } -function filterCollectionData(collection) { - if (!collection) return null; - - if (!collection?.chartData) { - console.warn('Collection data is missing chart data.'); - return collection; - } - collection.chartData.allXYValues = filterUniqueDataPoints( - collection?.chartData?.allXYValues - ); - collection.currentChartDataSets = filterUniqueDataPoints( - collection?.currentChartDataSets - ); - collection.currentChartDataSets2 = filterUniqueDataPoints( - collection?.currentChartDataSets2 - ); - - collection?.chartData?.datasets.forEach((dataset) => { - dataset?.data?.forEach((dataEntry) => { - dataEntry.xys = filterUniqueDataPoints(dataEntry?.xys); - }); - }); - - // Apply the filter function to 'xys' in 'chartData' - collection.chartData.xys = filterUniqueDataPoints(collection.chartData.xys); - - // If the 'cards' array contains structures with 'label', 'x', and 'y' properties - collection.cards.forEach((card) => { - if (card.chart_datasets) { - card.chart_datasets = filterUniqueDataPoints(card.chart_datasets); - } - }); - - return collection; -} - export const CollectionProvider = ({ children }) => { const [cookies] = useCookies(['user']); const [selectedCollection, setSelectedCollection] = useState( @@ -104,11 +68,10 @@ export const CollectionProvider = ({ children }) => { setUpdatedPricesFromCombinedContext, ] = useState([]); const [xyData, setXyData] = useState([]); - const [currentChartDataSets, setCurrentChartDataSets] = useState([]); const [currentChartDataSets2, setCurrentChartDataSets2] = useState([]); const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = useState(false); - const userId = cookies.user?.id; + const userId = cookies?.user?.id; const lastFetchedTime = useRef(null); const fetchAndSetCollections = useCallback(async () => { @@ -653,7 +616,6 @@ export const CollectionProvider = ({ children }) => { // Optionally handle the response here, e.g., update the state with the response data if necessary } catch (error) { console.error('Error updating collection details:', error); - // Handle error, e.g., revert state changes or display error message to the user } } else { console.error('Selected collection or collection ID is missing.'); @@ -766,22 +728,10 @@ export const CollectionProvider = ({ children }) => { }); }, [allCollections, selectedCollection, totalCost, totalPrice, xyData]); - // useEffect(() => { - // console.log('UPDATED COLLECTION DATA POST SERVER:', collectionData); - // }, [collectionData]); - useEffect(() => { if (userId) fetchAndSetCollections(); }, [userId]); - // useEffect(() => { - // if (selectedCollection?.chartData) - // // setCurrentChartDataSets( - // // getCurrentChartDataSets(selectedCollection?.chartData) - // // ); - // console.log('CURRENT CHART DATASETS:', currentChartDataSets); - // }, [selectedCollection]); - useEffect(() => { if (selectedCollection?.cards) { setTotalPrice(calculateCollectionValue(selectedCollection)); diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx index 93ee1af..75ff649 100644 --- a/src/context/CollectionContext/collectionUtility.jsx +++ b/src/context/CollectionContext/collectionUtility.jsx @@ -654,6 +654,58 @@ const constructPayloadWithDifferences = ( // return currentChartDataSets; // } +const getAllCardPrices = (cards) => + cards.flatMap((card) => Array(card.quantity).fill(card.price)); + +function 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()); +} + +function filterCollectionData(collection) { + if (!collection) return null; + + if (!collection?.chartData) { + console.warn('Collection data is missing chart data.'); + return collection; + } + collection.chartData.allXYValues = filterUniqueDataPoints( + collection?.chartData?.allXYValues + ); + collection.currentChartDataSets = filterUniqueDataPoints( + collection?.currentChartDataSets + ); + collection.currentChartDataSets2 = filterUniqueDataPoints( + collection?.currentChartDataSets2 + ); + + collection?.chartData?.datasets.forEach((dataset) => { + dataset?.data?.forEach((dataEntry) => { + dataEntry.xys = filterUniqueDataPoints(dataEntry?.xys); + }); + }); + + // Apply the filter function to 'xys' in 'chartData' + collection.chartData.xys = filterUniqueDataPoints(collection.chartData.xys); + + // If the 'cards' array contains structures with 'label', 'x', and 'y' properties + collection.cards.forEach((card) => { + if (card.chart_datasets) { + card.chart_datasets = filterUniqueDataPoints(card.chart_datasets); + } + }); + + return collection; +} + const calculateCollectionValue = (cards) => { if ( !cards?.cards && diff --git a/src/context/DeckContext/DeckContext.js b/src/context/DeckContext/DeckContext.js index 13822bf..deac22f 100644 --- a/src/context/DeckContext/DeckContext.js +++ b/src/context/DeckContext/DeckContext.js @@ -41,11 +41,11 @@ const removeDuplicateDecks = (decks) => { }; export const DeckProvider = ({ children }) => { const { getCardData } = useCardStore(); - const [cookies, setCookie] = useCookies(['user']); + const [cookies] = useCookies(['user']); const [deckData, setDeckData] = useState({}); const [allDecks, setAllDecks] = useState([]); const [selectedDeck, setSelectedDeck] = useState({}); - const userId = cookies.user?.id; + const userId = cookies?.user?.id; const calculateAndUpdateTotalPrice = (deck) => { let totalPrice = 0; @@ -58,6 +58,10 @@ export const DeckProvider = ({ children }) => { }; const fetchDecksForUser = useCallback(async () => { + if (!userId) { + console.error('No user ID found.'); + return null; + } try { const url = `${apiBase}/${userId}/decks`; return await fetchWrapper(url, 'GET'); @@ -71,8 +75,9 @@ export const DeckProvider = ({ children }) => { try { const userDecks = await fetchDecksForUser(); - if (userDecks && userDecks.length > 0) { - const uniqueDecks = removeDuplicateDecks(userDecks); + if (userDecks.data && userDecks.data.length > 0) { + const uniqueDecks = removeDuplicateDecks(userDecks.data); + console.log('UNIQUE DECKS:', uniqueDecks); setAllDecks((prevDecks) => removeDuplicateDecks([...prevDecks, ...uniqueDecks]) ); @@ -132,6 +137,11 @@ export const DeckProvider = ({ children }) => { : [...newAllDecks, newDeckData]; }); + if (!userId) { + console.error('No user ID found.'); + return; + } + try { const url = `${apiBase}/${userId}/decks/${selectedDeck._id}`; // Removed deckId from the URL const bodyData = { @@ -147,19 +157,20 @@ export const DeckProvider = ({ children }) => { const createUserDeck = async (userId, newDeckInfo) => { try { const url = `${apiBase}/${userId}/decks`; - const cards = newDeckInfo.cards - ? [formatCardData(newDeckInfo.cards)] + const cards = Array.isArray(newDeckInfo.cards) + ? newDeckInfo.cards.map(formatCardData) : []; const data = await fetchWrapper(url, 'POST', { - // ...newDeckInfo, - cards: cards, + cards, userId, totalPrice: 0, description: newDeckInfo.description || '', name: newDeckInfo.name || '', }); console.log('NEW DECK DATA:', data); - setDeckData(data); + setDeckData(data.data); + setSelectedDeck(data.data); + setAllDecks((prevDecks) => [...prevDecks, data.data]); } catch (error) { console.error(`Failed to create a new deck: ${error.message}`); } @@ -181,7 +192,9 @@ export const DeckProvider = ({ children }) => { } // Create a copy of the current state - const currentCards = [...(selectedDeck?.cards || [])]; + const currentCards = Array.isArray(selectedDeck?.cards) + ? [...selectedDeck.cards] + : []; let currentTotalPrice = selectedDeck.totalPrice || 0; // Find the card index in the current state @@ -213,17 +226,18 @@ export const DeckProvider = ({ children }) => { }); try { - const url = `${apiBase}/users/${userId}/decks/${selectedDeck._id}`; + const url = `${apiBase}/${userId}/decks/${selectedDeck._id}`; const updatedDeck = await fetchWrapper(url, 'PUT', { cards: currentCards, totalPrice: currentTotalPrice, }); setSelectedDeck({ - ...updatedDeck, + ...updatedDeck.data, + cards: currentCards, totalPrice: currentTotalPrice, }); - updateAndSyncDeck({ ...updatedDeck, totalPrice: currentTotalPrice }); + updateAndSyncDeck({ ...updatedDeck.data, totalPrice: currentTotalPrice }); } catch (error) { console.error(`Failed to update the deck: ${error.message}`); } @@ -242,6 +256,7 @@ export const DeckProvider = ({ children }) => { deckData, allDecks, selectedDeck, + userDecks: allDecks, totalQuantity: getQuantity, setSelectedDeck, addOneToDeck: (card) => addOrRemoveCard(card, true, false), @@ -256,11 +271,11 @@ export const DeckProvider = ({ children }) => { useEffect(() => { console.log('DECKCONTEXT:', contextValue); - const userId = cookies.userCookie?.id; + const userId = cookies?.user?.id; if (userId) { fetchAndSetDecks(); } - }, [cookies.userCookie?.id, fetchAndSetDecks]); + }, [cookies?.user?.id, fetchAndSetDecks]); return ( {children} diff --git a/src/index.js b/src/index.js index e6a9146..74c02c2 100644 --- a/src/index.js +++ b/src/index.js @@ -22,6 +22,7 @@ import { useMode } from './context/hooks/colormode'; import { PopoverProvider } from './context/PopoverContext/PopoverContext'; import { CronJobProvider } from './context/CronJobContext/CronJobContext'; import { CardImagesProvider } from './context/CardImagesContext/CardImagesContext'; +import { BrowserRouter as Router } from 'react-router-dom'; const root = document.getElementById('root'); @@ -31,19 +32,20 @@ function Main() { return ( - - - - - - - - - - - - - + + + + + + + + + + + + + + {/* */} @@ -54,19 +56,20 @@ function Main() { - - - - - - - - - - - - - + {/* */} + + + + + + + + + + + + + ); diff --git a/src/pages/CartPage.js b/src/pages/CartPage.js index 85a85ec..a7f113c 100644 --- a/src/pages/CartPage.js +++ b/src/pages/CartPage.js @@ -17,7 +17,6 @@ const CartPage = () => { const [cookies] = useCookies(['user']); const user = cookies.user; const userId = user?.id; - // useUpdateAppContext(); // This will set the context to 'Deck' when this page is rendered const { cartData, diff --git a/src/pages/CollectionPage.js b/src/pages/CollectionPage.js index cc4aac6..2f611df 100644 --- a/src/pages/CollectionPage.js +++ b/src/pages/CollectionPage.js @@ -8,17 +8,14 @@ import CardPortfolio from '../components/collection/CardPortfolio'; import Subheader from '../components/reusable/Subheader'; import { useCollectionStore } from '../context/CollectionContext/CollectionContext'; import { ModalContext } from '../context/ModalContext/ModalContext'; -import GenericCardModal from '../components/modals/GenericCardModal'; +import GenericCardModal from '../components/modals/cardModal/GenericCardModal'; import { CollectionBanner } from './pageStyles/StyledComponents'; -import useUpdateAppContext from '../context/hooks/useUpdateContext'; -// Hero section with customizable props const HeroCenter = ({ decorative, title, subtitle }) => ( { - // Destructuring the first element directly from the useCookies hook for cleaner code const [{ user }] = useCookies(['user']); const { allCollections, selectedCollection, loading, error } = useCollectionStore(); const { openModalWithCard, closeModal, isModalOpen, modalContent } = useContext(ModalContext); const userId = user?.id; - // useUpdateAppContext(); // This will set the context to 'Deck' when this page is rendered - // Handling loading and error states upfront for better user experience if (loading) return ; if (error) return ; @@ -94,9 +88,7 @@ const CollectionPage = () => { diff --git a/src/pages/DeckBuilderPage.js b/src/pages/DeckBuilderPage.js index 4b9cdb1..f8f1e46 100644 --- a/src/pages/DeckBuilderPage.js +++ b/src/pages/DeckBuilderPage.js @@ -1,12 +1,12 @@ import React, { useEffect, useState, useContext } from 'react'; import { useCookies } from 'react-cookie'; -import { DeckContext } from '../context/DeckContext/DeckContext'; +import { DeckContext, useDeckStore } from '../context/DeckContext/DeckContext'; import LoadingIndicator from '../components/reusable/indicators/LoadingIndicator'; import ErrorIndicator from '../components/reusable/indicators/ErrorIndicator'; import DeckBuilderContainer from '../containers/deckBuilderPageContainers/DeckBuilderContainer'; import { Box, Grid, Typography } from '@mui/material'; import { ModalContext } from '../context/ModalContext/ModalContext'; -import GenericCardModal from '../components/modals/GenericCardModal'; +import GenericCardModal from '../components/modals/cardModal/GenericCardModal'; import HeaderTitle from '../components/reusable/HeaderTitle'; import { useMode } from '../context/hooks/colormode'; import { DeckBuilderBanner } from './pageStyles/StyledComponents'; @@ -54,8 +54,7 @@ const DeckBuilderPage = () => { const [userDecks, setUserDecks] = useState([]); const { user } = useCookies(['user'])[0]; const { theme } = useMode(); - const { fetchAllDecksForUser, allDecks, loading, error } = - useContext(DeckContext); + const { fetchAllDecksForUser, allDecks, loading, error } = useDeckStore(); const { openModalWithCard, closeModal, isModalOpen, modalContent } = useContext(ModalContext); const userId = user?.id; @@ -70,7 +69,8 @@ const DeckBuilderPage = () => { useEffect(() => { if (allDecks && userId) { - const filteredDecks = allDecks.filter((deck) => deck.userId === userId); + const filteredDecks = allDecks?.filter((deck) => deck?.userId === userId); + console.log('filteredDecks', filteredDecks); setUserDecks(filteredDecks); } }, [allDecks, userId]); diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js index 6fcc033..d690580 100644 --- a/src/pages/HomePage.js +++ b/src/pages/HomePage.js @@ -17,15 +17,15 @@ import { } from '@mui/material'; import { useMode } from '../context/hooks/colormode'; import useStyles from './pageStyles/styles'; -import DeckOfCardsIcon from '../components/icons/DeckOfCardsIcon'; +import DeckOfCardsIcon from '../components/reusable/icons/DeckOfCardsIcon'; import { ModalContext } from '../context/ModalContext/ModalContext'; -import GenericCardModal from '../components/modals/GenericCardModal'; +import GenericCardModal from '../components/modals/cardModal/GenericCardModal'; import { TertiaryContainer, SecondaryContainer, } from './pageStyles/StyledComponents'; -import LoadingIndicator2 from '../components/reusable/indicators/LoadingIndicator2'; -import LoadingCardAnimation from '../assets/animations/LoadingCardAnimation'; +// import LoadingIndicator2 from '../components/reusable/indicators/LoadingIndicator2'; +// import LoadingCardAnimation from '../assets/animations/LoadingCardAnimation'; import pages from './pages.json'; const CarouselImage = ({ image, caption }) => { return ( @@ -86,7 +86,7 @@ const HomePage = () => { color="text.primary" gutterBottom > - Album layout + Enhanced Cardstore
{ color="text.secondary" paragraph > - Something short and leading about the collection below—its contents, - the creator, etc. Make it short and sweet, but not too short so - folks don't simply skip over it entirely. + The purpose of this site is to provide an enhanced user experience + for competitive and novice card collectors alike. We provide an + advanced collection tracker and deck builder which allows users to + build decks and track the prices of the cards they need so they can + make the best financial decisions when buying and selling cards. - - - + > {/*
*/}
@@ -175,7 +164,9 @@ const HomePage = () => { title={tier.title} subheader={tier.subheader} titleTypographyProps={{ align: 'center' }} - action={tier.title === 'Pro' ? : null} + action={ + tier.title === 'Deck Builder' ? : null + } subheaderTypographyProps={{ align: 'center', }} @@ -195,18 +186,7 @@ const HomePage = () => { alignItems: 'baseline', mb: 2, }} - > - - ${tier.price} - - - /mo - -
+ >
    {tier.description.map((line) => ( { return ( @@ -18,19 +20,81 @@ const SearchContainer = () => { }; const CardContainer = () => { + const { theme } = useMode(); + return ( - + ); }; +const HeroCenter3 = ({ decorative, title, subtitle, theme }) => ( + + + {decorative} + + + {title} + + + {subtitle} + + +); + +HeroCenter3.defaultProps = { + title: 'Welcome to Store', + subtitle: 'Search for cards and add them to your cart.', +}; + const StorePage = () => { - const [cookies] = useCookies(['userCookie']); + const [cookies] = useCookies(['user']); + + const { theme } = useMode(); const { fetchUserCart, loading, error } = useCartStore(); const { searchData } = useCardStore(); - const userId = cookies.userCookie?.id; + const userId = cookies?.user?.id; useEffect(() => { if (userId) { @@ -50,13 +114,28 @@ const StorePage = () => { } return ( - - Store - + + + - + - + ); }; diff --git a/src/pages/pages.json b/src/pages/pages.json index eedc150..ed27827 100644 --- a/src/pages/pages.json +++ b/src/pages/pages.json @@ -10,39 +10,36 @@ "tiers": [ { "title": "Deck Builder", - "price": "0", "description": [ - "10 users included", - "2 GB of storage", - "Help center access", - "Email support" + "Build and test decks digitally", + "Access to a wide library of cards", + "Deck performance analytics", + "Community deck sharing and rating" ], - "buttonText": "Sign up for free", + "buttonText": "Get Started", "buttonVariant": "outlined" }, { "title": "Store", "subheader": "Most popular", - "price": "15", "description": [ - "20 users included", - "10 GB of storage", - "Help center access", - "Priority email support" + "Sell and buy cards with ease", + "Secure transaction platform", + "Real-time market price tracking", + "Exclusive member discounts" ], - "buttonText": "Get started", + "buttonText": "Shop now", "buttonVariant": "contained" }, { "title": "Collection Tracker", - "price": "30", "description": [ - "50 users included", - "30 GB of storage", - "Help center access", - "Phone & email support" + "Track and manage your card collection", + "Value estimation based on market trends", + "Automatic collection organization", + "Trade and wishlist creation tools" ], - "buttonText": "Contact us", + "buttonText": "Track Collection", "buttonVariant": "outlined" } ] diff --git a/src/setupProxy.js b/src/setupProxy.js deleted file mode 100644 index 9f0c65b..0000000 --- a/src/setupProxy.js +++ /dev/null @@ -1,18 +0,0 @@ -// // eslint-disable-next-line @typescript-eslint/no-var-requires -// const { createProxyMiddleware } = require('http-proxy-middleware'); - -// module.exports = function (app) { -// app.use( -// '/api', -// createProxyMiddleware({ -// target: 'https://images.ygoprodeck.com', -// headers: { -// // 'Access-Control-Allow-Origin': '*', -// crossorigin: 'anonymous', -// // 'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE,PATCH,OPTIONS', -// }, -// changeOrigin: true, -// pathRewrite: { '^/api': '' }, -// }) -// ); -// };