From a94320c56e3c36703ee935ba24e843fcd44b5fa6 Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Thu, 12 Oct 2023 03:17:24 -0700 Subject: [PATCH 1/4] add --- .../actionButtons/CardActionButtons.jsx | 7 +- .../actionButtons/ChooseCollectionDialog.jsx | 285 +++++++++++++----- .../actionButtons/GenericActionButtons.jsx | 76 +++-- src/components/cards/GenericCard.jsx | 18 +- src/components/cleanUp/DeckCardDialog.jsx | 16 + .../UpdateChartData.jsx | 0 src/components/dialogs/DeckCardDialog.jsx | 16 - .../grids/collectionGrids/CardList.jsx | 2 +- src/components/headings/header/Header.js | 4 +- .../headings/navigation/MenuItems.js | 17 +- src/components/media/CardMediaSection.js | 2 +- src/components/modals/GenericCardModal.jsx | 146 ++++++++- src/context/CartContext/CartContext.js | 53 ++-- .../CollectionContext/CollectionContext.jsx | 70 ++++- src/context/CombinedProvider.jsx | 115 +++---- src/pages/ProfilePage.js | 80 +---- src/withCollectionAndCombinedProviders.jsx | 36 --- src/withCollectionProviderHOC.jsx | 15 - src/withCombinedProviderHOC.jsx | 15 - 19 files changed, 578 insertions(+), 395 deletions(-) create mode 100644 src/components/cleanUp/DeckCardDialog.jsx rename src/components/{collection => cleanUp}/UpdateChartData.jsx (100%) delete mode 100644 src/components/dialogs/DeckCardDialog.jsx delete mode 100644 src/withCollectionAndCombinedProviders.jsx delete mode 100644 src/withCollectionProviderHOC.jsx delete mode 100644 src/withCombinedProviderHOC.jsx diff --git a/src/components/buttons/actionButtons/CardActionButtons.jsx b/src/components/buttons/actionButtons/CardActionButtons.jsx index 8a61fc9..7a4c705 100644 --- a/src/components/buttons/actionButtons/CardActionButtons.jsx +++ b/src/components/buttons/actionButtons/CardActionButtons.jsx @@ -1,9 +1,7 @@ import React, { useCallback } from 'react'; import { Button, Grid } from '@mui/material'; import { makeStyles } from '@mui/styles'; -import ChooseCollectionDialog from './ChooseCollectionDialog'; import { useModal } from '../../../context/hooks/modal'; -import { useCollectionStore } from '../../../context/hooks/collection'; const useStyles = makeStyles((theme) => ({ root: { @@ -37,13 +35,16 @@ const CardActionButtons = ({ handleOpenDialog, }) => { const classes = useStyles(); - const { addOneToCollection } = useCollectionStore(); 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`); diff --git a/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx b/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx index 687c659..9dc2a77 100644 --- a/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx +++ b/src/components/buttons/actionButtons/ChooseCollectionDialog.jsx @@ -1,106 +1,239 @@ -import React, { useCallback, useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { Dialog, DialogTitle, + Typography, List, ListItem, - ListItemText, ButtonBase, - Typography, - ListItemButton, + ListItemText, + Divider, + Snackbar, } from '@mui/material'; +import { makeStyles } from '@mui/styles'; 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 } = - useCollectionStore(); - const { hideModal } = useModal(); + +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); - // 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); - 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); - 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); + 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!'); } }, - [setSelectedCollection, onClose] + [allCollections, setSelectedCollection, onSave, onClose] ); - - const handleOpenDialog = () => { - if (deckDialogIsOpen) { - hideModal(); - } - }; - - console.log('SELECTED COLLECTION (SELECT COLLECTION):', selectedCollection); + 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/actionButtons/GenericActionButtons.jsx b/src/components/buttons/actionButtons/GenericActionButtons.jsx index 4ec7f4a..55c3eb1 100644 --- a/src/components/buttons/actionButtons/GenericActionButtons.jsx +++ b/src/components/buttons/actionButtons/GenericActionButtons.jsx @@ -1,11 +1,12 @@ -import React, { useState, useContext } from 'react'; +// Dependencies +import React, { useState, useEffect, useContext } from 'react'; import { makeStyles } from '@mui/styles'; -import CardActionButtons from './CardActionButtons'; -import DeckCardDialog from '../../dialogs/DeckCardDialog'; -import ChooseCollectionDialog from './ChooseCollectionDialog'; +import { CollectionContext } from '../../../context/CollectionContext/CollectionContext'; import { DeckContext } from '../../../context/DeckContext/DeckContext'; import { CartContext } from '../../../context/CartContext/CartContext'; -import { CollectionContext } from '../../../context/CollectionContext/CollectionContext'; +import GenericCardModal from '../../modals/GenericCardModal'; +import CardActionButtons from './CardActionButtons'; +// import ChooseCollectionDialog from './ChooseCollectionDialog'; const useStyles = makeStyles({ root: { @@ -16,27 +17,53 @@ const useStyles = makeStyles({ }, }); -const GenericActionButtons = ({ card, context }) => { +const GenericActionButtons = ({ card, context, component }) => { const classes = useStyles(); + + const collectionContext = useContext(CollectionContext); + + if (!collectionContext) { + console.error("The component isn't wrapped with CollectionProvider"); + return null; + } + + const { + allCollections, + selectedCollection, + collectionData, + totalCost, + openChooseCollectionDialog, + setOpenChooseCollectionDialog, + calculateTotalPrice, + getTotalCost, + createUserCollection, + removeCollection, + fetchAllCollectionsForUser, + setSelectedCollection, + setAllCollections, + addOneToCollection, + removeOneFromCollection, + } = collectionContext; + const contexts = { Deck: useContext(DeckContext), Cart: useContext(CartContext), - Store: useContext(CartContext), // Assuming the Store context is similar to Cart - Collection: useContext(CollectionContext), + Store: useContext(CartContext), + Collection: collectionContext, }; const [openDialog, setOpenDialog] = useState(false); - const [openCollectionDialog, setOpenCollectionDialog] = useState(false); const contextProps = contexts[context] || {}; - const toggleDialog = (setState) => () => setState((prevState) => !prevState); - // console.log('context', context); - // console.log('contextProps', contextProps); - // console.log('contexts', contexts); - // console.log('card', card); - // console.log('openDialog', openDialog); - // console.log('openCollectionDialog', openCollectionDialog); + // useEffect(() => { + // console.log(`openChooseCollectionDialog is: ${openChooseCollectionDialog}`); + // }, [openChooseCollectionDialog]); + + const toggleDialog = (setState) => () => { + setState((prevState) => !prevState); + }; + return (
{ handleOpenDialog={toggleDialog(setOpenDialog)} /> {context in contexts && ( - )} - {openDialog && ( - - )} + {/* */}
); }; diff --git a/src/components/cards/GenericCard.jsx b/src/components/cards/GenericCard.jsx index f1b6b80..a6af08a 100644 --- a/src/components/cards/GenericCard.jsx +++ b/src/components/cards/GenericCard.jsx @@ -8,7 +8,7 @@ import { Button, } from '@mui/material'; import CardToolTip from './CardToolTip'; -import DeckCardDialog from '../dialogs/DeckCardDialog'; +// import DeckCardDialog from '../cleanUp/DeckCardDialog'; import CardMediaSection from '../media/CardMediaSection'; import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons'; import placeholderImage from '../../assets/placeholder.jpeg'; @@ -59,10 +59,10 @@ const GenericCard = ({ card, context, cardInfo }) => { }, []); useEffect(() => { - 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`; + 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]); @@ -149,7 +149,7 @@ const GenericCard = ({ card, context, cardInfo }) => { Quantity:{' '} - {contextProps.deckCardQuantity?.quantityOfSameId || 'Not in deck'} + {contextProps?.deckCardQuantity?.quantityOfSameId || 'Not in deck'} ); @@ -167,11 +167,11 @@ const GenericCard = ({ card, context, cardInfo }) => { cardInfo={cardInfo} /> ) : ( - + )} - {card.name} + {card?.name} {context === 'Store' && ( @@ -231,7 +231,7 @@ const GenericCard = ({ card, context, cardInfo }) => { /> )} {context === 'Deck' && ( - { +// return ( +// +// ); +// }; + +// export default DeckCardDialog; diff --git a/src/components/collection/UpdateChartData.jsx b/src/components/cleanUp/UpdateChartData.jsx similarity index 100% rename from src/components/collection/UpdateChartData.jsx rename to src/components/cleanUp/UpdateChartData.jsx diff --git a/src/components/dialogs/DeckCardDialog.jsx b/src/components/dialogs/DeckCardDialog.jsx deleted file mode 100644 index ebebfba..0000000 --- a/src/components/dialogs/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/grids/collectionGrids/CardList.jsx b/src/components/grids/collectionGrids/CardList.jsx index 112520f..84b1095 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -25,7 +25,7 @@ const CardList = ({ selectedCards, removeCard }) => { // console.log('SELECTED COLLECTION:', selectedCollection); const itemsPerPage = 10; const [currentPage, setCurrentPage] = useState(1); - const maxPages = Math.ceil(selectedCards.length / itemsPerPage); + const maxPages = Math.ceil(selectedCards?.length / itemsPerPage); // Handler to go to the next page const nextPage = () => { diff --git a/src/components/headings/header/Header.js b/src/components/headings/header/Header.js index aa15b80..043cf8a 100644 --- a/src/components/headings/header/Header.js +++ b/src/components/headings/header/Header.js @@ -37,8 +37,8 @@ const Header = () => { setAnchorElNav(event.currentTarget); }; - const currentPage = window.location.pathname; - console.log('CURRENT PAGE:', currentPage); + // const currentPage = window.location.pathname; + // console.log('CURRENT PAGE:', currentPage); return ( ({ '& .MuiTypography-root': { fontSize: '1.2em', @@ -54,9 +55,7 @@ const LogoutContainer = styled(Box)({ cursor: 'pointer', }); -const currentPage = window.location.pathname; -console.log('CURRENT PAGE:', currentPage); -console.log('theme', theme); +// console.log('theme', theme); function MenuItems({ isLoggedIn, @@ -64,6 +63,16 @@ function MenuItems({ handleDrawerClose, handleCloseNavMenu, }) { + const location = useLocation(); // get current location + const previousPageRef = useRef(); // useRef to track the previous page + + useEffect(() => { + if (location.pathname !== previousPageRef.current) { + // only log if the current page is different from the previously logged page + console.log('CURRENT PAGE:', location.pathname); + previousPageRef.current = location.pathname; // update ref with current page + } + }, [location.pathname]); return ( <> diff --git a/src/components/media/CardMediaSection.js b/src/components/media/CardMediaSection.js index 62485db..d3755ce 100644 --- a/src/components/media/CardMediaSection.js +++ b/src/components/media/CardMediaSection.js @@ -27,7 +27,7 @@ const CardMediaSection = ({ useEffect(() => { if (!hasLoggedCard) { - console.log('CARD:', card); + // console.log('CARD:', card); setHasLoggedCard(true); } }, [hasLoggedCard, card]); diff --git a/src/components/modals/GenericCardModal.jsx b/src/components/modals/GenericCardModal.jsx index 5288a4d..d923eba 100644 --- a/src/components/modals/GenericCardModal.jsx +++ b/src/components/modals/GenericCardModal.jsx @@ -1,10 +1,16 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useEffect, useState, useCallback } from 'react'; import { Dialog, DialogContent, DialogTitle, Grid, Snackbar, + List, + ListItem, + ButtonBase, + ListItemText, + Divider, + Typography, } from '@mui/material'; import CardMediaSection from '../media/CardMediaSection'; import CardDetailsContainer from './cardModal/CardDetailsContainer'; @@ -13,6 +19,7 @@ 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: { @@ -40,9 +47,24 @@ const useStyles = makeStyles((theme) => ({ 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 = ({ isOpen, onClose, card, context }) => { +const GenericCardModal = ({ isOpen, onClose, card, cardInfo, context }) => { const classes = useStyles(); const deckContext = useContext(DeckContext); const cartContext = useContext(CartContext); @@ -50,6 +72,36 @@ const GenericCardModal = ({ isOpen, onClose, card, context }) => { const [openSnackbar, setOpenSnackbar] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(''); + if (!collectionContext) { + console.error("The component isn't wrapped with CollectionProvider"); + return null; + } + + const { + // allCollections, + selectedCollection, + collectionData, + totalCost, + // openChooseCollectionDialog, + // setOpenChooseCollectionDialog, + calculateTotalPrice, + getTotalCost, + createUserCollection, + removeCollection, + fetchAllCollectionsForUser, + // setSelectedCollection, + setAllCollections, + addOneToCollection, + removeOneFromCollection, + } = collectionContext; + + const { + allCollections, + setSelectedCollection, + openChooseCollectionDialog, + setOpenChooseCollectionDialog, + } = useCollectionStore(); + const contextProps = { Deck: deckContext, @@ -58,9 +110,24 @@ const GenericCardModal = ({ isOpen, onClose, card, context }) => { Collection: collectionContext, }[context] || {}; - const productQuantity = contextProps.getCardQuantity - ? contextProps.getCardQuantity(card.id) - : 0; + const handleSelectCollection = useCallback( + (collectionId) => { + const foundCollection = allCollections.find( + (collection) => collection._id === collectionId + ); + + if (foundCollection) { + setSelectedCollection(foundCollection); + setOpenChooseCollectionDialog(false); + setSnackbarMessage('Collection selected successfully!'); + setOpenSnackbar(true); + } else { + setSnackbarMessage('Collection not found!'); + setOpenSnackbar(true); + } + }, + [allCollections, setSelectedCollection] + ); const handleClose = (event, reason) => { if (reason === 'backdropClick' || reason === 'escapeKeyDown') { @@ -70,7 +137,14 @@ const GenericCardModal = ({ isOpen, onClose, card, context }) => { } }; - if (!card) return null; + console.log('openChooseCollectionDialog', openChooseCollectionDialog); + + useEffect(() => { + if (openChooseCollectionDialog) { + console.log('Fetching collections...', openChooseCollectionDialog); + fetchAllCollectionsForUser(); + } + }); return ( @@ -93,22 +167,64 @@ const GenericCardModal = ({ isOpen, onClose, card, context }) => { <> {context === 'Deck' && ( - + <> + + + )} )} + {openChooseCollectionDialog && ( + setOpenChooseCollectionDialog(false)} + > + Select a Collection + + + {allCollections.map((collection) => ( + + + handleSelectCollection(collection._id)} + > + + + + + + ))} + + + setOpenSnackbar(false)} + message={snackbarMessage} + autoHideDuration={3000} + /> + + )} { useEffect(() => { if (userId && typeof userId === 'string') { - console.log('Fetching user cart'); + // console.log('Fetching user cart'); fetchUserCart(userId) .then((data) => { if (data && data.cart) { @@ -228,47 +228,38 @@ export const CartProvider = ({ children }) => { const value = { cartData, - getTotalCost, getCardQuantity, - fetchUserCart, + cartCardQuantity: cartData.cart?.reduce( + (acc, card) => acc + card.quantity, + 0 + ), + cartCardCount: cartData.cart?.length, + cartValue: cartData.cart?.reduce( + (acc, card) => acc + card.card_prices[0].tcgplayer_price * card.quantity, + 0 + ), addOneToCart, removeOneFromCart, deleteFromCart, + getTotalCost, + fetchUserCart, createUserCart, }; useEffect(() => { console.log('CART CONTEXT: ', { - value, + cartData, + // getTotalCost, + // // getCardQuantity, + // fetchUserCart, + // addOneToCart, + // removeOneFromCart, + // deleteFromCart, + // createUserCart, }); - }, [value]); + }, [cartData]); - return ( - acc + card.quantity, - 0 - ), - cartCardCount: cartData.cart?.length, - cartValue: cartData.cart?.reduce( - (acc, card) => - acc + card.card_prices[0].tcgplayer_price * card.quantity, - 0 - ), - addOneToCart, - removeOneFromCart, - deleteFromCart, - getTotalCost, - fetchUserCart, - createUserCart, - }} - > - {children} - - ); + return {children}; }; export const useCartStore = () => { diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index d46ebfe..642cfd7 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -103,10 +103,11 @@ export const CollectionProvider = ({ children }) => { const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`; const [cookies] = useCookies(['userCookie']); const { triggerCronJob } = useUserContext(); - const [collectionData, setCollectionData] = useState(initialCollectionState); const [allCollections, setAllCollections] = useState([]); const [selectedCollection, setSelectedCollection] = useState({}); + const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = + useState(false); const chartData = selectedCollection?.chartData || {}; // const datasets = chartData?.datasets || []; const userId = cookies.userCookie?.id; @@ -224,6 +225,7 @@ export const CollectionProvider = ({ children }) => { totalQuantity: 0, allCardPrices: [], cards: [], + chartData: {}, }; const response = await fetchWrapper(url, 'POST', initialData); @@ -314,6 +316,7 @@ export const CollectionProvider = ({ children }) => { const collectionId = selectedCollection?._id || allCollections[0]?._id; if (!collectionId) { console.error('No valid collection selected.'); + setOpenChooseCollectionDialog(true); return; } @@ -349,6 +352,8 @@ export const CollectionProvider = ({ children }) => { const updateInfo = { ...cardInfo, + name: selectedCollection?.name, + description: selectedCollection?.description, cards: updatedCards, userId: userId, totalCost: updatedPrice, @@ -375,25 +380,53 @@ export const CollectionProvider = ({ children }) => { selectedCollection, allCollections, userId, + openChooseCollectionDialog, handleCardAddition, handleCardRemoval, updateCollectionData, + setOpenChooseCollectionDialog, ] ); const updateActiveCollection = useCallback( async (updatedCollectionData) => { + let endpoint; + let method; + + // Check if collection ID is missing or undefined. if (!updatedCollectionData?._id) { - console.error('Collection ID or Data is undefined.'); - return; + console.warn( + 'Collection ID is missing. Assuming this is a new collection.' + ); + endpoint = `${BASE_API_URL}/${userId}/collections`; + method = 'POST'; + } else { + endpoint = `${BASE_API_URL}/${userId}/collections/${updatedCollectionData._id}`; + method = 'PUT'; } - const url = `${BASE_API_URL}/${userId}/collections/${updatedCollectionData._id}`; + try { - const { updatedCollection } = await fetchWrapper( - url, - 'PUT', + const response = await fetchWrapper( + endpoint, + method, updatedCollectionData ); + + let updatedCollection; + let all; + + if (method === 'POST' && response.newCollection) { + updatedCollection = response.newCollection; + } else if (method === 'PUT' && response.updatedCollection) { + updatedCollection = response.updatedCollection; + console.log( + 'UPDATED COLLECTION NOW UPDATES ALL AS WELL:', + (all = response.allCollections) + ); + } else { + throw new Error('Unexpected response format'); + } + const newChartData = { ...updatedCollection.chartData, datasets: [ @@ -418,6 +451,7 @@ export const CollectionProvider = ({ children }) => { }, ], }; + updatedCollection.chartData = newChartData; console.log('UPDATED COLLECTION FROM SERVER:', updatedCollection); updateCollectionData(updatedCollection, 'selectedCollection'); @@ -436,6 +470,8 @@ export const CollectionProvider = ({ children }) => { selectedCollection, collectionData, totalCost, + openChooseCollectionDialog, + setOpenChooseCollectionDialog, // FUNCTIONS calculateTotalPrice: () => getCardPrice(selectedCollection), getTotalCost: () => getTotalCost(selectedCollection), @@ -458,6 +494,12 @@ export const CollectionProvider = ({ children }) => { contextValue, }); }, [contextValue]); + useEffect(() => { + console.log( + 'OPEN CHOOSE COLLECTION DIALOG UPDATED:', + openChooseCollectionDialog + ); + }, [openChooseCollectionDialog]); useEffect(() => { if (selectedCollection && totalCost) { @@ -480,3 +522,17 @@ export const CollectionProvider = ({ 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/CombinedProvider.jsx b/src/context/CombinedProvider.jsx index 28852db..63355af 100644 --- a/src/context/CombinedProvider.jsx +++ b/src/context/CombinedProvider.jsx @@ -173,6 +173,17 @@ export const CombinedProvider = ({ children }) => { // setAllCollections(collections); }; + const handleError = (errorData) => { + console.error('Server Error:', errorData.message); + console.error('Error Source:', errorData.source); + + // If you sent error detail from server, you can also log it + if (errorData.detail) { + console.error('Error Detail:', errorData.detail); + } + setDataFunctions.error(errorData); + }; + const handleCronJobTracker = (incomingData) => { console.log('Automated Message from Server:', incomingData.message); @@ -190,10 +201,10 @@ export const CombinedProvider = ({ children }) => { setDataFunctions.cronData({ data }); }; - // const handleExistingCollectionData = (userId, selectedCollection) => { - // console.log('Received existing collection data:', selectedCollection); - // setDataFunctions.collectionData({ selectedCollection }); - // }; + const handleExistingCollectionData = (userId, selectedCollection) => { + console.log('Received existing collection data:', selectedCollection); + setDataFunctions.collectionData({ selectedCollection }); + }; const handleExistingChartData = (data) => { if (!data) { @@ -203,9 +214,17 @@ export const CombinedProvider = ({ children }) => { ); return; } - console.log('Received existing CHART data:', data); + if (Array.isArray(data?.chartData)) { + console.error('The provided chartData is an array, not an object!'); + return; // Exit without saving or updating the data + } + console.log('chartData being saved:', data); + + const { chartData } = data; + + // console.log('Received existing CHART data:', data); - setDataFunctions.chartData(data); + setDataFunctions.chartData({ chartData }); }; const handleCollectionUpdated = ({ message, data }) => { @@ -262,47 +281,20 @@ export const CombinedProvider = ({ children }) => { setDataFunctions.cardPrices({ allPrices }); }; - // const handleChartUpdated = ({ message, data }) => { - // console.log('Message:', message); - // console.log('Updated chart Data:', data); - // setDataFunctions.updatedChartData(data?.data); - // }; - - // const handleNewChartCreated = ({ message, data }) => { - // console.log('Message:', message); - // console.log('Updated New CHart Data:', data?.data); - // setDataFunctions.chartData(data?.data); - // }; - - // const handleCardStatsUpdate = (data) => { - // console.log('Card stats updated:', data?.data); - // setDataFunctions.cardStats(data?.data); - // }; - - // const cardStatsUpdate = (data) => { - // console.log('Card stats updated:', data); - // setDataFunctions.cardStatsArray(data?.data); - // }; - socket.on('MESSAGE_TO_CLIENT', handleReceive); socket.on('RESPONSE_EXISTING_CHART_DATA', handleExistingChartData); socket.on('RESPONSE_CRON_DATA', handleCronJobTracker); - // socket.on( - // 'RESPONSE_EXISTING_COLLECTION_DATA', - // handleExistingCollectionData - // ); + socket.on( + 'RESPONSE_EXISTING_COLLECTION_DATA', + handleExistingCollectionData + ); socket.on( 'RESPONSE_CRON_UPDATED_CARDS_IN_COLLECTION', handleCardPricesUpdated ); + socket.on('ERROR', handleError); socket.on('RESPONSE_CRON_UPDATED_ALLCOLLECTIONS', handleCronJobResponse); socket.on('COLLECTION_UPDATED', handleCollectionUpdated); - // socket.on('CARD_STATS_UPDATE', handleCardStatsUpdate); - // socket.on('CHART_UPDATED', handleChartUpdated); - // socket.on('ALL_DATA_ITEMS', handleAllDataItemsReceived); - // socket.on('updateCollection', cardStatsUpdate); - - // Cleanup to avoid multiple listeners return () => { socket.off('MESSAGE_TO_CLIENT', handleReceive); socket.off('RESPONSE_EXISTING_CHART_DATA', handleExistingChartData); @@ -331,7 +323,7 @@ export const CombinedProvider = ({ children }) => { if (!userId || !selectedCollection) return console.error('Missing userId or selectedCollection.'); // socket.emit('REQUEST_EXISTING_COLLECTION_DATA', userId); - socket.emit('RESPONSE_EXISTING_COLLECTION_DATA', { + socket.emit('REQUEST_EXISTING_COLLECTION_DATA', { userId, selectedCollection: selectedCollection, }); @@ -343,13 +335,11 @@ export const CombinedProvider = ({ children }) => { return console.error( 'Missing selectedCollection for chart data request.' ); - if (selectedCollection.chartData === undefined || null || '') { + if (selectedCollection.chartData === undefined || null) { if (selectedCollection.chartData === undefined) console.log('chartData is undefined'); if (selectedCollection.chartData === null) console.log('chartData is null'); - if (selectedCollection.chartData === '') - console.log('chartData is empty'); return console.error( 'The selected collections chart data is missing, null or undefined.' ); @@ -359,11 +349,11 @@ export const CombinedProvider = ({ children }) => { 'Attempting to retrieve chart data', selectedCollection?.chartData ); - const chartData = selectedCollection?.chartData; + const chartData = selectedCollection?.chartData || {}; socket.emit('REQUEST_EXISTING_CHART_DATA', { data: { userId, - chartData, + chartData: chartData, }, }); }, @@ -373,25 +363,10 @@ export const CombinedProvider = ({ children }) => { if (!message) return console.error('Message content is missing.'); socket.emit('MESSAGE_FROM_CLIENT', { message }); }, - // updateCollection: () => { - // if (!userId || !selectedCollection) - // return console.error('Invalid data for collection update.'); - // socket.emit('REQUEST_UPDATE_COLLECTION', { - // userId, - // collectionId: selectedCollection._id, - // data: selectedCollection, - // }); - // }, - // updateChart: () => { - // if (!userId || !selectedCollection) - // return console.error('Missing userId or collection data.'); - // socket.emit('REQUEST_UPDATE_OR_CREATE_CHART', { - // userId, - // chartId: selectedCollection.chartId, // Assuming your collection has a chartId field - // datasets: selectedCollection.datasets, - // name: selectedCollection.name, - // }); - // }, + stopCronJob: (userId) => { + if (!userId) return console.error('Missing userId for cron job stop.'); + socket.emit('REQUEST_CRON_STOP', { userId }); + }, checkAndUpdateCardPrices: ( userId, listOfMonitoredCards, @@ -449,7 +424,7 @@ export const CombinedProvider = ({ children }) => { useEffect(() => { if (allCollections) { - console.log('allCollections', allCollections); + // console.log('allCollections', allCollections); console.log('listOfMonitoredCards', listOfMonitoredCards); if ( @@ -491,18 +466,11 @@ export const CombinedProvider = ({ children }) => { handleSend: handleSocketInteraction.sendAction.message, handleRequestCollectionData: handleSocketInteraction.requestData.collection, - // handleRequestChartDataFunction: handleSocketInteraction.requestData.chart, // Ensure it's provided here handleRequestChartData: handleSocketInteraction.requestData.chart, // Assuming this is the correct mapping handleSendAllCardsInCollections: handleSocketInteraction.sendAction.checkAndUpdateCardPrices, // Ensure it's provided here + handleRequestCronStop: handleSocketInteraction.sendAction.stopCronJob, // Ensure it's provided here handleRetreiveListOfMonitoredCards: retrieveListOfMonitoredCards, - // handleSendCollectionData: - // handleSocketInteraction.sendAction.updateCollection, // Ensure it's provided here - // handleSendChartData: handleSocketInteraction.sendAction.updateChart, // Ensure it's provided here - - // handleRequestData: handleSocketInteraction.requestData.collection, // Assuming this is the correct mapping - // handleSendData: handleSocketInteraction.sendAction.updateCollection, - // handleSendChart: handleSocketInteraction.sendAction.updateChart, handleSocketInteraction, setDataFunctions, socket, @@ -523,6 +491,7 @@ export const CombinedProvider = ({ children }) => { cardPrices: value.cardPrices, retrievedListOfMonitoredCards: value.retrievedListOfMonitoredCards, listOfMonitoredCards: value.listOfMonitoredCards, + error: value.error, // isLoading: value.isLoading, // cronTriggerTimestamps: value.cronTriggerTimestamps, // error: value.error, @@ -533,7 +502,7 @@ export const CombinedProvider = ({ children }) => { useEffect(() => { console.log('COMBINED CONTEXT VALUE:', dataValues); - }, [dataValues]); + }, [dataValues.cronData, dataValues.chartData, dataValues.collectionData]); return ( diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 457b8e8..977f6fc 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -58,47 +58,6 @@ const DataTextStyled = styled(Typography)({ fontSize: '0.9rem', }); -// function convertCardToChartData(cardData, userId) { -// const currentDate = new Date(); - -// // Extracting values from the card data -// const cardName = cardData.name; -// const cardPrice = cardData.card_prices[0]?.tcgplayer_price || 0; // Assuming tcgplayer_price is the main price point -// const totalQuantity = cardData.quantity; -// const totalPrice = cardPrice * totalQuantity; - -// // Constructing the data point -// const dataPoint = { -// cardName: cardName, -// x: currentDate, // Current date for the data point -// y: cardPrice, // Single card price -// totalQuantity: totalQuantity, -// totalPrice: totalPrice, // Total price for this card -// // _id: new mongoose.Types.ObjectId(), // New MongoDB Object ID for the data point -// }; - -// // Constructing the dataset (assuming you might have more datasets in future) -// const dataset = { -// label: cardName, // Using card name as label for the dataset -// totalquantity: totalQuantity, -// data: { -// points: [dataPoint], // Initial single data point for the card -// }, -// // You can also define other properties like backgroundColor, borderColor etc. -// }; - -// // Constructing the entire chart data -// const chartData = { -// // _id: new mongoose.Types.ObjectId(), // New MongoDB Object ID for the chart data -// name: cardName, // Using card name as chart name -// userId: userId, -// datasets: [dataset], // Initial single dataset for the chart data -// // You can also define other properties like collectionId, chartId etc. -// }; - -// return chartData; -// } - const ProfileForm = ({ userName, name, age, status, onSave }) => { const [formData, setFormData] = useState({ userName: userName || '', // default to empty string if undefined @@ -194,6 +153,7 @@ const ProfilePage = () => { handleSendData, handleSendChart, handleCronRequest, + handleRequestCronStop, chartDataToSend, handleSendAllCardsInCollections, listOfMonitoredCards, @@ -263,30 +223,14 @@ const ProfilePage = () => { } }; - // const handleSendCollectionData = () => { - // if (userId && selectedCollection) { - // handleSendData({ - // userId, - // collectionId: selectedCollection._id, - // collectionData: selectedCollection.cards, - // }); - // } - // }; + const handleStopCronJob = () => { + if (userId) { + handleRequestCronStop(userId); + console.log('STOPPING CRON JOB'); + } - // const handleSendChartData = () => { - // if (userId && selectedCollection) { - // const datasets = selectedCollection.cards.map((card) => - // convertCardToChartData(card, userId) - // ); - // handleSendChart({ - // userId, - // chartId: selectedCollection.chartId, // Assuming the collection object has a chartId field - // datasets, - // name: selectedCollection.name, - // }); - // } - // }; - // const chartData = {}; + openSnackbar('Cron job stopped.'); + }; return ( @@ -329,7 +273,13 @@ const ProfilePage = () => { > Trigger Cron Job - + + Stop Cron Job + {/* { - const ComponentWithProviders = (props) => { - const EnhancedComponent = withCollectionProviderHOC(WrappedComponent); - - return ( - - - - - - - - ); - }; - ComponentWithProviders.displayName = `withCollectionAndCombinedProviders(${ - WrappedComponent.displayName || WrappedComponent.name || 'Component' - })`; - return ComponentWithProviders; -}; - -export default withCollectionAndCombinedProviders; diff --git a/src/withCollectionProviderHOC.jsx b/src/withCollectionProviderHOC.jsx deleted file mode 100644 index 9edc93d..0000000 --- a/src/withCollectionProviderHOC.jsx +++ /dev/null @@ -1,15 +0,0 @@ -// CollectionProviderHOC.js - -import { CollectionProvider } from './context/CollectionContext/CollectionContext'; - -const withCollectionProvider = (Component) => { - return function WrappedComponent(props) { - return ( - - - - ); - }; -}; - -export default withCollectionProvider; diff --git a/src/withCombinedProviderHOC.jsx b/src/withCombinedProviderHOC.jsx deleted file mode 100644 index 4f02c2e..0000000 --- a/src/withCombinedProviderHOC.jsx +++ /dev/null @@ -1,15 +0,0 @@ -// CombinedProviderHOC.js - -import { CombinedProvider } from './context/CombinedProvider'; - -const withCombinedProviderHOC = (Component) => { - return function WrappedComponent(props) { - return ( - - - - ); - }; -}; - -export default withCombinedProviderHOC; From 679ae3bc0326f11f7feed4135c0f0abfdbdf09b3 Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Thu, 12 Oct 2023 21:16:39 -0700 Subject: [PATCH 2/4] completed the server side data management updates and did a general code clean --- .../grids/deckBuilderGrids/DeckButtonList.js | 18 +++--- src/components/modals/GenericCardModal.jsx | 36 ++++-------- src/context/CardContext/CardStore.js | 1 + .../CollectionContext/CollectionContext.jsx | 56 +++++++++++++++---- src/context/CombinedProvider.jsx | 51 +++++++++++------ 5 files changed, 97 insertions(+), 65 deletions(-) diff --git a/src/components/grids/deckBuilderGrids/DeckButtonList.js b/src/components/grids/deckBuilderGrids/DeckButtonList.js index f6b17f2..0a87a9b 100644 --- a/src/components/grids/deckBuilderGrids/DeckButtonList.js +++ b/src/components/grids/deckBuilderGrids/DeckButtonList.js @@ -6,7 +6,7 @@ import { DeckContext } from '../../../context/DeckContext/DeckContext'; const useDeckButtonListStyles = makeStyles((theme) => ({ grid: { - marginBottom: theme.spacing(2), + marginBottom: theme?.spacing(2), }, deckButton: { width: '100%', @@ -16,15 +16,15 @@ const useDeckButtonListStyles = makeStyles((theme) => ({ '&:hover': { backgroundColor: 'rgba(0, 0, 0, 0.04)', }, - margin: theme.spacing(1), - [theme.breakpoints.down('xs')]: { + margin: theme?.spacing(1), + [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', }, }, @@ -57,7 +57,7 @@ const DeckButtonList = ({ userDecks, handleSelectDeck }) => { // console.log('userDecks', userDecks); return ( - {userDecks.map((deck) => ( + {userDecks?.map((deck) => ( - - - - - - - {authContext.error &&

{authContext.error}

} - {authContext.isLoading &&

Loading...

} - - ); -} - -export default Login; diff --git a/src/components/buttons/actionButtons/GenericActionButtons.jsx b/src/components/buttons/actionButtons/GenericActionButtons.jsx index 55c3eb1..4f56698 100644 --- a/src/components/buttons/actionButtons/GenericActionButtons.jsx +++ b/src/components/buttons/actionButtons/GenericActionButtons.jsx @@ -17,7 +17,14 @@ const useStyles = makeStyles({ }, }); -const GenericActionButtons = ({ card, context, component }) => { +const GenericActionButtons = ({ + card, + context, + open, + component, + closeModal, + // handleOpenDialog, +}) => { const classes = useStyles(); const collectionContext = useContext(CollectionContext); @@ -27,24 +34,6 @@ const GenericActionButtons = ({ card, context, component }) => { return null; } - const { - allCollections, - selectedCollection, - collectionData, - totalCost, - openChooseCollectionDialog, - setOpenChooseCollectionDialog, - calculateTotalPrice, - getTotalCost, - createUserCollection, - removeCollection, - fetchAllCollectionsForUser, - setSelectedCollection, - setAllCollections, - addOneToCollection, - removeOneFromCollection, - } = collectionContext; - const contexts = { Deck: useContext(DeckContext), Cart: useContext(CartContext), @@ -70,12 +59,14 @@ const GenericActionButtons = ({ card, context, component }) => { card={card} context={context} contextProps={contextProps} + // handleOpenDialog={handleOpenDialog} handleOpenDialog={toggleDialog(setOpenDialog)} /> {context in contexts && ( diff --git a/src/components/cards/CardToolTip.jsx b/src/components/cards/CardToolTip.jsx index ed25f8e..47585b9 100644 --- a/src/components/cards/CardToolTip.jsx +++ b/src/components/cards/CardToolTip.jsx @@ -1,64 +1,124 @@ -import React, { useEffect } from 'react'; -import { commonStyles } from './cardStyles'; +import React from 'react'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@mui/styles'; +import { Tooltip } from '@mui/material'; -const CardToolTip = ({ cardInfo, isHovering, isDeckModalOpen, tooltipRef }) => { - useEffect(() => { - if (isHovering && tooltipRef.current) { - const cardRect = tooltipRef.current.getBoundingClientRect(); - tooltipRef.current.style.top = `${cardRect.top}px`; - tooltipRef.current.style.left = `${cardRect.right}px`; - } - }, [isHovering]); +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), + }, +})); - const classes = commonStyles(); +const formatKey = (key) => { + return key + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +}; + +const CardToolTip = ({ card }) => { + const classes = useStyles(); + const { + name, + desc, + attributes, + set_name, + set_code, + set_rarity, + set_rarity_code, + set_price, + id, + image_url, + image_url_small, + image_url_cropped, + cardmarket_price, + tcgplayer_price, + cardkingdom_price, + cardhoarder_price, + } = card; + + // Conditional handling for prices + const price = set_price?.value || 'N/A'; + const cmPrice = cardmarket_price?.value || 'N/A'; + const tcgPrice = tcgplayer_price?.value || 'N/A'; + const ckPrice = cardkingdom_price?.value || 'N/A'; + const chPrice = cardhoarder_price?.value || 'N/A'; + + // Ensure safe rendering for URLs + const imageUrl = image_url || ''; + const imageUrlSmall = image_url_small || ''; + const imageUrlCropped = image_url_cropped || ''; return ( -
+ // {`${set_name} + //

{`Set: ${set_name} (${set_code})`}

+ //

{`Rarity: ${set_rarity} (${set_rarity_code})`}

+ //

{`Price: ${set_price?.value || 'N/A'}`}

+ //

{`Cardmarket Price: ${cardmarket_price?.value || 'N/A'}`}

+ //

{`TCGPlayer Price: ${tcgplayer_price?.value || 'N/A'}`}

+ //

{`Card Kingdom Price: ${cardkingdom_price?.value || 'N/A'}`}

+ //

{`Cardhoarder Price: ${cardhoarder_price?.value || 'N/A'}`}

+ // + // } + arrow > - {cardInfo?.name && ( -

{cardInfo.name}

- )} - {cardInfo?.level && ( - - LV: {cardInfo.level} - - )} - {cardInfo?.type && ( - - Type: {cardInfo.type} - - )} - {cardInfo?.race && ( - - Race: {cardInfo.race} - - )} - {cardInfo?.attribute && ( - - Attribute: {cardInfo.attribute} - - )} - {cardInfo?.atk && ( - - ATK: {cardInfo.atk} - - )} - {cardInfo?.def && ( - - DEF: {cardInfo.def} - - )} - {cardInfo?.desc && ( - - Description: {cardInfo.desc} - - )} -
+
+ {name &&

{name}

} + {attributes && + Object.entries(attributes).map(([key, value]) => ( + + {formatKey(key)}: {value} + + ))} + {desc && ( + + Description: {desc} + + )} +
+ ); }; +CardToolTip.propTypes = { + card: PropTypes.shape({ + name: PropTypes.string, + desc: PropTypes.string, + attributes: PropTypes.object, + set_name: PropTypes.string, + set_code: PropTypes.string, + set_rarity: PropTypes.string, + set_rarity_code: PropTypes.string, + set_price: PropTypes.object, + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + image_url: PropTypes.string, + image_url_small: PropTypes.string, + image_url_cropped: PropTypes.string, + cardmarket_price: PropTypes.object, + tcgplayer_price: PropTypes.object, + cardkingdom_price: PropTypes.object, + cardhoarder_price: PropTypes.object, + }).isRequired, +}; + export default CardToolTip; diff --git a/src/components/cards/CustomPopover.jsx b/src/components/cards/CustomPopover.jsx new file mode 100644 index 0000000..47e744b --- /dev/null +++ b/src/components/cards/CustomPopover.jsx @@ -0,0 +1,69 @@ +import React, { useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import Popover from '@mui/material/Popover'; +import CardToolTip from './CardToolTip'; +import { makeStyles } from '@mui/styles'; +import GenericCard from './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/cards/GenericCard.jsx b/src/components/cards/GenericCard.jsx index a6af08a..94d9b76 100644 --- a/src/components/cards/GenericCard.jsx +++ b/src/components/cards/GenericCard.jsx @@ -6,251 +6,214 @@ import { CardMedia, Typography, Button, + Popover, + Grid, + ButtonGroup, } from '@mui/material'; -import CardToolTip from './CardToolTip'; -// import DeckCardDialog from '../cleanUp/DeckCardDialog'; import CardMediaSection from '../media/CardMediaSection'; import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons'; -import placeholderImage from '../../assets/placeholder.jpeg'; -import { CartContext } from '../../context/CartContext/CartContext'; +import placeholderImage from '../../assets/images/placeholder.jpeg'; import { DeckContext } from '../../context/DeckContext/DeckContext'; -import { - mergeStyles, - commonStyles, - deckCardStyles, - productCardStyles, -} from './cardStyles'; -// import CardModal from '../modals/cardModal/CardModal'; +import { CartContext } from '../../context/CartContext/CartContext'; import { CollectionContext } from '../../context/CollectionContext/CollectionContext'; -import GenericCardModal from '../modals/GenericCardModal'; - -const GenericCard = ({ card, context, cardInfo }) => { +import { makeStyles } from '@mui/styles'; + +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 [buttonVariant, setButtonVariant] = useState('contained'); const [isModalOpen, setModalOpen] = useState(false); - const [isHovering, setHovering] = useState(false); - const tooltipRef = useRef(null); - const cardRef = useRef(null); + // const [anchorEl, setAnchorEl] = useState(null); const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage; + const open = Boolean(hoveredCard === card); const openModal = () => setModalOpen(true); const closeModal = () => setModalOpen(false); - - const buttonStyles = { - maxWidth: '200px', - minHeight: '40px', - maxHeight: '60px', - width: '100%', - }; - - const handleResize = () => { - setButtonVariant(window.innerWidth < 768 ? 'outlined' : 'contained'); - }; - - useEffect(() => { - handleResize(); - window.addEventListener('resize', handleResize); - return () => { - window.removeEventListener('resize', handleResize); - }; - }, []); - - useEffect(() => { - 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]); - - const styles = { - Deck: deckCardStyles(), - Store: productCardStyles(), - Cart: productCardStyles(), - Collection: productCardStyles(), - }; - - const classes = mergeStyles(commonStyles(), styles[context] || {}); - - const openProductModal = () => openModal(); - const getContextSpecificProps = (context, contextContext) => { - const contextProps = { - deckCardQuantity: 0, - addOne: () => console.log(`addOneTo${context}`), - removeOne: () => console.log(`removeOneFrom${context}`), - removeAll: () => console.log(`removeAllFrom${context}`), - }; - - if (contextContext) { - contextProps.deckCardQuantity = contextContext.getCardQuantity(card.id); - contextProps.addOne = - contextContext.addOneToDeck || - contextContext.addOneToCart || - contextContext.addOneToCollection; - contextProps.removeOne = - contextContext.removeOneFromDeck || - contextContext.removeOneFromCart || - contextContext.removeOneFromCollection; - contextProps.removeAll = contextContext.removeAllFromCollection; - } - - return contextProps; + const openProductModal = openModal; + // const handlePopoverOpen = () => { + // setHoveredCard(card); + // }; + + // const handlePopoverClose = () => { + // setHoveredCard(null); + // }; + + const classes = useStyles(); + const handleCardHover = (cardData) => { + setHoveredCard(cardData); + setIsPopoverOpen(true); // or based on other logic you might have }; - const contextMapping = { - Deck: deckContext, - Cart: cartContext, - Collection: collectionContext, + const CardContentByContext = ({ context }) => { + const price = `Price: ${card?.card_prices?.[0]?.tcgplayer_price}`; + return ( + <> + + {price} + + + {/* Quantity: {contextProps?.deckCardQuantity?.quantityOfSameId || `Not in ${context?.toLowerCase()}`} */} + + + ); }; - - const contextContext = contextMapping[context]; - - const contextProps = getContextSpecificProps(context, contextContext); - - const StoreCardContent = () => ( - <> - - Price: {card?.card_prices?.[0]?.tcgplayer_price} - - - Quantity:{' '} - {contextProps.deckCardQuantity?.quantityOfSameId || 'Not in cart'} - - - ); - - const CartCardContent = () => ( - <> - - Price: {card?.card_prices?.[0]?.tcgplayer_price} - - - Quantity:{' '} - {contextProps.deckCardQuantity?.quantityOfSameId || 'Not in cart'} - - - ); - - const DeckCardContent = () => ( - <> - - Price: {card?.card_prices?.[0]?.tcgplayer_price} - - - Quantity:{' '} - {contextProps?.deckCardQuantity?.quantityOfSameId || 'Not in deck'} - - - ); - + // 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' && } - {context === 'Deck' && } - - - {context === 'Store' || context === 'Cart' ? ( -
-
+ + + {card?.name} + + + + {context === 'Store' || context === 'Cart' ? ( +
-
-
-
- ) : ( - - )} - - {context === 'Deck' && isHovering && ( - - )} - {context === 'Deck' && ( - - )} - {(context === 'Store' || context === 'Cart') && ( - - )} + ) : ( + + {/* */} + + + )} + + ); }; diff --git a/src/components/cards/cardStyles.js b/src/components/cards/cardStyles.js index ade90e2..a818c1a 100644 --- a/src/components/cards/cardStyles.js +++ b/src/components/cards/cardStyles.js @@ -1,17 +1,27 @@ import { makeStyles } from '@mui/styles'; -export const commonStyles = makeStyles({ - // Existing 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: 0.75, + flex: 1, + objectFit: 'cover', }, content: { - flex: 0.25, + flex: 1, + overflow: 'auto', }, button: { margin: '4px', @@ -22,7 +32,6 @@ export const commonStyles = makeStyles({ margin: '8px 0', borderRadius: '8px', }, - // New tooltip styles tooltip: { display: 'none', position: 'absolute', @@ -58,21 +67,17 @@ export const commonStyles = makeStyles({ textAlign: 'center', }, }, -}); +})); export const deckCardStyles = makeStyles({ card: { - position: 'relative', // Add this - display: 'flex', - flexDirection: 'column', + position: 'relative', + maxHeight: 100, height: '100%', width: '100%', - flexGrow: 1, }, content: { - flex: '1 1 auto', overflow: 'hidden', - // padding: theme.spacing(1), }, media: { width: '100%', @@ -95,6 +100,9 @@ export const productCardStyles = makeStyles({ margin: '0 8px', padding: '10px', fontSize: '20px', + '@media(max-width: 600px)': { + fontSize: '16px', + }, }, actionButtons: { padding: '10px', @@ -105,18 +113,16 @@ export const productCardStyles = makeStyles({ // Function to merge common and specific styles export const mergeStyles = (common, specific) => { - return { + const mergedStyles = { ...common, ...specific, + }; + + // Convert the merged styles into valid class names + const classNames = { card: { ...common.card, ...specific.card, - // width: '100%', // <- Set width to 100% of parent container - // '@media (min-width:600px)': { - // // <- Media query example - // width: '80%', - // }, - // }, }, media: { ...common.media, @@ -135,4 +141,8 @@ export const mergeStyles = (common, specific) => { ...specific.actionButtons, }, }; + for (const key in mergedStyles) { + classNames[key] = Object.keys(mergedStyles[key]).join(' '); + } + return classNames; }; diff --git a/src/components/collection/CollectionForm.jsx b/src/components/cleanUp/CollectionForm.jsx similarity index 100% rename from src/components/collection/CollectionForm.jsx rename to src/components/cleanUp/CollectionForm.jsx diff --git a/src/components/content/PortfolioContent.jsx b/src/components/content/PortfolioContent.jsx index e09774a..d7a70fb 100644 --- a/src/components/content/PortfolioContent.jsx +++ b/src/components/content/PortfolioContent.jsx @@ -1,71 +1,68 @@ import React from 'react'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; import { Box } from '@mui/system'; import PortfolioHeader from '../headings/PortfolioHeader'; import PortfolioListContainer from '../../containers/PortfolioListContainer'; -import PortfolioChart from '../other/PortfolioChart'; import { useCollectionStore } from '../../context/hooks/collection'; +import PortfolioChartContainer from '../../containers/PortfolioChartContainer'; const PortfolioContent = ({ error, selectedCards, removeCard }) => { const { selectedCollection } = useCollectionStore(); - // Define a custom theme with a breakpoint at 950px - const theme = createTheme({ - breakpoints: { - values: { - xs: 0, - sm: 600, - md: 950, // Set md breakpoint to 950px - lg: 1280, - xl: 1920, - }, - }, - }); - return ( - + + - + + + - - - - - - + - + ); }; diff --git a/src/components/dialogs/LoginDialog.jsx b/src/components/dialogs/LoginDialog.jsx new file mode 100644 index 0000000..7841f3a --- /dev/null +++ b/src/components/dialogs/LoginDialog.jsx @@ -0,0 +1,111 @@ +// 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, + IconButton, +} from '@mui/material'; +import { AuthContext } from '../../context/Auth/authContext.js'; +import LoginForm from '../forms/LoginForm.jsx'; +import SignupSwitch from '../Auth/SignupSwitch.jsx'; +// import { ThemeContext } from 'styled-components'; +import Brightness4Icon from '@mui/icons-material/Brightness4'; +import Brightness7Icon from '@mui/icons-material/Brightness7'; +import { useMode } from '../../context/hooks/colormode.jsx'; +function LoginDialog({ open, onClose, onLogin }) { + 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 { darkMode, toggleDarkMode } = useContext(ThemeContext); + const { toggleColorMode, mode } = useMode(); + + 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') && + window.location.pathname !== '/profile' + ) { + onLogin(); + // navigate('/profile'); + } + }, [authContext.isloggedin, navigate]); + + return ( + + {signupMode ? 'Sign Up' : 'Login'} + + {!mode ? : } + + + + + + + + + + + {authContext.error &&

{authContext.error}

} + {authContext.isLoading &&

Loading...

} +
+
+ ); +} + +export default LoginDialog; diff --git a/src/components/dialogs/login.jsx b/src/components/dialogs/login.jsx new file mode 100644 index 0000000..e564868 --- /dev/null +++ b/src/components/dialogs/login.jsx @@ -0,0 +1,92 @@ +// // 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/forms/LoginForm.jsx b/src/components/forms/LoginForm.jsx index fecb2d8..ec7f7e0 100644 --- a/src/components/forms/LoginForm.jsx +++ b/src/components/forms/LoginForm.jsx @@ -1,7 +1,6 @@ // LoginForm.js import React from 'react'; -import { TextField, Button } from '@mui/material'; -import styled from 'styled-components'; +import { FormWrapper, StyledTextField, StyledButton } from './styled'; const LoginForm = ({ username, @@ -18,49 +17,40 @@ const LoginForm = ({ handleSubmit, }) => { return ( -
-
- setUsername(e.target.value)} - variant="outlined" - /> - {/* setEmail(e.target.value)} - variant="outlined" - /> */} - setPassword(e.target.value)} - variant="outlined" - /> - {signupMode && ( - <> - setName(e.target.value)} - variant="outlined" - /> - setRoleData(e.target.value)} - variant="outlined" - /> - - )} - -
-
+ + setUsername(e.target.value)} + variant="outlined" + /> + {signupMode && ( + <> + setName(e.target.value)} + variant="outlined" + /> + setRoleData(e.target.value)} + variant="outlined" + /> + + )} + setPassword(e.target.value)} + variant="outlined" + /> + + {signupMode ? 'Sign Up' : 'Login'} + + ); }; diff --git a/src/components/forms/ProfileForm.jsx b/src/components/forms/ProfileForm.jsx new file mode 100644 index 0000000..e071132 --- /dev/null +++ b/src/components/forms/ProfileForm.jsx @@ -0,0 +1,83 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { TextField, Button } from '@mui/material'; + +const ProfileForm = ({ userName, name, age, status, onSave }) => { + const [formData, setFormData] = useState({ + userName: userName || '', // default to empty string if undefined + name: name || '', + age: age || '', + status: status || '', + }); + const handleChange = (e) => { + setFormData({ + ...formData, + [e.target.id]: e.target.value, + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + onSave(formData); + }; + + useEffect(() => { + setFormData({ + userName: userName || '', + name: name || '', + age: age || '', + status: status || '', + }); + }, [userName, name, age, status]); + + return ( +
+ + + + + {/* Add more TextField components as needed */} + + + ); +}; + +ProfileForm.propTypes = { + userName: PropTypes.string, + name: PropTypes.string, + age: PropTypes.string, + status: PropTypes.string, + onSave: PropTypes.func.isRequired, +}; + +export default ProfileForm; diff --git a/src/components/forms/styled.jsx b/src/components/forms/styled.jsx new file mode 100644 index 0000000..f3fd7db --- /dev/null +++ b/src/components/forms/styled.jsx @@ -0,0 +1,22 @@ +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; + } +`; + +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 84b1095..615d311 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -1,144 +1,140 @@ -import React, { useEffect, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { Box, - Button, Typography, Paper, - Grid, - Divider, Container, - useMediaQuery, - Stack, IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableFooter, + TablePagination, + TableRow, + Button, } from '@mui/material'; -import CronTrigger from '../../buttons/CronTrigger'; import { useCollectionStore } from '../../../context/hooks/collection'; import AssessmentIcon from '@mui/icons-material/Assessment'; -const CardList = ({ selectedCards, removeCard }) => { +import TablePaginationActions from './TablePaginationActions'; // Assume the provided pagination component is here +import Logger from './Logger'; + +const CardList = ({ selectedCards }) => { const { getTotalCost, selectedCollection, removeOneFromCollection, addOneToCollection, } = useCollectionStore(); - const isSmScreen = useMediaQuery((theme) => theme.breakpoints.down('sm')); - // console.log('SELECTED COLLECTION:', selectedCollection); - const itemsPerPage = 10; - const [currentPage, setCurrentPage] = useState(1); - const maxPages = Math.ceil(selectedCards?.length / itemsPerPage); + 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 handleChangePage = (event, newPage) => { + // setPage(newPage); + // }; + + // const handleChangeRowsPerPage = (event) => { + // setRowsPerPage(parseInt(event.target.value, 10)); + // setPage(0); + // }; + const cardLogger = new Logger([ + 'Action', + 'Card Name', + 'Quantity', + 'Total Price', + ]); - // Handler to go to the next page - const nextPage = () => { - if (currentPage < maxPages) { - setCurrentPage((prevPage) => prevPage + 1); - } + const handleChangePage = (event, newPage) => { + cardLogger.logCardAction('Change Page', {}); + setPage(newPage); }; - // Handler to go to the previous page - const prevPage = () => { - if (currentPage > 1) { - setCurrentPage((prevPage) => prevPage - 1); - } + const handleChangeRowsPerPage = (event) => { + cardLogger.logCardAction('Change Rows Per Page', {}); + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); }; - const isIdUnique = (id, cards) => { - let count = 0; - for (const card of cards) { - if (card.id === id) count++; - if (count > 1) return false; - } - return true; + const handleRemoveCard = (card) => { + removeOneFromCollection(card, card.id); + cardLogger.logCardAction('Remove Card', card); }; - // useEffect(() => { - // console.log('CardList rendered with selectedCards:', selectedCards); - // }, [selectedCards]); + const handleAddCard = (card) => { + addOneToCollection(card, card.id); + cardLogger.logCardAction('Add Card', card); + }; + + const chartDimensions = useMemo( + () => + chartContainerRef.current?.getBoundingClientRect() || { + width: 400, + height: 600, + }, + [chartContainerRef.current] + ); return ( - - - - {/* Adding an icon next to the title */} - - Cards in Portfolio - - {/* Include the CronTrigger button */} - - - {selectedCards && selectedCards?.length > 0 ? ( - selectedCards - .slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage) - .map((card, index) => { - const key = isIdUnique(card?.id || index, selectedCards) - ? card.id - : `${card.id}-${index}`; - - return ( - - - - {card.name} - - - - - {card.card_prices && - card?.card_prices[0] && - card?.card_prices[0]?.tcgplayer_price - ? `$${card?.card_prices[0]?.tcgplayer_price}` - : 'Price not available'} - - - + + + + Cards in Portfolio + + {/* Include the CronTrigger button */} + + + + {(rowsPerPage > 0 + ? selectedCards.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + : selectedCards + ).map((card, index) => ( + + + {card?.name} + + {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'} + + - - - - - - ); - }) - ) : ( - - No cards selected. - - )} + + + ))} + {emptyRows > 0 && ( + + + + )} + + + + + + +
+
{`Total: $${selectedCollection?.totalPrice}`} - - - - Page {currentPage} of {maxPages} - - -
); diff --git a/src/components/grids/collectionGrids/Logger.jsx b/src/components/grids/collectionGrids/Logger.jsx new file mode 100644 index 0000000..b6a9f1f --- /dev/null +++ b/src/components/grids/collectionGrids/Logger.jsx @@ -0,0 +1,54 @@ +// Define a Logger class +class Logger { + constructor(headers) { + this.headers = headers || []; + this.data = []; + } + + // Add a new row of data + addRow(row) { + if (!this.validateRow(row)) { + console.error('Row data does not match headers.'); + return; + } + this.data.push(row); + } + + // Validate that the row data matches the headers + validateRow(row) { + for (let header of this.headers) { + if (!(header in row)) { + return false; + } + } + return true; + } + + // Log the data as a table + logTable() { + if (this.data.length === 0) { + console.log('No data to display.'); + return; + } + console.table(this.data); + } + + // Clear the logged data + clearData() { + this.data = []; + } + + // Function to log actions specific to CardList events + logCardAction(action, card) { + this.addRow({ + Action: action, + 'Card Name': card?.name || '', + Quantity: card?.quantity || '', + 'Total Price': card?.totalPrice || '', + }); + this.logTable(); + } +} + +// Define a Logger class +export default Logger; diff --git a/src/components/grids/collectionGrids/TablePaginationActions.jsx b/src/components/grids/collectionGrids/TablePaginationActions.jsx new file mode 100644 index 0000000..a8d7193 --- /dev/null +++ b/src/components/grids/collectionGrids/TablePaginationActions.jsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { useTheme } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import FirstPageIcon from '@mui/icons-material/FirstPage'; +import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'; +import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'; +import LastPageIcon from '@mui/icons-material/LastPage'; + +const TablePaginationActions = (props) => { + const theme = useTheme(); + const { count, page, rowsPerPage, onPageChange } = props; + + const handleFirstPageButtonClick = (event) => { + onPageChange(event, 0); + }; + + const handleBackButtonClick = (event) => { + onPageChange(event, page - 1); + }; + + const handleNextButtonClick = (event) => { + onPageChange(event, page + 1); + }; + + const handleLastPageButtonClick = (event) => { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + }; + + return ( + + + {theme.direction === 'rtl' ? : } + + + {theme.direction === 'rtl' ? ( + + ) : ( + + )} + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="next page" + > + {theme.direction === 'rtl' ? ( + + ) : ( + + )} + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="last page" + > + {theme.direction === 'rtl' ? : } + + + ); +}; + +TablePaginationActions.propTypes = { + count: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, + page: PropTypes.number.isRequired, + rowsPerPage: PropTypes.number.isRequired, +}; + +export default TablePaginationActions; diff --git a/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx b/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx index 19cd88c..316ece7 100644 --- a/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx +++ b/src/components/grids/searchResultGrids/DeckSearchCardGrid.jsx @@ -1,68 +1,162 @@ -import React from 'react'; -import { Grid, useMediaQuery, useTheme } from '@mui/material'; -// import { useTheme } from '@emotion/react'; -import DeckCard from '../../cleanUp/DeckCard'; +import React, { useEffect, useRef, useState } 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 LoadingIndicator from '../../indicators/LoadingIndicator'; const useStyles = makeStyles((theme) => ({ - card: { - position: 'relative', // Add this + cardContainer: { display: 'flex', - flexDirection: 'column', - height: '100%', - width: '100%', - flexGrow: 1, + 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 }, - media: { + card: { width: '100%', - objectFit: 'contain', - }, - content: { - flex: '1 1 auto', - overflow: 'hidden', - // padding: theme.spacing(1), - }, - text: { - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', + // transform: 'scale(0.9)', // scales down to 90% of the original size }, - 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 + loading: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100vh', }, })); const DeckSearchCardGrid = ({ cards, userDecks }) => { - const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const classes = useStyles(); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme?.breakpoints?.down('sm')); + const [hoveredCard, setHoveredCard] = useState(null); + const [isPopoverOpen, setIsPopoverOpen] = 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); + }, []); + + if (isLoading) { + return ( + + + + ); + } return ( - - {cards.map((card, i) => ( - - - - ))} - + + + {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; diff --git a/src/components/headings/footer/Footer.js b/src/components/headings/footer/Footer.js index 3e0b580..812d5cd 100644 --- a/src/components/headings/footer/Footer.js +++ b/src/components/headings/footer/Footer.js @@ -44,8 +44,8 @@ const Footer = () => { return ( - © {new Date().getFullYear()} Reed Vogt | Designed with ❤️ by{' '} - Your Portfolio + © {new Date().getFullYear()} Reed Vogt | Designed with ❤️ by + ReedVogt.com ); diff --git a/src/components/headings/header/Header.js b/src/components/headings/header/Header.js index 043cf8a..717d265 100644 --- a/src/components/headings/header/Header.js +++ b/src/components/headings/header/Header.js @@ -1,138 +1,135 @@ -import React, { useContext, useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import { useAuthContext } from '../../../context/hooks/auth'; +import TopBar from '../navigation/TopBar'; +import SideBar from '../navigation/SideBar'; +import { useSidebarContext } from '../../../context/SideBarProvider'; import { - AppBar, - Box, - Toolbar, - IconButton, - Typography, - Drawer, - Container, - Menu, - useMediaQuery, - useTheme, -} from '@mui/material'; -import { Link } from 'react-router-dom'; -import MenuIcon from '@mui/icons-material/Menu'; -import { AuthContext } from '../../../context/Auth/authContext'; -import MenuItems from '../navigation/MenuItems'; -import theme from '../../../assets/styles/themes'; -import logo from '../../../assets/navlogo.png'; -import { Image } from '@mui/icons-material'; + 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'; const Header = () => { - const [isOpen, setIsOpen] = useState(false); - const { isLoggedIn, logout } = useContext(AuthContext); - const [anchorElNav, setAnchorElNav] = useState(null); - - // Adjusted the breakpoint to 'xs' to switch to Drawer for smaller screens - const isXsDown = useMediaQuery(theme.breakpoints.down('xs')); - - const handleDrawerOpen = () => setIsOpen(true); - const handleDrawerClose = () => setIsOpen(false); + 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 handleCloseNavMenu = () => { - setAnchorElNav(null); + const handleDrawerClose = () => { + setIsOpen(false); }; - const handleOpenNavMenu = (event) => { - setAnchorElNav(event.currentTarget); + + const updateView = () => { + setIsMobileView(window.innerWidth <= 598); }; - // const currentPage = window.location.pathname; - // console.log('CURRENT PAGE:', currentPage); + useEffect(() => { + window.addEventListener('resize', updateView); + if (!isMobileView && isOpen) { + handleDrawerClose(); + } + if (!isMobileView && !isOpen) { + handleDrawerClose(); + } + if (isMobileView && isOpen) { + handleDrawerOpen(); + } + if (isMobileView && !isOpen) { + handleDrawerClose(); + } + return () => window.removeEventListener('resize', updateView); + }, [isMobileView]); return ( - - - + + {isMobileView && ( // Only render SideBar in mobile view + - - - - - - - Logo - - - - - {isXsDown ? ( - - - - ) : ( - - - - )} - - - - + + + )} + ); }; - export default Header; - -{ - /* */ -} diff --git a/src/components/headings/navigation/MenuItems.js b/src/components/headings/navigation/MenuItems.js index 85647f0..4a1c397 100644 --- a/src/components/headings/navigation/MenuItems.js +++ b/src/components/headings/navigation/MenuItems.js @@ -1,29 +1,34 @@ -import HomeIcon from '@mui/icons-material/Home'; -import StoreIcon from '@mui/icons-material/Store'; -import CartIcon from '@mui/icons-material/ShoppingCart'; -import LogoutIcon from '@mui/icons-material/Logout'; -import AccountCircleIcon from '@mui/icons-material/AccountCircle'; -import { Link, useLocation } from 'react-router-dom'; -import Login from '../../Auth/login'; - -import MenuItem from '@mui/material/MenuItem'; -import { styled } from '@mui/system'; -// import CartModal from '../../modals/CartModal'; -// import Stripe from '../../Stripe'; +import React, { useEffect, useRef, useState } from 'react'; import { - Button, + MenuItem, + Box, + Typography, Dialog, DialogContent, DialogTitle, DialogActions, - Box, - Typography, + Button, + List, + Divider, + ListItemIcon, + useTheme, } from '@mui/material'; -import DeckOfCardsIcon from '../../icons/DeckOfCardsIcon'; -// import TestingIcon from '../../icons/TestingIcon'; -import theme from '../../../assets/styles/themes'; -import AssessmentIcon from '@mui/icons-material/Assessment'; -import { useEffect, useRef } from 'react'; +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 { AuthContext } from '../../../context/Auth/authContext'; +import { StyledListItem, StyledTypography } from './styled'; + const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ '& .MuiTypography-root': { fontSize: '1.2em', @@ -32,7 +37,6 @@ const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ '& .MuiSvgIcon-root': { marginRight: theme.spacing(1), }, - justifyContent: 'right', })); const StyledLink = styled(Link)(({ theme }) => ({ @@ -44,84 +48,145 @@ const StyledLink = styled(Link)(({ theme }) => ({ height: '100%', })); -// const StyledHomeIcon = styled(HomeIcon)({ -// fontSize: '1em', // or any other value you want -// }); - -// Style the container of the Logout label and icon -const LogoutContainer = styled(Box)({ +const MenuBox = styled(Box)(({ theme }) => ({ + padding: (props) => (props.isLoggedIn ? '1em' : '2em'), display: 'flex', - alignItems: 'center', - cursor: 'pointer', -}); + flexDirection: 'column', + [theme.breakpoints.up('sm')]: { + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', + }, +})); -// console.log('theme', theme); +const menuItemsData = [ + { name: 'Home', icon: , to: '/home', requiresLogin: false }, + { name: 'Store', icon: , to: '/store', requiresLogin: true }, + { + name: 'Deck Builder', + icon: , + to: '/deckbuilder', + requiresLogin: false, + // requiresLogin: true, + }, + { name: 'Cart', icon: , to: '/cart', requiresLogin: true }, + { + name: 'Collection', + icon: , + to: '/collection', + requiresLogin: true, + }, +]; -function MenuItems({ - isLoggedIn, - logout, +const MenuItems = ({ handleDrawerClose, - handleCloseNavMenu, -}) { - const location = useLocation(); // get current location - const previousPageRef = useRef(); // useRef to track the previous page + 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 useEffect(() => { if (location.pathname !== previousPageRef.current) { - // only log if the current page is different from the previously logged page console.log('CURRENT PAGE:', location.pathname); - previousPageRef.current = location.pathname; // update ref with current page + 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); + }; + + const handleLoginDialogClose = () => { + setIsLoginDialogOpen(false); + }; + + const handleLoginSuccess = () => { + setIsLoginDialogOpen(false); + // Handle additional logic upon successful login if needed + }; + useEffect(() => { + if (location.pathname !== previousPageRef.current) { + console.log('CURRENT PAGE:', location.pathname); + previousPageRef.current = location.pathname; + } + }, [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; + }); + }; + return ( - <> - - Home - - {isLoggedIn && ( - <> - - Store - - - Deck Builder +
+ + {renderMenuItems(menuItemsData)} + + {isloggedin ? ( + + Logout - - )} - - Cart - - - Collection - - {isLoggedIn ? ( - - - {/* Use the LogoutContainer component to wrap the LogoutIcon and the label */} - - - Logout - + ) : ( + + Login - - ) : ( - - Login - - - - - - - - - )} - + )} + + +
); -} +}; export default MenuItems; diff --git a/src/components/headings/navigation/SideBar.jsx b/src/components/headings/navigation/SideBar.jsx new file mode 100644 index 0000000..979346c --- /dev/null +++ b/src/components/headings/navigation/SideBar.jsx @@ -0,0 +1,152 @@ +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); + + const handleLoginClick = () => { + setIsLoginDialogOpen(true); + }; + + const handleItemClick = (name) => { + setSelected(name); + handleDrawerClose(); + }; + + if (!isMobileView) { + handleDrawerClose(); + } + + 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)} + /> +
+ //
+ //
+ //
+ //
+ ); +}; + +export default SideBar; diff --git a/src/components/headings/navigation/TopBar.jsx b/src/components/headings/navigation/TopBar.jsx new file mode 100644 index 0000000..ce6ba86 --- /dev/null +++ b/src/components/headings/navigation/TopBar.jsx @@ -0,0 +1,116 @@ +import React, { useState } from 'react'; +import { + AppBar, + Toolbar, + IconButton, + Typography, + Box, + Container, + List, + Divider, + ListItemIcon, + useTheme, + createTheme, +} 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'; + +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 handleItemClick = (name) => { + setSelected(name); + handleDrawerClose(); + }; + if (!isMobileView) { + handleDrawerClose(); + } + return ( + + + + + {isMobileView && ( + + + + )} + + Logo + SiteName + + + {!isMobileView && ( + + )} + + + + + ); +}; + +export default TopBar; diff --git a/src/components/headings/navigation/styled.js b/src/components/headings/navigation/styled.js new file mode 100644 index 0000000..3e8726c --- /dev/null +++ b/src/components/headings/navigation/styled.js @@ -0,0 +1,48 @@ +import { Box, styled } from '@mui/system'; +import { + AppBar, + Drawer, + IconButton, + ListItem, + Toolbar, + Typography, +} from '@mui/material'; +export const StyledDrawer = styled(Drawer)(({ theme, bgColor }) => ({ + '.MuiPaper-root': { + backgroundColor: bgColor || theme.palette.background.paper, + }, +})); + +export const StyledListItem = styled(ListItem)(({ theme }) => ({ + cursor: 'pointer', + '&:hover': { + backgroundColor: theme.palette.action, + }, +})); + +export const StyledTypography = styled(Typography)(({ theme }) => ({ + margin: theme.spacing(1, 2), +})); + +export const StyledAppBar = styled(AppBar)(({ theme }) => ({ + // backgroundColor: theme.palette.primary.main, + padding: theme.spacing(0, 4), +})); + +export const StyledToolbar = styled(Toolbar)(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + maxWidth: '100vw', +})); + +export const StyledIconButton = styled(IconButton)(({ theme }) => ({ + color: theme.palette.text, +})); + +export const StyledBox = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + flexDirection: 'row', +})); diff --git a/src/components/indicators/ErrorIndicator.js b/src/components/indicators/ErrorIndicator.js index 3fe7102..f19f6e1 100644 --- a/src/components/indicators/ErrorIndicator.js +++ b/src/components/indicators/ErrorIndicator.js @@ -4,6 +4,7 @@ import Typography from '@mui/material/Typography'; 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: { @@ -33,7 +34,10 @@ const ErrorIndicator = ({ error }) => { - Error: {error} + + Error + This is an error alert — Error: {error} + diff --git a/src/components/indicators/LoadingIndicator.js b/src/components/indicators/LoadingIndicator.js index 5cafec2..3d62b11 100644 --- a/src/components/indicators/LoadingIndicator.js +++ b/src/components/indicators/LoadingIndicator.js @@ -1,18 +1,12 @@ -import React from 'react'; -import { BeatLoader } from 'react-spinners'; +import * as React from 'react'; +import CircularProgress from '@mui/material/CircularProgress'; +import Box from '@mui/material/Box'; const LoadingIndicator = () => { return ( -
- -
+ + + ); }; diff --git a/src/components/media/CardMediaSection.js b/src/components/media/CardMediaSection.js index d3755ce..b2d3066 100644 --- a/src/components/media/CardMediaSection.js +++ b/src/components/media/CardMediaSection.js @@ -1,7 +1,9 @@ import React, { useState, useEffect } from 'react'; import ReusableCardMedia from './CardMedia'; -import placeholderImage from '../../assets/placeholder.jpeg'; +import placeholderImage from '../../assets/images/placeholder.jpeg'; import { makeStyles } from '@mui/styles'; +import { Popover } from '@mui/material'; +import CardToolTip from '../cards/CardToolTip'; const useStyles = makeStyles({ mediaContainer: { @@ -16,11 +18,15 @@ const useStyles = makeStyles({ }); const CardMediaSection = ({ - imgUrl, + imgUrl = placeholderImage, card, openModal, - setHovering, cardRef, + modalIsOpen, + onCardHover, + cardData, + setIsPopoverOpen, + isPopoverOpen, }) => { const classes = useStyles(); const [hasLoggedCard, setHasLoggedCard] = useState(false); @@ -32,17 +38,68 @@ const CardMediaSection = ({ } }, [hasLoggedCard, card]); + // useEffect(() => { + // if (!modalIsOpen) { + // setIsPopoverOpen(false); + // } + // }, [modalIsOpen]); + + const open = Boolean(cardData === card) && !modalIsOpen; + + const handleMouseEnter = () => { + if (!modalIsOpen && typeof onCardHover === 'function') { + onCardHover(card); + } + }; + + const handleMouseLeave = () => { + if (!modalIsOpen && typeof onCardHover === 'function') { + onCardHover(null); + // setIsPopoverOpen(false); + } + }; + + // const handleClick = () => { + // // Close the popover and open the modal + // setIsPopoverOpen(false); + // openModal(); + // }; + return ( -
+
setHovering(true)} - // onMouseOut={() => setHovering(false)} + onClick={() => { + openModal(); + setIsPopoverOpen(false); + }} >
+ + onCardHover(null)} + disableRestoreFocus + > + +
); }; export default CardMediaSection; + +CardMediaSection.defaultProps = { + // eslint-disable-next-line @typescript-eslint/no-empty-function + onCardHover: () => {}, // provide a no-op function as default +}; diff --git a/src/components/modals/GenericCardModal.jsx b/src/components/modals/GenericCardModal.jsx index 643b83e..6b24f36 100644 --- a/src/components/modals/GenericCardModal.jsx +++ b/src/components/modals/GenericCardModal.jsx @@ -64,7 +64,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -const GenericCardModal = ({ isOpen, onClose, card, cardInfo, context }) => { +const GenericCardModal = ({ open, onClose, card, cardInfo, context }) => { const classes = useStyles(); const deckContext = useContext(DeckContext); const cartContext = useContext(CartContext); @@ -119,17 +119,17 @@ const GenericCardModal = ({ isOpen, onClose, card, cardInfo, context }) => { } }; - console.log('openChooseCollectionDialog', openChooseCollectionDialog); + // console.log('openChooseCollectionDialog', openChooseCollectionDialog); useEffect(() => { - if (openChooseCollectionDialog) { + if (openChooseCollectionDialog === true) { console.log('Fetching collections...', openChooseCollectionDialog); fetchAllCollectionsForUser(); } - }); - + }, [openChooseCollectionDialog]); + console.log('open --------> ', open); return ( - + {card?.name} @@ -176,7 +176,7 @@ const GenericCardModal = ({ isOpen, onClose, card, cardInfo, context }) => { )} {openChooseCollectionDialog && ( setOpenChooseCollectionDialog(false)} > Select a Collection diff --git a/src/components/other/LinearChart.js b/src/components/other/LinearChart.js index 10308d8..35cb65b 100644 --- a/src/components/other/LinearChart.js +++ b/src/components/other/LinearChart.js @@ -1,11 +1,9 @@ -import React, { useMemo, useState } from 'react'; +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 CircularProgress from '@mui/material/CircularProgress'; import Box from '@mui/material/Box'; import Tooltip from '@mui/material/Tooltip'; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; const useStyles = makeStyles((theme) => ({ chartContainer: { @@ -49,10 +47,15 @@ const useStyles = makeStyles((theme) => ({ const CustomTooltip = ({ point }) => { const theme = useTheme(); - const { serieId } = point; - const label = point?.data?.label; - const x = point?.data?.xFormatted; - const y = point?.data?.yFormatted; + const { serieId, data } = point; + const { label, xFormatted, yFormatted } = data || {}; + const series = { + type: { + Collection: 'Collection', + Card: 'Card', + Deck: 'Deck', + }, + }; return ( { {`Series: ${serieId}`} - - {`Card: ${label}`} - + {series.type[label] === 'Card' && ( + + {`Card: ${label}`} + + )} - {`Time: ${new Date(point?.data?.x || x).toLocaleString()}`} + {`Time: ${new Date(xFormatted).toLocaleString()}`} - {`Value: $${point?.data?.y?.toFixed(2) || y}`} + {`Value: $${parseFloat(yFormatted).toFixed(2)}`} ); }; -// const LinearChart = ({ data = [], dimensions, loading, error }) => { -const LinearChart = ({ data, latestData, dimensions }) => { +const roundToNearestTenth = (value) => { + return Math.round(value * 10) / 10; +}; + +const getFilteredData = (data, timeRange) => { + const cutOffTime = new Date().getTime() - timeRange; + return data + .filter((d) => new Date(d.x).getTime() >= cutOffTime) + .map((d) => ({ ...d, y: roundToNearestTenth(d.y) })); +}; + +const useMovingAverage = (data, numberOfPricePoints) => { + return useMemo(() => { + if (!Array.isArray(data)) { + 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: row.x, + y: sum / subset.length || 0, + }; + }); + }, [data, numberOfPricePoints]); +}; + +const getAveragedData = (filteredData) => { + const averaged = useMovingAverage(filteredData, 6); + return useMemo(() => { + return averaged; + }, [averaged]); +}; + +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 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 LinearChart = ({ + data, + latestData, + dimensions, + timeRanges, + timeRange, +}) => { const classes = useStyles(); const theme = useTheme(); const [isZoomed, setIsZoomed] = useState(false); - const [hoveredData, setHoveredData] = useState(null); + const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); - // Ensure data is an array - if (!Array.isArray(data) || data.some((d) => !d.x || !d.y)) { + const isValidDataPoint = (d) => d && 'x' in d && 'y' in d; + if (!Array.isArray(data) || !data.every(isValidDataPoint)) { return No valid data available; } - // console.log('datasets', data); - // console.log('latestData', latestData); - - const LinearChartData = useMemo(() => { - return [ - { - id: 'Dataset', - data: data?.map((d) => ({ - x: new Date(d?.x), - y: parseFloat(d?.y), - })), - }, - ]; + + const filteredData = useMemo( + () => getFilteredData(data, timeRange), + [data, timeRange] + ); + const averagedData = getAveragedData(filteredData); + // console.log('averagedData', averagedData); + const tickValues = useMemo( + () => getTickValues(timeRange, timeRanges), + [timeRange] + ); + + const lastData = useMemo(() => { + if (data && data.length) { + return { + x: data[data.length - 1].x, + y: data[data.length - 1].y, + }; + } + return {}; }, [data]); - // console.log('chartData', chartData); return (
{ }} yScale={{ type: 'linear', min: 'auto', max: 'auto' }} axisBottom={{ - tickValues: 'every 1 minute', + tickValues: tickValues, + tickSize: 5, tickPadding: 5, tickRotation: 0, @@ -147,13 +231,15 @@ const LinearChart = ({ data, latestData, dimensions }) => { pointLabelYOffset={-12} pointSize={6} pointBorderWidth={1} - pointBorderColor={{ from: 'color', modifiers: [['darker', 0.7]] }} + // 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: { - border: '1px solid #bbb', - transition: 'all 250ms', + ...classes.customPoint, // Added for improved style + border: '1px solid ' + theme.palette.secondary.main, }, tooltip: { container: { @@ -172,38 +258,58 @@ const LinearChart = ({ data, latestData, dimensions }) => { 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)} - onMouseMove={(point) => { + tooltip={({ point }) => } + sliceTooltip={({ slice }) => { + const point = slice.points.find( + (p) => p.id === 'Dataset' && p.data.x === lastData.x + ); if (point) { - setHoveredData({ - x: point?.data?.x, - y: point?.data?.y, - }); + return ; } + return null; }} - onMouseLeave={() => setHoveredData(null)} - tooltip={({ point }) => } /> {hoveredData - ? new Date(hoveredData?.x).toLocaleString() + ? new Date(hoveredData.x).toLocaleString() : latestData?.x} - {`$${hoveredData ? hoveredData?.y : latestData?.y}`} + {`$${hoveredData ? hoveredData.y : latestData?.y}`}
diff --git a/src/components/other/PortfolioChart.jsx b/src/components/other/PortfolioChart.jsx index d4d77c0..a116633 100644 --- a/src/components/other/PortfolioChart.jsx +++ b/src/components/other/PortfolioChart.jsx @@ -1,67 +1,160 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { Grid } from '@mui/material'; +import { + Container, + Grid, + MenuItem, + Paper, + Select, + debounce, +} from '@mui/material'; import { styled } from '@mui/system'; import LinearChart from './LinearChart'; -import useUpdateChartData from '../../context/cleanUp/useUpdateChartData'; import { useCollectionStore } from '../../context/hooks/collection'; +import TimeRangeSelector, { timeRanges } from './TimeRangeSelector'; +import { useChartContext } from '../../context/ChartContext/ChartContext'; +import ErrorBoundary from '../../context/ErrorBoundary'; const ChartPaper = styled('div')(({ theme }) => ({ borderRadius: '12px', - width: '100%', + // 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 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] = useState(null); + const { latestData, setLatestData, timeRange, setTimeRange } = + useChartContext(); const [lastUpdateTime, setLastUpdateTime] = useState(null); const chartContainerRef = useRef(null); const { selectedCollection } = useCollectionStore(); - const datasets = selectedCollection?.chartData?.allXYValues; + const datasets = selectedCollection?.chartData?.allXYValues || []; + const datasets2 = useMemo(() => groupAndAverageData(datasets), [datasets]); - useEffect(() => { - if (!datasets) return; - const currentTime = new Date().getTime(); + // Debounced function for setting the last update time + const debouncedSetLastUpdateTime = useMemo( + () => + debounce(() => { + const currentTime = new Date().getTime(); - if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { - setLastUpdateTime(currentTime); + if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { + setLastUpdateTime(currentTime); - const lastDatasetIndex = datasets.length - 1; - const lastDataset = datasets[lastDatasetIndex]; - lastDataset && setLatestData(lastDataset); - } - // If you intend to add timeouts or intervals later, ensure you add the cleanup - // return () => { - // clearTimeout(yourTimeout); - // }; - }, [datasets, lastUpdateTime]); + const lastDatasetIndex = datasets.length - 1; + const lastDataset = datasets[lastDatasetIndex]; + lastDataset && setLatestData(lastDataset); + } + }, 100), + [datasets, lastUpdateTime] + ); + + useEffect(() => { + if (!datasets) return; + debouncedSetLastUpdateTime(); + }, [datasets, debouncedSetLastUpdateTime]); const chartDimensions = useMemo( () => chartContainerRef.current?.getBoundingClientRect() || { width: 400, - height: 500, + height: 600, }, [chartContainerRef.current] ); return ( - - - {datasets && datasets?.length > 0 ? ( - - ) : ( -
No data available
- )} -
-
+ + + {/* */} + setTimeRange(e.target.value)} + /> + + + {datasets2.length > 0 ? ( + + ) : ( +
No data available
+ )} +
+
+ {/*
*/} +
+
); }; diff --git a/src/components/other/TimeRangeSelector.jsx b/src/components/other/TimeRangeSelector.jsx new file mode 100644 index 0000000..330f24a --- /dev/null +++ b/src/components/other/TimeRangeSelector.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { MenuItem, Select } from '@mui/material'; + +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 }, +]; + +const TimeRangeSelector = ({ value, onChange }) => ( + +); + +export default TimeRangeSelector; diff --git a/src/components/reusable/HeaderTitle.jsx b/src/components/reusable/HeaderTitle.jsx index f1727db..9d0559a 100644 --- a/src/components/reusable/HeaderTitle.jsx +++ b/src/components/reusable/HeaderTitle.jsx @@ -1,45 +1,39 @@ -import { Typography } from '@mui/material'; -import { Container } from '@mui/system'; +// components/reusable/HeaderTitle.jsx +import { Typography, Container } from '@mui/material'; import React from 'react'; -import theme from '../../assets/styles/themes'; -import { themeSettings } from '../../theme'; -const HeaderTitle = ({ title, size, location }) => { - const fontSize = - { - huge: '2.5rem', - large: '1.75rem', - medium: '1.5rem', - small: '1.25rem', - tiny: '1rem', - extraSmall: '0.75rem', - }[size] || '0.75rem'; // Default to 'extraSmall' size +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', + }; const textAlign = { left: 'left', center: 'center', right: 'right', - }[location]; // Default to 'left' justify + }; - // console.log('theme', theme); - // console.log('theme', themeSettings); return ( theme.palette.background, - padding: (theme) => theme.spacing(2), + backgroundColor: (theme) => theme.palette.background.default, + padding: 2, }} - // justifyContent={justifyContent} > theme.palette.contrastText, + color: (theme) => theme.palette.text.primary, }} > {title} diff --git a/src/components/reusable/Subheader.jsx b/src/components/reusable/Subheader.jsx new file mode 100644 index 0000000..6c5ca78 --- /dev/null +++ b/src/components/reusable/Subheader.jsx @@ -0,0 +1,41 @@ +// Subheader.jsx +import { Typography, Container } from '@mui/material'; +import React from 'react'; + +const Subheader = ({ subtitle, size = 'medium', location = 'left' }) => { + const fontSize = + { + large: '1.5rem', + medium: '1.2rem', + small: '1rem', + extraSmall: '0.85rem', + }[size] || '1rem'; // Default to 'medium' size + + const textAlign = { + left: 'left', + center: 'center', + right: 'right', + }[location]; // Default to 'left' justify + + return ( + theme.spacing(1), + }} + > + theme.palette.text.secondary, + }} + > + {subtitle} + + + ); +}; + +export default Subheader; diff --git a/src/containers/PortfolioChartContainer.jsx b/src/containers/PortfolioChartContainer.jsx new file mode 100644 index 0000000..93332ac --- /dev/null +++ b/src/containers/PortfolioChartContainer.jsx @@ -0,0 +1,26 @@ +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 + +const PortfolioChartContainer = ({ selectedCards, removeCard }) => { + return ( + + + + + + ); +}; + +export default PortfolioChartContainer; diff --git a/src/containers/PortfolioListContainer.jsx b/src/containers/PortfolioListContainer.jsx index e8c0b72..4865b33 100644 --- a/src/containers/PortfolioListContainer.jsx +++ b/src/containers/PortfolioListContainer.jsx @@ -1,26 +1,22 @@ -// Import dependencies import React from 'react'; import { Grid, Paper } from '@mui/material'; -import { makeStyles } from '@mui/styles'; import CardList from '../components/grids/collectionGrids/CardList'; -const useStyles = makeStyles((theme) => ({ - listPaper: { - background: - 'linear-gradient(45deg, rgba(255,255,255,1) 30%, rgba(204,204,204,1) 100%)', - borderRadius: '12px', - padding: '20px', - height: '100%', - width: '100%', - boxShadow: '0 3px 5px 2px rgba(0, 0, 0, .3)', - }, -})); - const PortfolioListContainer = ({ selectedCards, removeCard }) => { - const classes = useStyles(); return ( - + diff --git a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js index ae12f32..7efbedd 100644 --- a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js +++ b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js @@ -64,7 +64,8 @@ import { styled } from '@mui/system'; // Use @mui/system for Emotion styling // Define your components using the styled function from @mui/system const SearchGrid = styled(Grid)(({ theme }) => ({ - [theme.breakpoints.up('lg')]: { flexBasis: '35%' }, + [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%' }, })); @@ -85,17 +86,33 @@ const RootGrid = styled(Grid)(({ theme }) => ({ 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 (isSmallScreen || isMediumScreen) return 5; - return 3; + // 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; + // if (isSmallScreen || isMediumScreen) return 7; + // return 9; + if (isLargeScreen) return 9; + if (isMediumScreen) return 8; + return 7; }; return ( diff --git a/src/context/Auth/authContext.js b/src/context/Auth/authContext.js index a83eca5..45f009f 100644 --- a/src/context/Auth/authContext.js +++ b/src/context/Auth/authContext.js @@ -8,17 +8,21 @@ export const AuthContext = React.createContext(); export default function AuthProvider(props) { const [cookies, setCookie, removeCookie] = useCookies(['auth', 'userCookie']); const [isLoading, setIsLoading] = useState(false); - const [isLoggedIn, setIsLoggedIn] = useState(false); - const [user, setUser] = useState({ capabilities: [] }); + const [isloggedin, setIsloggedin] = useState(false); + const [user, setUser] = useState({ capabilities: ['admin'] }); const [error, setError] = useState(null); const [token, setToken] = useState(undefined); const REACT_APP_SERVER = process.env.REACT_APP_SERVER; const setLoginState = (loggedIn, token, user, error = null) => { - setCookie('auth', token, { secure: true, sameSite: 'strict' }); + setCookie('auth', token, { + secure: true, + sameSite: 'strict', + capabilities: user.capabilities || ['admin'], + }); setCookie('userCookie', user, { secure: true, sameSite: 'strict' }); - setIsLoggedIn(loggedIn); + setIsloggedin(loggedIn); // Updated here setToken(token); setUser(user); setError(error); @@ -46,7 +50,11 @@ export default function AuthProvider(props) { } ); if (response.status === 200) { - setUser((prevUser) => ({ ...prevUser, ...response.data })); + setUser((prevUser) => ({ + ...prevUser, + ...response.data, + ...user.capabilities, + })); } } catch (e) { setError('Token validation failed'); @@ -112,7 +120,7 @@ export default function AuthProvider(props) { const logout = () => { removeCookie('auth'); removeCookie('userCookie'); - localStorage.removeItem('isLoggedIn'); + localStorage.removeItem('isloggedin'); setLoginState(false, null, {}); }; @@ -126,7 +134,7 @@ export default function AuthProvider(props) { { - const [chartData, setChartData] = useState({}); - const [{ userCookie }] = useCookies(['userCookie']); - const userId = userCookie?.id; - const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/chart-data`; - const { isCronJobTriggered, setIsCronJobTriggered } = - useContext(UtilityContext); - - const fetchData = async () => { - if (!userId) return; - try { - const response = await axios.get(`${BASE_API_URL}/${userId}`); - setChartData(response.data); - } catch (error) { - console.error('Error fetching updated data:', error); - // More informative error handling can be added here - } - }; - - // Effect for initial data fetch or when cron job triggers - // useEffect(() => { - // if (isUpdated) { - // fetchData(); - // setIsUpdated(false); - // } - // }, [isUpdated, userId]); - - const updateServerData = async (updatedData) => { - if (!userId) return; - - const reducedData = updatedData?.reduce((accumulator, currentObject) => { - if (currentObject?.data && currentObject?.data?.length > 0) { - accumulator = [...accumulator, ...currentObject.data]; - } - return accumulator; - }, []); - - // Filter out identical data - const uniqueData = Array.from( - new Set(reducedData?.map(JSON.stringify)) - ).map(JSON.parse); - - try { - await axios.post(`${BASE_API_URL}/updateChart/${userId}`, { - data: uniqueData, - }); - setChartData({ uniqueData }); - setIsCronJobTriggered(true); - } catch (error) { - console.error('Error updating server data:', error); - // More informative error handling can be added here - } - }; +export const useChartContext = () => { + const context = useContext(ChartContext); + if (!context) { + throw new Error('useChartContext must be used within a ChartProvider'); + } + return context; +}; - useEffect(() => { - if (isCronJobTriggered) { - fetchData(); - setIsCronJobTriggered(false); - } - }, [isCronJobTriggered, userId]); +export const ChartProvider = ({ children }) => { + const [latestData, setLatestData] = useState(null); + const [timeRange, setTimeRange] = useState(24 * 60 * 60 * 1000); // Default to 24 hours return ( {children} diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index 7b72472..e54c1e9 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -25,10 +25,13 @@ import moment from 'moment'; // 1. Define a default context value const defaultContextValue = { allCollections: [], + xy: [], selectedCollection: {}, collectionData: initialCollectionState, totalCost: 0, openChooseCollectionDialog: false, + updatedPricesFromCombinedContext: {}, + setUpdatedPricesFromCombinedContext: () => {}, setOpenChooseCollectionDialog: () => {}, calculateTotalPrice: () => {}, getTotalCost: () => {}, @@ -43,8 +46,9 @@ const defaultContextValue = { // 2. Replace null with the default value when creating the context export const CollectionContext = createContext(defaultContextValue); + const filterOutDuplicateYValues = (datasets) => { - console.log('DATASETS:', datasets); + // console.log('DATASETS:', datasets); const seenYValues = new Set(); return datasets?.filter((data) => { const yValue = data?.y; @@ -78,6 +82,36 @@ const transformChartData = (chartData) => { return pointsArray; }; +function convertData(originalData) { + // Initialize the finalDataForChart array to hold the xy data. + let finalDataForChart = []; + + // Extract the datasets array from the originalData + const { datasets } = originalData; + + // Check if datasets exists and is an array with at least one element + if (Array.isArray(datasets) && datasets.length > 0) { + // Get the last object in the datasets array + const lastDataset = datasets[datasets.length - 1]; + + // Check if data exists and is an array with at least one element + if (Array.isArray(lastDataset.data) && lastDataset.data.length > 0) { + // Get xy data from each data object in the lastDataset.data array and push it to finalDataForChart + lastDataset.data.forEach((dataObj) => { + if (dataObj.xy) { + finalDataForChart.push(dataObj.xy); + } + }); + } + } + + // Return a new object containing the original data plus the new finalDataForChart array. + return { + originalData, // (or ...originalData) depending on your use-case + finalDataForChart, + }; +} + const createDataset = (label, priceData) => ({ name: label, color: 'blue', @@ -122,11 +156,18 @@ const handleCardRemoval = (currentCards, cardToRemove) => { }; export const CollectionProvider = ({ children }) => { + // const { cardPrices } = useCombinedContext(); const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`; const [cookies] = useCookies(['userCookie']); const { triggerCronJob } = useUserContext(); const [collectionData, setCollectionData] = useState(initialCollectionState); const [allCollections, setAllCollections] = useState([]); + const [xyData, setXyData] = useState({}); // New state to hold xy data + // const [updatedPrices, setUpdatedPrices] = useState([]); + const [ + updatedPricesFromCombinedContext, + setUpdatedPricesFromCombinedContext, + ] = useState({}); const [selectedCollection, setSelectedCollection] = useState({}); const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = useState(false); @@ -229,21 +270,23 @@ export const CollectionProvider = ({ children }) => { return; } - console.log('COLLECTION:', collection); - console.log('NAME:', name); - console.log('DESCRIPTION:', description); - console.log('SELECTED COLLECTION:', selectedCollection); + // console.log('COLLECTION:', collection); + // console.log('NAME:', name); + // console.log('DESCRIPTION:', description); + // console.log('SELECTED COLLECTION:', selectedCollection); try { const url = `${BASE_API_URL}/${collection.userId}/collections/newCollection/${collection.userId}`; + console.log('XYDATASET-----2:', xyData); const initialData = { - name: collection?.name || '', - description: collection?.description || '', - userId: collection?.userId, + name: collection?.name || name, + description: collection?.description || description, + userId: collection?.userId || userId, totalCost: 0, totalPrice: 0, quantity: 0, + xy: xyData || [], totalQuantity: 0, allCardPrices: [], cards: [], @@ -255,12 +298,10 @@ export const CollectionProvider = ({ children }) => { }; console.log('PAYLOAD:', paylaod); const response = await fetchWrapper(url, 'POST', initialData); - if (response.error) { - console.error('Failed to create a new collection:', response.error); - return; - } - const { savedCollection } = response; + const { message, newCollection } = response.data; + console.log('RESPONSE FOR CREATING COLLECTION:', message); + const savedCollection = { ...newCollection }; if (!savedCollection._id) { // Check if savedCollection has _id throw new Error('Response does not contain saved collection with _id'); @@ -327,18 +368,41 @@ export const CollectionProvider = ({ children }) => { }); }; - const getNewChartData = (activeCollection, updatedPrice, newDataSet) => ({ - name: `Chart for Collection: ${activeCollection?.name}`, - userId: userId, - updatedPrice: updatedPrice, - datasets: [...(selectedCollection?.chartData?.datasets || []), newDataSet], - allXYValues: [ + 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( (dataset) => dataset.data ) || []), newDataSet.data[0].xy, - ], - }); + ]; + + const filteredXYValues = getUniqueFilteredXYValues(combinedXYValues); + + return { + name: `Chart for Collection: ${activeCollection?.name}`, + userId: userId, + updatedPrice: updatedPrice, + datasets: [ + ...(selectedCollection?.chartData?.datasets || []), + newDataSet, + ], + allXYValues: filteredXYValues, + }; + }; const addOrRemoveCard = useCallback( async (card, cardInfo, operation) => { @@ -378,7 +442,7 @@ export const CollectionProvider = ({ children }) => { }, ], }; - + console.log('XYDATASET-----1:', xyData); const updateInfo = { ...cardInfo, name: selectedCollection?.name, @@ -386,6 +450,8 @@ export const CollectionProvider = ({ children }) => { cards: updatedCards, userId: userId, totalCost: updatedPrice, + totalPrice: updatedPrice, + xy: xyData, quantity: updatedCards.length, totalQuantity: updatedCards.reduce( (acc, card) => acc + card.quantity, @@ -446,6 +512,7 @@ export const CollectionProvider = ({ children }) => { ); let updatedCollection; + let updatedChartData; // Check if it is a POST method and the response contains 'savedCollection' if (method === 'POST' && response.data?.newCollection) { @@ -455,10 +522,15 @@ export const CollectionProvider = ({ children }) => { else if (method === 'PUT' && response.data?.updatedCollection) { console.log('RESPONSE REGARDING UPDATE:', response.data.message); updatedCollection = response.data.updatedCollection; + updatedChartData = response.data.updatedChartData; } else { throw new Error('Unexpected response format'); } - + console.log( + 'UPDATED COLLECTION PRICE FROM SERVER:', + updatedCollection.totalPrice + ); + console.log('UPDATED CHART FROM SERVER:', updatedChartData); const newChartData = { ...updatedCollection.chartData, datasets: [ @@ -483,9 +555,22 @@ export const CollectionProvider = ({ children }) => { }, ], }; - + // console.log('NEW CHART AFTER SERVER:', newChartData); updatedCollection.chartData = newChartData; - console.log('UPDATED COLLECTION FROM SERVER:', updatedCollection); + + const convertedData = convertData(newChartData); + + // console.log( + // '<----------$$$$$$$$$CONVERTED DATA FOR CHART777777777$$$$$$$$$---------->', + // convertedData + // ); + updatedCollection.xy = convertedData; + setXyData(convertedData.finalDataForChart); + // console.log( + // '<----------$$$$$$$$$CONVERTED DATA FOR CHART$$$$$$$$$---------->', + // xyData + // ); + // console.log('UPDATED COLLECTION FROM SERVER:', updatedCollection); updateCollectionData(updatedCollection, 'selectedCollection'); updateCollectionData(updatedCollection, 'allCollections'); } catch (error) { @@ -494,6 +579,59 @@ export const CollectionProvider = ({ children }) => { }, [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 isPriceUpdated = updatedPricesArray.some((card) => { + const currentCardPrice = selectedCollection?.cards[card?.id]?.price; + + // Check if this is the special tagged card + if (card._tag === 'special') { + console.log('Found the special card:', card); + } + + if (card?.updatedPrice === currentCardPrice) { + console.log( + '[2][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][CARD]==========>', + card + ); + console.log( + 'CARD FROM SELECTED COLLECTIONS:', + selectedCollection.cards[card.id] + ); + console.log('Price has not been updated for card with ID:', card.id); + return false; + } + + // Logic to determine if the price has been updated or card added + return /* condition to check if price updated or card added */; + }); + + if (isPriceUpdated) { + // Call the addOrRemoveCard or any other function to trigger collection update + addOrRemoveCard(/* parameters as needed */); + } + }, [updatedPricesFromCombinedContext]); const contextValue = useMemo( () => ({ @@ -502,7 +640,13 @@ export const CollectionProvider = ({ children }) => { selectedCollection, collectionData, totalCost, + xy: 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), @@ -525,13 +669,8 @@ export const CollectionProvider = ({ children }) => { console.log('COLLECTION CONTEXT: ', { contextValue, }); - }, [contextValue]); - useEffect(() => { - console.log( - 'OPEN CHOOSE COLLECTION DIALOG UPDATED:', - openChooseCollectionDialog - ); - }, [openChooseCollectionDialog]); + }, [contextValue, updatedPricesFromCombinedContext]); + // Assuming updatedPrices is passed as a prop or state useEffect(() => { if (selectedCollection && totalCost) { diff --git a/src/context/ColorModeProvider.jsx b/src/context/ColorModeProvider.jsx index 40bce62..d5b1a19 100644 --- a/src/context/ColorModeProvider.jsx +++ b/src/context/ColorModeProvider.jsx @@ -1,40 +1,58 @@ -import { useState, useMemo, createContext } from 'react'; +import { useState, useMemo, createContext, useEffect } from 'react'; import { createTheme } from '@mui/material/styles'; -import { themeSettings } from '../theme'; +import { useCookies } from 'react-cookie'; +import { themeSettings } from '../themeSettings'; export const ColorModeContext = createContext({ - mode: 'light', + mode: 'dark', colorMode: {}, - theme: themeSettings('light'), // default theme is light mode theme - toggleColorMode: () => { - // toggle color mode logic here - }, + theme: themeSettings('dark'), // default theme is light mode theme + + // eslint-disable-next-line @typescript-eslint/no-empty-function + toggleColorMode: () => {}, }); export const ColorModeProvider = ({ children }) => { - const [mode, setMode] = useState('dark'); - console.log('mode', mode); + // Get the mode from the cookie or default to 'dark' if the cookie doesn't exist + const [cookie, setCookie] = useCookies(['colorMode']); + const initialMode = cookie['colorMode'] || 'dark'; + const [mode, setMode] = useState(initialMode); + + useEffect(() => { + // Set the cookie whenever the mode changes + setCookie('colorMode', mode, { path: '/' }); + }, [mode]); + const colorMode = useMemo( () => ({ - toggleColorMode: () => - setMode((prev) => (prev === 'light' ? 'dark' : 'light')), + toggleColorMode: () => { + const newMode = mode === 'light' ? '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] ); - console.log('colorMode', colorMode); + const theme = useMemo(() => createTheme(themeSettings(mode)), [mode]); - console.log('theme', theme); const contextValue = { mode, colorMode, theme, - setMode: (mode) => setMode(mode), + setMode: (newMode) => { + setMode(newMode); + setCookie('colorMode', newMode); // also set the cookie here for immediate effect + }, toggleColorMode: colorMode.toggleColorMode, }; + return ( - + {children} ); }; + +export default ColorModeProvider; diff --git a/src/context/CombinedProvider.jsx b/src/context/CombinedProvider.jsx index c179124..6780e88 100644 --- a/src/context/CombinedProvider.jsx +++ b/src/context/CombinedProvider.jsx @@ -16,11 +16,16 @@ const initialState = { allData: {}, data: {}, chartData: {}, + existingChartData: {}, collectionData: {}, + currentChartData: {}, allCollectionsUpdated: {}, allCollectionData: {}, cronData: {}, cardPrices: {}, + previousDayTotalPrice: 0, + dailyPriceChange: 0, + priceDifference: 0, retrievedListOfMonitoredCards: {}, isLoading: false, cronTriggerTimestamps: [], @@ -37,21 +42,62 @@ const initialState = { // 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; + }); +}; export const CombinedProvider = ({ children }) => { const { userCookie } = useCookies(['userCookie']); const [state, setState] = useState(initialState); const userId = userCookie?.userID; - const { selectedCollection, allCollections, setAllCollections } = - useContext(CollectionContext); + const { + selectedCollection, + allCollections, + setAllCollections, + updatedPricesFromCombinedContext, + setUpdatedPricesFromCombinedContext, + } = useContext(CollectionContext); const socket = useSocket(); // ----------- STATE UPDATER FUNCTIONS ----------- const setDataFunctions = { + data: useCallback( + (data) => setState((prev) => ({ ...prev, chartData: data })), + [] + ), chartData: useCallback( (data) => setState((prev) => ({ ...prev, chartData: data })), [] ), + existingChartData: useCallback( + (data) => setState((prev) => ({ ...prev, existingChartData: data })), + [] + ), // cardStats: useCallback( // (data) => setState((prev) => ({ ...prev, cardStats: data })), // [] @@ -86,6 +132,10 @@ export const CombinedProvider = ({ children }) => { // })), // [] // ), + currentChartData: useCallback( + (data) => setState((prev) => ({ ...prev, currentChartData: data })), + [] + ), checkAndUpdateCardPrice: useCallback( (data) => setState((prev) => @@ -121,7 +171,7 @@ export const CombinedProvider = ({ children }) => { const listOfMonitoredCards = useMemo(() => { const cards = allCollections?.flatMap((collection) => collection?.cards); - console.log('cards', 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) @@ -143,7 +193,7 @@ export const CombinedProvider = ({ children }) => { const retrieveListOfMonitoredCards = useCallback(() => { const cards = allCollections?.flatMap((collection) => collection?.cards); - console.log('cards', cards); + // console.log('cards', cards); const uniqueCards = Array.from(new Set(cards.map((card) => card?.id))).map( (id) => cards.find((card) => card?.id === id) ); @@ -171,49 +221,26 @@ export const CombinedProvider = ({ children }) => { console.log('Received message:', message); }; - // const handleAllDataItemsReceived = (data) => { - // console.log('Received all data items:', data); - // setDataFunctions.cronData(data); - // }; - - const handleCronJobResponse = (data) => { - console.log('Cron job triggered:', data); - - // Destructure the data - const { pricingData, collections } = data; - + const handleCronJobResponse = (message, collections) => { + console.log('MESSAGE: CRON JOB COMPLETE ==========]>:', message); + console.log('COLLECTIONS: CRON JOB COMPLETE ==========]>:', collections); setDataFunctions.checkAndUpdateCardPrice({ collections }); - // If you want to update the collections in your state, - // you can do so with the following (assuming setAllCollections is available): - // setAllCollections(collections); }; const handleError = (errorData) => { console.error('Server Error:', errorData.message); console.error('Error Source:', errorData.source); - - // If you sent error detail from server, you can also log it if (errorData.detail) { console.error('Error Detail:', errorData.detail); } setDataFunctions.error(errorData); }; - const handleCronJobTracker = (incomingData) => { - console.log('Automated Message from Server:', incomingData.message); - - const { data } = incomingData; - if (!data) { - console.error('No data received from server for RESPONSE_CRON_DATA.'); - return; - } - - // const { time, runs } = data; - - // console.log('Cron Time:', cronTime); - // console.log('Cron Runs:', runs); - - setDataFunctions.cronData({ data }); + const handleCronJobTracker = (message, existingCronData) => { + console.log('Automated Message from Server:', message); + const data = existingCronData; + const filteredRuns = filterDuplicatePrices(data); + setDataFunctions.cronData({ ...data, runs: filteredRuns }); }; const handleExistingCollectionData = (userId, selectedCollection) => { @@ -222,35 +249,29 @@ export const CombinedProvider = ({ children }) => { }; const handleExistingChartData = (data) => { - if (!data) { + if (!data || Array.isArray(data?.existingChartData)) { console.error( 'Unexpected data structure received for chart data update:', data ); return; } - if (Array.isArray(data?.chartData)) { - console.error('The provided chartData is an array, not an object!'); - return; // Exit without saving or updating the data - } - console.log('chartData being saved:', data); - - const { chartData } = data; - - // console.log('Received existing CHART data:', data); - - setDataFunctions.chartData({ chartData }); + const { existingChartData } = data; + setDataFunctions.existingChartData({ existingChartData }); }; - const handleCollectionUpdated = ({ message, data }) => { - console.log('Message:', message); - // console.log('Updated Collection Data:', data?.data); - // setDataFunctions.collectionData(data.data); + const handleChartDatasetsUpdated = ({ + message, + collectionId, + currentChartDatasets, + }) => { + const currentChartData = { + [collectionId]: currentChartDatasets, + }; + setDataFunctions.currentChartData(currentChartData); }; const handleCardPricesUpdated = ({ message, pricingData }) => { - console.log('Message:', message); - if ( !pricingData || !pricingData.updatedPrices || @@ -265,37 +286,63 @@ export const CombinedProvider = ({ children }) => { } const { updatedPrices, previousPrices, priceDifferences } = pricingData; - console.log('Previous Card Prices:', previousPrices); - console.log('Updated Card Prices:', updatedPrices); - console.log('Price Differences:', priceDifferences); - - // Update the listOfMonitoredCards with the new prices - listOfMonitoredCards.forEach((card) => { - if (updatedPrices[card.id]) { - card.updatedPrice = updatedPrices[card.id]; - } - }); - // Update the state with the new prices - state.retrievedListOfMonitoredCards.forEach((card) => { - if (updatedPrices[card.id]) { - card.updatedPrice = updatedPrices[card.id]; - } - }); - - // Assuming you want to combine both updated and previous prices const allPrices = { ...updatedPrices, ...previousPrices, - // ...priceDifferences, - previousPrices, - updatedPrices, + nestedUpdatedPrices: updatedPrices, + nestedPreviousPrices: previousPrices, priceDifferences, }; + setUpdatedPricesFromCombinedContext(allPrices); setDataFunctions.cardPrices({ allPrices }); }; + const handleNewCardDataObject = ({ message, updatedData }) => { + // setUpdatedPricesFromCombinedContext(updatedData); + + setDataFunctions.data(updatedData); + }; + + // Socket event registrations + // 2. Handle the initiation of scheduleCheckCardPrices + socket.on('INITIATE_SCHEDULE_CHECK_CARD_PRICES', (data) => { + console.log('Received INITIATE_SCHEDULE_CHECK_CARD_PRICES'); + socket.emit('INITIATE_CHECK_CARD_PRICES', data); + }); + + // 4. Handle the initiation of checkCardPrices and its outcomes + socket.on('INITIATE_HANDLE_CHECK_CARD_PRICES', (data) => { + console.log('Received INITIATE_HANDLE_CHECK_CARD_PRICES'); + // socket.emit('SEND_PRICING_DATA_TO_CLIENT', data); + socket.emit('HANDLE_CHECK_CARD_PRICES', data); + }); + + // 6. Handle updating user data based on new pricing data + socket.on( + 'INITIATE_UPDATE_USER_DATA', + ({ userId, pricingData, message }) => { + console.log('Received INITIATE_UPDATE_USER_DATA message', message); + socket.emit('HANDLE_UPDATE_USER_DATA', { + userId, + pricingData, + body: state?.collectionData, + }); + } + ); + + socket.on( + 'INITIATE_UPDATE_USER_COLLECTION', + ({ userId, updatedData, body }) => { + console.log('Received INITIATE_UPDATE_USER_COLLECTION', updatedData); + socket.emit('HANDLE_UPDATE_USER_COLLECTION', { + userId, + pricingData: updatedData, // Here's the correction + body: selectedCollection, // You can replace this if you have another variable storing the selected collection + }); + } + ); socket.on('MESSAGE_TO_CLIENT', handleReceive); socket.on('RESPONSE_EXISTING_CHART_DATA', handleExistingChartData); socket.on('RESPONSE_CRON_DATA', handleCronJobTracker); @@ -303,30 +350,25 @@ export const CombinedProvider = ({ children }) => { 'RESPONSE_EXISTING_COLLECTION_DATA', handleExistingCollectionData ); - socket.on( - 'RESPONSE_CRON_UPDATED_CARDS_IN_COLLECTION', - handleCardPricesUpdated - ); + socket.on('SEND_PRICING_DATA_TO_CLIENT', handleCardPricesUpdated); + socket.on('SEND_UPDATED_DATA_TO_CLIENT', handleNewCardDataObject); + socket.on('CHART_DATASETS_UPDATED', handleChartDatasetsUpdated); socket.on('ERROR', handleError); socket.on('RESPONSE_CRON_UPDATED_ALLCOLLECTIONS', handleCronJobResponse); - socket.on('COLLECTION_UPDATED', handleCollectionUpdated); + return () => { socket.off('MESSAGE_TO_CLIENT', handleReceive); socket.off('RESPONSE_EXISTING_CHART_DATA', handleExistingChartData); - // socket.off( - // 'RESPONSE_EXISTING_COLLECTION_DATA', - // handleExistingCollectionData - // ); + socket.off('RESPONSE_CRON_DATA', handleCronJobTracker); socket.off( - 'RESPONSE_CRON_UPDATED_CARDS_IN_COLLECTION', - handleCardPricesUpdated + 'RESPONSE_EXISTING_COLLECTION_DATA', + handleExistingCollectionData ); + socket.off('SEND_PRICING_DATA_TO_CLIENT', handleCardPricesUpdated); socket.off('RESPONSE_CRON_UPDATED_ALLCOLLECTIONS', handleCronJobResponse); - socket.off('COLLECTION_UPDATED', handleCollectionUpdated); - // socket.off('CARD_STATS_UPDATE', handleCardStatsUpdate); - // socket.off('CHART_UPDATED', handleChartUpdated); - // socket.off('ALL_DATA_ITEMS', handleAllDataItemsReceived); - // socket.off('updateCollection', cardStatsUpdate); + socket.off('SEND_UPDATED_DATA_TO_CLIENT', handleNewCardDataObject); + socket.off('CHART_DATASETS_UPDATED', handleChartDatasetsUpdated); + socket.off('ERROR', handleError); }; }, [socket]); @@ -440,7 +482,7 @@ export const CombinedProvider = ({ children }) => { useEffect(() => { if (allCollections) { // console.log('allCollections', allCollections); - console.log('listOfMonitoredCards', listOfMonitoredCards); + // console.log('listOfMonitoredCards', listOfMonitoredCards); if ( JSON.stringify(allCollections) !== @@ -499,6 +541,8 @@ export const CombinedProvider = ({ children }) => { allData: value.allData, data: value.data, chartData: value.chartData, + currentChartData: value.currentChartData, + existingChartData: value.existingChartData, collectionData: value.collectionData, allCollectionData: value.allCollectionData, allCollectionsUpdated: value.allCollectionsUpdated, diff --git a/src/context/DeckContext/DeckContext.js b/src/context/DeckContext/DeckContext.js index ba65f36..2409cb0 100644 --- a/src/context/DeckContext/DeckContext.js +++ b/src/context/DeckContext/DeckContext.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ import React, { createContext, useState, @@ -8,8 +9,18 @@ import React, { import { useCookies } from 'react-cookie'; import { useCardStore } from '../CardContext/CardStore'; -export const DeckContext = createContext(null); - +export const DeckContext = createContext({ + deckData: {}, // Provide default values for context properties + allDecks: [], + selectedDeck: {}, + setSelectedDeck: () => {}, + addOneToDeck: () => {}, + removeOneFromDeck: () => {}, + getTotalCost: () => 0, + getCardQuantity: () => 0, + updateAndSyncDeck: () => {}, + fetchAllDecksForUser: () => {}, +}); const apiBase = `${process.env.REACT_APP_SERVER}/api`; const fetchWrapper = async (url, method, body = null) => { @@ -217,18 +228,26 @@ export const DeckProvider = ({ children }) => { } }; + const getQuantity = (cardId) => { + const foundCard = selectedDeck?.cards?.find((item) => item.id === cardId); + return foundCard?.quantity || 0; + }; + const contextValue = { deckData, allDecks, selectedDeck, + totalQuantity: getQuantity, setSelectedDeck, addOneToDeck: (card) => addOrRemoveCard(card, true, false), removeOneFromDeck: (card) => addOrRemoveCard(card, false, true), getTotalCost: () => selectedDeck?.cards?.reduce((acc, card) => acc + (card.cost || 0), 0) || 0, - getCardQuantity: (cardId) => - selectedDeck?.cards?.find((item) => item.id === cardId)?.quantity || 0, + getCardQuantity: (cardId) => { + const foundCard = selectedDeck?.cards?.find((item) => item.id === cardId); + return foundCard?.quantity || 0; + }, updateAndSyncDeck, fetchAllDecksForUser: fetchAndSetDecks, }; diff --git a/src/context/SideBarProvider.jsx b/src/context/SideBarProvider.jsx new file mode 100644 index 0000000..64ddff9 --- /dev/null +++ b/src/context/SideBarProvider.jsx @@ -0,0 +1,50 @@ +import React, { createContext, useContext, useState } from 'react'; + +const SidebarContext = createContext(); + +export const SidebarProvider = ({ children }) => { + const [isOpen, setIsOpen] = useState(false); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [sidebarBackgroundColor, setSidebarBackgroundColor] = + useState('#FFFFFF'); + const [sidebarImage, setSidebarImage] = useState(null); + + const toggleSidebar = () => { + setIsOpen(!isOpen); + }; + + const login = () => { + setIsLoggedIn(true); + }; + + const logout = () => { + setIsLoggedIn(false); + }; + + return ( + + {children} + + ); +}; + +export const useSidebarContext = () => { + const context = useContext(SidebarContext); + if (!context) { + throw new Error('useSidebarContext must be used within a SidebarProvider'); + } + return context; +}; diff --git a/src/context/hooks/auth.jsx b/src/context/hooks/auth.jsx new file mode 100644 index 0000000..6d4ed12 --- /dev/null +++ b/src/context/hooks/auth.jsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { AuthContext } from '../Auth/authContext'; + +export const useAuthContext = () => { + const context = useContext(AuthContext); + + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + + return context; +}; diff --git a/src/index.js b/src/index.js index ba0053b..239ee87 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; +import './assets/styles/index.css'; import App from './App'; -import GlobalStyles from './GlobalStyles'; +import GlobalStyles from './assets/GlobalStyles'; import AuthProvider from './context/Auth/authContext'; import { CartProvider } from './context/CartContext/CartContext'; import { DeckProvider } from './context/DeckContext/DeckContext'; @@ -17,6 +17,8 @@ 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'; const root = document.getElementById('root'); @@ -38,7 +40,11 @@ function Main() { - + + + + + diff --git a/src/pages/CardDeckAnimation.js b/src/pages/CardDeckAnimation.js new file mode 100644 index 0000000..18bbe07 --- /dev/null +++ b/src/pages/CardDeckAnimation.js @@ -0,0 +1,71 @@ +import React, { useEffect } from 'react'; +import * as THREE from 'three'; +import placeholder from '../assets/images/placeholder.jpeg'; + +const CardDeckAnimation = () => { + // Initialize the scene, camera, and renderer here + useEffect(() => { + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera( + 75, + window.innerWidth / window.innerHeight, + 0.1, + 1000 + ); + const renderer = new THREE.WebGLRenderer(); + + renderer.setSize(window.innerWidth, window.innerHeight); + const container = document.getElementById('card-deck-container'); + + if (container) { + container.appendChild(renderer.domElement); + } + + document + .getElementById('card-deck-container') + .appendChild(renderer.domElement); + + // Position the camera + camera.position.z = 15; + // Create a placeholder array with 6 images + const monsterImageUrls = Array(6).fill(placeholder); + + // Create a group to hold the cards + const cardGroup = new THREE.Group(); + + // Create cards and add them to the group + for (let i = 0; i < monsterImageUrls.length; i++) { + const imageURL = monsterImageUrls[i]; + const geometry = new THREE.BoxGeometry(2, 3, 0.2); + const textureLoader = new THREE.TextureLoader(); + const cardTexture = textureLoader.load(imageURL); + const material = new THREE.MeshBasicMaterial({ map: cardTexture }); + const card = new THREE.Mesh(geometry, material); + + // Position cards in a circle + const angle = (i / monsterImageUrls.length) * Math.PI * 2; + const radius = 5; + card.position.x = Math.cos(angle) * radius; + card.position.z = Math.sin(angle) * radius; + card.rotation.y = angle; + + // Add the card to the group + cardGroup.add(card); + } + scene.add(cardGroup); + + // Create an animation loop + const animate = () => { + requestAnimationFrame(animate); + cardGroup.rotation.y += 0.01; + renderer.render(scene, camera); + }; + + // Start the animation loop + animate(); + }, []); // Empty dependency array ensures this effect runs once when the component mounts + + return
; +}; + +export default CardDeckAnimation; diff --git a/src/pages/CollectionPage.js b/src/pages/CollectionPage.js index fb6e1a2..8bfb325 100644 --- a/src/pages/CollectionPage.js +++ b/src/pages/CollectionPage.js @@ -7,6 +7,7 @@ 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'; const CollectionPage = () => { // const [defaultCollection, setDefaultCollection] = useState([]); @@ -39,7 +40,12 @@ const CollectionPage = () => { return (
- + +
diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js index a749aae..63c2002 100644 --- a/src/pages/HomePage.js +++ b/src/pages/HomePage.js @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +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'; @@ -6,48 +6,9 @@ import { makeStyles, useTheme } from '@mui/styles'; import { ColorModeContext } from '../context/ColorModeProvider'; import { useMode } from '../context/hooks/colormode'; import HeaderTitle from '../components/reusable/HeaderTitle'; +import useStyles from './styles'; // import Hero from './pageStyles/Hero'; -const useStyles = makeStyles((theme) => ({ - arrowStyles: { - backgroundColor: theme.palette.primary.main, - borderRadius: '50%', - }, - imageStyles: { - height: '600px', - width: '100%', - objectFit: 'cover', - }, - captionBox: { - position: 'absolute', - bottom: 0, - backgroundColor: 'rgba(0, 0, 0, 0.5)', - width: '100%', - color: theme.palette.common.white, - padding: theme.spacing(2), - textAlign: 'center', - }, - 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', - }, - carouselContainer: { - padding: theme.spacing(4), - backgroundColor: theme.palette.common.white, - borderRadius: theme.spacing(2), - }, - welcomeMessage: { - marginBottom: theme.spacing(4), - textAlign: 'center', - color: theme.palette.text.primary, - fontWeight: 'bold', - }, -})); - const carouselImages = [ { image: '/images/yugioh.jpeg', caption: 'Yu-Gi-Oh!' }, { image: '/images/pokemon.jpeg', caption: 'Pokemon' }, @@ -76,11 +37,7 @@ const HomeBanner = ({ children }) => { }; const WelcomeMessage = () => { - // const classes = useStyles(); return ( - // - // Welcome to Mythical Card-Mart! - // { ); }; -const CarouselContainer = () => { +const CarouselContainer = ({ isMounted }) => { + if (!isMounted.current) { + return; + } const classes = useStyles(); - return ( { autoPlay className={classes.carouselContainer} > - {carouselImages.map(({ image, caption }, index) => ( + {carouselImages?.map(({ image, caption }, index) => ( ))} @@ -138,7 +97,13 @@ const CarouselContainer = () => { const HomePage = () => { const theme = useTheme(); - console.log('theme', theme); + const isMounted = useRef(true); // Initialize a ref to track if the component is mounted + + useEffect(() => { + return () => { + isMounted.current = false; + }; + }, []); return ( <> {/* */} @@ -154,7 +119,7 @@ const HomePage = () => { maxWidth="md" > - +
diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 977f6fc..f4a5705 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -12,129 +12,20 @@ import { TextField, } from '@mui/material'; import { Edit as EditIcon } from '@mui/icons-material'; -import placeholder from '../assets/placeholder.jpeg'; +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 { useCombinedContext } from '../context/CombinedProvider'; import { useCollectionStore } from '../context/hooks/collection'; - -const AvatarStyled = styled(Avatar)({ - width: 60, - height: 60, - marginBottom: 15, -}); - -const TypographyStyled = styled(Typography)({ - marginBottom: 15, -}); - -const IconButtonStyled = styled(IconButton)({ - marginBottom: 20, -}); - -const DataBoxStyled = styled(Box)({ - margin: '10px 0', - padding: '10px', - border: '1px solid #ddd', - borderRadius: '8px', - textAlign: 'center', - width: '100%', -}); - -const ButtonStyled = styled(Button)({ - margin: '15px 0', - padding: '10px', - color: '#fff', - backgroundColor: '#3f51b5', - '&:hover': { - backgroundColor: '#303f9f', - }, -}); - -const DataTextStyled = styled(Typography)({ - margin: '5px 0', - fontSize: '0.9rem', -}); - -const ProfileForm = ({ userName, name, age, status, onSave }) => { - const [formData, setFormData] = useState({ - userName: userName || '', // default to empty string if undefined - name: name || '', - age: age || '', - status: status || '', - }); - const handleChange = (e) => { - setFormData({ - ...formData, - [e.target.id]: e.target.value, - }); - }; - - const handleSubmit = (e) => { - e.preventDefault(); - onSave(formData); - }; - - useEffect(() => { - setFormData({ - userName: userName || '', - name: name || '', - age: age || '', - status: status || '', - }); - }, [userName, name, age, status]); - - return ( -
- - - - - {/* Add more TextField components as needed */} - - - ); -}; - -ProfileForm.propTypes = { - userName: PropTypes.string, - name: PropTypes.string, - age: PropTypes.string, - status: PropTypes.string, - onSave: PropTypes.func.isRequired, -}; +import { + AvatarStyled, + TypographyStyled, + IconButtonStyled, + ButtonStyled, +} from './StyledComponents'; +import ProfileForm from '../components/forms/ProfileForm'; const ProfilePage = () => { const { selectedCollection } = useCollectionStore(); @@ -149,12 +40,7 @@ const ProfilePage = () => { handleSend, handleRequestChartData, handleRequestCollectionData, - // handleRequestCollectionData, - handleSendData, - handleSendChart, - handleCronRequest, handleRequestCronStop, - chartDataToSend, handleSendAllCardsInCollections, listOfMonitoredCards, handleRetreiveListOfMonitoredCards, @@ -280,38 +166,6 @@ const ProfilePage = () => { > Stop Cron Job - {/* - Request Chart Data Update - */} - - {/* - Send Collection Data - */} - {/* {chartDataToSend?.datasets?.map((dataset) => - dataset.data?.points?.map((data) => ( - - {data?.x && ( - <> - - Date: {new Date(data.x).toLocaleDateString()} - - - Time: {new Date(data.x).toLocaleTimeString()} - - - )} - Value: {data?.y} - - )) - )} */} { +// 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/StyledComponents.jsx b/src/pages/StyledComponents.jsx new file mode 100644 index 0000000..54975ef --- /dev/null +++ b/src/pages/StyledComponents.jsx @@ -0,0 +1,45 @@ +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/ThreeJsCube.js b/src/pages/ThreeJsCube.js similarity index 100% rename from src/ThreeJsCube.js rename to src/pages/ThreeJsCube.js diff --git a/src/pages/styles.jsx b/src/pages/styles.jsx new file mode 100644 index 0000000..42c0b81 --- /dev/null +++ b/src/pages/styles.jsx @@ -0,0 +1,168 @@ +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles((theme) => ({ + // return { + arrowStyles: { + backgroundColor: theme.palette.primary.main, + borderRadius: '50%', + }, + imageStyles: { + height: '600px', + width: '100%', + objectFit: 'cover', + }, + captionBox: { + position: 'absolute', + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + width: '100%', + color: theme.palette.common.white, + padding: theme.spacing(2), + textAlign: 'center', + }, + 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', + }, + carouselContainer: { + padding: theme.spacing(4), + backgroundColor: theme.palette.common.white, + borderRadius: theme.spacing(2), + }, + welcomeMessage: { + marginBottom: theme.spacing(4), + textAlign: 'center', + color: theme.palette.text.primary, + fontWeight: 'bold', + }, + toolBar: { + height: '10vh', + display: 'flex', + justifyContent: 'space-between', + padding: '20px', + backgroundColor: 'white', + }, + logo: { + color: 'blue', + cursor: 'pointer', + }, + link: { + color: '#000', + }, + menuIcon: { + color: '#000', + }, + formContainer: { + flexGrow: 1, + padding: '10px', + maxWidth: '700px', + margin: '30px auto', + [theme.breakpoints.between('xs', 'sm')]: { + width: '100%', + }, + }, + form: { + marginTop: '30px', + }, + formHeading: { + textAlign: 'center', + }, + heroBox: { + width: '100%', + display: 'flex', + minHeight: '600px', + alignItems: 'center', + justifyContent: 'center', + }, + gridContainer: { + display: 'flex', + alignItems: 'center', + maxWidth: '1300px', + padding: '50px', + }, + aboutUsContainer: { + width: '100%', + display: 'flex', + minHeight: '400px', + alignItems: 'center', + justifyContent: 'center', + margin: '30px 0px 50px 0px', + }, + aboutUsSubtitle: { + opacity: '0.7', + paddingBottom: '30px', + fontSize: '18px', + }, + title: { + paddingBottom: '15px', + }, + subtitle: { + opacity: '0.4', + paddingBottom: '30px', + }, + largeImage: { + width: '100%', + }, + sectionGridContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + minHeight: '500px', + }, + sectionGridItem: { + backgroundColor: '#f2f0f1', + textAlign: 'center', + padding: '30px', + width: '200px', + borderRadius: '10px', + margin: '10px !important', + }, + inputField: { + marginBottom: '20px !important', + }, + textArea: { + width: '100%', + marginBottom: '20px', + fontSize: '16px', + padding: '10px', + }, + footerContainer: { + display: 'flex', + alignItems: 'center', + miHeight: '10vh', + padding: '20px', + justifyContent: 'center', + backgroundColor: '#f2f0f1', + flexDirection: 'column', + }, + footerText: { + paddingBottom: '10px', + }, + footerDate: { + opacity: '0.4', + }, + testimonialCard: { + backgroundColor: '#fff', + padding: '10px', + minHeight: '200px', + display: 'flex', + alignItems: 'center', + }, + testimonialStatement: { + paddingBottom: '25px', + }, + avatar: { + marginRight: '10px', + }, + testimonialPosition: { + fontSize: '14px', + opacity: '0.6', + }, +})); + +export default useStyles; diff --git a/src/theme.jsx b/src/theme.jsx index bc75a27..e69de29 100644 --- a/src/theme.jsx +++ b/src/theme.jsx @@ -1,229 +0,0 @@ -import { createContext, useState, useMemo } from 'react'; -import { createTheme } from '@mui/material/styles'; - -// color design tokens export - -// mui theme settings -export const tokens = (mode) => ({ - ...(mode === 'dark' - ? { - grey: { - 100: '#e0e0e0', - 200: '#c2c2c2', - 300: '#a3a3a3', - 400: '#858585', - 500: '#666666', - 600: '#525252', - 700: '#3d3d3d', - 800: '#292929', - 900: '#141414', - }, - primary: { - 100: '#d0d1d5', - 200: '#a1a4ab', - 300: '#727681', - 400: '#1F2A40', - 500: '#141b2d', - 600: '#101624', - 700: '#0c101b', - 800: '#080b12', - 900: '#040509', - }, - greenAccent: { - 100: '#dbf5ee', - 200: '#b7ebde', - 300: '#94e2cd', - 400: '#70d8bd', - 500: '#4cceac', - 600: '#3da58a', - 700: '#2e7c67', - 800: '#1e5245', - 900: '#0f2922', - }, - redAccent: { - 100: '#f8dcdb', - 200: '#f1b9b7', - 300: '#e99592', - 400: '#e2726e', - 500: '#db4f4a', - 600: '#af3f3b', - 700: '#832f2c', - 800: '#58201e', - 900: '#2c100f', - }, - blueAccent: { - 100: '#e1e2fe', - 200: '#c3c6fd', - 300: '#a4a9fc', - 400: '#868dfb', - 500: '#6870fa', - 600: '#535ac8', - 700: '#3e4396', - 800: '#2a2d64', - 900: '#151632', - }, - } - : { - grey: { - 100: '#141414', - 200: '#292929', - 300: '#3d3d3d', - 400: '#525252', - 500: '#666666', - 600: '#858585', - 700: '#a3a3a3', - 800: '#c2c2c2', - 900: '#e0e0e0', - }, - primary: { - 100: '#040509', - 200: '#080b12', - 300: '#0c101b', - 400: '#f2f0f0', // manually changed - 500: '#141b2d', - 600: '#1F2A40', - 700: '#727681', - 800: '#a1a4ab', - 900: '#d0d1d5', - }, - // secondary: { - // 100: '#ff7961', - // 200: '#f44336', - // }, - greenAccent: { - 100: '#0f2922', - 200: '#1e5245', - 300: '#2e7c67', - 400: '#3da58a', - 500: '#4cceac', - 600: '#70d8bd', - 700: '#94e2cd', - 800: '#b7ebde', - 900: '#dbf5ee', - }, - redAccent: { - 100: '#2c100f', - 200: '#58201e', - 300: '#832f2c', - 400: '#af3f3b', - 500: '#db4f4a', - 600: '#e2726e', - 700: '#e99592', - 800: '#f1b9b7', - 900: '#f8dcdb', - }, - blueAccent: { - 100: '#151632', - 200: '#2a2d64', - 300: '#3e4396', - 400: '#535ac8', - 500: '#6870fa', - 600: '#868dfb', - 700: '#a4a9fc', - 800: '#c3c6fd', - 900: '#e1e2fe', - }, - }), -}); - -export const themeSettings = (mode) => { - const colors = tokens(mode); - - return { - width: '100vw', - palette: { - mode: mode, - primary: { - main: mode === 'dark' ? colors.primary[500] : colors.primary[100], - contrastText: mode === 'dark' ? '#ffffff' : '#000000', - }, - primaryLight: { - main: colors.primary[300], - }, - primaryDark: { - main: colors.primary[900], - }, - common: { - white: colors.grey[100], - black: colors.grey[900], - }, - secondary: { - main: colors.greenAccent[500], - }, - neutral: { - dark: colors.grey[700], - main: colors.grey[500], - light: colors.grey[100], - }, - background: { - default: mode === 'dark' ? colors.primary[500] : '#fcfcfc', - }, - error: { - main: colors.redAccent[500], - }, - warning: { - main: colors.redAccent[500], - }, - success: { - main: colors.greenAccent[500], - }, - info: { - main: colors.blueAccent[500], - }, - text: { - primary: colors.grey[900], - secondary: colors.grey[300], - }, - divider: mode === 'dark' ? colors.grey[800] : colors.grey[200], - action: { - hover: mode === 'dark' ? colors.grey[800] : colors.grey[200], - }, - }, - spacing: (factor) => `${0.25 * factor}rem`, - shape: { - borderRadius: 4, - }, - // You need to decide what to return here... - shadows: [ - 'none', - '0px 2px 1px -1px rgba(0,0,0,0.1),0px 1px 1px 0px rgba(0,0,0,0.06),0px 1px 3px 0px rgba(0,0,0,0.04)', // example for theme.shadows[1] - '0px 3px 1px -2px rgba(0,0,0,0.1),0px 2px 2px 0px rgba(0,0,0,0.06),0px 1px 5px 0px rgba(0,0,0,0.04)', // example for theme.shadows[2] - '0px 3px 3px -2px rgba(0,0,0,0.1),0px 3px 4px 0px rgba(0,0,0,0.06),0px 1px 8px 0px rgba(0,0,0,0.04)', // example for theme.shadows[3] - '0px 2px 4px -1px rgba(0,0,0,0.1),0px 4px 5px 0px rgba(0,0,0,0.06),0px 1px 10px 0px rgba(0,0,0,0.04)', // example for theme.shadows[4] - '0px 3px 5px -1px rgba(0,0,0,0.1),0px 5px 8px 0px rgba(0,0,0,0.06),0px 1px 14px 0px rgba(0,0,0,0.04)', // example for theme.shadows[5] - '0px 3px 5px -1px rgba(0,0,0,0.1),0px 6px 10px 0px rgba(0,0,0,0.06),0px 1px 18px 0px rgba(0,0,0,0.04)', // example for theme.shadows[6] - '0px 4px 5px -2px rgba(0,0,0,0.1),0px 7px 10px 1px rgba(0,0,0,0.06),0px 2px 16px 1px rgba(0,0,0,0.04)', // example for theme.shadows[7] - '0px 5px 5px -3px rgba(0,0,0,0.1),0px 8px 10px 1px rgba(0,0,0,0.06),0px 3px 14px 2px rgba(0,0,0,0.04)', // example for theme.shadows[8] - '0px 5px 6px -3px rgba(0,0,0,0.1),0px 9px 12px 1px rgba(0,0,0,0.06),0px 3px 16px 2px rgba(0,0,0,0.04)', // example for theme.shadows[9] - '0px 5px 15px rgba(0,0,0,0.1)', // example for theme.shadows[10] - ], - typography: { - fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), - fontSize: 12, - h1: { - fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), - fontSize: 40, - }, - h2: { - fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), - fontSize: 32, - }, - h3: { - fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), - fontSize: 24, - }, - h4: { - fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), - fontSize: 20, - }, - h5: { - fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), - fontSize: 16, - }, - h6: { - fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), - fontSize: 14, - }, - }, - }; -}; diff --git a/src/themeSettings.jsx b/src/themeSettings.jsx new file mode 100644 index 0000000..997a5ff --- /dev/null +++ b/src/themeSettings.jsx @@ -0,0 +1,106 @@ +import { tokens } from './tokens'; + +export const themeSettings = (mode) => { + const colors = tokens(mode); + + return { + width: '100vw', + palette: { + mode: mode, + primary: { + main: mode === 'dark' ? colors.primary[500] : colors.primary[100], + contrastText: mode === 'dark' ? '#ffffff' : '#000000', + }, + primaryLight: { + main: colors.primary[300], + }, + primaryDark: { + main: colors.primary[900], + }, + common: { + white: colors.grey[100], + black: colors.grey[900], + }, + secondary: { + main: colors.greenAccent[500], + }, + neutral: { + dark: colors.grey[700], + main: colors.grey[500], + light: colors.grey[100], + }, + background: { + default: mode === 'dark' ? colors.primary[500] : '#fcfcfc', + }, + error: { + main: colors.redAccent[500], + }, + warning: { + main: colors.redAccent[500], + }, + success: { + main: colors.greenAccent[500], + }, + info: { + main: colors.blueAccent[500], + }, + text: { + primary: colors.grey[900], + secondary: colors.grey[300], + }, + divider: mode === 'dark' ? colors.grey[800] : colors.grey[200], + action: { + hover: mode === 'dark' ? colors.grey[800] : colors.grey[200], + }, + }, + spacing: (factor) => `${0.25 * factor}rem`, + shape: { + borderRadius: 4, + }, + action: { + hover: mode === 'dark' ? colors.grey[800] : colors.grey[200], + }, + // You need to decide what to return here... + shadows: [ + 'none', + '0px 2px 1px -1px rgba(0,0,0,0.1),0px 1px 1px 0px rgba(0,0,0,0.06),0px 1px 3px 0px rgba(0,0,0,0.04)', // example for theme.shadows[1] + '0px 3px 1px -2px rgba(0,0,0,0.1),0px 2px 2px 0px rgba(0,0,0,0.06),0px 1px 5px 0px rgba(0,0,0,0.04)', // example for theme.shadows[2] + '0px 3px 3px -2px rgba(0,0,0,0.1),0px 3px 4px 0px rgba(0,0,0,0.06),0px 1px 8px 0px rgba(0,0,0,0.04)', // example for theme.shadows[3] + '0px 2px 4px -1px rgba(0,0,0,0.1),0px 4px 5px 0px rgba(0,0,0,0.06),0px 1px 10px 0px rgba(0,0,0,0.04)', // example for theme.shadows[4] + '0px 3px 5px -1px rgba(0,0,0,0.1),0px 5px 8px 0px rgba(0,0,0,0.06),0px 1px 14px 0px rgba(0,0,0,0.04)', // example for theme.shadows[5] + '0px 3px 5px -1px rgba(0,0,0,0.1),0px 6px 10px 0px rgba(0,0,0,0.06),0px 1px 18px 0px rgba(0,0,0,0.04)', // example for theme.shadows[6] + '0px 4px 5px -2px rgba(0,0,0,0.1),0px 7px 10px 1px rgba(0,0,0,0.06),0px 2px 16px 1px rgba(0,0,0,0.04)', // example for theme.shadows[7] + '0px 5px 5px -3px rgba(0,0,0,0.1),0px 8px 10px 1px rgba(0,0,0,0.06),0px 3px 14px 2px rgba(0,0,0,0.04)', // example for theme.shadows[8] + '0px 5px 6px -3px rgba(0,0,0,0.1),0px 9px 12px 1px rgba(0,0,0,0.06),0px 3px 16px 2px rgba(0,0,0,0.04)', // example for theme.shadows[9] + '0px 5px 15px rgba(0,0,0,0.1)', // example for theme.shadows[10] + ], + typography: { + fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), + fontSize: 12, + h1: { + fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), + fontSize: 40, + }, + h2: { + fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), + fontSize: 32, + }, + h3: { + fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), + fontSize: 24, + }, + h4: { + fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), + fontSize: 20, + }, + h5: { + fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), + fontSize: 16, + }, + h6: { + fontFamily: ['Source Sans Pro', 'sans-serif'].join(','), + fontSize: 14, + }, + }, + }; +}; diff --git a/src/tokens.jsx b/src/tokens.jsx new file mode 100644 index 0000000..d14f882 --- /dev/null +++ b/src/tokens.jsx @@ -0,0 +1,121 @@ +export const tokens = (mode) => ({ + ...(mode === 'dark' + ? { + grey: { + 100: '#e0e0e0', + 200: '#c2c2c2', + 300: '#a3a3a3', + 400: '#858585', + 500: '#666666', + 600: '#525252', + 700: '#3d3d3d', + 800: '#292929', + 900: '#141414', + }, + primary: { + 100: '#d0d1d5', + 200: '#a1a4ab', + 300: '#727681', + 400: '#1F2A40', + 500: '#141b2d', + 600: '#101624', + 700: '#0c101b', + 800: '#080b12', + 900: '#040509', + }, + greenAccent: { + 100: '#dbf5ee', + 200: '#b7ebde', + 300: '#94e2cd', + 400: '#70d8bd', + 500: '#4cceac', + 600: '#3da58a', + 700: '#2e7c67', + 800: '#1e5245', + 900: '#0f2922', + }, + redAccent: { + 100: '#f8dcdb', + 200: '#f1b9b7', + 300: '#e99592', + 400: '#e2726e', + 500: '#db4f4a', + 600: '#af3f3b', + 700: '#832f2c', + 800: '#58201e', + 900: '#2c100f', + }, + blueAccent: { + 100: '#e1e2fe', + 200: '#c3c6fd', + 300: '#a4a9fc', + 400: '#868dfb', + 500: '#6870fa', + 600: '#535ac8', + 700: '#3e4396', + 800: '#2a2d64', + 900: '#151632', + }, + } + : { + grey: { + 100: '#141414', + 200: '#292929', + 300: '#3d3d3d', + 400: '#525252', + 500: '#666666', + 600: '#858585', + 700: '#a3a3a3', + 800: '#c2c2c2', + 900: '#e0e0e0', + }, + primary: { + 100: '#040509', + 200: '#080b12', + 300: '#0c101b', + 400: '#f2f0f0', // manually changed + 500: '#141b2d', + 600: '#1F2A40', + 700: '#727681', + 800: '#a1a4ab', + 900: '#d0d1d5', + }, + // secondary: { + // 100: '#ff7961', + // 200: '#f44336', + // }, + greenAccent: { + 100: '#0f2922', + 200: '#1e5245', + 300: '#2e7c67', + 400: '#3da58a', + 500: '#4cceac', + 600: '#70d8bd', + 700: '#94e2cd', + 800: '#b7ebde', + 900: '#dbf5ee', + }, + redAccent: { + 100: '#2c100f', + 200: '#58201e', + 300: '#832f2c', + 400: '#af3f3b', + 500: '#db4f4a', + 600: '#e2726e', + 700: '#e99592', + 800: '#f1b9b7', + 900: '#f8dcdb', + }, + blueAccent: { + 100: '#151632', + 200: '#2a2d64', + 300: '#3e4396', + 400: '#535ac8', + 500: '#6870fa', + 600: '#868dfb', + 700: '#a4a9fc', + 800: '#c3c6fd', + 900: '#e1e2fe', + }, + }), +}); From 4109ff17a059ac34b07c433f970a62b6754976bc Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Fri, 27 Oct 2023 00:18:48 -0700 Subject: [PATCH 4/4] pretty much rewrote entire contexts --- package.json | 2 +- src/App.js | 272 +++++----- src/AppWrapper.jsx | 13 + src/components/Auth/auth.jsx | 20 +- .../actionButtons/ChooseCollectionDialog.jsx | 2 +- src/components/cards/GenericCard.jsx | 2 +- src/components/dialogs/LoginDialog.jsx | 112 ++-- src/components/forms/LoginForm.jsx | 22 +- src/components/forms/styled.jsx | 1 + .../grids/collectionGrids/CardList.jsx | 28 +- src/components/headings/PortfolioHeader.jsx | 6 +- src/components/media/CardMediaSection.js | 3 +- src/components/modals/GenericCardModal.jsx | 14 +- src/context/Auth/authContext.js | 175 ++++--- .../CollectionContext/CollectionContext.jsx | 388 ++++++++------ .../CollectionContext/exampleImport.js | 2 +- src/context/CombinedProvider.jsx | 487 ++++++++++++------ src/context/Settings/index.jsx | 27 +- src/context/UserContext/UserContext.js | 16 +- src/context/UtilityContext/UtilityContext.jsx | 317 ++++-------- src/index.js | 65 +-- src/pages/HomePage.js | 74 +-- src/pages/SplashPage.js | 74 +++ src/pages/styles.jsx | 27 +- 24 files changed, 1209 insertions(+), 940 deletions(-) create mode 100644 src/AppWrapper.jsx create mode 100644 src/pages/SplashPage.js diff --git a/package.json b/package.json index b334795..15f8115 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "react-pro-sidebar": "^1.1.0-alpha.1", "react-redux": "^8.1.1", "react-responsive-carousel": "^3.2.23", - "react-router-dom": "^6.14.2", + "react-router-dom": "^6.17.0", "react-scripts": "5.0.1", "react-spinners": "^0.13.8", "react-stripe-checkout": "^2.6.3", diff --git a/src/App.js b/src/App.js index 7ae9143..bfc7c23 100644 --- a/src/App.js +++ b/src/App.js @@ -1,51 +1,43 @@ +// External Imports import React, { useEffect, useState } from 'react'; -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +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 CartPage from './pages/CartPage'; -import DeckBuilderPage from './pages/DeckBuilderPage'; +import PrivateRoute from './components/Auth/PrivateRoute'; + +// Page Imports +import SplashPage from './pages/SplashPage'; import HomePage from './pages/HomePage'; import StorePage from './pages/StorePage'; +import CartPage from './pages/CartPage'; +import ProfilePage from './pages/ProfilePage'; import CollectionPage from './pages/CollectionPage'; -import ThreeJsCube from './pages/ThreeJsCube'; // Import your Three.js component +import DeckBuilderPage from './pages/DeckBuilderPage'; +import ThreeJsCube from './pages/ThreeJsCube'; import CardDeckAnimation from './pages/CardDeckAnimation'; -import ProfilePage from './pages/ProfilePage'; -import styled from 'styled-components'; -import { createGlobalStyle } from 'styled-components'; + +// Context Hooks Imports import { useCombinedContext } from './context/CombinedProvider'; import { useUserContext } from './context/UserContext/UserContext'; -import PrivateRoute from './components/Auth/PrivateRoute'; -// import Splash from './pages/Splash'; +import { useUtilityContext } from './context/UtilityContext/UtilityContext'; +// Styled Components const AppContainer = styled.div` display: flex; flex-direction: column; - min-height: 100vh; - width: 100vw; - ${'' /* max-width: 100vw; */} + height: 100vh; `; -const AppWrapper = styled.div` - flex: 1; - max-width: 100vw; +const GlobalStyle = createGlobalStyle` + /* Your global styles here */ `; -const App = () => { - const { user } = useUserContext(); - const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [snackbarData, setSnackbarData] = useState({ - open: false, - message: '', - }); - useEffect(() => { - const timer = setTimeout(() => { - setIsLoading(false); - }, 5500); - return () => clearTimeout(timer); - }, []); - +// Custom Hook for Cron Job +const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { const { cronData, handleSendAllCardsInCollections, @@ -54,123 +46,137 @@ const App = () => { retrievedListOfMonitoredCards, allCollectionsUpdated, } = useCombinedContext(); - const userId = user?.userID; - const handleTriggerCronJob = () => { - // Check if the last cron job trigger was more than 1 minute ago - const currentTime = new Date().getTime(); - if (currentTime - lastCronJobTriggerTime >= 60000) { - // Update the last trigger time - setLastCronJobTriggerTime(currentTime); - - if (userId && listOfMonitoredCards && retrievedListOfMonitoredCards) { - handleSendAllCardsInCollections( - userId, - listOfMonitoredCards, - retrievedListOfMonitoredCards - ); - console.log('SENDING ALL CARDS IN COLLECTIONS'); - } else if ( - userId && - listOfMonitoredCards && - !retrievedListOfMonitoredCards - ) { - console.log('RETRIEVING LIST OF MONITORED CARDS'); - handleSendAllCardsInCollections( - userId, - listOfMonitoredCards, - handleRetreiveListOfMonitoredCards() - ); - console.log('Triggered the cron job.'); - } - } - }; + const { user } = useUserContext(); + const userId = user?.userID; useEffect(() => { setLastCronJobTriggerTime(new Date().getTime()); - }, []); + }, [setLastCronJobTriggerTime]); useEffect(() => { - const interval = setInterval(() => { - handleTriggerCronJob(); - }, 60000); + const handleTriggerCronJob = () => { + const currentTime = new Date().getTime(); + 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() + ); + console.log('Triggered the cron job.'); + } + } + }; + const interval = setInterval(handleTriggerCronJob, 60000); return () => clearInterval(interval); - }, [cronData, lastCronJobTriggerTime, allCollectionsUpdated]); + }, [ + cronData, + lastCronJobTriggerTime, + allCollectionsUpdated, + handleRetreiveListOfMonitoredCards, + handleSendAllCardsInCollections, + listOfMonitoredCards, + retrievedListOfMonitoredCards, + userId, + ]); +}; + +// Main Component +const App = () => { + const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); + const { isLoading, setIsContextLoading } = useUtilityContext(); + + useEffect(() => { + if (!isLoading) { + setIsContextLoading(false); + } + }, [isLoading, setIsContextLoading]); + + useEffect(() => { + const timer = setTimeout(() => { + setIsContextLoading(false); + }, 5500); + return () => clearTimeout(timer); + }, [setIsContextLoading]); + + useCronJob(lastCronJobTriggerTime, setLastCronJobTriggerTime); + return ( <> + + + + + + {isLoading ? ( -
- {/* */} - -
+ ) : ( - <> - - - - + +
+ + } /> + } /> + } /> + + + + } + /> + + + + } + /> + + + + } /> - + + + } /> - - -
- - - } /> - } /> - } /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> - } /> - } - /> - - -