-
{
- openModal();
- setIsPopoverOpen(false);
- }}
- >
+ // Event handlers are now only assigned if isRequired is true
+ const eventHandlers = isRequired
+ ? {
+ onMouseEnter: () => {
+ if (typeof handleInteraction === 'function') {
+ handleInteraction(true);
+ }
+ },
+ onMouseLeave: () => {
+ if (typeof handleInteraction === 'function') {
+ handleInteraction(false);
+ }
+ },
+ onClick: () => {
+ if (typeof handleClick === 'function') {
+ handleClick();
+ }
+ if (typeof setClickedCard === 'function') {
+ setClickedCard(card);
+ }
+ },
+ }
+ : {};
+ return (
+
+ {isRequired && isHovered && (
+
handleInteraction(false)}
+ anchorOrigin={anchorOrigin}
+ transformOrigin={transformOrigin}
+ disableRestoreFocus
+ >
+
+
+ )}
+ );
+ }
+);
+// return (
+//
+//
+// {isRequired && isHovered && ref.current && (
+//
{
+// if (typeof handleInteraction === 'function') {
+// handleInteraction(false);
+// }
+// }}
+// disableRestoreFocus
+// >
+//
+//
+// )}
+//
+// );
+// }
+// );
-
onCardHover(null)}
- disableRestoreFocus
- >
-
-
-
- );
-};
+CardMediaSection.displayName = 'CardMediaSection';
-export default CardMediaSection;
+CardMediaSection.propTypes = {
+ imgUrl: PropTypes.string.isRequired,
+ card: PropTypes.object.isRequired,
+ isHovered: PropTypes.bool,
+ handleInteraction: PropTypes.func,
+ handleClick: PropTypes.func,
+ setClickedCard: PropTypes.func,
+ isRequired: PropTypes.bool,
+};
CardMediaSection.defaultProps = {
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- onCardHover: () => {}, // provide a no-op function as default
+ isHovered: false,
+ handleInteraction: null,
+ handleClick: null,
+ setClickedCard: null,
+ isRequired: true,
};
+
+export default CardMediaSection;
diff --git a/src/components/media/DeckCardMedia.jsx b/src/components/media/DeckCardMedia.jsx
deleted file mode 100644
index a4d94f3..0000000
--- a/src/components/media/DeckCardMedia.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-// import React, { useState, useRef, useEffect } from 'react';
-// import { CardMedia } from '@mui/material';
-// import placeholderImage from '../../assets/placeholder.jpeg';
-
-// const DeckCardMedia = ({
-// card,
-// classes,
-// openDeckModal,
-// setHovering,
-// cardRef,
-// }) => {
-// const [hasLoggedCard, setHasLoggedCard] = useState(false);
-// // const cardRef = useRef(null);
-
-// const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage;
-
-// useEffect(() => {
-// if (!hasLoggedCard) {
-// console.log('CARD:', card);
-// setHasLoggedCard(true);
-// }
-// }, [hasLoggedCard, card]);
-
-// return (
-//
-// setHovering(true)}
-// onMouseOut={() => setHovering(false)}
-// />
-//
-// );
-// };
-
-// export default DeckCardMedia;
diff --git a/src/components/media/mediaStyles.jsx b/src/components/media/mediaStyles.jsx
new file mode 100644
index 0000000..e4d45fe
--- /dev/null
+++ b/src/components/media/mediaStyles.jsx
@@ -0,0 +1,19 @@
+import { makeStyles } from '@mui/styles';
+
+export const useStyles = makeStyles({
+ mediaContainer: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ cursor: 'pointer',
+ '&:hover': {
+ opacity: 0.7,
+ },
+ },
+ media: {
+ width: '100%',
+ flexGrow: 1, // Allow the media part to fill the space
+ // paddingTop: '140%', // Aspect ratio 7:10 (height 10, width 7)
+ // backgroundSize: 'cover',
+ },
+});
diff --git a/src/components/modals/GenericCardModal.jsx b/src/components/modals/GenericCardModal.jsx
deleted file mode 100644
index c4e201a..0000000
--- a/src/components/modals/GenericCardModal.jsx
+++ /dev/null
@@ -1,222 +0,0 @@
-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';
-import GenericActionButtons from '../buttons/actionButtons/GenericActionButtons';
-import { DeckContext } from '../../context/DeckContext/DeckContext';
-import { CartContext } from '../../context/CartContext/CartContext';
-import { CollectionContext } from '../../context/CollectionContext/CollectionContext';
-import { makeStyles } from '@mui/styles';
-import { useCollectionStore } from '../../context/hooks/collection';
-
-const useStyles = makeStyles((theme) => ({
- actionButtons: {
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- gap: '1.5rem',
- },
- media: {
- objectFit: 'cover',
- borderRadius: '4px',
- boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
- },
- details: {
- display: 'flex',
- alignItems: 'center',
- gap: '1.5rem',
- marginBottom: '1.5rem',
- },
- dialogTitle: {
- fontSize: '1.5rem',
- fontWeight: 600,
- color: theme.palette.primary.dark,
- },
- dialogContent: {
- padding: '2rem',
- },
- listItem: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- padding: theme.spacing(2),
- backgroundColor: '#ffffff',
- borderRadius: '8px',
- marginBottom: theme.spacing(2),
- boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)',
- },
- listItemText: {
- flex: 1,
- textAlign: 'left',
- marginLeft: theme.spacing(3),
- },
-}));
-
-const GenericCardModal = ({ open, onClose, card, cardInfo, context }) => {
- const classes = useStyles();
- const deckContext = useContext(DeckContext);
- const cartContext = useContext(CartContext);
- const collectionContext = useContext(CollectionContext);
- const [openSnackbar, setOpenSnackbar] = useState(false);
- const [snackbarMessage, setSnackbarMessage] = useState('');
-
- if (!collectionContext) {
- console.error("The component isn't wrapped with CollectionProvider");
- return null;
- }
-
- const contextProps =
- {
- Deck: deckContext,
- Cart: cartContext,
- Store: cartContext,
- Collection: collectionContext,
- }[context] || {};
-
- const {
- openChooseCollectionDialog,
- setOpenChooseCollectionDialog,
- allCollections,
- fetchAllCollectionsForUser,
- setSelectedCollection,
- } = contextProps;
- 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') {
- setSnackbarMessage(`${context} successfully updated`);
- setOpenSnackbar(true);
- onClose();
- }
- };
-
- // console.log('openChooseCollectionDialog', openChooseCollectionDialog);
-
- // useEffect(() => {
- // if (openChooseCollectionDialog === true) {
- // // console.log('Fetching collections...', openChooseCollectionDialog);
- // fetchAllCollectionsForUser();
- // }
- // }, [openChooseCollectionDialog]);
- // console.log('open --------> ', open);
- return (
-
- );
-};
-
-export default GenericCardModal;
diff --git a/src/components/modals/cardModal/GenericCardModal.jsx b/src/components/modals/cardModal/GenericCardModal.jsx
new file mode 100644
index 0000000..4161d78
--- /dev/null
+++ b/src/components/modals/cardModal/GenericCardModal.jsx
@@ -0,0 +1,100 @@
+import React, { useContext, useEffect, useState } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ Snackbar,
+ Alert,
+} from '@mui/material';
+import { useStyles } from '../modalStyles';
+import useAppContext from '../../../context/hooks/useAppContext';
+import useSnackbar from '../../../context/hooks/useSnackBar';
+import CardMediaAndDetails from '../../media/CardMediaAndDetails';
+import GenericActionButtons from '../../buttons/actionButtons/GenericActionButtons';
+import { ModalContext } from '../../../context/ModalContext/ModalContext';
+
+const GenericCardModal = ({ open, card, context }) => {
+ const classes = useStyles();
+ const { contextProps, isContextAvailable } = useAppContext(context);
+ const [snackbar, handleSnackbar, handleCloseSnackbar] = useSnackbar();
+ // const [isOpen, setIsOpen] = useState(false);
+ const [hasLoggedCard, setHasLoggedCard] = useState(false);
+ const { openModalWithCard, closeModal, isModalOpen, modalContent } =
+ useContext(ModalContext);
+
+ const requiresDoubleButtons = context === 'Deck' || context === 'Collection';
+
+ useEffect(() => {
+ if (open && card && !hasLoggedCard) {
+ console.log('Modal opened with card:', card);
+ handleSnackbar('Card details loaded successfully.', 'success');
+ setHasLoggedCard(true);
+ }
+ }, [open, card, hasLoggedCard, handleSnackbar]);
+
+ useEffect(() => {
+ if (!open) {
+ setHasLoggedCard(false); // Reset hasLoggedCard when modal closes
+ }
+ }, [open]); // Removed hasLoggedCard from dependency array
+
+ // Example function to be called when an action is successful
+ const handleActionSuccess = () => {
+ handleSnackbar('Action was successful!', 'success');
+ };
+
+ // Example function to be called when an action fails
+ const handleActionFailure = (error) => {
+ console.error('Action failed:', error);
+ handleSnackbar('Action failed. Please try again.', 'error');
+ };
+
+ return (
+
{
+ closeModal(); // Call closeModal directly
+ }}
+ fullWidth
+ maxWidth="md"
+ >
+ {card?.name}
+
+
+ {requiresDoubleButtons && (
+ <>
+
+
+ >
+ )}
+
+
+
+ {snackbar.message}
+
+
+
+ );
+};
+
+export default GenericCardModal;
diff --git a/src/components/modals/modalStyles.jsx b/src/components/modals/modalStyles.jsx
new file mode 100644
index 0000000..1badb2e
--- /dev/null
+++ b/src/components/modals/modalStyles.jsx
@@ -0,0 +1,44 @@
+import { makeStyles } from '@mui/styles';
+
+export const useStyles = makeStyles((theme) => ({
+ actionButtons: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ gap: '1.5rem',
+ },
+ media: {
+ objectFit: 'cover',
+ borderRadius: '4px',
+ boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
+ },
+ details: {
+ display: 'flex',
+ alignItems: 'center',
+ gap: '1.5rem',
+ marginBottom: '1.5rem',
+ },
+ dialogTitle: {
+ fontSize: '1.5rem',
+ fontWeight: 600,
+ color: theme.palette.primary.dark,
+ },
+ dialogContent: {
+ padding: '2rem',
+ },
+ listItem: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: theme.spacing(2),
+ backgroundColor: '#ffffff',
+ borderRadius: '8px',
+ marginBottom: theme.spacing(2),
+ boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)',
+ },
+ listItemText: {
+ flex: 1,
+ textAlign: 'left',
+ marginLeft: theme.spacing(3),
+ },
+}));
diff --git a/src/components/modals/stripeModal/StripeCheckoutModal.js b/src/components/modals/stripeModal/StripeCheckoutModal.js
index 17931bf..8189dc6 100644
--- a/src/components/modals/stripeModal/StripeCheckoutModal.js
+++ b/src/components/modals/stripeModal/StripeCheckoutModal.js
@@ -1,72 +1,69 @@
import React from 'react';
-import { Modal, Backdrop, Fade, Box, Typography } from '@mui/material';
+import { Modal, Fade, Box, Typography, Backdrop } from '@mui/material';
import { loadStripe } from '@stripe/stripe-js';
import { Elements } from '@stripe/react-stripe-js';
import StripeForm from '../../forms/customerCheckoutForm/StripeForm';
-// Make sure to call `loadStripe` outside of a component’s render to avoid recreating the `Stripe` object on every render.
-console.log('Stripe key: ', process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY);
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY);
const StripeCheckoutModal = ({ open, onClose, onToken, purchases, total }) => {
- // Rest of your component code...
return (
-
-
-
- Your Purchases
-
- {purchases.map((purchase, index) => (
-
- {purchase.name}
-
- ${purchase.card_prices[0].tcgplayer_price}
-
-
- ))}
+ theme.zIndex.drawer + 1 }} // Ensures the backdrop is above other elements but below the modal
+ >
+
- Total:
- ${total}
+
+ Your Purchases
+
+ {purchases.map((purchase, index) => (
+
+ {purchase.name}
+
+ ${purchase.card_prices[0].tcgplayer_price}
+
+
+ ))}
+
+ Total:
+ ${total}
+
+
+
+
-
-
-
-
-
+
+
);
};
diff --git a/src/components/other/CardCountDisplay.jsx b/src/components/other/CardCountDisplay.jsx
deleted file mode 100644
index 46f3362..0000000
--- a/src/components/other/CardCountDisplay.jsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-import { Grid } from '@mui/material';
-
-const CardCountDisplay = ({ quantity, label, className }) => {
- // Destructure only if `quantity` is not null or undefined
- const { totalItems, quantityOfSameId } = quantity || {};
-
- console.log('Quantity:', quantity, 'Type:', typeof quantity);
- console.log('Label:', label, 'Type:', typeof label);
- console.log('ClassName:', className, 'Type:', typeof className);
- console.log('Total Items:', totalItems, 'Type:', typeof totalItems);
-
- return (
-
-
- {/* Use optional chaining to safely access `totalItems` */}
- {label}: {quantity?.totalItems || 0}
-
-
- );
-};
-
-export default CardCountDisplay;
diff --git a/src/components/other/CardNameInput.js b/src/components/other/CardNameInput.js
deleted file mode 100644
index d9b600b..0000000
--- a/src/components/other/CardNameInput.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import { Input } from '@mui/material';
-import { useCardStore } from '../../context/CardContext/CardStore';
-
-const CardNameInput = ({ value, setValue }) => {
- const { handleRequest } = useCardStore();
-
- // const handleKeyDown = (event) => {
- // if (event.key === 'Enter') {
- // handleRequest(searchParams); // Use handleRequest from context
- // }
- // };
-
- // const handleChange = (event) => {
- // setSearchParams((prevState) => ({
- // ...prevState,
- // name: event.target.value,
- // }));
- // };
- const handleKeyDown = (event) => {
- if (event.key === 'Enter') {
- handleRequest(); // Use handleRequest from context
- }
- };
-
- const handleChange = (event) => {
- setValue(event.target.value);
- };
- return (
-
- );
-};
-
-export default CardNameInput;
diff --git a/src/components/other/CollectionEditPanel.jsx b/src/components/other/CollectionEditPanel.jsx
deleted file mode 100644
index 01df715..0000000
--- a/src/components/other/CollectionEditPanel.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { makeStyles } from '@mui/styles';
-import { Button, TextField, Paper, Typography } from '@mui/material';
-
-const useStyles = makeStyles((theme) => ({
- root: {
- padding: theme.spacing(4),
- borderRadius: '12px',
- backgroundColor: theme.palette.grey[100],
- boxShadow: '0px 8px 16px 0px rgba(0,0,0,0.2)',
- },
- header: {
- paddingBottom: theme.spacing(2),
- borderBottom: `2px solid ${theme.palette.secondary.main}`,
- },
- textField: {
- marginTop: theme.spacing(3),
- '& .MuiOutlinedInput-root': {
- '&:hover fieldset': {
- borderColor: theme.palette.secondary.main,
- },
- '&.Mui-focused fieldset': {
- borderColor: theme.palette.secondary.dark,
- },
- },
- },
- saveButton: {
- marginTop: theme.spacing(3),
- borderRadius: '24px',
- textTransform: 'none',
- fontSize: 18,
- padding: theme.spacing(1, 4),
- },
-}));
-
-const CollectionEditPanel = ({
- collection,
- onSave,
- isNew,
- name,
- setName,
- setDescription,
- description,
-}) => {
- const classes = useStyles();
-
- const handleSave = () => {
- onSave({
- ...collection,
- name,
- description,
- });
- };
-
- return (
-
-
- {isNew ? 'Create Collection' : 'Edit Collection'}
-
- setName(e.target.value)}
- />
- setDescription(e.target.value)}
- />
-
-
- );
-};
-
-export default CollectionEditPanel;
diff --git a/src/components/other/CustomSelector.js b/src/components/other/CustomSelector.js
deleted file mode 100644
index d3978e5..0000000
--- a/src/components/other/CustomSelector.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-import { Grid, FormControl, InputLabel, Select, MenuItem } from '@mui/material';
-
-const CustomSelector = ({ label, name, value, setValue, values }) => {
- const handleChange = (event) => {
- setValue(
- event.target.value.toLowerCase() === 'unset' ? '' : event.target.value
- );
- };
-
- return (
-
-
- {label}
-
-
-
- );
-};
-
-export default CustomSelector;
diff --git a/src/components/other/DeckDisplay.js b/src/components/other/DeckDisplay.js
deleted file mode 100644
index 4309e4e..0000000
--- a/src/components/other/DeckDisplay.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import React, { useContext, useEffect, useState } from 'react';
-import { Paper, Button } from '@mui/material';
-import { DeckContext } from '../../context/DeckContext/DeckContext';
-import DeckButtonList from '../grids/deckBuilderGrids/DeckButtonList';
-import CardsGrid from '../grids/deckBuilderGrids/CardsGrid';
-import DeckEditPanel from './DeckEditPanel';
-import { makeStyles } from '@mui/styles';
-
-const useStyles = makeStyles((theme) => ({
- root: { backgroundColor: '#f4f6f8' },
- paper: {
- padding: theme.spacing(3),
- borderRadius: '10px',
- boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
- },
- deckEditPanel: {
- padding: theme.spacing(2),
- backgroundColor: '#ffffff',
- border: '1px solid #ddd',
- borderRadius: theme.spacing(1),
- boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
- transition: 'opacity 0.5s ease-in-out',
- },
- noCardsText: {
- color: '#666',
- fontStyle: 'italic',
- },
-}));
-
-const DeckDisplay = ({ userDecks = [] }) => {
- const classes = useStyles();
- const { setSelectedDeck, selectedDeck, updateAndSyncDeck } =
- useContext(DeckContext);
- const [selectedCards, setSelectedCards] = useState([]);
- const [showAllDecks, setShowAllDecks] = useState(false);
-
- useEffect(() => {
- setSelectedCards(selectedDeck?.cards?.slice(0, 30) || []);
- }, [selectedDeck]);
-
- const handleSelectDeck = (deckId) => {
- const foundDeck = userDecks.find((deck) => deck?._id === deckId);
- if (foundDeck) {
- setSelectedDeck(foundDeck);
- }
- };
-
- return (
-
-
-
- {showAllDecks && (
-
- )}
- {selectedDeck && (
-
- )}
- {selectedCards.length > 0 ? (
-
- ) : (
- No cards to display
- )}
-
-
- );
-};
-
-export default DeckDisplay;
diff --git a/src/components/other/DeckEditPanel.js b/src/components/other/DeckEditPanel.js
deleted file mode 100644
index c544dc0..0000000
--- a/src/components/other/DeckEditPanel.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, { useState } from 'react';
-import { makeStyles } from '@mui/styles';
-import { Button, TextField, Paper } from '@mui/material';
-
-const useStyles = makeStyles((theme) => ({
- root: {
- padding: theme.spacing(3),
- borderRadius: theme.shape.borderRadius,
- boxShadow: theme.shadows[3],
- },
- textField: {
- marginBottom: theme.spacing(2),
- '& .MuiOutlinedInput-root': {
- '&:hover fieldset': {
- borderColor: theme.palette.primary.main,
- },
- '&.Mui-focused fieldset': {
- borderColor: theme.palette.primary.dark,
- },
- },
- },
- saveButton: {
- boxShadow: 'none',
- textTransform: 'none',
- fontSize: 16,
- padding: theme.spacing(1, 2),
- border: `1px solid ${theme.palette.primary.main}`,
- lineHeight: 1.5,
- backgroundColor: theme.palette.primary.main,
- color: theme.palette.primary.contrastText,
- '&:hover': {
- backgroundColor: theme.palette.primary.dark,
- borderColor: theme.palette.primary.dark,
- boxShadow: 'none',
- },
- },
-}));
-
-const DeckEditPanel = ({ selectedDeck, onSave }) => {
- const classes = useStyles();
- const [name, setName] = useState(selectedDeck?.name || '');
- const [description, setDescription] = useState(
- selectedDeck?.description || ''
- );
-
- const handleSave = () => {
- // Log the new deck name and description before saving
- console.log('New Deck Name:', name);
- console.log('New Deck Description:', description);
-
- onSave({
- ...selectedDeck,
- name,
- description,
- });
- };
-
- return (
-
- setName(e.target.value)}
- />
- setDescription(e.target.value)}
- />
-
-
- );
-};
-
-export default DeckEditPanel;
diff --git a/src/components/other/InputComponents/CardNameInput.js b/src/components/other/InputComponents/CardNameInput.js
new file mode 100644
index 0000000..d8b8365
--- /dev/null
+++ b/src/components/other/InputComponents/CardNameInput.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Input } from '@mui/material';
+import { useCardStore } from '../../../context/CardContext/CardStore';
+
+const CardNameInput = ({ value, setValue, handleChange }) => {
+ const { handleRequest } = useCardStore();
+
+ // const handleChange = (event) => {
+ // setValue(event.target.value);
+ // };
+ return (
+
{
+ if (event.key === 'Enter') {
+ handleRequest(value);
+ }
+ }}
+ value={value}
+ />
+ );
+};
+
+export default CardNameInput;
diff --git a/src/components/other/InputComponents/CollectionStatisticsSelector.jsx b/src/components/other/InputComponents/CollectionStatisticsSelector.jsx
new file mode 100644
index 0000000..c55a545
--- /dev/null
+++ b/src/components/other/InputComponents/CollectionStatisticsSelector.jsx
@@ -0,0 +1,169 @@
+import React, { useState, useMemo } from 'react';
+import {
+ MenuItem,
+ Select,
+ Typography,
+ Box,
+ Grid,
+ CardContent,
+ Card,
+} from '@mui/material';
+import { getFilteredData2 } from '../../reusable/chartUtils';
+import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext';
+
+function calculatePriceChanges(data) {
+ const sortedData = data.sort((a, b) => new Date(a.x) - new Date(b.x));
+ const latestDataPoint = sortedData[sortedData.length - 1];
+ const latestTime = new Date(latestDataPoint.x).getTime();
+ const twentyFourHoursAgo = latestTime - 24 * 60 * 60 * 1000;
+
+ // Find the data point closest to 24 hours before the latest data point
+ let closestIndex = -1;
+ let closestTimeDifference = Number.MAX_SAFE_INTEGER;
+
+ for (let i = 0; i < sortedData.length - 1; i++) {
+ const time = new Date(sortedData[i].x).getTime();
+ const timeDifference = Math.abs(time - twentyFourHoursAgo);
+
+ if (timeDifference < closestTimeDifference) {
+ closestTimeDifference = timeDifference;
+ closestIndex = i;
+ }
+ }
+
+ if (closestIndex !== -1) {
+ const pastPrice = sortedData[closestIndex].y;
+ // console.log('pastPrice', pastPrice);
+ const priceChange = latestDataPoint.y - pastPrice;
+ console.log('priceChange', priceChange);
+ const percentageChange = ((priceChange / pastPrice) * 100).toFixed(2);
+ // console.log('percentageChange', percentageChange);
+
+ return [
+ {
+ startDate: sortedData[closestIndex].x,
+ lowPoint: pastPrice.toFixed(2),
+ highPoint: latestDataPoint?.y?.toFixed(2),
+ endDate: latestDataPoint?.x,
+ priceChange: priceChange.toFixed(2),
+ percentageChange: `${percentageChange}%`,
+ priceIncreased: priceChange > 0,
+ },
+ ];
+ }
+
+ return [];
+}
+
+export const calculateStatistics = (data, timeRange) => {
+ if (!data || data.length === 0) return {};
+ const filteredData = data?.data?.filter(
+ (item) => new Date(item?.x).getTime() >= Date.now() - timeRange
+ );
+ if (filteredData.length === 0) return {};
+ let highPoint = 0;
+ let lowPoint = 0;
+ let sum = 0;
+ let averageData = 0;
+ let average = 0;
+ let volume = 0;
+ let mean = 0;
+ let squaredDiffs = 0;
+ let volatility = 0;
+ // const filteredData2 = getFilteredData2(data, timeRange);
+ // console.log('filteredData2', filteredData2);
+ // console.log('filteredData', filteredData);
+ for (const data of filteredData) {
+ highPoint = Math.max(...filteredData.map((item) => item.y));
+ lowPoint = Math.min(...filteredData.map((item) => item.y));
+ sum = filteredData.reduce((acc, curr) => acc + curr.y, 0);
+ averageData = calculatePriceChanges(filteredData);
+ average = sum / filteredData.length || 0;
+ volume = filteredData.length;
+ mean = sum / volume;
+ squaredDiffs = filteredData.map((item) => {
+ const diff = item.y - mean;
+ return diff * diff;
+ });
+ volatility = Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / volume);
+ }
+
+ return {
+ highPoint: highPoint.toFixed(2),
+ lowPoint: lowPoint.toFixed(2),
+ twentyFourHourAverage: {
+ startDate: averageData[0]?.startDate,
+ endDate: averageData[0]?.endDate,
+ lowPoint: averageData[0]?.lowPoint,
+ highPoint: averageData[0]?.highPoint,
+ priceChange: averageData[0]?.priceChange,
+ percentageChange: averageData[0]?.percentageChange,
+ priceIncreased: averageData[0]?.priceIncreased,
+ },
+ average: average?.toFixed(2),
+ volume,
+ volatility: volatility?.toFixed(2),
+ };
+};
+
+const CollectionStatisticsSelector = ({ data, timeRange, stats }) => {
+ const [selectedStat, setSelectedStat] = useState('');
+
+ const handleChange = (event) => setSelectedStat(event.target.value);
+
+ const StatCard = ({ title, value }) => (
+
+
+
+ {title}
+
+
+ {value}
+
+
+
+ );
+
+ return (
+
+
+
+
+ {selectedStat && (
+
+
+
+ )}
+
+
+ );
+};
+
+export default CollectionStatisticsSelector;
diff --git a/src/components/other/InputComponents/CustomSelector.js b/src/components/other/InputComponents/CustomSelector.js
new file mode 100644
index 0000000..2724fa2
--- /dev/null
+++ b/src/components/other/InputComponents/CustomSelector.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import { Grid, FormControl, InputLabel, Select, MenuItem } from '@mui/material';
+import { useMode } from '../../../context/hooks/colormode';
+
+const CustomSelector = ({ label, name, value, handleChange, values }) => {
+ // const handleChange = (event) => {
+ // setValue(
+ // event.target.value.toLowerCase() === 'unset' ? '' : event.target.value
+ // );
+ // };
+ const defaultValue = value || 'Unset';
+
+ if (!values) {
+ return
Values not provided
;
+ }
+
+ if (!handleChange) {
+ return
handleChange not provided
;
+ }
+
+ if (!defaultValue) {
+ return
Value not provided
;
+ }
+ const { theme } = useMode();
+
+ return (
+
+ {label}
+
+
+ );
+};
+
+export default CustomSelector;
diff --git a/src/components/other/InputComponents/DeckEditPanel.js b/src/components/other/InputComponents/DeckEditPanel.js
new file mode 100644
index 0000000..cf5f991
--- /dev/null
+++ b/src/components/other/InputComponents/DeckEditPanel.js
@@ -0,0 +1,109 @@
+import React, { useState } from 'react';
+import { Button, TextField, Paper, Typography } from '@mui/material';
+import { useMode } from '../../../context/hooks/colormode';
+
+const DeckEditPanel = ({ selectedDeck, onSave }) => {
+ const { theme } = useMode();
+ const [name, setName] = useState(selectedDeck?.name || '');
+ const [description, setDescription] = useState(
+ selectedDeck?.description || ''
+ );
+
+ const handleSave = () => {
+ onSave({
+ ...selectedDeck,
+ name,
+ description,
+ });
+ };
+
+ return (
+
+
+ Edit Deck
+
+ setName(e.target.value)}
+ />
+ setDescription(e.target.value)}
+ />
+
+
+ );
+};
+
+export default DeckEditPanel;
diff --git a/src/components/other/InputComponents/TimeRangeSelector.jsx b/src/components/other/InputComponents/TimeRangeSelector.jsx
new file mode 100644
index 0000000..dedff6c
--- /dev/null
+++ b/src/components/other/InputComponents/TimeRangeSelector.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { MenuItem, Select } from '@mui/material';
+import { useChartContext } from '../../../context/ChartContext/ChartContext';
+
+const TimeRangeSelector = () => {
+ const { timeRange, timeRanges, handleChange } = useChartContext();
+
+ return (
+
+ );
+};
+
+export default TimeRangeSelector;
diff --git a/src/components/other/InputComponents/UpdateStatusBox.jsx b/src/components/other/InputComponents/UpdateStatusBox.jsx
new file mode 100644
index 0000000..bfbfff1
--- /dev/null
+++ b/src/components/other/InputComponents/UpdateStatusBox.jsx
@@ -0,0 +1,100 @@
+import React, { useState, useEffect } from 'react';
+import { Typography } from '@mui/material';
+import { useCombinedContext } from '../../../context/CombinedProvider';
+import { useCookies } from 'react-cookie';
+
+const UpdateStatusBox = ({ socket }) => {
+ const { allCollectionData, listOfMonitoredCards, listOfSimulatedCards } =
+ useCombinedContext();
+ const [currentTime, setCurrentTime] = useState(new Date());
+ const [updateStatus, setUpdateStatus] = useState('Waiting for updates...');
+ const [cookies] = useCookies(['user']);
+
+ useEffect(() => {
+ const timeInterval = setInterval(() => {
+ setCurrentTime(new Date());
+ }, 1000);
+
+ const handleStatusUpdate = (statusUpdate) => {
+ setUpdateStatus(statusUpdate.message || 'Waiting for updates...');
+ };
+
+ if (socket) {
+ socket.on('STATUS_UPDATE', handleStatusUpdate);
+ }
+
+ // Cleanup function
+ return () => {
+ clearInterval(timeInterval);
+ if (socket) {
+ socket.off('STATUS_UPDATE', handleStatusUpdate);
+ }
+ };
+ }, [socket]);
+
+ const sendUpdateRequest = () => {
+ if (socket) {
+ socket.emit('STATUS_UPDATE_REQUEST', {
+ message: 'Requesting status update...',
+ data: listOfMonitoredCards,
+ });
+ }
+ };
+
+ // Styling for dark theme
+ const styles = {
+ container: {
+ padding: '15px',
+ border: '2px solid #444',
+ borderRadius: '8px',
+ backgroundColor: '#222',
+ color: '#fff',
+ // margin: '20px auto',
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
+ fontFamily: '"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
+ maxWidth: '400px',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ height: '100%', // Adjust height here
+ width: '100%', // Adjust width here
+ },
+ statusBox: {
+ marginTop: '15px',
+ padding: '10px',
+ background: '#333',
+ borderRadius: '6px',
+ border: '1px solid #555',
+ },
+ button: {
+ padding: '10px 20px',
+ marginTop: '10px',
+ border: 'none',
+ borderRadius: '5px',
+ cursor: 'pointer',
+ backgroundColor: '#5CDB95',
+ color: 'white',
+ fontWeight: 'bold',
+ fontSize: '14px',
+ letterSpacing: '1px',
+ outline: 'none',
+ },
+ };
+
+ return (
+
+
+ Current Time: {currentTime.toLocaleTimeString()}
+
+
+ Update Status: {updateStatus}
+
+
+
+ );
+};
+
+export default UpdateStatusBox;
diff --git a/src/components/other/InputComponents/UpdateStatusBox2.jsx b/src/components/other/InputComponents/UpdateStatusBox2.jsx
new file mode 100644
index 0000000..53d7a35
--- /dev/null
+++ b/src/components/other/InputComponents/UpdateStatusBox2.jsx
@@ -0,0 +1,128 @@
+import React, { useState, useEffect } from 'react';
+import { Snackbar, Typography } from '@mui/material';
+import { useCombinedContext } from '../../../context/CombinedProvider';
+import { useCookies } from 'react-cookie';
+
+const styles = {
+ container: {
+ padding: '15px',
+ border: '2px solid #444',
+ borderRadius: '8px',
+ backgroundColor: '#222',
+ color: '#fff',
+ // margin: '20px auto',
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
+ fontFamily: '"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
+ maxWidth: '400px',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ height: '100%', // Adjust height here
+ width: '100%', // Adjust width here
+ },
+ statusBox: {
+ marginTop: '15px',
+ padding: '10px',
+ background: '#333',
+ borderRadius: '6px',
+ border: '1px solid #555',
+ },
+ button: {
+ padding: '10px 20px',
+ marginTop: '10px',
+ border: 'none',
+ borderRadius: '5px',
+ cursor: 'pointer',
+ backgroundColor: '#5CDB95',
+ color: 'white',
+ fontWeight: 'bold',
+ fontSize: '14px',
+ letterSpacing: '1px',
+ outline: 'none',
+ },
+};
+
+const UpdateStatusBox2 = ({ socket }) => {
+ const {
+ allCollectionData,
+ listOfMonitoredCards,
+ handleSendAllCardsInCollections,
+ } = useCombinedContext();
+ const [currentTime, setCurrentTime] = useState(new Date());
+ const [updateStatus, setUpdateStatus] = useState('Waiting for cron...');
+ // const [openSnackbar, setOpenSnackbar] = useState(false);
+ const [cookies] = useCookies(['user']);
+ const [snackbarData, setSnackbarData] = useState({
+ open: false,
+ message: '',
+ });
+ const userId = cookies.user?.id;
+ const openSnackbar = (message) => {
+ setSnackbarData({ open: true, message });
+ };
+ useEffect(() => {
+ const timeInterval = setInterval(() => {
+ setCurrentTime(new Date());
+ }, 1000);
+
+ const handleStatusUpdate = (statusUpdate) => {
+ setUpdateStatus(statusUpdate.message || 'Waiting for updates...');
+ };
+
+ if (socket) {
+ socket.on('INITIAL_RESPONSE', handleStatusUpdate);
+ }
+
+ // Cleanup function
+ return () => {
+ clearInterval(timeInterval);
+ if (socket) {
+ socket.off('INITIAL_RESPONSE', handleStatusUpdate);
+ }
+ };
+ }, [socket]);
+ const handleTriggerCronJob = () => {
+ console.log('TRIGGERING CRON JOB');
+ console.log('USER ID:', userId);
+ console.log('LIST OF MONITORED CARDS:', listOfMonitoredCards);
+ if (userId && listOfMonitoredCards) {
+ handleSendAllCardsInCollections(userId, listOfMonitoredCards);
+ console.log('SENDING ALL CARDS IN COLLECTIONS');
+ openSnackbar('Triggered the cron job.');
+ }
+ };
+
+ // const sendUpdateRequest = () => {
+ // if (socket) {
+ // socket.emit('STATUS_UPDATE_REQUEST', {
+ // message: 'Requesting status update...',
+ // data: listOfMonitoredCards,
+ // });
+ // }
+ // };
+
+ // Styling for dark theme
+
+ return (
+
+
+ Current Time: {currentTime.toLocaleTimeString()}
+
+
+ Update Status: {updateStatus}
+
+
+
setSnackbarData({ ...snackbarData, open: false })}
+ message={snackbarData.message}
+ />
+
+ );
+};
+
+export default UpdateStatusBox2;
diff --git a/src/components/other/LinearChart.js b/src/components/other/LinearChart.js
deleted file mode 100644
index 35cb65b..0000000
--- a/src/components/other/LinearChart.js
+++ /dev/null
@@ -1,319 +0,0 @@
-import React, { useState, useMemo, useCallback } from 'react';
-import { ResponsiveLine } from '@nivo/line';
-import { makeStyles, useTheme } from '@mui/styles';
-import Typography from '@mui/material/Typography';
-import Box from '@mui/material/Box';
-import Tooltip from '@mui/material/Tooltip';
-
-const useStyles = makeStyles((theme) => ({
- chartContainer: {
- position: 'relative',
- width: '100%',
- height: '100%',
- },
- loadingContainer: {
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- height: '100%',
- },
- xAxisLabel: {
- position: 'absolute',
- bottom: 0,
- left: '50%',
- transform: 'translateX(-50%)',
- textAlign: 'center',
- marginTop: theme.spacing(2),
- fontSize: '1rem',
- color: theme.palette.text.primary,
- },
- yAxisLabel: {
- position: 'absolute',
- top: '50%',
- left: 0,
- transform: 'translateY(-50%) rotate(-90deg)',
- textAlign: 'center',
- marginTop: theme.spacing(2),
- fontSize: '1rem',
- color: theme.palette.text.primary,
- },
- customTooltip: {
- borderRadius: theme.shape.borderRadius,
- padding: theme.spacing(1),
- backgroundColor: theme.palette.background.paper,
- boxShadow: theme.shadows[3],
- },
-}));
-
-const CustomTooltip = ({ point }) => {
- const theme = useTheme();
- const { serieId, data } = point;
- const { label, xFormatted, yFormatted } = data || {};
- const series = {
- type: {
- Collection: 'Collection',
- Card: 'Card',
- Deck: 'Deck',
- },
- };
- return (
-
-
- {`Series: ${serieId}`}
-
- {series.type[label] === 'Card' && (
-
- {`Card: ${label}`}
-
- )}
-
- {`Time: ${new Date(xFormatted).toLocaleString()}`}
-
-
- {`Value: $${parseFloat(yFormatted).toFixed(2)}`}
-
-
- );
-};
-
-const roundToNearestTenth = (value) => {
- return Math.round(value * 10) / 10;
-};
-
-const getFilteredData = (data, timeRange) => {
- const cutOffTime = new Date().getTime() - timeRange;
- return data
- .filter((d) => 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, handleMouseMove, handleMouseLeave } = useEventHandlers();
-
- const isValidDataPoint = (d) => d && 'x' in d && 'y' in d;
- if (!Array.isArray(data) || !data.every(isValidDataPoint)) {
- return
No valid data available;
- }
-
- const filteredData = useMemo(
- () => getFilteredData(data, timeRange),
- [data, timeRange]
- );
- const averagedData = getAveragedData(filteredData);
- // console.log('averagedData', averagedData);
- const tickValues = useMemo(
- () => getTickValues(timeRange, timeRanges),
- [timeRange]
- );
-
- const lastData = useMemo(() => {
- if (data && data.length) {
- return {
- x: data[data.length - 1].x,
- y: data[data.length - 1].y,
- };
- }
- return {};
- }, [data]);
-
- return (
-
- `$${value}`,
- }}
- enablePointLabel
- pointLabel="y"
- pointLabelYOffset={-12}
- pointSize={6}
- pointBorderWidth={1}
- // pointBorderColor={{ from: 'color', modifiers: [['darker', 0.7]] }}
- pointColor={theme.palette.primary.main}
- // pointBorderColor={theme.palette.secondary.main}
- colors={[theme.palette.primary.light]}
- theme={{
- points: {
- dot: {
- ...classes.customPoint, // Added for improved style
- border: '1px solid ' + theme.palette.secondary.main,
- },
- tooltip: {
- container: {
- borderRadius: theme.shape.borderRadius,
- },
- },
- },
- grid: {
- line: {
- stroke: theme.palette.divider,
- strokeWidth: 1,
- strokeDasharray: '4 4',
- },
- },
- }}
- lineWidth={3}
- curve="monotoneX"
- useMesh={true}
- data={[
- {
- id: 'Dataset',
- data: averagedData?.map((d) => ({
- x: new Date(d.x),
- y: parseFloat(d.y),
- })),
- },
- ]}
- // data={[
- // {
- // id: 'Dataset',
- // data: averagedData.map((d) => ({
- // x: new Date(d.x).getTime(),
- // y: parseFloat(d.y),
- // })),
- // },
- // ]}
- onMouseMove={handleMouseMove}
- onMouseLeave={handleMouseLeave}
- onClick={() => setIsZoomed(!isZoomed)}
- tooltip={({ point }) => }
- sliceTooltip={({ slice }) => {
- const point = slice.points.find(
- (p) => p.id === 'Dataset' && p.data.x === lastData.x
- );
- if (point) {
- return ;
- }
- return null;
- }}
- />
-
-
- {hoveredData
- ? new Date(hoveredData.x).toLocaleString()
- : latestData?.x}
-
-
-
-
- {`$${hoveredData ? hoveredData.y : latestData?.y}`}
-
-
-
- );
-};
-
-export default LinearChart;
diff --git a/src/components/other/PortfolioChart.jsx b/src/components/other/PortfolioChart.jsx
deleted file mode 100644
index a116633..0000000
--- a/src/components/other/PortfolioChart.jsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react';
-import {
- Container,
- Grid,
- MenuItem,
- Paper,
- Select,
- debounce,
-} from '@mui/material';
-import { styled } from '@mui/system';
-import LinearChart from './LinearChart';
-import { useCollectionStore } from '../../context/hooks/collection';
-import TimeRangeSelector, { timeRanges } from './TimeRangeSelector';
-import { useChartContext } from '../../context/ChartContext/ChartContext';
-import ErrorBoundary from '../../context/ErrorBoundary';
-
-const ChartPaper = styled('div')(({ theme }) => ({
- borderRadius: '12px',
- // width: '100%',
- height: '100%',
- minWidth: '500px',
- maxWidth: '800px',
- boxShadow: '0 3px 5px 2px rgba(0, 0, 0, .3)',
- backgroundColor: '#ffffff',
- color: '#333',
- position: 'relative',
- [theme.breakpoints.down('sm')]: {
- minWidth: '300px',
- },
- [theme.breakpoints.up('md')]: {
- minWidth: '500px',
- },
- [theme.breakpoints.up('lg')]: {
- minWidth: '700px',
- },
-}));
-
-const groupAndAverageData = (data, threshold = 1) => {
- if (!data || data.length === 0) return [];
-
- // 1. Create the clusters
- const clusters = [];
- let currentCluster = [data[0]];
-
- for (let i = 1; i < data.length; i++) {
- if (data[i].x - data[i - 1].x <= threshold) {
- currentCluster.push(data[i]);
- } else {
- clusters.push(currentCluster);
- currentCluster = [data[i]];
- }
- }
- clusters.push(currentCluster); // Push the last cluster
-
- // 2. For each cluster, find the middlemost x-value and average y-values
- const averagedData = clusters.map((cluster) => {
- const middleIndex = Math.floor(cluster.length / 2);
- const avgY =
- cluster.reduce((sum, point) => sum + point.y, 0) / cluster.length;
-
- return {
- x: cluster[middleIndex].x,
- y: avgY,
- };
- });
-
- return averagedData;
-};
-
-const PortfolioChart = () => {
- const { latestData, setLatestData, timeRange, setTimeRange } =
- useChartContext();
- const [lastUpdateTime, setLastUpdateTime] = useState(null);
- const chartContainerRef = useRef(null);
- const { selectedCollection } = useCollectionStore();
- const datasets = selectedCollection?.chartData?.allXYValues || [];
- const datasets2 = useMemo(() => groupAndAverageData(datasets), [datasets]);
-
- // Debounced function for setting the last update time
- const debouncedSetLastUpdateTime = useMemo(
- () =>
- debounce(() => {
- const currentTime = new Date().getTime();
-
- if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) {
- setLastUpdateTime(currentTime);
-
- const lastDatasetIndex = datasets.length - 1;
- const lastDataset = datasets[lastDatasetIndex];
- lastDataset && setLatestData(lastDataset);
- }
- }, 100),
- [datasets, lastUpdateTime]
- );
-
- useEffect(() => {
- if (!datasets) return;
- debouncedSetLastUpdateTime();
- }, [datasets, debouncedSetLastUpdateTime]);
-
- const chartDimensions = useMemo(
- () =>
- chartContainerRef.current?.getBoundingClientRect() || {
- width: 400,
- height: 600,
- },
- [chartContainerRef.current]
- );
-
- return (
-
-
- {/* */}
- setTimeRange(e.target.value)}
- />
-
-
- {datasets2.length > 0 ? (
-
- ) : (
- No data available
- )}
-
-
- {/* */}
-
-
- );
-};
-
-export default PortfolioChart;
diff --git a/src/components/other/TimeRangeSelector.jsx b/src/components/other/TimeRangeSelector.jsx
deleted file mode 100644
index 330f24a..0000000
--- a/src/components/other/TimeRangeSelector.jsx
+++ /dev/null
@@ -1,21 +0,0 @@
-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/other/dataDisplay/CardCountDisplay.jsx b/src/components/other/dataDisplay/CardCountDisplay.jsx
new file mode 100644
index 0000000..ecf90b4
--- /dev/null
+++ b/src/components/other/dataDisplay/CardCountDisplay.jsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { Grid, Typography } from '@mui/material';
+import { styled } from '@mui/material/styles';
+import PropTypes from 'prop-types';
+
+// Styled components
+const StyledGrid = styled(Grid)(({ theme }) => ({
+ padding: theme.spacing(1),
+ backgroundColor: theme.palette.background.paper,
+ borderRadius: theme.shape.borderRadius,
+ boxShadow: theme.shadows[2],
+ textAlign: 'center',
+}));
+
+const CardCountDisplay = ({ quantity, label, className }) => {
+ const totalItems = quantity && quantity.totalItems ? quantity.totalItems : 0;
+
+ return (
+
+
+
+ {label}: {totalItems}
+
+
+
+ );
+};
+
+CardCountDisplay.propTypes = {
+ quantity: PropTypes.shape({
+ totalItems: PropTypes.number,
+ }),
+ label: PropTypes.string,
+ className: PropTypes.string,
+};
+
+export default CardCountDisplay;
diff --git a/src/components/other/CartSummary.js b/src/components/other/dataDisplay/CartSummary.js
similarity index 100%
rename from src/components/other/CartSummary.js
rename to src/components/other/dataDisplay/CartSummary.js
diff --git a/src/components/other/CartTotal.jsx b/src/components/other/dataDisplay/CartTotal.jsx
similarity index 100%
rename from src/components/other/CartTotal.jsx
rename to src/components/other/dataDisplay/CartTotal.jsx
diff --git a/src/components/other/dataDisplay/CollectionValueTracker.jsx b/src/components/other/dataDisplay/CollectionValueTracker.jsx
new file mode 100644
index 0000000..54a534f
--- /dev/null
+++ b/src/components/other/dataDisplay/CollectionValueTracker.jsx
@@ -0,0 +1,182 @@
+import React from 'react';
+import {
+ Typography,
+ Box,
+ useTheme,
+ Grid,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Container,
+} from '@mui/material';
+import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
+import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
+import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext';
+import { styled } from '@mui/styles';
+import { GridExpandMoreIcon } from '@mui/x-data-grid';
+import ChangeHistoryIcon from '@mui/icons-material/ChangeHistory';
+const StatBox = styled(Box)(({ theme }) => ({
+ backgroundColor: theme.palette.background.default,
+ borderRadius: theme.shape.borderRadius,
+ padding: theme.spacing(2),
+ boxShadow: theme.shadows[3],
+ color: theme.palette.text.primary,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ gap: theme.spacing(1), // Adds spacing between items
+}));
+
+const StatItem = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ marginBottom: theme.spacing(1),
+ '&:hover': {
+ backgroundColor: theme.palette.action.hover, // Hover effect
+ },
+}));
+const styles = {
+ container: {
+ // padding: '15px',
+ border: '2px solid #444',
+ borderRadius: '8px',
+ backgroundColor: '#222',
+ color: '#fff',
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
+ fontFamily: '"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
+ maxWidth: '100%', // Adjusted for full width
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ height: '100%',
+ },
+ statusBox: {
+ width: '100%', // Set to full width
+ // marginTop: '15px',
+ padding: '3px',
+ margin: '2px',
+ // padding: '10px',
+ background: '#333',
+ borderRadius: '6px',
+ border: '1px solid #555',
+ },
+};
+const CollectionValueTracker = ({ stats }) => {
+ const theme = useTheme();
+ const { getTotalPrice, selectedCollection } = useCollectionStore();
+ const twentyFourHourChange = stats.twentyFourHourAverage;
+ const newTotal = getTotalPrice();
+
+ console.log('newTotal:', newTotal);
+
+ const statsArray = [
+ {
+ label: 'Total Collection Value',
+ value: `$${newTotal}`,
+ },
+ {
+ label: '24 Hour Change (%)',
+ value: `${twentyFourHourChange?.percentageChange}`,
+ isIncrease: twentyFourHourChange?.priceIncreased,
+ },
+ {
+ label: '24 Hour Change ($)',
+ value: `$${twentyFourHourChange?.priceChange}`,
+ isIncrease: twentyFourHourChange?.priceIncreased,
+ },
+ // Additional stats
+ {
+ label: 'Last Price',
+ value: `${selectedCollection?.lastSavedPrice?.num}`,
+ },
+ { label: 'Average Price', value: `${stats?.average}` },
+ { label: 'Volume', value: `${stats?.volume}` },
+ { label: 'Volatility', value: `${stats?.volatility}` },
+ { label: 'High Point', value: `${twentyFourHourChange?.highPoint}` },
+ { label: 'Low Point', value: `${twentyFourHourChange?.lowPoint}` },
+ // TODO: add stats for top performing cards in certain sets
+ ];
+
+ return (
+
+
+
+
+
+ Performance:{' '}
+ {twentyFourHourChange?.percentageChange > 0
+ ? 'Positive'
+ : 'Negative'}
+
+ {/*
*/}
+ {/* */}
+
+ {' '}
+ {twentyFourHourChange?.percentageChange}%
+
+
+ }
+ aria-controls="collection-stats-content"
+ id="collection-stats-header"
+ >
+ Collection Statistics
+
+
+
+ {statsArray.map((stat, index) => {
+ const IconComponent =
+ stat?.isIncrease !== undefined
+ ? stat?.isIncrease
+ ? ArrowUpwardIcon
+ : ArrowDownwardIcon
+ : null;
+ const iconColor = stat?.isIncrease
+ ? theme.palette.success.main
+ : theme.palette.error.main;
+
+ return (
+
+ {IconComponent && (
+
+ )}
+
+ {stat?.label}:
+
+
+ {stat?.value}
+
+
+ );
+ })}
+
+
+
+
+
+
+
+ );
+};
+
+export default CollectionValueTracker;
diff --git a/src/components/other/dataDisplay/ProgressCircle.jsx b/src/components/other/dataDisplay/ProgressCircle.jsx
new file mode 100644
index 0000000..50638ed
--- /dev/null
+++ b/src/components/other/dataDisplay/ProgressCircle.jsx
@@ -0,0 +1,23 @@
+import { Box, useTheme } from '@mui/material';
+import { tokens } from '../styles/theme';
+
+const ProgressCircle = ({ progress = '0.75', size = '40' }) => {
+ const theme = useTheme();
+ const colors = tokens(theme.palette.mode);
+ const angle = progress * 360;
+
+ return (
+
+ );
+};
+
+export default ProgressCircle;
diff --git a/src/components/other/dataDisplay/StatBox.jsx b/src/components/other/dataDisplay/StatBox.jsx
new file mode 100644
index 0000000..2aa5798
--- /dev/null
+++ b/src/components/other/dataDisplay/StatBox.jsx
@@ -0,0 +1,43 @@
+import { Box, Typography, useTheme } from '@mui/material';
+import { tokens } from '../styles/theme';
+import ProgressCircle from './ProgressCircle';
+
+const StatBox = ({ title, subtitle, icon, progress, increase }) => {
+ const theme = useTheme();
+ const colors = tokens(theme.palette.mode);
+
+ return (
+
+
+
+ {icon}
+
+ {title}
+
+
+
+
+
+
+
+
+
+ {subtitle}
+
+
+ {increase}
+
+
+
+ );
+};
+
+export default StatBox;
diff --git a/src/components/other/dataDisplay/StatisticsArea.jsx b/src/components/other/dataDisplay/StatisticsArea.jsx
new file mode 100644
index 0000000..c55f24b
--- /dev/null
+++ b/src/components/other/dataDisplay/StatisticsArea.jsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Grid } from '@mui/material';
+
+import withTheme from '@mui/styles/withTheme';
+
+function StatisticsArea(props) {
+ const { theme, CardChart, data } = props;
+ return (
+ CardChart &&
+ data.profit.length >= 2 &&
+ data.views.length >= 2 && (
+
+
+
+
+
+
+
+
+ )
+ );
+}
+
+StatisticsArea.propTypes = {
+ theme: PropTypes.object.isRequired,
+ data: PropTypes.object.isRequired,
+ CardChart: PropTypes.elementType,
+};
+
+export default withTheme(StatisticsArea);
diff --git a/src/components/other/dataDisplay/TopCardsDisplay.jsx b/src/components/other/dataDisplay/TopCardsDisplay.jsx
new file mode 100644
index 0000000..be8a8da
--- /dev/null
+++ b/src/components/other/dataDisplay/TopCardsDisplay.jsx
@@ -0,0 +1,167 @@
+import React, { useContext, useEffect, useRef, useState } from 'react';
+import SwipeableViews from 'react-swipeable-views';
+import {
+ Box,
+ Button,
+ Card,
+ CardContent,
+ CardMedia,
+ Grid,
+ MobileStepper,
+ Paper,
+ Typography,
+ useTheme,
+} from '@mui/material';
+import { KeyboardArrowLeft, KeyboardArrowRight } from '@mui/icons-material';
+import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext';
+import { useMode } from '../../../context/hooks/colormode';
+import { makeStyles } from '@mui/styles';
+import { ModalContext } from '../../../context/ModalContext/ModalContext';
+import GenericCard from '../../cards/GenericCard';
+import {
+ MainContainer2,
+ MainContainer,
+} from '../../../pages/pageStyles/StyledComponents';
+const useStyles = makeStyles((theme) => ({
+ stepper: {
+ // backgroundColor: theme.palette.success.main,
+ background: theme.palette.primary.light,
+ color: 'white',
+ marginTop: 'auto',
+ },
+}));
+
+const CarouselCard = ({ card }) => {
+ const { theme } = useMode();
+ const classes = useStyles();
+ const placeholderImage = '../../assets/images/placeholder.jpeg';
+ const imgUrl = card?.card_images?.[0]?.image_url || placeholderImage;
+ const { openModalWithCard, closeModal, isModalOpen, modalContent } =
+ useContext(ModalContext);
+ const cardRef = useRef(null);
+
+ const handleClick = () => {
+ openModalWithCard(card);
+ };
+ return (
+
+
+
+
+
+
+
+ {card.name}
+
+
+ {card.description}
+
+
+ Price: ${card?.latestPrice?.num ?? 'N/A'}
+
+ {/* Additional statistics */}
+
+
+
+ );
+};
+
+const TopCardsDisplay = () => {
+ const { theme } = useMode();
+ const { selectedCollection } = useCollectionStore();
+ const [top5Cards, setTop5Cards] = useState([]);
+ const [activeStep, setActiveStep] = useState(0);
+ const classes = useStyles();
+
+ useEffect(() => {
+ const sortedCards = selectedCollection?.cards
+ ?.map((card) => {
+ const latestPrice = card.latestPrice?.num ?? 0;
+ const lastSavedPrice = card.lastSavedPrice?.num ?? 0;
+
+ if (
+ latestPrice === 0 ||
+ latestPrice === undefined ||
+ lastSavedPrice === 0 ||
+ lastSavedPrice === undefined
+ ) {
+ console.warn(`Price missing for card: ${card.name}`);
+ return { ...card, diff: 0 };
+ }
+
+ console.log('latestPrice', latestPrice);
+ console.log('lastSavedPrice', lastSavedPrice);
+ return { ...card, diff: Math.abs(latestPrice - lastSavedPrice) };
+ })
+ .sort((a, b) => b.diff - a.diff || b.price - a.price)
+ .slice(0, 5);
+
+ console.log('sortedCards', sortedCards);
+ setTop5Cards(sortedCards);
+ }, [selectedCollection]);
+
+ const maxSteps = top5Cards.length;
+
+ const handleNext = () =>
+ setActiveStep((prevActiveStep) => prevActiveStep + 1);
+ const handleBack = () =>
+ setActiveStep((prevActiveStep) => prevActiveStep - 1);
+
+ return (
+
+
+ {top5Cards.map((card, index) => (
+
+
+
+ ))}
+
+
+ {theme.direction === 'rtl' ? (
+
+ ) : (
+
+ )}{' '}
+ Next
+
+ }
+ backButton={
+
+ }
+ />
+
+ );
+};
+
+export default TopCardsDisplay;
diff --git a/src/components/other/UserStats.jsx b/src/components/other/dataDisplay/UserStats.jsx
similarity index 76%
rename from src/components/other/UserStats.jsx
rename to src/components/other/dataDisplay/UserStats.jsx
index 466e111..0cb073a 100644
--- a/src/components/other/UserStats.jsx
+++ b/src/components/other/dataDisplay/UserStats.jsx
@@ -1,8 +1,8 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
-import { useDeckStore } from '../../context/DeckContext/DeckContext';
-import { useCollectionStore } from '../../context/hooks/collection';
-import { useCartStore } from '../../context/CartContext/CartContext';
+import { useDeckStore } from '../../../context/DeckContext/DeckContext';
+import { useCartStore } from '../../../context/CartContext/CartContext';
+import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext';
const UserStats = () => {
const { allDecks } = useDeckStore();
diff --git a/src/components/reusable/ChartErrorBoundary.jsx b/src/components/reusable/ChartErrorBoundary.jsx
new file mode 100644
index 0000000..7ec649a
--- /dev/null
+++ b/src/components/reusable/ChartErrorBoundary.jsx
@@ -0,0 +1,30 @@
+import { Typography } from '@mui/material';
+import React from 'react';
+
+class ChartErrorBoundary extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { hasError: false, error: null };
+ }
+
+ static getDerivedStateFromError(error) {
+ // Update state so the next render will show the fallback UI.
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error, errorInfo) {
+ // You can log the error to an error reporting service
+ console.error('Error in chart component:', error, errorInfo);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ // You can render any custom fallback UI
+ return
Unable to display chart;
+ }
+
+ return this.props.children;
+ }
+}
+
+export default ChartErrorBoundary;
diff --git a/src/components/other/CustomPagination.jsx b/src/components/reusable/CustomPagination.jsx
similarity index 100%
rename from src/components/other/CustomPagination.jsx
rename to src/components/reusable/CustomPagination.jsx
diff --git a/src/components/reusable/HeaderTitle.jsx b/src/components/reusable/HeaderTitle.jsx
index 9d0559a..b70c0da 100644
--- a/src/components/reusable/HeaderTitle.jsx
+++ b/src/components/reusable/HeaderTitle.jsx
@@ -4,12 +4,12 @@ import React from 'react';
const HeaderTitle = ({ title, size = 'extraSmall', location = 'left' }) => {
const sizes = {
- huge: '2.5rem',
- large: '1.75rem',
- medium: '1.5rem',
- small: '1.25rem',
- tiny: '1rem',
- extraSmall: '0.75rem',
+ huge: { fontSize: '2.5rem', lineHeight: '3rem' },
+ large: { fontSize: '1.75rem', lineHeight: '2.25rem' },
+ medium: { fontSize: '1.5rem', lineHeight: '2rem' },
+ small: { fontSize: '1.25rem', lineHeight: '1.75rem' },
+ tiny: { fontSize: '1rem', lineHeight: '1.5rem' },
+ extraSmall: { fontSize: '0.75rem', lineHeight: '1.25rem' },
};
const textAlign = {
@@ -18,24 +18,33 @@ const HeaderTitle = ({ title, size = 'extraSmall', location = 'left' }) => {
right: 'right',
};
+ const containerStyles = {
+ backgroundColor: (theme) => theme.palette.background.default,
+ padding: 2,
+ transition: 'background-color 0.3s ease',
+ '&:hover': {
+ backgroundColor: (theme) => theme.palette.background.paper,
+ },
+ };
+
+ const typographyStyles = {
+ fontSize: sizes[size].fontSize,
+ lineHeight: sizes[size].lineHeight,
+ textAlign: textAlign[location],
+ fontWeight: 700,
+ letterSpacing: '0.05em',
+ margin: '0 auto',
+ color: (theme) => theme.palette.text.primary,
+ textShadow: '1px 1px 2px rgba(0, 0, 0, 0.2)',
+ transition: 'color 0.3s ease',
+ '&:hover': {
+ color: (theme) => theme.palette.secondary.main,
+ },
+ };
+
return (
-
theme.palette.background.default,
- padding: 2,
- }}
- >
- theme.palette.text.primary,
- }}
- >
+
+
{title}
diff --git a/src/components/reusable/PrivateRoute.jsx b/src/components/reusable/PrivateRoute.jsx
new file mode 100644
index 0000000..20cebd4
--- /dev/null
+++ b/src/components/reusable/PrivateRoute.jsx
@@ -0,0 +1,18 @@
+import React, { useContext } from 'react';
+import { Navigate } from 'react-router-dom';
+import { useCookies } from 'react-cookie';
+import { useAuthContext } from '../../context/hooks/auth';
+
+const PrivateRoute = ({ children }) => {
+ const authContext = useAuthContext();
+
+ // Use react-cookie's useCookies hook to read the 'isLoggedIn' cookie
+ const [cookies] = useCookies(['isLoggedIn']);
+
+ const isLoggedIn = authContext.isLoggedIn || cookies.isLoggedIn;
+
+ // If isLoggedIn from either cookie or context is true, proceed to the route
+ return isLoggedIn ? children : ;
+};
+
+export default PrivateRoute;
diff --git a/src/components/reusable/chartUtils.jsx b/src/components/reusable/chartUtils.jsx
new file mode 100644
index 0000000..474420f
--- /dev/null
+++ b/src/components/reusable/chartUtils.jsx
@@ -0,0 +1,173 @@
+export const getUniqueValidData = (currentChartData) => {
+ if (!Array.isArray(currentChartData)) {
+ console.error('Invalid input: currentChartData should be an array');
+ return [];
+ }
+
+ const uniqueLabels = new Set();
+ const uniqueXValues = new Set();
+
+ return currentChartData
+ .filter((entry) => {
+ // Check if entry is valid, y is a number and not zero, and label is unique
+ return (
+ entry &&
+ typeof entry === 'object' &&
+ typeof entry.y === 'number' &&
+ entry.y !== 0 &&
+ entry.y !== null &&
+ entry.y !== undefined &&
+ entry.label &&
+ !uniqueLabels.has(entry.label)
+ );
+ })
+ .filter((entry) => {
+ // Check if x is present, not null, not undefined, and unique
+ const hasValidX =
+ entry && 'x' in entry && entry.x !== null && entry.x !== undefined;
+ if (hasValidX && !uniqueXValues.has(entry.x)) {
+ uniqueXValues.add(entry.x);
+ uniqueLabels.add(entry.label);
+ return true;
+ }
+ return false;
+ })
+ .map((entry) => ({
+ label: entry.label,
+ x: entry.x,
+ y: entry.y,
+ }));
+};
+
+export const groupAndAverageData = (data, threshold = 600000) => {
+ if (!data || data.length === 0) return [];
+
+ const clusters = [];
+ let currentCluster = [data[0]];
+
+ // console.log('Initial cluster with first data point: ', currentCluster);
+
+ for (let i = 1; i < data.length; i++) {
+ const prevTime = new Date(data[i - 1].x).getTime();
+ const currentTime = new Date(data[i].x).getTime();
+ const timeDiff = currentTime - prevTime;
+
+ // console.log(
+ // `Time difference between points ${i - 1} and ${i}: ${timeDiff}ms`
+ // );
+
+ if (timeDiff <= threshold) {
+ currentCluster.push(data[i]);
+ } else {
+ clusters.push(currentCluster);
+ currentCluster = [data[i]];
+ }
+ }
+ clusters.push(currentCluster); // Push the last cluster
+ // console.log('Final cluster: ', currentCluster);
+
+ // Process each cluster to create the desired output format
+ clusters.map((cluster) => {
+ const middleIndex = Math.floor(cluster.length / 2);
+ const middleDatum = cluster[middleIndex];
+ const avgY =
+ cluster.reduce((sum, point) => sum + point.y, 0) / cluster.length;
+
+ const date = new Date(middleDatum.x);
+ const formattedDate = date.toISOString();
+
+ return {
+ label: `Price at ${formattedDate}`,
+ x: formattedDate,
+ y: avgY,
+ };
+ });
+
+ return clusters;
+};
+
+export const getAveragedData = (data) => {
+ // Use a regular function instead of a hook
+ if (!Array.isArray(data)) {
+ return [];
+ }
+ return data.map((row, index, total) => {
+ const start = Math.max(0, index - 6);
+ const end = index;
+ const subset = total.slice(start, end + 1);
+ const sum = subset.reduce((a, b) => a + b.y, 0);
+ return {
+ x: row.x,
+ y: sum / subset.length || 0,
+ };
+ });
+};
+
+export const getTickValues = (timeRange) => {
+ console.log('timeRange: ', timeRange);
+ const mapping = {
+ 600000: 'every 10 minutes',
+ 900000: 'every 15 minutes',
+ 3600000: 'every hour',
+ 7200000: 'every 2 hours',
+ 86400000: 'every day',
+ 604800000: 'every week',
+ 2592000000: 'every month',
+ };
+ return mapping[timeRange] || 'every day'; // Default to 'every week' if no match
+};
+
+export const convertDataForNivo2 = (rawData2) => {
+ if (!Array.isArray(rawData2) || rawData2?.length === 0) {
+ console.error('Invalid or empty rawData provided', rawData2);
+ return [];
+ }
+
+ // console.log('rawData2: ', rawData2);
+ // console.log('rawData2.data: ', rawData2[0]);
+ // rawData is assumed to be an array of objects with 'label', 'x', and 'y' properties
+ const nivoData = rawData2[0].map((dataPoint) => ({
+ x: dataPoint.x, // x value is already an ISO date string
+ y: dataPoint.y, // y value
+ }));
+
+ // Wrapping the data for a single series. You can add more series similarly
+ return [
+ {
+ id: 'Averaged Data',
+ color: '#4cceac',
+ data: nivoData,
+ },
+ ];
+};
+
+const roundToNearestTenth = (value) => {
+ return Math.round(value * 10) / 10;
+};
+
+export const getFilteredData = (data, timeRange) => {
+ const cutOffTime = new Date().getTime() - timeRange;
+ return data
+ .filter((d) => {
+ const date = new Date(d.x);
+ if (isNaN(date.getTime())) {
+ console.error('Invalid date:', d.x);
+ return false;
+ }
+ return date.getTime() >= cutOffTime;
+ })
+ .map((d) => ({ ...d, y: roundToNearestTenth(d.y) }));
+};
+
+export const formatDateToString = (date) => {
+ if (!(date instanceof Date) || isNaN(date.getTime())) {
+ console.error('Invalid date:', date);
+ return '';
+ }
+ return `${date.getFullYear()}-${(date.getMonth() + 1)
+ .toString()
+ .padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date
+ .getHours()
+ .toString()
+ .padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
+};
diff --git a/src/components/icons/CartIcon.jsx b/src/components/reusable/icons/CartIcon.jsx
similarity index 100%
rename from src/components/icons/CartIcon.jsx
rename to src/components/reusable/icons/CartIcon.jsx
diff --git a/src/components/icons/CollectionIcon.jsx b/src/components/reusable/icons/CollectionIcon.jsx
similarity index 100%
rename from src/components/icons/CollectionIcon.jsx
rename to src/components/reusable/icons/CollectionIcon.jsx
diff --git a/src/components/reusable/icons/DeckOfCardsIcon.jsx b/src/components/reusable/icons/DeckOfCardsIcon.jsx
new file mode 100644
index 0000000..32afe04
--- /dev/null
+++ b/src/components/reusable/icons/DeckOfCardsIcon.jsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { IconButton } from '@mui/material';
+import deckIcon1 from '../../../assets/deckIcon2.png';
+
+const DeckOfCardsIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export default DeckOfCardsIcon;
diff --git a/src/components/icons/TestingIcon.jsx b/src/components/reusable/icons/TestingIcon.jsx
similarity index 100%
rename from src/components/icons/TestingIcon.jsx
rename to src/components/reusable/icons/TestingIcon.jsx
diff --git a/src/components/indicators/ErrorIndicator.js b/src/components/reusable/indicators/ErrorIndicator.js
similarity index 68%
rename from src/components/indicators/ErrorIndicator.js
rename to src/components/reusable/indicators/ErrorIndicator.js
index f19f6e1..f3c5fa2 100644
--- a/src/components/indicators/ErrorIndicator.js
+++ b/src/components/reusable/indicators/ErrorIndicator.js
@@ -5,26 +5,7 @@ import { makeStyles } from '@mui/styles';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import Container from '@mui/material/Container';
import { Alert, AlertTitle } from '@mui/material';
-
-const useStyles = makeStyles((theme) => ({
- paper: {
- padding: theme.spacing(2),
- margin: theme.spacing(2),
- backgroundColor: theme.palette.error.light,
- },
- typography: {
- color: theme.palette.error.dark,
- },
- container: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'flex-start',
- },
- icon: {
- marginRight: theme.spacing(1),
- color: theme.palette.error.main,
- },
-}));
+import { useStyles } from './styles';
const ErrorIndicator = ({ error }) => {
const classes = useStyles();
diff --git a/src/components/indicators/LoadingIndicator.js b/src/components/reusable/indicators/LoadingIndicator.js
similarity index 100%
rename from src/components/indicators/LoadingIndicator.js
rename to src/components/reusable/indicators/LoadingIndicator.js
diff --git a/src/components/reusable/indicators/LoadingIndicator2.js b/src/components/reusable/indicators/LoadingIndicator2.js
new file mode 100644
index 0000000..150584d
--- /dev/null
+++ b/src/components/reusable/indicators/LoadingIndicator2.js
@@ -0,0 +1,13 @@
+// import * as React from 'react';
+// import Box from '@mui/material/Box';
+// import LoadingCardAnimation from '../../../assets/animations/LoadingCardAnimation';
+
+// const LoadingIndicator2 = () => {
+// return (
+//
+//
+//
+// );
+// };
+
+// export default LoadingIndicator2;
diff --git a/src/components/reusable/indicators/styles.js b/src/components/reusable/indicators/styles.js
new file mode 100644
index 0000000..c5f9378
--- /dev/null
+++ b/src/components/reusable/indicators/styles.js
@@ -0,0 +1,21 @@
+import { makeStyles } from '@mui/styles';
+
+export const useStyles = makeStyles((theme) => ({
+ paper: {
+ padding: theme.spacing(2),
+ margin: theme.spacing(2),
+ backgroundColor: theme.palette.error.light,
+ },
+ typography: {
+ color: theme.palette.error.dark,
+ },
+ container: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'flex-start',
+ },
+ icon: {
+ marginRight: theme.spacing(1),
+ color: theme.palette.error.main,
+ },
+}));
diff --git a/src/components/search/DeckSearch.js b/src/components/search/DeckSearch.js
index aee988a..b024742 100644
--- a/src/components/search/DeckSearch.js
+++ b/src/components/search/DeckSearch.js
@@ -9,10 +9,9 @@ import {
Pagination,
} from '@mui/material';
import { useCardStore } from '../../context/CardContext/CardStore';
-import { useTheme } from '@emotion/react';
-import SearchForm from './SearchForm';
+import SearchForm from '../forms/SearchForm';
import DeckSearchCardGrid from '../grids/searchResultGrids/DeckSearchCardGrid';
-import CustomPagination from '../other/CustomPagination';
+import CustomPagination from '../reusable/CustomPagination';
const DeckSearch = ({ userDecks }) => {
const [searchTerm, setSearchTerm] = useState('');
@@ -29,11 +28,21 @@ const DeckSearch = ({ userDecks }) => {
const itemsPerPage = 36;
const start = (page - 1) * itemsPerPage;
const end = start + itemsPerPage;
- const currentDeckSearchData = deckSearchData.slice(start, end);
+ const currentDeckSearchData = deckSearchData?.slice(start, end);
return (
-
+
(
-
-
- setSearchParams((prevState) => ({
- ...prevState,
- name: newValue,
- }))
- }
- handleRequest={() => handleRequest(searchParams)}
- />
-
-
-);
-
-export default PortfolioCardSearch;
diff --git a/src/components/search/SearchBar.js b/src/components/search/SearchBar.js
index d8d8338..072669a 100644
--- a/src/components/search/SearchBar.js
+++ b/src/components/search/SearchBar.js
@@ -1,103 +1,113 @@
-import React from 'react';
-import { Grid, Box, Typography, Container } from '@mui/material';
+import React, { useState } from 'react';
+import { Grid, Box, Typography, Container, Paper } from '@mui/material';
import { useCardStore } from '../../context/CardContext/CardStore';
import SearchButton from '../buttons/other/SearchButton';
-import CardNameInput from '../other/CardNameInput';
-import CustomSelector from '../other/CustomSelector';
-import { useCombinedContext } from '../../context/CombinedProvider';
-
-const initialState = {
- name: '',
- race: '',
- type: '',
- attribute: '',
- level: '',
-};
+import CardNameInput from '../other/InputComponents/CardNameInput';
+import CustomSelector from '../other/InputComponents/CustomSelector';
+import search from './search.json';
+import { useMode } from '../../context/hooks/colormode';
const SearchBar = () => {
- const { searchParams, setSearchParams } = useCombinedContext(initialState);
- const { handleRequest } = useCardStore();
-
- const filters = [
- {
- label: 'Level',
- name: 'level',
- values: [
- 'Unset',
- '1',
- '2',
- '3',
- '4',
- '5',
- '6',
- '7',
- '8',
- '9',
- '10',
- '11',
- '12',
- ],
- },
- { label: 'Race', name: 'race', values: ['Unset', 'Aqua', 'Beast'] },
- {
- label: 'Type',
- name: 'type',
- values: ['Unset', 'Effect Monster', 'Flip Effect Monster'],
- },
- {
- label: 'Attribute',
- name: 'attribute',
- values: [
- 'Unset',
- 'Dark',
- 'Divine',
- 'Earth',
- 'Fire',
- 'Light',
- 'Water',
- 'Wind',
- ],
- },
- ];
+ const { theme } = useMode();
+ const { initialState, filters } = search;
+ const [searchParams, setSearchParams] = useState({
+ name: '',
+ type: '',
+ attribute: '',
+ race: '',
+ });
+ const { handleRequest } = useCardStore();
+ // const handleChange = (name, newValue) => {
+ // setSearchParams((prev) => ({ ...prev, [name]: newValue }));
+ // };
+ const handleChange = (name, newValue) => {
+ setSearchParams((prev) => ({ ...prev, [name]: newValue }));
+ };
+ // Correct handleSubmit to use handleRequest with searchParams
+ // const handleSubmit = (event) => {
+ // event.preventDefault();
+ // handleRequest(searchParams);
+ // };
+ const handleSubmit = () => {
+ handleRequest(searchParams);
+ };
return (
-
-
-
+
+
+
Search Cards
- setSearchParams((prevState) => ({
- ...prevState,
- name: newValue,
- }))
- }
- handleRequest={() => handleRequest(searchParams)}
+ value={searchParams.name}
+ handleChange={(event) => handleChange('name', event.target.value)}
/>
{filters?.map((filter) => (
-
- setSearchParams((prevState) => ({
- ...prevState,
- [filter?.name]: newValue,
- }))
- }
- values={filter?.values}
- />
+
+
+ handleChange(filter.label, event.target.value)
+ }
+ // handleChange={(event) =>
+ // handleChange(filter.name, event.target.value)
+ // }
+ // setValue={(newValue) =>
+ // handleChange({ target: { value: newValue } })
+ // }
+ // handleChange={handleChange}
+ // setValue={(newValue) =>
+ // setSearchParams((prevState) => ({
+ // ...prevState,
+ // [filter?.name]: newValue,
+ // }))
+ // }
+ setSearchParams={setSearchParams}
+ values={filter?.values}
+ />
+
))}
-
+
+
+
-
+
);
};
diff --git a/src/components/search/SearchForm.jsx b/src/components/search/SearchForm.jsx
deleted file mode 100644
index 923b629..0000000
--- a/src/components/search/SearchForm.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { TextField, Button } from '@mui/material';
-import { makeStyles } from '@mui/styles';
-const useStyles = makeStyles((theme) => ({
- form: {
- // minWidth: '90%', // Set the min-width
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2), // You can control the gap between the TextField and Button
- },
-}));
-
-const SearchForm = ({ searchTerm, handleChange, handleSubmit }) => {
- const classes = useStyles();
-
- return (
-
- );
-};
-
-export default SearchForm;
diff --git a/src/components/search/SearchResultsDisplay.jsx b/src/components/search/SearchResultsDisplay.jsx
deleted file mode 100644
index e69de29..0000000
diff --git a/src/components/search/search.json b/src/components/search/search.json
new file mode 100644
index 0000000..d5b07c4
--- /dev/null
+++ b/src/components/search/search.json
@@ -0,0 +1,54 @@
+{
+ "initialState": {
+ "name": "",
+ "race": "",
+ "type": "",
+ "attribute": "",
+ "level": ""
+ },
+ "filters": [
+ {
+ "label": "Level",
+ "name": "level",
+ "values": [
+ "Unset",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "10",
+ "11",
+ "12"
+ ]
+ },
+ {
+ "label": "Race",
+ "name": "race",
+ "values": ["Unset", "Aqua", "Beast"]
+ },
+ {
+ "label": "Type",
+ "name": "type",
+ "values": ["Unset", "Effect Monster", "Flip Effect Monster"]
+ },
+ {
+ "label": "Attribute",
+ "name": "attribute",
+ "values": [
+ "Unset",
+ "Dark",
+ "Divine",
+ "Earth",
+ "Fire",
+ "Light",
+ "Water",
+ "Wind"
+ ]
+ }
+ ]
+}
diff --git a/src/containers/CardDetailsContainer.jsx b/src/containers/CardDetailsContainer.jsx
new file mode 100644
index 0000000..266ae68
--- /dev/null
+++ b/src/containers/CardDetailsContainer.jsx
@@ -0,0 +1,26 @@
+// Import necessary modules from MUI or other libraries
+import React from 'react';
+import { Typography, Grid } from '@mui/material';
+import { useStyles } from '../components/cards/cardStyles';
+
+const CardDetailsContainer = ({ card }) => {
+ const classes = useStyles();
+
+ // Here you can add more details about the card using the 'card' object
+ // For now, just displaying the card name and description if available
+ return (
+
+
+
+ {card?.name}
+
+
+ {card?.desc || 'No description available.'}
+
+ {/* You can continue adding more details here */}
+
+
+ );
+};
+
+export default CardDetailsContainer;
diff --git a/src/containers/PortfolioChartContainer.jsx b/src/containers/PortfolioChartContainer.jsx
deleted file mode 100644
index 93332ac..0000000
--- a/src/containers/PortfolioChartContainer.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-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
deleted file mode 100644
index 4865b33..0000000
--- a/src/containers/PortfolioListContainer.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import { Grid, Paper } from '@mui/material';
-import CardList from '../components/grids/collectionGrids/CardList';
-
-const PortfolioListContainer = ({ selectedCards, removeCard }) => {
- return (
-
-
-
-
-
- );
-};
-
-export default PortfolioListContainer;
diff --git a/src/containers/cartPageContainers/AddressForm.jsx b/src/containers/cartPageContainers/AddressForm.jsx
new file mode 100644
index 0000000..7ef5ff6
--- /dev/null
+++ b/src/containers/cartPageContainers/AddressForm.jsx
@@ -0,0 +1,111 @@
+import * as React from 'react';
+import Grid from '@mui/material/Grid';
+import Typography from '@mui/material/Typography';
+import TextField from '@mui/material/TextField';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Checkbox from '@mui/material/Checkbox';
+
+export default function AddressForm() {
+ return (
+
+
+ Shipping address
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ label="Use this address for payment details"
+ />
+
+
+
+ );
+}
diff --git a/src/containers/CartContainer.jsx b/src/containers/cartPageContainers/CartContainer.jsx
similarity index 100%
rename from src/containers/CartContainer.jsx
rename to src/containers/cartPageContainers/CartContainer.jsx
diff --git a/src/components/content/CartContent.js b/src/containers/cartPageContainers/CartContent.js
similarity index 84%
rename from src/components/content/CartContent.js
rename to src/containers/cartPageContainers/CartContent.js
index a3d926f..c95db93 100644
--- a/src/components/content/CartContent.js
+++ b/src/containers/cartPageContainers/CartContent.js
@@ -1,9 +1,9 @@
import React from 'react';
import { Typography } from '@mui/material';
import { useCartStore } from '../../context/CartContext/CartContext';
-import CartContainer from '../../containers/CartContainer';
-import CartItem from '../grids/CartItem';
-import CartTotal from '../other/CartTotal';
+import CartContainer from './CartContainer';
+import CartItem from '../../components/grids/CartItem';
+import CartTotal from '../../components/other/dataDisplay/CartTotal';
const CartContent = () => {
const { cartData, getTotalCost } = useCartStore();
diff --git a/src/containers/CartContentContainer.js b/src/containers/cartPageContainers/CartContentContainer.js
similarity index 79%
rename from src/containers/CartContentContainer.js
rename to src/containers/cartPageContainers/CartContentContainer.js
index 89161b4..5dd3823 100644
--- a/src/containers/CartContentContainer.js
+++ b/src/containers/cartPageContainers/CartContentContainer.js
@@ -1,7 +1,7 @@
import React from 'react';
import { Box } from '@mui/system';
-import LoadingIndicator from '../components/indicators/LoadingIndicator';
-import CartContent from '../components/content/CartContent';
+import LoadingIndicator from '../../components/reusable/indicators/LoadingIndicator';
+import CartContent from './CartContent';
const CartContentContainer = ({
cartData,
diff --git a/src/containers/cartPageContainers/Checkout.jsx b/src/containers/cartPageContainers/Checkout.jsx
new file mode 100644
index 0000000..e4d1065
--- /dev/null
+++ b/src/containers/cartPageContainers/Checkout.jsx
@@ -0,0 +1,126 @@
+import * as React from 'react';
+import CssBaseline from '@mui/material/CssBaseline';
+import AppBar from '@mui/material/AppBar';
+import Box from '@mui/material/Box';
+import Container from '@mui/material/Container';
+import Toolbar from '@mui/material/Toolbar';
+import Paper from '@mui/material/Paper';
+import Stepper from '@mui/material/Stepper';
+import Step from '@mui/material/Step';
+import StepLabel from '@mui/material/StepLabel';
+import Button from '@mui/material/Button';
+import Link from '@mui/material/Link';
+import Typography from '@mui/material/Typography';
+import AddressForm from './AddressForm';
+import PaymentForm from './PaymentForm';
+import Review from './Review';
+
+function Copyright() {
+ return (
+
+ {'Copyright © '}
+
+ Your Website
+ {' '}
+ {new Date().getFullYear()}
+ {'.'}
+
+ );
+}
+
+const steps = ['Shipping address', 'Payment details', 'Review your order'];
+
+function getStepContent(step) {
+ switch (step) {
+ case 0:
+ return ;
+ case 1:
+ return ;
+ case 2:
+ return ;
+ default:
+ throw new Error('Unknown step');
+ }
+}
+
+export default function Checkout() {
+ const [activeStep, setActiveStep] = React.useState(0);
+
+ const handleNext = () => {
+ setActiveStep(activeStep + 1);
+ };
+
+ const handleBack = () => {
+ setActiveStep(activeStep - 1);
+ };
+
+ return (
+
+
+ `1px solid ${t.palette.divider}`,
+ }}
+ >
+
+
+ Company name
+
+
+
+
+
+
+ Checkout
+
+
+ {steps.map((label) => (
+
+ {label}
+
+ ))}
+
+ {activeStep === steps.length ? (
+
+
+ Thank you for your order.
+
+
+ Your order number is #2001539. We have emailed your order
+ confirmation, and will send you an update when your order has
+ shipped.
+
+
+ ) : (
+
+ {getStepContent(activeStep)}
+
+ {activeStep !== 0 && (
+
+ )}
+
+
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/src/containers/CustomerFormContainer.js b/src/containers/cartPageContainers/CustomerFormContainer.js
similarity index 72%
rename from src/containers/CustomerFormContainer.js
rename to src/containers/cartPageContainers/CustomerFormContainer.js
index d55887a..46d79bb 100644
--- a/src/containers/CustomerFormContainer.js
+++ b/src/containers/cartPageContainers/CustomerFormContainer.js
@@ -1,6 +1,6 @@
import React from 'react';
import { Box } from '@mui/system';
-import CustomerForm from '../components/forms/customerCheckoutForm/CustomerForm';
+import CustomerForm from '../../components/forms/customerCheckoutForm/CustomerForm';
const CustomerFormContainer = () => {
return (
diff --git a/src/containers/cartPageContainers/PaymentForm.jsx b/src/containers/cartPageContainers/PaymentForm.jsx
new file mode 100644
index 0000000..9116fd0
--- /dev/null
+++ b/src/containers/cartPageContainers/PaymentForm.jsx
@@ -0,0 +1,65 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+import Grid from '@mui/material/Grid';
+import TextField from '@mui/material/TextField';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Checkbox from '@mui/material/Checkbox';
+
+export default function PaymentForm() {
+ return (
+
+
+ Payment method
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ label="Remember credit card details for next time"
+ />
+
+
+
+ );
+}
diff --git a/src/containers/cartPageContainers/Review.jsx b/src/containers/cartPageContainers/Review.jsx
new file mode 100644
index 0000000..ad827af
--- /dev/null
+++ b/src/containers/cartPageContainers/Review.jsx
@@ -0,0 +1,88 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+import List from '@mui/material/List';
+import ListItem from '@mui/material/ListItem';
+import ListItemText from '@mui/material/ListItemText';
+import Grid from '@mui/material/Grid';
+
+const products = [
+ {
+ name: 'Product 1',
+ desc: 'A nice thing',
+ price: '$9.99',
+ },
+ {
+ name: 'Product 2',
+ desc: 'Another thing',
+ price: '$3.45',
+ },
+ {
+ name: 'Product 3',
+ desc: 'Something else',
+ price: '$6.51',
+ },
+ {
+ name: 'Product 4',
+ desc: 'Best thing of all',
+ price: '$14.11',
+ },
+ { name: 'Shipping', desc: '', price: 'Free' },
+];
+
+const addresses = ['1 MUI Drive', 'Reactville', 'Anytown', '99999', 'USA'];
+const payments = [
+ { name: 'Card type', detail: 'Visa' },
+ { name: 'Card holder', detail: 'Mr John Smith' },
+ { name: 'Card number', detail: 'xxxx-xxxx-xxxx-1234' },
+ { name: 'Expiry date', detail: '04/2024' },
+];
+
+export default function Review() {
+ return (
+
+
+ Order summary
+
+
+ {products.map((product) => (
+
+
+ {product.price}
+
+ ))}
+
+
+
+ $34.06
+
+
+
+
+
+
+ Shipping
+
+ John Smith
+ {addresses.join(', ')}
+
+
+
+ Payment details
+
+
+ {payments.map((payment) => (
+
+
+ {payment.name}
+
+
+ {payment.detail}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/containers/collectionPageContainers/PortfolioChartContainer.jsx b/src/containers/collectionPageContainers/PortfolioChartContainer.jsx
new file mode 100644
index 0000000..f3cf490
--- /dev/null
+++ b/src/containers/collectionPageContainers/PortfolioChartContainer.jsx
@@ -0,0 +1,77 @@
+import React, { useMemo } from 'react';
+import { Box, Grid, Paper, Container, useTheme } from '@mui/material';
+import PortfolioChart from '../../components/chart/PortfolioChart';
+import TimeRangeSelector from '../../components/other/InputComponents/TimeRangeSelector';
+import CollectionStatisticsSelector, {
+ calculateStatistics,
+} from '../../components/other/InputComponents/CollectionStatisticsSelector';
+import UpdateStatusBox2 from '../../components/other/InputComponents/UpdateStatusBox2';
+import TopCardsDisplay from '../../components/other/dataDisplay/TopCardsDisplay';
+import { useChartContext } from '../../context/ChartContext/ChartContext';
+import { useCollectionStore } from '../../context/CollectionContext/CollectionContext';
+import { useCombinedContext } from '../../context/CombinedProvider';
+
+const PortfolioChartContainer = ({ selectedCards, removeCard }) => {
+ const theme = useTheme();
+ const { timeRange } = useChartContext();
+ const { allCollections } = useCollectionStore();
+ const { socket } = useCombinedContext();
+
+ const data = allCollections.map((collection) => ({
+ data: collection?.currentChartDataSets2,
+ }));
+ const dataForStats = data[0];
+ const stats = useMemo(
+ () => calculateStatistics(dataForStats, timeRange),
+ [dataForStats, timeRange]
+ );
+
+ return (
+
+ {/* Updaters Row */}
+
+
+
+
+
+
+
+
+
+ {/* Additional Components */}
+
+
+
+ {/* Main Grid Container */}
+
+ {/* Portfolio Chart Row */}
+
+
+
+
+ {/* Additional Rows */}
+
+
+
+
+
+ );
+};
+
+export default PortfolioChartContainer;
diff --git a/src/containers/collectionPageContainers/PortfolioContent.jsx b/src/containers/collectionPageContainers/PortfolioContent.jsx
new file mode 100644
index 0000000..2a7d5d2
--- /dev/null
+++ b/src/containers/collectionPageContainers/PortfolioContent.jsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import { Box, Container, Grid, Paper, useTheme } from '@mui/material';
+import PortfolioListContainer from './PortfolioListContainer';
+import PortfolioChartContainer from './PortfolioChartContainer';
+import HeaderTitle from '../../components/reusable/HeaderTitle';
+import { useCollectionStore } from '../../context/CollectionContext/CollectionContext';
+import { useMode } from '../../context/hooks/colormode';
+
+const PortfolioContent = ({ error, selectedCards, removeCard }) => {
+ const { theme } = useMode();
+ const { selectedCollection } = useCollectionStore();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default PortfolioContent;
diff --git a/src/containers/collectionPageContainers/PortfolioListContainer.jsx b/src/containers/collectionPageContainers/PortfolioListContainer.jsx
new file mode 100644
index 0000000..579f87b
--- /dev/null
+++ b/src/containers/collectionPageContainers/PortfolioListContainer.jsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { Box, Grid, Paper } from '@mui/material';
+import CardList from '../../components/grids/collectionGrids/CardList';
+import { useTheme } from '@mui/material/styles';
+import { useMode } from '../../context/hooks/colormode';
+
+const PortfolioListContainer = ({ selectedCards, removeCard }) => {
+ const { theme } = useMode();
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default PortfolioListContainer;
diff --git a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js
index 7efbedd..2f1a775 100644
--- a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js
+++ b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js
@@ -1,126 +1,45 @@
-// import React from 'react';
-// import { Grid, useMediaQuery } from '@mui/material';
-// import { useTheme } from '@emotion/react';
-// import DeckDisplay from '../../components/other/DeckDisplay';
-// import DeckSearch from '../../components/search/DeckSearch';
-// import { makeStyles } from '@mui/styles';
-
-// const useStyles = makeStyles((theme) => ({
-// root: {
-// overflow: 'auto',
-// backgroundColor: '#f4f6f8',
-// padding: theme.spacing(3),
-// borderRadius: '10px',
-// boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
-// width: '100%', // Add this to make sure the container takes full width
-// },
-// searchGrid: {
-// [theme.breakpoints.up('lg')]: { flexBasis: '35%' }, // Increased from 30%
-// [theme.breakpoints.between('md', 'lg')]: { flexBasis: '50%' }, // Increased from 45%
-// [theme.breakpoints.down('sm')]: { flexBasis: '50%' }, // Increased from 40%
-// },
-// displayGrid: {
-// flex: 1,
-// padding: theme.spacing(1),
-// },
-// }));
-
-// const DeckBuilderContainer = ({ userDecks }) => {
-// const classes = useStyles();
-// const theme = useTheme();
-// const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md'));
-// const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
-
-// const getXsValue = () => {
-// if (isSmallScreen || isMediumScreen) return 5; // Increased from 4
-// return 3;
-// };
-
-// const getDisplayXsValue = () => {
-// if (isSmallScreen || isMediumScreen) return 7; // Decreased from 8
-// return 9;
-// };
-
-// return (
-//
-//
-//
-//
-//
-//
-//
-//
-// );
-// };
-
-// export default DeckBuilderContainer;
import React from 'react';
-import { Grid, useMediaQuery, useTheme } from '@mui/material';
-// Removed import { useTheme } from '@emotion/react';
-
-import DeckDisplay from '../../components/other/DeckDisplay';
+import { Grid } from '@mui/material';
+import DeckDisplay from '../../components/grids/DeckDisplay';
import DeckSearch from '../../components/search/DeckSearch';
-import { styled } from '@mui/system'; // Use @mui/system for Emotion styling
+import { styled } from '@mui/styles';
+import { useMode } from '../../context/hooks/colormode';
-// Define your components using the styled function from @mui/system
const SearchGrid = styled(Grid)(({ theme }) => ({
- [theme.breakpoints.up('xl')]: { flexBasis: '60%' },
- [theme.breakpoints.up('lg')]: { flexBasis: '50%' },
- [theme.breakpoints.between('md', 'lg')]: { flexBasis: '50%' },
- [theme.breakpoints.down('sm')]: { flexBasis: '50%' },
+ padding: theme.spacing(2),
+ [theme.breakpoints.down('md')]: {
+ width: '50%', // Half width on small and medium screens
+ },
}));
const DisplayGrid = styled(Grid)(({ theme }) => ({
- flex: 1,
- padding: theme.spacing(1),
+ padding: theme.spacing(2),
+ [theme.breakpoints.down('md')]: {
+ width: '50%', // Half width on small and medium screens
+ },
}));
const RootGrid = styled(Grid)(({ theme }) => ({
overflow: 'auto',
- backgroundColor: '#f4f6f8',
+ display: 'flex',
+ flexDirection: 'row',
+ flexWrap: 'wrap', // Ensure wrapping on smaller screens
+ backgroundColor: theme.palette.background.secondary,
padding: theme.spacing(3),
- borderRadius: '10px',
- boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
+ borderRadius: theme.shape.borderRadius,
+ boxShadow: theme.shadows[5],
width: '100%',
}));
const DeckBuilderContainer = ({ userDecks }) => {
- const theme = useTheme(); // Use the theme from Emotion
- const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg'));
- const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md'));
- const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
-
- const getXsValue = () => {
- // if (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 (isLargeScreen) return 9;
- if (isMediumScreen) return 8;
- return 7;
- };
+ const { theme } = useMode();
return (
-
-
+
+
-
+
diff --git a/src/context/AppContext/AppContextProvider.jsx b/src/context/AppContext/AppContextProvider.jsx
new file mode 100644
index 0000000..295d4ff
--- /dev/null
+++ b/src/context/AppContext/AppContextProvider.jsx
@@ -0,0 +1,54 @@
+// CombinedContext.js
+import React, { createContext, useContext, useState } from 'react';
+import { DeckContext } from '../DeckContext/DeckContext';
+import { CartContext } from '../CartContext/CartContext';
+import { CollectionContext } from '../CollectionContext/CollectionContext';
+
+// Create the combined context
+export const AppContext = createContext({});
+
+// Create a provider component that combines the contexts
+export const AppContextProvider = ({ children }) => {
+ const [context, setContext] = useState({});
+ const Deck = useContext(DeckContext);
+ const Cart = useContext(CartContext);
+ const Collection = useContext(CollectionContext);
+
+ // Combine the context values into one object
+ const appContextValues = { Deck, Cart, Collection };
+
+ return (
+
+ {children}
+
+ );
+};
+
+// import React, { createContext, useContext, useState } from 'react';
+// import { DeckContext } from '../DeckContext/DeckContext';
+// import { CartContext } from '../CartContext/CartContext';
+// import { CollectionContext } from '../CollectionContext/CollectionContext';
+
+// export const AppContext = createContext({
+// Deck: {},
+// Cart: {},
+// Collection: {},
+// context: '',
+// // eslint-disable-next-line @typescript-eslint/no-empty-function
+// setContext: () => {},
+// });
+
+// export const AppContextProvider = ({ children }) => {
+// const [context, setContext] = useState('Collection'); // Default context
+// const Deck = useContext(DeckContext);
+// const Cart = useContext(CartContext);
+// const Collection = useContext(CollectionContext);
+
+// const appContextValues = { Deck, Cart, Collection, context, setContext };
+
+// return (
+//
+// {children}
+//
+// );
+// };
diff --git a/src/context/Auth/authContext.js b/src/context/Auth/authContext.js
index 8a6b524..f130a86 100644
--- a/src/context/Auth/authContext.js
+++ b/src/context/Auth/authContext.js
@@ -1,162 +1,735 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import jwt_decode from 'jwt-decode';
+import React, { useState, useEffect, useRef, useCallback } from 'react';
import axios from 'axios';
+import jwt_decode from 'jwt-decode';
import { useCookies } from 'react-cookie';
-import { useUtilityContext } from '../UtilityContext/UtilityContext';
+const LOGGED_IN_COOKIE = 'loggedIn';
+const AUTH_COOKIE = 'authToken';
+const USER_COOKIE = 'user';
+
+// Validator function
+const validateData = (data, eventName, functionName) => {
+ if (!data || Object.keys(data).length === 0) {
+ console.warn(`Invalid data in ${functionName} for ${eventName}`);
+ return false;
+ }
+ return true;
+};
+
+// Process the server response based on the action type (Login/Signup)
+const processResponseData = (data, type) => {
+ if (!validateData(data, `${type} Response`, `process${type}Data`))
+ return null;
+
+ if (type === 'Login') {
+ const token = data?.data?.token;
+ if (!token) return null;
+ const user = jwt_decode(token);
+ return { token, user };
+ }
+
+ if (type === 'Signup') {
+ const { success, newUser } = data;
+ if (success && newUser) return { success, newUser };
+ }
+
+ return null;
+};
+
+// Main AuthContext Provider
export const AuthContext = React.createContext();
-export default function AuthProvider({ children }) {
- const [cookies, setCookie, removeCookie] = useCookies(['auth', 'userCookie']);
+export default function AuthProvider({ children, serverUrl }) {
+ const [cookies, setCookie, removeCookie] = useCookies([
+ LOGGED_IN_COOKIE,
+ AUTH_COOKIE,
+ USER_COOKIE,
+ ]);
const [isLoading, setIsLoading] = useState(false);
- const [isloggedin, setIsloggedin] = useState(false);
+ const [isLoggedIn, setisLoggedIn] = useState(false);
const [user, setUser] = useState({});
- const [users, setUsers] = useState([]);
const [error, setError] = useState(null);
- const [token, setToken] = useState(undefined);
- const { directedResponses, fetchDirectedResponses } = useUtilityContext();
-
- const REACT_APP_SERVER = process.env.REACT_APP_SERVER;
-
- // const updateUser = useCallback((newUser) => {
- // setUsers((prevUsers) =>
- // prevUsers.map((u) => (u.id === newUser.id ? newUser : u))
- // );
- // }, []);
-
- const setLoginState = useCallback(
- (loggedIn, token, user, error = null) => {
- setCookie('auth', token, { secure: true, sameSite: 'strict' });
- if (user) {
- setCookie('userCookie', JSON.stringify(user), {
- secure: true,
- sameSite: 'strict',
- });
- }
- setIsloggedin(loggedIn);
- setToken(token);
- setUser(user);
- setError(error);
- },
- [setCookie]
- );
+ const [token, setToken] = useState(null);
+ // const isMounted = useRef(true);
- const validateToken = useCallback(async () => {
+ const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER;
+
+ // Execute authentication actions like login, signup
+ const executeAuthAction = async (actionType, url, requestData) => {
setIsLoading(true);
try {
- const latestSignInResponse = directedResponses.find(
- (res) => res.eventType === 'SIGNIN'
+ const response = await axios.post(
+ `${REACT_APP_SERVER}/api/users/${url}`,
+ requestData
);
-
- if (latestSignInResponse) {
- const decodedUser = jwt_decode(
- latestSignInResponse.response.data.token
- );
- setLoginState(
- true,
- latestSignInResponse.response.data.token,
- decodedUser
- );
- } else {
- throw new Error('Token validation failed');
+ const processedData = processResponseData(response.data, actionType);
+ if (processedData) {
+ const { token, user, newUser } = processedData;
+ setCookie(AUTH_COOKIE, token, { path: '/' });
+ setCookie(USER_COOKIE, user || newUser, { path: '/' });
+ setCookie(LOGGED_IN_COOKIE, true, { path: '/' });
+ setisLoggedIn(true);
+ setToken(token);
+ setUser(user || newUser);
}
- } catch (error) {
- setError('Token validation failed');
- setLoginState(false, null, {}, 'Token validation failed');
+ } catch (err) {
+ setError(err.message);
} finally {
setIsLoading(false);
}
- }, [directedResponses, setLoginState]);
-
- const login = async (username, password) => {
- setIsLoading(true);
- try {
- await axios.post(
- `${REACT_APP_SERVER}/api/users/signin`,
- { username, password },
- {
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${token}`,
- },
- }
- );
+ };
- await fetchDirectedResponses(); // Update directedResponses in utilityContext
+ // In App.js or inside AuthProvider component
+ axios.interceptors.request.use(
+ (config) => {
+ const token = cookies[AUTH_COOKIE];
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+ },
+ (error) => Promise.reject(error)
+ );
- validateToken();
- return {
- loggedIn: true,
- token,
- };
- } catch (error) {
- setError('Login failed');
- setLoginState(false, null, {}, 'Login failed');
- } finally {
- setIsLoading(false);
- }
+ // Login function
+ const login = async (username, password) => {
+ await executeAuthAction('Login', 'signin', { username, password });
};
- const signup = async (username, password, email, basic_info, role_data) => {
- setIsLoading(true);
- try {
- const response = await axios.post(
- `${REACT_APP_SERVER}/api/users/signup`,
- {
- login_data: { username, password, email, role_data },
- basic_info,
- }
- );
- await validateToken(response.data.token);
- return response.data.token;
- } catch (err) {
- setError('Signup failed');
- setLoginState(false, null, {}, 'Signup failed');
- } finally {
- setIsLoading(false);
- }
+ // Signup function
+ const signup = async (loginData, basicInfo, otherInfo) => {
+ await executeAuthAction('Signup', 'signup', {
+ login_data: loginData,
+ basic_info: basicInfo,
+ ...otherInfo,
+ });
};
+ // Logout function
const logout = () => {
- removeCookie('auth');
- setLoginState(false, null, {});
+ removeCookie(AUTH_COOKIE);
+ setisLoggedIn(false);
+ setToken(null);
+ setUser({});
};
+ // Validate token
+ const validateToken = useCallback(async () => {
+ // Validation logic here
+ }, []);
+
+ // Initialization logic to set user and token from cookies
useEffect(() => {
- const token =
- new URLSearchParams(window.location.search).get('token') || cookies.auth;
- if (token) validateToken(token);
- }, [validateToken, cookies.auth]);
-
- const contextValue = {
- isLoading,
- isloggedin,
- user: users.find((u) => u.id === user.id) || user,
- users,
- error,
- login,
- logout,
- signup,
- setUser,
- setLoginState,
- validateToken,
- // updateUser,
- };
- useEffect(() => {
- console.log('AUTH CONTEXT VALUE:', contextValue);
- }, [
- // isLoading,
- // isloggedin,
- // user,
- // users,
- // error,
- login,
- logout,
- signup,
- setUser,
- ]);
+ // if (!isMounted.current) return;
+
+ const storedToken = cookies[AUTH_COOKIE];
+ const storedUser = cookies[USER_COOKIE];
+
+ if (storedToken && storedUser) {
+ setToken(storedToken);
+ setUser(storedUser);
+ setisLoggedIn(true);
+ }
+
+ // isMounted.current = false;
+ }, [cookies]);
return (
- {children}
+
+ {children}
+
);
}
+
+// import React, { useState, useEffect, useCallback, useRef } from 'react';
+// import jwt_decode from 'jwt-decode';
+// import axios from 'axios';
+// import { useCookies } from 'react-cookie';
+// import { useUtilityContext } from '../UtilityContext/UtilityContext';
+// // Initialize constants
+// const ONE_MINUTE = 60000;
+// const AUTH_COOKIE = 'auth';
+// const USER_COOKIE = 'userCookie';
+
+// export const AuthContext = React.createContext();
+
+// export default function AuthProvider({ children, serverUrl }) {
+// // State and Context Setup
+// const { directedResponses, fetchDirectedResponses } = useUtilityContext();
+// const [cookies, setCookie, removeCookie] = useCookies(['auth', 'userCookie']);
+// const [isLoading, setIsLoading] = useState(false);
+// const [isLoggedIn, setisLoggedIn] = useState(false);
+// const [user, setUser] = useState({});
+// const [users, setUsers] = useState([]);
+// const [error, setError] = useState(null);
+// const [token, setToken] = useState(undefined);
+// const [loginAttempts, setLoginAttempts] = useState(0);
+// const [lastAttemptTime, setLastAttemptTime] = useState(null);
+
+// const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER;
+// const isMounted = useRef(true);
+
+// // Cleanup
+// useEffect(() => {
+// return () => {
+// isMounted.current = false;
+// };
+// }, []);
+// // Handle Login Attempts
+// useEffect(() => {
+// if (loginAttempts >= 2) {
+// const timerId = setTimeout(() => {
+// setLoginAttempts(0);
+// }, 60000); // Reset after 1 minute
+// return () => clearTimeout(timerId);
+// }
+// }, [loginAttempts]);
+// // Initialize and Validate Token
+// useEffect(() => {
+// if (isMounted.current) {
+// const queryToken = new URLSearchParams(window.location.search).get(
+// 'token'
+// );
+// const cookieToken = cookies.auth;
+// const activeToken = queryToken || cookieToken;
+// if (activeToken && activeToken !== token) {
+// validateToken(activeToken);
+// }
+// }
+// }, [validateToken, cookies.auth]);
+// // Utility Function to Set Login State
+// const setLoginState = useCallback(
+// (loggedIn, token, user, error = null) => {
+// setCookie('auth', token, { secure: true, sameSite: 'strict' });
+// setCookie('isLoggedIn', String(loggedIn), {
+// secure: true,
+// sameSite: 'strict',
+// });
+// setCookie(AUTH_COOKIE, token, { secure: true, sameSite: 'strict' });
+
+// if (user) {
+// setCookie('userCookie', JSON.stringify(user), {
+// secure: true,
+// sameSite: 'strict',
+// });
+// }
+// setisLoggedIn(loggedIn);
+// setToken(token);
+// setUser(user);
+// setError(error);
+// },
+// [setCookie]
+// );
+
+// const onLogin = async (username, password) => {
+// const currentTime = new Date().getTime();
+// const oneMinute = 60000; // 60 seconds * 1000 milliseconds
+
+// if (
+// loginAttempts < 2 ||
+// (lastAttemptTime && currentTime - lastAttemptTime > oneMinute)
+// ) {
+// // If under limit or last attempt was more than a minute ago, proceed
+// setLoginAttempts(loginAttempts + 1);
+// setLastAttemptTime(currentTime);
+
+// try {
+// const loginResult = await login(username, password);
+// if (loginResult?.loggedIn) {
+// if (onLogin) {
+// onLogin(); // Call the passed down function when login is successful
+// }
+// }
+// setisLoggedIn(loginResult?.loggedIn);
+// return loginResult;
+// } catch (error) {
+// console.error('Login failed:', error);
+// }
+// } else {
+// // If over the limit
+// setError('Too many login attempts. Please wait for 1 minute.');
+// }
+// };
+
+// // Reset the login attempts and time after a minute
+// useEffect(() => {
+// if (loginAttempts >= 2) {
+// const timerId = setTimeout(() => {
+// setLoginAttempts(0);
+// }, 60000); // 1 minute = 60000 milliseconds
+
+// return () => clearTimeout(timerId);
+// }
+// }, [loginAttempts]);
+
+// // In AuthProvider
+// const login = async (username, password) => {
+// console.log('Login method invoked');
+// setIsLoading(true);
+
+// try {
+// const signInResponse = await axios.post(
+// `${REACT_APP_SERVER}/api/users/signin`,
+// { username, password },
+// {
+// headers: {
+// 'Content-Type': 'application/json',
+// Authorization: `Bearer ${token}`,
+// },
+// }
+// );
+
+// console.log('Fetching directed responses...');
+// await fetchDirectedResponses();
+
+// // Do not call validateToken here.
+// return { loggedIn: isLoggedIn, token };
+// } catch (error) {
+// console.error(`Error during login: ${error}`);
+// setError('Login failed');
+// setLoginState(false, null, {}, 'Login failed');
+// } finally {
+// setIsLoading(false);
+// }
+// };
+
+// const validateToken = useCallback(async () => {
+// if (!isMounted.current) return;
+
+// setIsLoading(true);
+// try {
+// const latestSignInResponse = directedResponses.find(
+// (res) => res.eventType === 'SIGNIN'
+// );
+
+// if (
+// latestSignInResponse &&
+// latestSignInResponse.response.data.token !== token
+// ) {
+// const newToken = latestSignInResponse.response.data.token;
+// const decodedUser = jwt_decode(newToken);
+// setLoginState(true, newToken, decodedUser);
+// } else {
+// throw new Error('Token validation failed');
+// }
+// } catch (error) {
+// console.error(`Error during validateToken: ${error}`);
+
+// setError('Token validation failed');
+// setLoginState(false, null, {}, 'Token validation failed');
+// } finally {
+// setIsLoading(false);
+// }
+// }, [directedResponses, setLoginState, token]);
+
+// const signup = async (username, password, email, basic_info, role_data) => {
+// setIsLoading(true);
+// try {
+// const response = await axios.post(
+// `${REACT_APP_SERVER}/api/users/signup`,
+// {
+// login_data: { username, password, email, role_data },
+// basic_info,
+// }
+// );
+// await validateToken(response.data.token);
+// return response.data.token;
+// } catch (err) {
+// setError('Signup failed');
+// setLoginState(false, null, {}, 'Signup failed');
+// } finally {
+// setIsLoading(false);
+// }
+// };
+
+// const logout = () => {
+// removeCookie('auth');
+// removeCookie(AUTH_COOKIE);
+// setLoginState(false, null, {});
+// console.log('Logout method invoked');
+// };
+
+// // In AuthProvider
+// useEffect(() => {
+// if (isMounted.current) {
+// const queryToken = new URLSearchParams(window.location.search).get(
+// 'token'
+// );
+// const cookieToken = cookies[AUTH_COOKIE];
+// const activeToken = queryToken || cookieToken;
+
+// if (activeToken && activeToken !== token) {
+// validateToken(activeToken);
+// }
+// }
+// }, [validateToken, cookies[AUTH_COOKIE]]);
+
+// const contextValue = {
+// isLoading: isLoading,
+// isLoggedIn: isLoggedIn,
+// user: users.find((u) => u.id === user.id) || user,
+// users,
+// error,
+// login,
+// onLogin, // Add this line to pass it down
+// logout,
+// signup,
+// setUser,
+// setLoginState,
+// validateToken,
+// // updateUser,
+// };
+
+// return (
+// {children}
+// );
+// }
+
+// /**
+// * Handler function for processing login data.
+// *
+// * @param {Object} data - The data received from login API.
+// */
+// function processLoginData(data) {
+// // Validate the data before processing
+// if (!validateData(data, 'Login Response', 'processLoginData')) {
+// console.warn('Invalid login data. Aborting processLoginData.');
+// return;
+// }
+
+// // Extract relevant fields from the received data
+// const { token, user } = data;
+
+// if (token && user) {
+// // Save token and user information to state or local storage
+// localStorage.setItem('authToken', token);
+// // Perform other login success logic here
+// } else {
+// console.error('Missing essential fields in login data.');
+// }
+// }
+
+// /**
+// * Handler function for processing signup data.
+// *
+// * @param {Object} data - The data received from signup API.
+// */
+// function processSignupData(data) {
+// // Validate the data before processing
+// if (!validateData(data, 'Signup Response', 'processSignupData')) {
+// console.warn('Invalid signup data. Aborting processSignupData.');
+// return;
+// }
+
+// // Extract relevant fields from the received data
+// const { success, newUser } = data;
+
+// if (success && newUser) {
+// // Assume `newUser` contains essential user info
+// const { id, username } = newUser;
+
+// // Save the new user ID or perform other signup success logic here
+// // For example, redirect to login or a welcome page
+// } else {
+// console.error('Missing essential fields in signup data.');
+// }
+// }
+
+// import React, { useState, useEffect, useCallback, useRef } from 'react';
+// import jwt_decode from 'jwt-decode';
+// import axios from 'axios';
+// import { useCookies } from 'react-cookie';
+// import { useUtilityContext } from '../UtilityContext/UtilityContext';
+
+// const LOGGED_IN_COOKIE = 'loggedIn';
+// const AUTH_COOKIE = 'authToken';
+// const USER_COOKIE = 'user';
+
+// const processResponseData = (data, type) => {
+// if (!validateData(data, `${type} Response`, `process${type}Data`)) {
+// console.warn(
+// `Invalid ${type.toLowerCase()} data. Aborting process${type}Data.`
+// );
+// return;
+// }
+
+// if (type === 'Login') {
+// const token = data?.data?.token;
+// if (token) {
+// localStorage.setItem('authToken', token);
+// const decodedUser = jwt_decode(token);
+// return { token, user: decodedUser };
+// }
+// } else if (type === 'Signup') {
+// const { success, newUser } = data;
+// if (success && newUser) {
+// return { success, newUser };
+// }
+// }
+
+// console.error(`Missing essential fields in ${type.toLowerCase()} data.`);
+// return null;
+// };
+// const isEmpty = (obj) => {
+// return (
+// [Object, Array].includes((obj || {}).constructor) &&
+// !Object.entries(obj || {}).length
+// );
+// };
+// // Validator function
+// const validateData = (data, eventName, functionName) => {
+// const dataType = typeof data;
+// console.log(
+// `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}`
+// );
+
+// if (data === null || data === undefined) {
+// console.warn(
+// `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}`
+// );
+// return false;
+// }
+
+// if (isEmpty(data)) {
+// console.error(
+// `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}`
+// );
+// return false;
+// }
+
+// return true;
+// };
+
+// export const AuthContext = React.createContext();
+
+// const setCookies = (
+// setCookieFunc,
+// authCookie,
+// userCookie,
+// loggedInCookie,
+// loggedIn,
+// token,
+// user
+// ) => {
+// setCookieFunc(authCookie, token);
+// setCookieFunc(userCookie, JSON.stringify(user));
+// setCookieFunc(loggedInCookie, String(loggedIn));
+// };
+// export default function AuthProvider({ children, serverUrl }) {
+// const { directedResponses, fetchDirectedResponses } = useUtilityContext();
+// const [cookies, setCookie, removeCookie] = useCookies([
+// LOGGED_IN_COOKIE,
+// AUTH_COOKIE,
+// USER_COOKIE,
+// ]);
+// const [isLoading, setIsLoading] = useState(false);
+// const [isLoggedIn, setisLoggedIn] = useState(false);
+// const [user, setUser] = useState({});
+// const [error, setError] = useState(null);
+// const [token, setToken] = useState(undefined);
+// const isMounted = useRef(true);
+// const [loginAttempts, setLoginAttempts] = useState(0);
+
+// const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER;
+
+// useEffect(
+// () => () => {
+// isMounted.current = false;
+// },
+// []
+// );
+
+// const setLoginState = useCallback(
+// (loggedIn, token, user, error = null) => {
+// setCookie(AUTH_COOKIE, token);
+// setCookie(USER_COOKIE, JSON.stringify(user));
+// setCookie(LOGGED_IN_COOKIE, String(loggedIn));
+// setisLoggedIn(loggedIn);
+// setToken(token);
+// setUser(user);
+// setError(error);
+// },
+// [setCookie]
+// );
+
+// const resetLoginAttempts = () =>
+// loginAttempts >= 2 && setTimeout(() => setLoginAttempts(0), 60000);
+// useEffect(resetLoginAttempts, [loginAttempts]);
+
+// const updateLoginState = useCallback(
+// (loggedIn, token, user, error = null) => {
+// setCookies(
+// setCookie,
+// AUTH_COOKIE,
+// USER_COOKIE,
+// LOGGED_IN_COOKIE,
+// loggedIn,
+// token,
+// user
+// );
+// setisLoggedIn(loggedIn);
+// setToken(token);
+// setUser(user);
+// setError(error);
+// },
+// [setCookie]
+// );
+
+// const validateToken = useCallback(async () => {
+// if (!isMounted.current) return;
+
+// setIsLoading(true);
+// try {
+// const latestSignInResponse = directedResponses.find(
+// (res) => res.eventType === 'SIGNIN'
+// );
+
+// if (
+// latestSignInResponse &&
+// latestSignInResponse.response.data.token !== token
+// ) {
+// const newToken = latestSignInResponse.response.data.token;
+// const decodedUser = jwt_decode(newToken);
+// setLoginState(true, newToken, decodedUser);
+// } else {
+// throw new Error('Token validation failed');
+// }
+// } catch (error) {
+// setError('Token validation failed');
+// setLoginState(false, null, {}, 'Token validation failed');
+// } finally {
+// setIsLoading(false);
+// }
+// }, [directedResponses, setLoginState, token]);
+// useEffect(() => {
+// const queryToken = new URLSearchParams(window.location.search).get('token');
+// const cookieToken = cookies[AUTH_COOKIE];
+// const activeToken = queryToken || cookieToken;
+// if (activeToken && activeToken !== token) {
+// validateToken(activeToken);
+// }
+// }, [validateToken, cookies[AUTH_COOKIE]]);
+
+// const safeRequest = useCallback(async (apiEndpoint, data, methodName) => {
+// try {
+// if (!validateData(data, apiEndpoint, methodName)) {
+// throw new Error(`Invalid data sent to API endpoint: ${apiEndpoint}`);
+// }
+// const response = await axios.post(apiEndpoint, data);
+// if (!validateData(response, apiEndpoint, methodName)) {
+// throw new Error(
+// `Invalid data received from API endpoint: ${apiEndpoint}`
+// );
+// }
+// return response.data;
+// } catch (error) {
+// console.error(`[Error] Failed to send request to: ${apiEndpoint}`, error);
+// setError({ message: error.message, source: methodName });
+// return null;
+// }
+// }, []);
+
+// const safeResponse = useCallback((data, eventName, handler) => {
+// try {
+// if (!validateData(data, eventName, handler.name)) {
+// throw new Error(`Invalid data received for event: ${eventName}`);
+// }
+// return handler(data);
+// } catch (error) {
+// console.error(`[Error] Failed to handle event: ${eventName}`, error);
+// setError({ message: error.message, source: eventName });
+// return null;
+// }
+// }, []);
+
+// const executeAuthAction = async (actionType, url, requestData) => {
+// const response = await safeRequest(
+// `${REACT_APP_SERVER}/api/users/${url}`,
+// requestData,
+// actionType.toLowerCase()
+// );
+
+// if (response) {
+// const processedData = safeResponse(
+// response,
+// actionType.toLowerCase(),
+// (data) => processResponseData(data, actionType)
+// );
+// if (processedData) {
+// setLoginState(
+// true,
+// processedData.token || null,
+// processedData.user || processedData.newUser || {}
+// );
+// }
+// }
+// };
+
+// const login = async (username, password) => {
+// const requestData = { username, password };
+// await executeAuthAction('Login', 'signin', requestData);
+// };
+
+// const signup = async (loginData, basicInfo, otherInfo) => {
+// const requestData = {
+// login_data: loginData,
+// basic_info: basicInfo,
+// ...otherInfo,
+// };
+// await executeAuthAction('Signup', 'signup', requestData);
+// };
+
+// const logout = () => {
+// removeCookie('auth');
+// removeCookie(AUTH_COOKIE);
+// setLoginState(false, null, {});
+// console.log('Logout method invoked');
+// };
+// // Add this in your Header component
+// useEffect(() => {
+// console.log('Value of isLoggedIn from context: ', isLoggedIn);
+// }, [isLoggedIn]);
+
+// useEffect(() => {
+// if (isMounted.current) {
+// const queryToken = new URLSearchParams(window.location.search).get(
+// 'token'
+// );
+// const cookieToken = cookies[AUTH_COOKIE];
+// const activeToken = queryToken || cookieToken;
+
+// if (activeToken && activeToken !== token) {
+// validateToken(activeToken);
+// }
+// }
+// }, [validateToken, cookies[AUTH_COOKIE]]);
+
+// const contextValue = {
+// isLoading,
+// isLoggedIn,
+// user,
+// // users,
+// error,
+// login,
+// // onLogin,
+// logout,
+// signup,
+// setUser,
+// setLoginState,
+// validateToken,
+// };
+
+// return (
+// {children}
+// );
+// }
diff --git a/src/context/CardContext/CardStore.js b/src/context/CardContext/CardStore.js
index f6566c0..491c2fb 100644
--- a/src/context/CardContext/CardStore.js
+++ b/src/context/CardContext/CardStore.js
@@ -8,10 +8,7 @@ const CardContext = createContext();
export const CardProvider = ({ children }) => {
const [cookies, setCookie] = useCookies(['cart'], ['deckData']);
const initialStore = cookies.store || [];
- // const initialCart = cookies.cart || [];
- // console.log('Initial cart:', initialCart);
const [cardsArray, setCardsArray] = useState(initialStore);
- // console.log('Initial store:', initialStore);
const currentCart = cookies.cart || [];
const [currenCartArray, setCurrentCartArray] = useState(currentCart);
const [searchData, setSearchData] = useState([]);
@@ -19,7 +16,6 @@ export const CardProvider = ({ children }) => {
const [savedDeckData, setSavedDeckData] = useState(currentDeckData);
const [deckSearchData, setDeckSearchData] = useState([]);
- // console.log('Current cart:', currentCart);
if (!currenCartArray || !savedDeckData) {
return Loading...
;
}
@@ -82,7 +78,6 @@ export const CardProvider = ({ children }) => {
searchData,
deckSearchData,
savedDeckData,
-
setSearchData,
setDeckSearchData,
setSavedDeckData,
@@ -104,6 +99,11 @@ export const CardProvider = ({ children }) => {
initialStore,
cookies,
currentCart,
+ // getCardQuantity: (cardId) => {
+ // if
+ // const card = cartData?.cart?.find((c) => c?.id === cardId);
+ // return card?.quantity || 0;
+ // },
setSearchData,
setDeckSearchData,
setSavedDeckData,
diff --git a/src/context/CardImagesContext/CardImagesContext.jsx b/src/context/CardImagesContext/CardImagesContext.jsx
new file mode 100644
index 0000000..2f67d6f
--- /dev/null
+++ b/src/context/CardImagesContext/CardImagesContext.jsx
@@ -0,0 +1,113 @@
+import React, { createContext, useContext, useEffect, useState } from 'react';
+
+const CardImagesContext = createContext();
+
+export const useCardImages = () => useContext(CardImagesContext);
+
+const fetchWrapper = async (url, method, body = null) => {
+ const options = {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ // 'Access-Control-Allow-Origin': '*',
+ // crossOrigin: 'anonymous',
+ },
+ ...(body && { body: JSON.stringify(body) }),
+ };
+
+ try {
+ const response = await fetch(url, options);
+ if (!response.ok) {
+ // We handle non-ok responses immediately
+ throw new Error(`API request failed with status ${response.status}`);
+ }
+ // updateLastRequestTime(method); // Assumed to be a function that updates some kind of state
+ return await response.json(); // Directly returning the JSON response
+ } catch (error) {
+ console.error(`Fetch failed: ${error}`);
+ console.trace();
+ throw error; // Re-throwing the error for upstream catch blocks to handle
+ }
+};
+
+export const CardImagesProvider = ({ children }) => {
+ const [cards, setCards] = useState([]);
+ const [images, setImages] = useState([]); // [
+ const [randomCardImage, setRandomCardImage] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+ // const BASE_API_URL = 'http://localhost:3001/api/card-image';
+ const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/card-image`;
+
+ const downloadCardImages = async () => {
+ setIsLoading(true);
+ try {
+ const response = await fetchWrapper(BASE_API_URL + '/download', 'GET');
+ console.log('Response from fetchWrapper:', response);
+
+ // If response is already JSON
+ setCards(response.data); // Directly setting the response if it's already in the desired format
+
+ cards.forEach((card) => {
+ if (card.card_images && card.card_images.length > 0) {
+ // Adding a dummy GET parameter to bypass caching
+ const imageUrl =
+ card.card_images[0].image_url + '?dummy=' + Date.now();
+ setImages(imageUrl);
+ }
+ });
+
+ // If response needs to be parsed as JSON
+ // const jsonResponse = await response.json(); // Uncomment if response is in JSON format
+ // setCards(jsonResponse); // Assuming jsonResponse is an array of cards
+ } catch (error) {
+ console.error('Error in downloadCardImages:', error);
+ setError(error.message);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ downloadCardImages();
+ }, []);
+
+ useEffect(() => {
+ if (cards && cards.length > 0) {
+ const randomCard = cards[Math.floor(Math.random() * cards.length)];
+ if (randomCard.card_images && randomCard.card_images.length > 0) {
+ // Adding a dummy GET parameter to bypass caching
+ const imageUrl =
+ randomCard.card_images[0].image_url + '?dummy=' + Date.now();
+ setRandomCardImage(imageUrl);
+ }
+ }
+ }, [cards]);
+
+ useEffect(() => {
+ if (images && images.length > 0) {
+ const randomCard = images[Math.floor(Math.random() * images.length)];
+ if (randomCard.card_images && randomCard.card_images.length > 0) {
+ setRandomCardImage(randomCard.card_images[0].image_url);
+ }
+ }
+ }, [images]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default CardImagesProvider;
diff --git a/src/context/CartContext/CartContext.js b/src/context/CartContext/CartContext.js
index a96e45e..23b3748 100644
--- a/src/context/CartContext/CartContext.js
+++ b/src/context/CartContext/CartContext.js
@@ -34,8 +34,8 @@ export const CartProvider = ({ children }) => {
quantity: 0, // Total quantity of items
totalPrice: 0, // Total price of items
});
- const [cookies, setCookie] = useCookies(['userCookie', 'cart']);
- const userId = cookies.userCookie?.id;
+ const [cookies, setCookie] = useCookies(['user', 'cart']);
+ const userId = cookies?.user?.id;
const { getCardData } = useCardStore();
const isMounted = useRef(true);
@@ -45,7 +45,6 @@ export const CartProvider = ({ children }) => {
};
}, []);
- //Utility functions
const fetchFromServer = async (url, options = {}) => {
const response = await fetch(
`${process.env.REACT_APP_SERVER}${url}`,
@@ -56,36 +55,49 @@ export const CartProvider = ({ children }) => {
}
return await response.json();
};
-
const updateCart = async (cartId, updatedCart) => {
- const formattedCartData = updatedCart.map((item) => ({
- ...item,
- quantity: item.quantity,
- }));
+ if (!cartId) return;
+ const formattedCartData = {
+ cartItems: updatedCart.map((item) => ({
+ ...item,
+ id: item.id, // Make sure 'id' is a number
+ quantity: item.quantity,
+ })),
+ userId: userId,
+ };
const data = await fetchFromServer(`/api/carts/${cartId}/update`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formattedCartData),
});
+ // if (data && data.cart) {
+ // setCartDataAndCookie(data.cart);
+ // }
setCartDataAndCookie(data);
+ return data;
};
-
const createUserCart = useCallback(
async (userId) => {
- const newCartData = await fetchFromServer(
- `/api/carts/${userId}/newCart`,
- { method: 'POST' }
- );
- const newCartItems = Array.isArray(newCartData.cart)
- ? newCartData.cart
- : [];
- setCookie('cart', newCartItems, {
- // Changed this line
- path: '/',
- secure: true,
- sameSite: 'none',
- });
- return newCartItems;
+ try {
+ const newCartData = await fetchFromServer(
+ '/api/carts/createEmptyCart',
+ {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ userId }),
+ }
+ );
+ // setCookie('cart', newCartData, {
+ // // Changed this line
+ // path: '/',
+ // secure: true,
+ // sameSite: 'none',
+ // });
+ setCartDataAndCookie(newCartData);
+ return newCartData;
+ } catch (error) {
+ console.error('Error creating cart:', error);
+ }
},
[setCookie]
);
@@ -94,46 +106,24 @@ export const CartProvider = ({ children }) => {
async (userId) => {
try {
const data = await fetchFromServer(`/api/carts/userCart/${userId}`);
- setCookie('cart', data.cart || [], {
- path: '/',
- secure: true,
- sameSite: 'none',
- });
+ // setCookie('cart', data.cart || [], {
+ // path: '/',
+ // secure: true,
+ // sameSite: 'none',
+ // });
+ setCartDataAndCookie(data);
return data;
} catch (error) {
if (error.message === 'HTTP error! status: 404') {
- return createUserCart(userId);
+ await createUserCart(userId);
} else {
- console.error(error.message);
+ console.error('Error fetching user cart:', error);
}
}
},
- [createUserCart, setCookie]
+ [createUserCart]
);
- useEffect(() => {
- if (userId && typeof userId === 'string') {
- // console.log('Fetching user cart');
- fetchUserCart(userId)
- .then((data) => {
- if (data && data.cart) {
- setCartDataAndCookie(data);
- }
- })
- .catch((error) => console.log('Error fetching user cart:', error));
- }
- }, [userId, fetchUserCart]);
-
- const setCartDataAndCookie = (newCartData) => {
- setCartData(newCartData);
- setCookie('cart', newCartData.cart, {
- // Changed this line
- path: '/',
- secure: true,
- sameSite: 'none',
- });
- };
-
const getCardQuantity = (cardId) => {
let totalItems = 0;
let quantityOfSameId = 0;
@@ -148,7 +138,8 @@ export const CartProvider = ({ children }) => {
const addOneToCart = async (cardInfo) => {
if (!cartData._id) return;
- const { quantityOfSameId } = getCardQuantity(cardInfo.id);
+ console.log('Adding one to cart', cardInfo);
+ const { quantityOfSameId, totalItems } = getCardQuantity(cardInfo.id);
if (quantityOfSameId >= 3) return;
let updatedCart = cartData.cart.map((item) =>
item.id === cardInfo.id ? { ...item, quantity: item.quantity + 1 } : item
@@ -157,6 +148,7 @@ export const CartProvider = ({ children }) => {
updatedCart = [...updatedCart, { ...cardInfo, quantity: 1 }];
}
const updatedCartData = await updateCart(cartData._id, updatedCart);
+ console.log('UPDATED CART DATA:', updatedCartData);
if (updatedCartData) setCartData(updatedCartData);
};
@@ -180,6 +172,17 @@ export const CartProvider = ({ children }) => {
if (updatedCartData) setCartData(updatedCartData);
};
+ const setCartDataAndCookie = (newCartData) => {
+ if (newCartData && Array.isArray(newCartData.cart)) {
+ setCartData(newCartData);
+ setCookie('cart', newCartData.cart, {
+ path: '/',
+ secure: true,
+ sameSite: 'none',
+ });
+ }
+ };
+
useEffect(() => {
if (userId && typeof userId === 'string' && isMounted.current) {
fetchUserCart(userId)
@@ -190,7 +193,22 @@ export const CartProvider = ({ children }) => {
})
.catch((error) => console.log('Error fetching user cart:', error));
}
+ return () => {
+ isMounted.current = false;
+ };
}, [userId, fetchUserCart]);
+ // useEffect(() => {
+ // if (userId && typeof userId === 'string') {
+ // // console.log('Fetching user cart');
+ // fetchUserCart(userId)
+ // .then((data) => {
+ // if (data && data.cart) {
+ // setCartDataAndCookie(data);
+ // }
+ // })
+ // .catch((error) => console.log('Error fetching user cart:', error));
+ // }
+ // }, [userId, fetchUserCart]);
const getTotalCost = useMemo(
() =>
@@ -210,6 +228,7 @@ export const CartProvider = ({ children }) => {
0
);
+ console.log('TOTAL QUANTITY:', totalQuantity);
if (
cartData.quantity !== totalQuantity ||
cartData.totalPrice !== getTotalCost
@@ -229,6 +248,10 @@ export const CartProvider = ({ children }) => {
const value = {
cartData,
getCardQuantity,
+ // getCardQuantity: (cardId) => {
+ // const card = cartData?.cart?.find((c) => c?.id === cardId);
+ // return card?.quantity || 0;
+ // },
cartCardQuantity: cartData.cart?.reduce(
(acc, card) => acc + card.quantity,
0
@@ -238,8 +261,8 @@ export const CartProvider = ({ children }) => {
(acc, card) => acc + card.card_prices[0].tcgplayer_price * card.quantity,
0
),
- addOneToCart,
- removeOneFromCart,
+ addOneToCart: addOneToCart,
+ removeOneFromCart: removeOneFromCart,
deleteFromCart,
getTotalCost,
fetchUserCart,
@@ -250,7 +273,7 @@ export const CartProvider = ({ children }) => {
console.log('CART CONTEXT: ', {
cartData,
// getTotalCost,
- // // getCardQuantity,
+ // getCardQuantity,
// fetchUserCart,
// addOneToCart,
// removeOneFromCart,
diff --git a/src/context/CartContext/keepsaved.js b/src/context/CartContext/keepsaved.js
deleted file mode 100644
index 98c7d5d..0000000
--- a/src/context/CartContext/keepsaved.js
+++ /dev/null
@@ -1,240 +0,0 @@
-// /* eslint-disable @typescript-eslint/no-empty-function */
-// import React, { createContext, useState, useEffect, useCallback } from 'react';
-// import { useCookies } from 'react-cookie';
-// import { useCardStore } from './CardStore';
-
-// export const CartContext = createContext({
-// cart: [],
-// getCardQuantity: () => {},
-// addOneToCart: () => {},
-// removeOneFromCart: () => {},
-// deleteFromCart: () => {},
-// getTotalCost: () => {},
-// setCart: () => {},
-// fetchUserCart: () => {},
-// });
-
-// export const CartProvider = ({ children }) => {
-// const [cartData, setCartData] = useState([]);
-// const [loading, setLoading] = useState(false);
-// const [error, setError] = useState(null);
-// const { getCardData } = useCardStore();
-// const [cookies, setCookie] = useCookies(['userCookie', 'cart']);
-
-// const userId = cookies.userCookie?.id; // Update the way to get the userId
-// console.log('cartData', cartData);
-
-// useEffect(() => {
-// let isMounted = true;
-
-// const fetchAndSetUserCart = async (userId) => {
-// try {
-// setLoading(true);
-// const cartData = await fetchUserCart(userId);
-// console.log('Fetched cartData:', cartData);
-
-// if (isMounted) {
-// setCartData(cartData);
-// setLoading(false);
-// }
-// } catch (error) {
-// if (isMounted) {
-// setError(`Failed to fetch user cart data: ${error.message}`);
-// setLoading(false);
-// }
-// }
-// };
-
-// if (userId && typeof userId === 'string') {
-// fetchAndSetUserCart(userId);
-// }
-
-// return () => {
-// isMounted = false;
-// };
-// }, [userId]);
-
-// // Add new function here
-// const createUserCart = async (userId) => {
-// const newCartResponse = await fetch(
-// `${process.env.REACT_APP_SERVER}/api/carts/newCart/${userId}`,
-// {
-// method: 'POST',
-// }
-// );
-
-// if (!newCartResponse.ok) {
-// throw new Error(`HTTP error! status: ${newCartResponse.status}`);
-// }
-
-// const newCartData = await newCartResponse.json();
-// console.log('CART CREATED:', newCartData);
-// const newCartItems = Array.isArray(newCartData.cart)
-// ? newCartData.cart
-// : [];
-// setCookie('activeCartCardsArray', newCartItems, { path: '/' });
-// setCookie('allCarts', newCartItems, { path: '/' });
-// return newCartItems;
-// };
-
-// const fetchUserCart = useCallback(
-// async (userId) => {
-// const response = await fetch(
-// `${process.env.REACT_APP_SERVER}/api/carts/userCart/${userId}`
-// );
-
-// if (!response.ok) {
-// if (response.status === 404) {
-// // Call the new function here
-// return createUserCart(userId);
-// } else {
-// throw new Error(`HTTP error! status: ${response.status}`);
-// }
-// } else {
-// const data = await response.json();
-// console.log('CART EXISTS:', data);
-// setCookie(
-// 'activeCartCardsArray',
-// Array.isArray(data.cart) ? data.cart : [],
-// {
-// path: '/',
-// }
-// );
-// setCookie('allCarts', Array.isArray(data) ? data : {}, {
-// path: '/',
-// });
-// return data;
-// }
-// },
-// [setCookie, createUserCart] // Added createUserCart to the dependencies array
-// );
-
-// const updateCartInBackend = async (cartId, cartData) => {
-// console.log('received values:', 'cartId', cartId, 'cartData', cartData);
-
-// // Ensure cartData is in the correct format
-// const formattedCartData = cartData.map((item) => ({
-// cardId: item.id,
-// quantity: item.quantity,
-// }));
-
-// try {
-// const response = await fetch(
-// `${process.env.REACT_APP_SERVER}/api/carts/${cartId}`,
-// {
-// method: 'PUT',
-// headers: {
-// 'Content-Type': 'application/json',
-// },
-// body: JSON.stringify(formattedCartData),
-// }
-// );
-
-// if (!response.ok) {
-// throw new Error(`HTTP error! status: ${response.status}`);
-// }
-
-// const data = await response.json();
-// return data;
-// } catch (error) {
-// console.error(`Failed to update cart in backend: ${error.message}`);
-// }
-// };
-
-// const getCardQuantity = (cardInfo) => {
-// const cartItem = cartData?.cart?.find((item) => item.id === cardInfo.id);
-// return cartItem ? cartItem.quantity : 0;
-// };
-
-// const addOneToCart = async (cardInfo) => {
-// const itemExistsInCart = cartData?.cart?.some(
-// (item) => item.id === cardInfo.id
-// );
-
-// let updatedCart;
-// if (itemExistsInCart) {
-// updatedCart =
-// cartData?.cart?.map((item) => {
-// if (item.id === cardInfo.id) {
-// return {
-// ...item,
-// quantity: item.quantity + 1,
-// };
-// }
-// return item;
-// }) ?? [];
-// } else {
-// updatedCart = [...(cartData?.cart || []), { ...cardInfo, quantity: 1 }];
-// }
-
-// const newCartData = await updateCartInBackend(cartData?._id, updatedCart);
-// setCartData(newCartData || []);
-// };
-
-// const removeOneFromCart = async (cardInfo) => {
-// const quantity = getCardQuantity(cardInfo.id);
-// const updatedCart =
-// quantity === 1
-// ? cartData?.cart?.filter(
-// (currentCard) => currentCard.id !== cardInfo.id
-// ) ?? []
-// : cartData?.cart?.map((card) =>
-// card.id === cardInfo.id
-// ? { ...card, quantity: card.quantity - 1 }
-// : card
-// ) ?? [];
-
-// const newCartData = await updateCartInBackend(cartData?._id, updatedCart);
-// setCartData(newCartData || []);
-// };
-
-// const deleteFromCart = async (cardInfo) => {
-// const updatedCart =
-// cartData?.cart?.filter(
-// (currentCard) => currentCard.cardId !== cardInfo.id
-// ) ?? [];
-
-// const newCartData = await updateCartInBackend(cartData?._id, updatedCart);
-// setCartData(newCartData || []);
-// };
-
-// const getTotalCost = () => {
-// if (!cartData?.cart || cartData.cart.length === 0) {
-// return 0;
-// }
-
-// console.log('cartData:', cartData);
-// return cartData.cart.reduce((totalCost, cartItem) => {
-// const cardData = getCardData(cartItem.id);
-// return totalCost + cardData.price * cartItem.quantity;
-// }, 0);
-// };
-
-// const setCart = async (cart) => {
-// const updatedCart = Array.isArray(cart) ? cart : [];
-// setCartData(updatedCart);
-// // update in backend
-// const newCartData = await updateCartInBackend(cartData?._id, updatedCart);
-// setCartData(newCartData || []);
-// };
-
-// const contextValue = {
-// cart: cartData.cart,
-// getCardQuantity,
-// addOneToCart,
-// createUserCart,
-// removeOneFromCart,
-// fetchUserCart,
-// deleteFromCart,
-// getTotalCost,
-// setCart,
-// loading,
-// error,
-// };
-
-// return (
-// {children}
-// );
-// };
-
-// export default CartProvider;
diff --git a/src/context/ChartContext/ChartContext.jsx b/src/context/ChartContext/ChartContext.jsx
index fb978e0..eefb826 100644
--- a/src/context/ChartContext/ChartContext.jsx
+++ b/src/context/ChartContext/ChartContext.jsx
@@ -12,11 +12,34 @@ export const useChartContext = () => {
export const ChartProvider = ({ children }) => {
const [latestData, setLatestData] = useState(null);
- const [timeRange, setTimeRange] = useState(24 * 60 * 60 * 1000); // Default to 24 hours
+ const [timeRange, setTimeRange] = useState(86400000 || 24 * 60 * 60 * 1000); // Default to 24 hours
+
+ // Correctly initialize timeRanges with the useState hook
+ const [timeRanges] = useState([
+ { label: '2 hours', value: 720000 || 2 * 60 * 60 * 1000 },
+ { label: '24 hours', value: 86400000 || 24 * 60 * 60 * 1000 },
+ { label: '7 days', value: 604800000 || 7 * 24 * 60 * 60 * 1000 },
+ { label: '1 month', value: 2592000000 || 30 * 24 * 60 * 60 * 1000 },
+ ]);
+
+ const currentValue = timeRanges.find((option) => option.value === timeRange);
+
+ // console.log('currentValue: ', currentValue);
+ const handleChange = (e) => {
+ setTimeRange(e.target.value);
+ };
return (
{children}
diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx
index 07a3560..d4bf44b 100644
--- a/src/context/CollectionContext/CollectionContext.jsx
+++ b/src/context/CollectionContext/CollectionContext.jsx
@@ -7,379 +7,180 @@ import React, {
useCallback,
useMemo,
useContext,
+ useRef,
} from 'react';
import { useCookies } from 'react-cookie';
import {
- initialCollectionState,
+ handleCardAddition,
+ handleCardRemoval,
+ createApiUrl,
fetchWrapper,
- removeDuplicateCollections,
- calculateAndUpdateTotalPrice,
- calculateTotalPrice,
getTotalCost,
+ initialCollectionState,
getCardPrice,
-} from './exampleImport.js';
-import { useCombinedContext } from '../CombinedProvider.jsx';
-import { useUserContext } from '../UserContext/UserContext.js';
+ defaultContextValue,
+ validateUserIdAndData,
+ getUpdatedChartData,
+ getPriceChange,
+ constructPayloadWithDifferences,
+ getCurrentChartDataSets,
+ calculateCollectionValue,
+} from './collectionUtility.jsx';
import moment from 'moment';
-// import { useUserContext } from '../UserContext/UserContext.js';
-// 1. Define a default context value
-const defaultContextValue = {
- allCollections: [],
- allCardPrices: [],
- xy: [],
- selectedCollection: {},
- collectionData: initialCollectionState,
- totalCost: 0,
- openChooseCollectionDialog: false,
- updatedPricesFromCombinedContext: {},
- setUpdatedPricesFromCombinedContext: () => {},
- setOpenChooseCollectionDialog: () => {},
- calculateTotalPrice: () => {},
- getTotalCost: () => {},
- createUserCollection: () => {},
- removeCollection: () => {},
- fetchAllCollectionsForUser: () => {},
- setSelectedCollection: () => {},
- setAllCollections: () => {},
- addOneToCollection: () => {},
- removeOneFromCollection: () => {},
-};
-// 2. Replace null with the default value when creating the context
export const CollectionContext = createContext(defaultContextValue);
-const filterOutDuplicateYValues = (datasets) => {
- // console.log('DATASETS:', datasets);
- const seenYValues = new Set();
- return datasets?.filter((data) => {
- const yValue = data?.y;
- if (seenYValues.has(yValue)) {
- return false;
- }
- seenYValues.add(yValue);
- return true;
- });
-};
-
-const transformChartData = (chartData) => {
- let pointsArray = [];
-
- if (Array.isArray(chartData?.datasets)) {
- chartData?.datasets?.forEach((dataset) => {
- dataset.data?.forEach((dataEntry) => {
- dataEntry.xys?.forEach((xyEntry) => {
- const { x, y } = xyEntry.data;
- if (x && y !== undefined) {
- pointsArray.push({ x, y });
- }
- });
- });
- });
- } else {
- console.error(
- 'Expected chartData.datasets to be an array, but got:',
- chartData
- );
- }
-
- return pointsArray;
-};
-
-function convertData(originalData) {
- let finalDataForChart = [];
+function transformPriceHistoryToXY(collectionPriceHistory) {
+ return collectionPriceHistory?.map((entry) => ({
+ x: entry?.timestamp, // x represents the timestamp
+ y: entry?.num, // y represents the numerical value
+ label: `Price at ${entry?.timestamp}`, // label can be customized as needed
+ }));
+}
- const { datasets } = originalData;
+const getAllCardPrices = (cards) =>
+ cards.flatMap((card) => Array(card.quantity).fill(card.price));
- if (Array.isArray(datasets) && datasets.length > 0) {
- const lastDataset = datasets[datasets.length - 1];
+function filterUniqueDataPoints(dataArray) {
+ const uniqueRecords = new Map();
- if (Array.isArray(lastDataset.data) && lastDataset.data.length > 0) {
- lastDataset.data.forEach((dataEntry) => {
- dataEntry.xys?.forEach((xyEntry) => {
- const { x, y } = xyEntry.data;
- if (x && y !== undefined) {
- finalDataForChart.push({ x, y });
- }
- });
- });
+ dataArray?.forEach((item) => {
+ const key = `${item?.label}-${item?.x}-${item?.y}`;
+ if (!uniqueRecords.has(key)) {
+ uniqueRecords.set(key, item);
}
- }
+ });
- return {
- ...originalData,
- finalDataForChart,
- };
+ return Array.from(uniqueRecords.values());
}
-const isEmpty = (obj) => {
- return (
- [Object, Array].includes((obj || {}).constructor) &&
- !Object.entries(obj || {}).length
- );
-};
-const validateData = (data, eventName, functionName) => {
- const dataType = typeof data;
- console.log(
- `[Info] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}`
- );
- if (data === null || data === undefined) {
- console.warn(
- `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}`
- );
- return false;
- }
- if (isEmpty(data)) {
- console.error(
- `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}`
- );
- return false;
- }
- return true;
-};
-
-const handleCardAddition = (currentCards, cardToAdd) => {
- // Check if the card already exists in the currentCards array
- const cardToAddId =
- typeof cardToAdd.id === 'number' ? String(cardToAdd.id) : cardToAdd.id;
-
- const matchingCard = currentCards.find((c) => c.id === cardToAddId);
-
- if (matchingCard) {
- matchingCard.quantity++;
- return [...currentCards];
- } else {
- return [...currentCards, { ...cardToAdd, id: cardToAddId, quantity: 1 }];
- }
-};
-
-const handleCardRemoval = (currentCards, cardToRemove) => {
- // Convert the cardToRemove's id to a string if it's a number
- const cardToRemoveId =
- typeof cardToRemove.id === 'number'
- ? String(cardToRemove.id)
- : cardToRemove.id;
-
- const matchingCard = currentCards.find((c) => c.id === cardToRemoveId);
-
- if (!matchingCard) {
- console.error('Card not found in the collection.');
- return [...currentCards];
- }
-
- if (matchingCard.quantity > 1) {
- matchingCard.quantity--;
- return [...currentCards];
- } else {
- return currentCards.filter((card) => card.id !== cardToRemoveId);
- }
-};
export const CollectionProvider = ({ children }) => {
- // const { cardPrices } = useCombinedContext();
- const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`;
- const [cookies] = useCookies(['userCookie']);
- const { triggerCronJob } = useUserContext();
+ const [cookies] = useCookies(['user']);
+ const [selectedCollection, setSelectedCollection] = useState(
+ initialCollectionState
+ );
const [collectionData, setCollectionData] = useState(initialCollectionState);
const [allCollections, setAllCollections] = useState([]);
- const [allCardPrices, setAllCardPrices] = useState([]);
- const [xyData, setXyData] = useState([
- // {
- // label: '',
- // data: [],
- // },
- ]); // New state to hold xy data
- // const [updatedPrices, setUpdatedPrices] = useState([]);
+ const [totalPrice, setTotalPrice] = useState(0);
+ const [totalCost, setTotalCost] = useState('');
const [
updatedPricesFromCombinedContext,
setUpdatedPricesFromCombinedContext,
- ] = useState({});
- const [selectedCollection, setSelectedCollection] = useState({});
+ ] = useState([]);
+ const [xyData, setXyData] = useState([]);
+ const [currentChartDataSets2, setCurrentChartDataSets2] = useState([]);
const [openChooseCollectionDialog, setOpenChooseCollectionDialog] =
useState(false);
- const chartData = selectedCollection?.chartData || {};
- // const datasets = chartData?.datasets || [];
- const userId = cookies.userCookie?.id;
- const totalCost = useMemo(
- () => getTotalCost(selectedCollection),
- [selectedCollection]
- );
-
- const calculateTotalFromAllCardPrices = (allCardPrices) => {
- if (!Array.isArray(allCardPrices)) return 0;
- return allCardPrices.reduce(
- (total, price) => total + ensureNumber(price, 0),
- 0
- );
- };
-
- const ensureNumber = (value, defaultValue = 0) => {
- let num = parseFloat(value);
- return isNaN(num) ? defaultValue : num;
- };
+ const userId = cookies?.user?.id;
+ const lastFetchedTime = useRef(null);
const fetchAndSetCollections = useCallback(async () => {
- if (!userId) {
- console.warn('userId is not set, aborting fetchAndSetCollections.');
- return;
- }
+ const shouldFetch = () => {
+ const fetchDelay = 60000; // 1 minute
+ const currentTime = Date.now();
+ return (
+ !lastFetchedTime.current ||
+ currentTime - lastFetchedTime.current >= fetchDelay
+ );
+ };
+
+ if (!shouldFetch()) return;
try {
- console.log('Fetching collections...');
- const collections = await fetchWrapper(
- `${BASE_API_URL}/${userId}/collections`,
+ lastFetchedTime.current = Date.now();
+ const response = await fetchWrapper(
+ createApiUrl(`${userId}/collections`),
'GET'
);
+ const collections = response.data || [];
- console.log('Fetched collections:', collections);
-
- if (!collections) {
- console.warn('No collections returned from the server.');
- // Consider setting an empty or error state here
- return;
- }
-
- const uniqueCollections = removeDuplicateCollections(collections);
- console.log('Unique collections:', uniqueCollections);
+ console.log('FETCHED COLLECTIONS:', collections);
- // Filter out invalid collections
- const validCollections = uniqueCollections.filter(Boolean);
-
- if (Array.isArray(validCollections)) {
- console.log('Valid collections:', validCollections);
- validCollections.forEach((collection) => {
- if (collection && collection.allCardPrices) {
- collection.totalPrice = calculateTotalFromAllCardPrices(
- collection.allCardPrices
- );
- } else {
- console.warn(
- 'Invalid collection or missing allCardPrices:',
- collection
- );
- }
- });
+ if (collections.length > 0) {
+ setAllCollections(collections);
+ setCollectionData(collections[0]);
+ setSelectedCollection(collections[0]);
} else {
- console.error('Valid collections is not an array', collections);
+ console.warn('No collections found.');
+ // Optionally, set a default or empty state if no collections are found
}
-
- setAllCollections(validCollections);
- setCollectionData(
- validCollections.length === 0
- ? initialCollectionState
- : validCollections[0]
- );
- setSelectedCollection(
- validCollections.length === 0
- ? initialCollectionState
- : validCollections[0]
- );
} catch (error) {
- if (error && error.message) {
- console.error(`Failed to fetch collections: ${error.message}`);
- // Consider setting an error state here, for example:
- // setErrorState(error.message);
- } else {
- console.error('An unexpected error occurred.');
- // Consider setting a generic error state here, for example:
- // setErrorState('An unexpected error occurred.');
- }
+ console.error(`Failed to fetch collections: ${error}`);
}
- }, [userId]);
+ }, [userId, setAllCollections, setCollectionData, setSelectedCollection]);
- const findCollectionIndex = useCallback(
- (collections, id) =>
- collections?.findIndex((collection) => collection?._id === id) ?? -1,
- []
- );
+ const updateCollectionArray = (collections, newData) => {
+ const index = collections.findIndex((c) => c._id === newData?._id);
+ return index === -1
+ ? [...collections, newData]
+ : collections.map((c) => (c._id === newData?._id ? newData : c));
+ };
const updateCollectionData = useCallback(
(newData, collectionType) => {
- if (collectionType === 'allCollections') {
- setAllCollections((prevCollections = []) => {
- const existingIndex = findCollectionIndex(
- prevCollections,
- newData?._id
- );
- if (existingIndex === -1) return [...prevCollections, newData];
- const updatedCollections = [...prevCollections];
- updatedCollections[existingIndex] = newData;
- return updatedCollections;
- });
- } else if (collectionType === 'selectedCollection') {
- setSelectedCollection(newData);
-
- // updateActiveCollection(newData);
- } else if (collectionType === 'collectionData') {
- setCollectionData(newData);
+ try {
+ switch (collectionType) {
+ case 'allCollections':
+ setAllCollections((prev) => updateCollectionArray(prev, newData));
+ break;
+ case 'selectedCollection':
+ setSelectedCollection(newData);
+ break;
+ case 'collectionData':
+ setCollectionData(newData);
+ break;
+ default:
+ console.warn('Unknown collection type for update:', collectionType);
+ }
+ } catch (error) {
+ console.error('Error updating collection data:', error);
}
},
- [findCollectionIndex]
+ [setAllCollections, setSelectedCollection, setCollectionData]
);
- const createApiUrl = (path) => `${BASE_API_URL}/${path}`;
-
- const handleApiResponse = (response, method) => {
- if (method === 'POST' && response.data?.newCollection) {
- return response.data.newCollection;
- }
- if (method === 'PUT' && response.data?.updatedCollection) {
- return response.data.updatedCollection;
- }
- throw new Error('Unexpected response format');
- };
-
- const createUserCollection = async (userId, collection) => {
+ const createUserCollection = async (
+ userId,
+ newCollectionInfo,
+ name,
+ description
+ ) => {
if (
- !validateData(
- collection,
- 'Create User Collection',
- 'createUserCollection'
+ !userId ||
+ !name ||
+ !validateUserIdAndData(
+ userId,
+ newCollectionInfo,
+ 'create a new collection'
)
) {
- console.error('Validation failed for collection data.');
- return;
- }
-
- if (!userId) {
- console.error('User ID is undefined.');
+ console.warn('Invalid inputs for creating user collection.');
return;
}
+ // const payload = createPayload(newCollectionInfo, name, description, userId);
const payload = {
- name: collection.name,
- description: collection.description,
- userId: collection.userId || userId,
- totalCost: 0,
- totalPrice: 0,
- quantity: 0,
- totalQuantity: 0,
- xys: xyData || [],
- allCardPrices: [],
- cards: [],
- chartData: {},
+ ...newCollectionInfo,
+ name,
+ description,
+ userId,
};
-
- try {
- const url = createApiUrl(`${userId}/collections/newCollection/${userId}`);
- const response = await fetchWrapper(url, 'POST', payload);
-
- if (!response) {
- console.error('Failed to connect to the server.');
- return;
- }
-
- if (response.error) {
- console.error(
- `Failed to create a new collection: ${response.error.message}`
- );
- return;
- }
-
- const savedCollection = handleApiResponse(response, 'POST');
- updateCollectionData(savedCollection, 'allCollections');
- updateCollectionData(savedCollection, 'collectionData');
- } catch (error) {
- console.error(`Failed to create a new collection: ${error.message}`);
- }
+ const url = createApiUrl(`${userId}/collections`);
+ console.log('Creating user collection with data:', {
+ userId,
+ newCollectionInfo,
+ name,
+ description,
+ });
+ console.log('Payload for user collection:', payload);
+
+ const response = await fetchWrapper(url, 'POST', payload);
+ console.log('6. Saved collection:', response);
+ console.log('6. Saved collection:', response.data);
+ console.log('6. Saved collection:', response.message);
+ updateCollectionData(response.data, 'allCollections');
+ updateCollectionData(response.data, 'collectionData');
+ updateCollectionData(response.data, 'selectedCollection');
};
const removeCollection = async (collection) => {
@@ -388,370 +189,567 @@ export const CollectionProvider = ({ children }) => {
return;
}
- try {
- const url = createApiUrl(`${userId}/collections/${collection._id}`);
- const response = await fetchWrapper(url, 'DELETE');
-
- if (response.error) {
- console.error('Failed to delete the collection:', response.error);
- return;
- }
-
- setAllCollections((prev) =>
- prev.filter((item) => item._id !== collection._id)
- );
+ const url = createApiUrl(`${userId}/collections/${collection._id}`);
+ await fetchWrapper(url, 'DELETE');
+ setAllCollections((prev) =>
+ prev.filter((item) => item._id !== collection._id)
+ );
- if (selectedCollection._id === collection._id) {
- setSelectedCollection(initialCollectionState);
- setCollectionData(initialCollectionState);
- }
- } catch (error) {
- console.error(`Failed to delete the collection: ${error.message}`);
+ if (selectedCollection._id === collection._id) {
+ setSelectedCollection(initialCollectionState);
+ setCollectionData(initialCollectionState);
}
};
- const getUpdatedCards = (activeCollection, card, operation) => {
- const cardsToUpdate =
- operation === 'add'
- ? handleCardAddition(activeCollection?.cards, card)
- : handleCardRemoval(activeCollection?.cards, card);
-
- return cardsToUpdate.map((card) => {
- const cardPrice = card.card_prices?.[0]?.tcgplayer_price;
- const computedPrice = cardPrice * card.quantity;
- console.log('COMPUTED PRICE:', computedPrice);
- const allDatasets = [
- ...(card?.chart_datasets || []),
- { x: moment().format('YYYY-MM-DD HH:mm'), y: computedPrice },
- ];
- card.chart_datasets = filterOutDuplicateYValues(allDatasets);
- card.price = cardPrice;
- card.totalPrice = computedPrice;
- return card;
- });
- };
+ const getUpdatedCards = (collection, cardUpdate, operation) => {
+ let cardsToUpdate;
+
+ switch (operation) {
+ case 'add':
+ cardsToUpdate = handleCardAddition(collection?.cards, cardUpdate);
+ break;
+ case 'remove':
+ cardsToUpdate = handleCardRemoval(collection?.cards, cardUpdate);
+ console.log('CARD REMOVAL:', cardUpdate);
+ console.log('CARD REMOVAL:', cardsToUpdate);
+ break;
+ case 'update':
+ if (!collection?.cards) {
+ console.error('No cards found in the collection.');
+ return collection?.cards;
+ }
+ if (!cardUpdate?.id) {
+ console.warn('Card ID is missing.', cardUpdate);
+ // return collection?.cards;
+ }
+ // eslint-disable-next-line no-case-declarations
+ const cards = collection?.cards;
+
+ for (let i = 0; i < cards?.length; i++) {
+ // eslint-disable-next-line no-case-declarations
+ // const cardIndex = selectedCollection?.cards?.findIndex(
+ // (c) => c?.id === cardUpdate?.id
+ // );
+ if (!cards[i]?.id) {
+ console.warn('Card ID is missing.', cards[i]);
+ continue;
+ }
+ cardUpdate = cards[i];
+ const cardIndex = selectedCollection?.cards?.findIndex(
+ (c) => c?.id === cardUpdate?.id
+ );
- const getUniqueFilteredXYValues = (allXYValues) => {
- const uniqueXValues = new Set();
+ if (cardIndex === -1) {
+ console.error(
+ 'Card not found in the collection.',
+ collection?.cards[cardIndex]
+ );
+ return collection?.cards;
+ }
- return allXYValues
- .filter((entry) => entry.y !== 0)
- .filter((entry) => {
- if (!uniqueXValues.has(entry.x)) {
- uniqueXValues.add(entry.x);
- return true;
+ // eslint-disable-next-line no-case-declarations
+ const existingCard = collection?.cards[cardIndex];
+ // eslint-disable-next-line no-case-declarations
+ const updatedPriceHistory = updatePriceHistory(
+ existingCard,
+ cardUpdate
+ );
+ // eslint-disable-next-line no-case-declarations
+ const updatedCard = getUpdatedCard(
+ existingCard,
+ cardUpdate,
+ updatedPriceHistory,
+ collection?._id
+ );
+ cardsToUpdate = replaceCardInArray(
+ collection?.cards,
+ updatedCard,
+ cardIndex
+ );
}
- return false;
- });
+ break;
+ default:
+ console.error('Unsupported operation:', operation);
+ return collection?.cards;
+ }
+
+ return cardsToUpdate;
};
- const getNewChartData = (activeCollection, updatedPrice, newDataSet) => {
- const combinedXYValues = [
- ...(selectedCollection?.chartData?.datasets?.flatMap(
- (dataset) => dataset.data
- ) || []),
- newDataSet.data[0].xy,
+ function replaceCardInArray(cardsArray, newCard, index) {
+ return [
+ ...cardsArray.slice(0, index),
+ newCard,
+ ...cardsArray.slice(index + 1),
];
+ }
- const filteredXYValues = getUniqueFilteredXYValues(combinedXYValues);
+ function getUpdatedCard(card, update, priceHistory, collectionId) {
+ const cardPrice = determineCardPrice(card, update);
+ const newChartDataEntry = createChartDataEntry(totalPrice);
return {
- name: `Chart for Collection: ${activeCollection?.name}`,
- userId: userId,
- updatedPrice: updatedPrice,
- xys: xyData || [],
- datasets: [
- ...(selectedCollection?.chartData?.datasets || []),
- newDataSet,
+ ...card,
+ price: cardPrice,
+ quantity: update.quantity || card.quantity,
+ collectionId: collectionId,
+ totalPrice: cardPrice * (update.quantity || card.quantity),
+ // lastSavedPrice: update.lastSavedPrice,
+ lastSavedPrice: {
+ num: card.price || card.card_prices[0].tcgplayer_price,
+ timestamp: new Date(),
+ },
+ latestPrice: update.latestPrice,
+ tag: 'monitored',
+ chart_datasets: [...(card.chart_datasets || []), newChartDataEntry],
+ priceHistory: priceHistory,
+ };
+ }
+
+ function determineCardPrice(card, update) {
+ if (update?.latestPrice?.num) return update.latestPrice.num;
+ if (card.price) return card.price;
+ return card.card_prices[0].tcgplayer_price;
+ }
+
+ function updatePriceHistory(card, update) {
+ const newPriceHistoryEntry = createPriceHistoryObject(
+ update?.latestPrice?.num
+ );
+ const lastPriceHistoryEntry =
+ card?.priceHistory[card?.priceHistory?.length - 1];
+
+ if (
+ !lastPriceHistoryEntry ||
+ lastPriceHistoryEntry?.num !== newPriceHistoryEntry?.num
+ ) {
+ return [...card.priceHistory, newPriceHistoryEntry];
+ }
+ return card?.priceHistory;
+ }
+
+ function createChartDataEntry(price) {
+ return {
+ x: moment().format('YYYY-MM-DD HH:mm'),
+ y: price,
+ };
+ }
+
+ function createPriceHistoryObject(price) {
+ return {
+ num: price,
+ timestamp: new Date(),
+ };
+ }
+
+ // Helper function to get updated collection data
+ const getUpdatedCollectionData = (
+ collectionWithCards,
+ updatedTotalPrice,
+ newCollectionPriceHistoryObject,
+ updatedChartData,
+ updatedTotalQuantity,
+ updatedCards
+ ) => {
+ // Check for null or undefined collectionWithCards
+ if (!collectionWithCards) {
+ console.error('No collection data provided');
+ return null; // or an appropriate default object
+ }
+
+ const {
+ allCardPrices = [],
+ description = '',
+ name = '',
+ // _id = '',
+ collectionPriceHistory = [],
+ cards = [],
+ } = collectionWithCards;
+
+ return {
+ allCardPrices,
+ description,
+ name,
+ userId: userId, // Make sure 'userId' is defined in the scope
+ totalPrice: updatedTotalPrice || 0,
+ totalCost: updatedTotalPrice ? updatedTotalPrice.toString() : '0',
+ totalQuantity: cards.reduce((acc, card) => acc + (card.quantity || 0), 0),
+ quantity: cards.length,
+ lastSavedPrice: {
+ num: collectionWithCards?.totalPrice || 0,
+ timestamp: collectionWithCards?.lastSavedPrice?.timeStamp || new Date(),
+ },
+ latestPrice: {
+ num: updatedTotalPrice || 0,
+ timestamp: new Date(),
+ },
+ dailyPriceChange:
+ getPriceChange(currentChartDataSets2)[0]?.priceChange || '',
+ currentChartDataSets2: filterUniqueDataPoints(
+ transformPriceHistoryToXY(collectionPriceHistory)
+ ),
+ collectionPriceHistory: [
+ ...collectionPriceHistory,
+ newCollectionPriceHistoryObject,
],
- allXYValues: filteredXYValues,
};
};
- const addOrRemoveCard = useCallback(
- async (card, cardInfo, operation) => {
- const collectionId = selectedCollection?._id || allCollections[0]?._id;
- if (!collectionId) {
- console.error('No valid collection selected.');
- setOpenChooseCollectionDialog(true);
- return;
- }
+ const getFilteredChartData = (chartData, updatedTotalPrice) => {
+ const filteredChartData = {
+ ...chartData,
+ allXYValues: filterUniqueDataPoints(chartData?.allXYValues),
+ datasets: chartData?.datasets.map((dataset) => ({
+ ...dataset,
+ data: dataset?.data.map((dataEntry) => ({
+ ...dataEntry,
+ xys: filterUniqueDataPoints(dataEntry?.xys),
+ })),
+ })),
+ };
+ return {
+ ...filteredChartData,
+ allXYValues: [
+ ...filteredChartData.allXYValues,
+ {
+ label: `Update - ${new Date().toISOString()}`,
+ x: new Date().toISOString(),
+ y: updatedTotalPrice,
+ },
+ ],
+ };
+ };
- // const updatedCards = getUpdatedCards(selectedCollection, card, operation);
- let updatedCards;
- if (operation === 'update') {
- updatedCards = [...selectedCollection.cards];
- const cardIndex = updatedCards.findIndex((c) => c.id === card.id);
- if (cardIndex !== -1) {
- updatedCards[cardIndex] = {
- ...updatedCards[cardIndex],
- ...card, // update card's details from the given card.
- quantity: updatedCards[cardIndex].quantity, // ensure quantity doesn't change.
- };
- }
- } else {
- updatedCards = getUpdatedCards(selectedCollection, card, operation);
- }
- const allCardPrices = updatedCards.flatMap((card) =>
- Array(card.quantity).fill(card.card_prices?.[0]?.tcgplayer_price)
- );
- const initialPrice = selectedCollection?.totalPrice;
- const updatedPrice = calculateTotalFromAllCardPrices(allCardPrices);
- const priceDifference =
- updatedPrice - (selectedCollection.chartData?.updatedPrice || 0);
- const newDataSet = {
- data: [
- {
- xy: [
- {
- label: `Update Number ${
- selectedCollection?.chartData?.datasets?.length + 1 || 1
- }`,
- x: moment().format('YYYY-MM-DD HH:mm'),
- y: updatedPrice,
- },
- ],
- additionalPriceData: {
- priceChanged: priceDifference !== 0,
- initialPrice: initialPrice,
- updatedPrice: updatedPrice,
- priceDifference: priceDifference,
- priceChange:
- Math.round((priceDifference / (initialPrice || 1)) * 100) / 100,
- },
- },
- ],
- };
- console.log('XYDATASET-----1:', xyData);
- const updateInfo = {
- ...cardInfo,
- name: selectedCollection?.name,
- description: selectedCollection?.description,
- cards: updatedCards,
- userId: userId,
- totalCost: updatedPrice,
- totalPrice: updatedPrice,
- xys: xyData,
- quantity: updatedCards.length,
- totalQuantity: updatedCards.reduce(
- (acc, card) => acc + card.quantity,
- 0
- ),
- chartData: getNewChartData(
- selectedCollection,
- updatedPrice,
- newDataSet
- ),
- allCardPrices: allCardPrices,
- _id: collectionId,
- };
+ const getUpdatedCollection = async (
+ collectionWithCards, // updated cards
+ cardUpdate, // updated card
+ operation,
+ userId
+ ) => {
+ const collectionId = collectionWithCards?._id || collectionData?._id;
+ if (!collectionId) {
+ console.error('Collection ID is missing.', collectionId);
+ return;
+ }
+
+ if (!userId) {
+ console.error('User ID is missing.', userId);
+ return;
+ }
+ const cardExists = collectionWithCards?.cards?.some(
+ (card) => card?.id === cardUpdate?.id
+ );
- const updatedCollection = { ...selectedCollection, ...updateInfo };
- console.log(
- 'UPDATED COLLECTION DATA PRIOR TO SERVER UPDATE:',
- updatedCollection
+ let multipleOfSameCard = [];
+ let cardQuantity = 0;
+ if (cardExists) {
+ multipleOfSameCard = collectionWithCards?.cards?.filter(
+ (card) => card?.id === cardUpdate?.id
);
- await updateActiveCollection(updatedCollection);
- updateCollectionData(updatedCollection, 'selectedCollection');
- updateCollectionData(updatedCollection, 'allCollections');
- },
- [
- selectedCollection,
- allCollections,
- userId,
- openChooseCollectionDialog,
- handleCardAddition,
- handleCardRemoval,
- updateCollectionData,
- setOpenChooseCollectionDialog,
- ]
- );
+ cardQuantity = multipleOfSameCard[0]?.quantity;
+ }
+ console.log('MULTIPLE OF SAME CARD:', multipleOfSameCard);
+ console.log('CARD QUANTITY:', cardQuantity);
+ console.log('CARD EXISTS:', cardExists);
+
+ let method;
+ if (operation === 'remove' && cardQuantity === 1) {
+ method = 'DELETE';
+ } else if (cardExists) {
+ method = 'PUT';
+ } else {
+ method = 'POST';
+ }
- const updateActiveCollection = useCallback(
- async (collectionData) => {
- const isCreatingNew = !collectionData?._id;
- const endpoint = isCreatingNew
- ? createApiUrl(`${userId}/collections`)
- : createApiUrl(`${userId}/collections/${collectionData._id}`);
- const method = isCreatingNew ? 'POST' : 'PUT';
+ let endpointSuffix;
+ if (operation === 'remove' && cardQuantity === 1) {
+ endpointSuffix = 'removeCards';
+ } else {
+ endpointSuffix = 'updateCards';
+ }
- try {
- const response = await fetchWrapper(endpoint, method, collectionData);
- const updatedCollection = handleApiResponse(response, method);
+ const endpoint = createApiUrl(
+ `${userId}/collections/${collectionId}/${endpointSuffix}`
+ );
+ console.log('CARDS BEFORE: ', collectionWithCards);
+ const updatedCards = getUpdatedCards(
+ collectionWithCards,
+ cardUpdate,
+ operation
+ // collectionId
+ );
+ console.log('CARDS AFTER: ', updatedCards);
- if (!isCreatingNew && !updatedCollection) {
- throw new Error('Failed to update the existing collection');
- }
- const newChartData = {
- ...updatedCollection.chartData,
- xys: [
- {
- label: `Update Number ${
- updatedCollection?.chartData?.datasets?.length + 1 || 1
- }`,
- data: [
- {
- x: moment().format('YYYY-MM-DD HH:mm'),
- y: updatedCollection.totalPrice,
- },
- ],
- },
- ],
- datasets: [
- ...(updatedCollection.chartData?.datasets || []),
- {
- data: [
- {
- xys: [
- {
- label: `Update Number ${
- updatedCollection?.chartData?.datasets?.length + 1 || 1
- }`,
- data: [
- {
- x: moment().format('YYYY-MM-DD HH:mm'),
- y: updatedCollection.totalPrice,
- },
- ],
- },
- ],
- additionalPriceData: {
- priceChanged: false,
- initialPrice:
- updatedCollection.chartData?.updatedPrice || 0,
- updatedPrice: updatedCollection.totalPrice,
- priceDifference: 0,
- priceChange: 0,
- },
- },
- ],
- },
- ],
+ const updatedTotalPrice = calculateCollectionValue(updatedCards);
+ setTotalPrice(updatedTotalPrice);
+
+ // const updatedTotalPrice = updatedCards.reduce(
+ // (total, card) => total + card.price * card.quantity,
+ // 0
+ // );
+ const updatedTotalQuantity = updatedCards?.reduce(
+ (total, card) => total + card?.quantity,
+ 0
+ );
+ const newCollectionPriceHistoryObject =
+ createPriceHistoryObject(updatedTotalPrice);
+
+ let cardsResponse;
+ let cardsPayload;
+ if (operation === 'remove') {
+ const allCardsWithIds = [];
+ for (const card of updatedCards) {
+ // const cardIds = updatedCards.map((card) => card.id);
+ // const cardObjIds = updatedCards.map((card) => card._id);
+ const cardIds = {
+ id: card?.id,
+ _id: card?._id,
};
- updatedCollection.chartData = newChartData;
-
- const convertedData = convertData(newChartData);
- updatedCollection.xys = convertedData;
- // setXyData(convertedData.finalDataForChart);
- xyData.push(convertedData.finalDataForChart);
- updateCollectionData(updatedCollection, 'selectedCollection');
- updateCollectionData(updatedCollection, 'allCollections');
- } catch (error) {
- console.error(`Failed to update the collection: ${error.message}`);
+
+ allCardsWithIds?.push(cardIds);
}
- },
- [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
- // }
+ const removeCard = allCardsWithIds?.find(
+ (idPair) => idPair?.id === cardUpdate?.id
+ );
+ cardsPayload = { cardIds: removeCard };
+ } else {
+ const allCardsWithIds = [];
+ for (const card of updatedCards) {
+ // const cardIds = updatedCards.map((card) => card.id);
+ // const cardObjIds = updatedCards.map((card) => card._id);
+ const cardIds = {
+ id: card.id,
+ _id: card._id,
+ };
- useEffect(() => {
- const updatedPricesArray = Object.keys(
- updatedPricesFromCombinedContext || {}
- ).map((cardId) => updatedPricesFromCombinedContext[cardId]);
+ allCardsWithIds?.push(cardIds);
+ }
+ const removeCard = allCardsWithIds?.find(
+ (idPair) => idPair?.id === cardUpdate?.id
+ );
+ cardsPayload = { cards: updatedCards, cardIds: removeCard };
+ }
+ console.log('CARDS PAYLOAD:', cardsPayload);
- console.log(
- '[1][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][UPDATED PRICES]==========>',
- updatedPricesArray
+ cardsResponse = await fetchWrapper(endpoint, method, cardsPayload);
+ const { cardMessage } = cardsResponse;
+
+ const updatedChartData = getFilteredChartData(
+ collectionWithCards.chartData,
+ updatedTotalPrice
+ );
+ const chartDataPayload = { chartData: updatedChartData };
+ const chartDataEndpoint = createApiUrl(
+ `${userId}/collections/${collectionId}/updateChartData`
+ );
+ const chartDataResponse = await fetchWrapper(
+ chartDataEndpoint,
+ 'PUT',
+ chartDataPayload
);
- const updatedCardPrices = [];
+ const { chartMessage } = chartDataResponse;
- updatedPricesArray.forEach((card) => {
- const currentCardPrice = selectedCollection?.cards[card?.id]?.price;
+ const updatedCollection = getUpdatedCollectionData(
+ selectedCollection,
+ updatedTotalPrice,
+ newCollectionPriceHistoryObject,
+ // updatedChartData,
+ updatedTotalQuantity
+ // updatedCards
+ );
- // Check if this is the special tagged card
- if (card._tag === 'updated') {
- console.log('Found the special card:', card);
- }
+ const collectionEndpoint = createApiUrl(
+ `${userId}/collections/${collectionId}`
+ );
+ const collectionResponse = await fetchWrapper(collectionEndpoint, 'PUT', {
+ updatedCollection,
+ });
+
+ const { collectionMessage } = collectionResponse;
+
+ setTotalPrice(calculateCollectionValue(updatedCards));
+ const restructuredCollection = {
+ ...collectionResponse.collectionData,
+ cards: cardsResponse.cards,
+ chartData: chartDataResponse.chartData,
+ };
+
+ console.log('RESTRUCTURED COLLECTION:', restructuredCollection);
+ return {
+ restructuredCollection,
+ };
+ };
+
+ const updateCollectionDetails = async (updatedInfo, userId, collectionId) => {
+ const { name, description } = updatedInfo;
+ if (selectedCollection && collectionId) {
+ const updatedCollection = {
+ ...selectedCollection,
+ name,
+ description,
+ };
+
+ setSelectedCollection(updatedCollection);
+ setAllCollections((prevCollections) =>
+ prevCollections.map((collection) =>
+ collection._id === collectionId ? updatedCollection : collection
+ )
+ );
- if (card?.updatedPrice !== currentCardPrice) {
- updatedCardPrices.push(card);
- console.log(
- '[2][PRICE UPDATE: COMBINED CONTEXT IN COLLECTION][CARD]==========>',
- card
+ try {
+ // Update the collection in the backend
+ const collectionEndpoint = createApiUrl(
+ `${userId}/collections/${collectionId}`
);
- console.log(
- 'CARD FROM SELECTED COLLECTIONS:',
- selectedCollection.cards[card.id]
+ const collectionResponse = await fetchWrapper(
+ collectionEndpoint,
+ 'PUT',
+ {
+ updatedCollection,
+ }
);
- } else {
- console.log('Price has not been updated for card with ID:', card.id);
+
+ const { collectionMessage } = collectionResponse;
+ console.log(collectionMessage);
+
+ // Optionally handle the response here, e.g., update the state with the response data if necessary
+ } catch (error) {
+ console.error('Error updating collection details:', error);
}
- });
+ } else {
+ console.error('Selected collection or collection ID is missing.');
+ }
+ };
- if (updatedCardPrices.length > 0) {
- updatedCardPrices.forEach((card) => {
- addOrRemoveCard(card, { updated: true }, 'update');
- });
+ const handleCardOperation = async (
+ card,
+ operation,
+ collection,
+ userId,
+ allCollections
+ ) => {
+ if (!card) {
+ console.error('Card is undefined.', card);
+ return;
}
- }, [updatedPricesFromCombinedContext]);
+
+ if (!collection) {
+ console.error(
+ `Collection with ID ${collection._id} not found in allCollections.`
+ );
+ return;
+ }
+
+ const updatedCollection = await getUpdatedCollection(
+ collection,
+ card,
+ operation,
+ userId
+ );
+
+ if (updatedCollection) {
+ console.log('UPDATED COLLECTION:', updatedCollection);
+ // Update the relevant collections in the state/context
+ updateCollectionData(
+ updatedCollection?.restructuredCollection,
+ 'allCollections'
+ );
+ if (collection._id === selectedCollection?._id) {
+ updateCollectionData(
+ updatedCollection?.restructuredCollection,
+ 'selectedCollection'
+ );
+ }
+ updateCollectionData(
+ updatedCollection?.restructuredCollection,
+ 'collectionData'
+ );
+ } else {
+ console.error('Failed to update collection.');
+ }
+
+ return updatedCollection;
+ };
const contextValue = useMemo(
() => ({
- // DATA
allCollections,
selectedCollection,
collectionData,
totalCost,
-
+ totalPrice,
+ totalQuantity: selectedCollection?.totalQuantity || 0,
allCardPrices: selectedCollection?.allCardPrices || [],
xys: xyData || [],
openChooseCollectionDialog,
- updatedPricesFromCombinedContext,
- setUpdatedPricesFromCombinedContext: (updatedPrices) => {
- // This is the function that will be passed to the combined context to update the prices
- setUpdatedPricesFromCombinedContext(updatedPrices);
- },
+ calculateCollectionValue,
+ setTotalPrice,
+ setXyData,
+ setUpdatedPricesFromCombinedContext,
setOpenChooseCollectionDialog,
- // FUNCTIONS
- calculateTotalPrice: () => getCardPrice(selectedCollection),
+ getTotalPrice2: () => getCardPrice(selectedCollection),
+ getTotalPrice: () => calculateCollectionValue(selectedCollection),
+ getNewTotalPrice: (values) => calculateCollectionValue(values),
getTotalCost: () => getTotalCost(selectedCollection),
- // FUNCTIONS
- createUserCollection: (collection, name, description) =>
- createUserCollection(userId, collection, name, description),
- removeCollection: (collection) => removeCollection(collection),
+ getCardQuantity: (cardId) =>
+ selectedCollection?.cards?.find((c) => c?.id === cardId)?.quantity || 0,
+ createUserCollection,
+ removeCollection,
+ addOneToCollection: (card) =>
+ handleCardOperation(card, 'add', selectedCollection, userId),
+ removeOneFromCollection: (card) =>
+ handleCardOperation(card, 'remove', selectedCollection, userId),
+ updateOneFromCollection: (card) =>
+ handleCardOperation(card, 'update', selectedCollection, userId),
+ updateCollection: (card, operation, collection) =>
+ handleCardOperation(card, operation, collection, userId),
+ getUpdatedCollection,
+ updateCollectionState: updateCollectionData,
+ updateCollectionDetails,
fetchAllCollectionsForUser: fetchAndSetCollections,
- setSelectedCollection: updateActiveCollection,
- setAllCollections: (collections) => setAllCollections(collections),
- addOneToCollection: (card, cardInfo) =>
- addOrRemoveCard(card, cardInfo, 'add'),
- removeOneFromCollection: (card) => addOrRemoveCard(card, null, 'remove'),
+ fetchAllCollections: fetchAndSetCollections,
+ setSelectedCollection,
+ setAllCollections,
}),
- [allCollections, selectedCollection, totalCost]
+ [allCollections, selectedCollection, totalCost, totalPrice, xyData]
);
useEffect(() => {
- console.log('COLLECTION CONTEXT: ', {
- contextValue,
+ console.log('CONTEXT STATE:', {
+ totalCost,
+ totalPrice,
+ totalQuantity: selectedCollection?.totalQuantity || 0,
+ selectedCollection,
+ allCollections,
+ collectionData,
+ updatedPricesFromCombinedContext,
+ xyData,
});
- }, [contextValue, updatedPricesFromCombinedContext]);
- // Assuming updatedPrices is passed as a prop or state
+ }, [allCollections, selectedCollection, totalCost, totalPrice, xyData]);
useEffect(() => {
- if (selectedCollection && totalCost) {
- // Trigger the cron job whenever the selectedCollection changes
- triggerCronJob();
+ if (userId) fetchAndSetCollections();
+ }, [userId]);
+
+ useEffect(() => {
+ if (selectedCollection?.cards) {
+ setTotalPrice(calculateCollectionValue(selectedCollection));
+ setTotalCost(getTotalCost(selectedCollection));
}
- }, [selectedCollection, triggerCronJob, totalCost]);
+ }, [selectedCollection, setTotalPrice]);
useEffect(() => {
- console.log('Total Cost has been updated:', totalCost);
- }, [totalCost]);
+ if (allCollections?.length === 0 || allCollections === undefined) {
+ fetchAndSetCollections();
+ }
+ }, [allCollections, fetchAndSetCollections]);
useEffect(() => {
- if (userId) fetchAndSetCollections();
- }, [fetchAndSetCollections, userId]);
+ if (selectedCollection === null || selectedCollection === undefined) {
+ setSelectedCollection(allCollections?.[0]);
+ }
+ }, [userId]);
return (
@@ -760,10 +758,6 @@ export const CollectionProvider = ({ children }) => {
);
};
-// useCollectionStore.js
-// import { useContext } from 'react';
-// import { CollectionContext } from '../CollectionContext/CollectionContext';
-
export const useCollectionStore = () => {
const context = useContext(CollectionContext);
if (!context) {
diff --git a/src/context/CollectionContext/cardHelpers.jsx b/src/context/CollectionContext/cardHelpers.jsx
new file mode 100644
index 0000000..c1ba28b
--- /dev/null
+++ b/src/context/CollectionContext/cardHelpers.jsx
@@ -0,0 +1,80 @@
+import moment from 'moment';
+import { useCollectionStore } from './CollectionContext';
+
+export const getCollectionId = (selectedCollection, allCollections) => {
+ return selectedCollection?._id || allCollections[0]?._id;
+};
+
+export const calculatePriceDifference = (updatedPrice, selectedCollection) => {
+ return updatedPrice - (selectedCollection.chartData?.updatedPrice || 0);
+};
+
+export const createNewDataSet = (updatedPrice, selectedCollection) => {
+ return {
+ data: [
+ {
+ xys: [
+ {
+ label: `Update Number ${
+ selectedCollection?.chartData?.datasets?.length + 1 || 1
+ }`,
+ data: {
+ x: moment().format('YYYY-MM-DD HH:mm'),
+ y: updatedPrice,
+ },
+ },
+ ],
+ additionalPriceData: {
+ priceChanged:
+ calculatePriceDifference(updatedPrice, selectedCollection) !== 0,
+ initialPrice: selectedCollection?.totalPrice,
+ updatedPrice: updatedPrice,
+ priceDifference: calculatePriceDifference(
+ updatedPrice,
+ selectedCollection
+ ),
+ priceChange:
+ Math.round(
+ (calculatePriceDifference(updatedPrice, selectedCollection) /
+ (selectedCollection?.totalPrice || 1)) *
+ 100
+ ) / 100,
+ },
+ },
+ ],
+ };
+};
+
+// export const createUpdateInfo = (
+// updatedCards,
+// updatedPrice,
+// cardInfo,
+// userId,
+// selectedCollection,
+// collectionId,
+// newDataSet,
+// xyData
+// ) => {
+// // const { updateCollectionChartData } = useCollectionStore();
+// return {
+// ...cardInfo,
+// name: selectedCollection?.name,
+// description: selectedCollection?.description,
+// cards: updatedCards,
+// userId: userId,
+// totalCost: updatedPrice,
+// totalPrice: updatedPrice,
+// xys: xyData,
+// quantity: updatedCards.length,
+// totalQuantity: updatedCards.reduce((acc, card) => acc + card.quantity, 0),
+// chartData: updateCollectionChartData(
+// updatedPrice,
+// selectedCollection,
+// newDataSet
+// ),
+// allCardPrices: updatedCards.flatMap((card) =>
+// Array(card.quantity).fill(card.card_prices?.[0]?.tcgplayer_price)
+// ),
+// _id: collectionId,
+// };
+// };
diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx
new file mode 100644
index 0000000..75ff649
--- /dev/null
+++ b/src/context/CollectionContext/collectionUtility.jsx
@@ -0,0 +1,937 @@
+/* eslint-disable @typescript-eslint/no-empty-function */
+const BASE_API_URL = `${process.env.REACT_APP_SERVER}/api/users`;
+const initialCollectionState = {
+ userId: '', // Assuming this is an ObjectId string
+ name: '', // Initialize as empty string if not provided
+ description: '', // Initialize as empty string if not provided
+ totalCost: '', // Initialize as empty string if not provided
+ totalPrice: 0, // Initialize as 0 if not provided
+ quantity: 0, // Initialize as 0 if not provided
+ totalQuantity: 0, // Initialize as 0 if not provided
+ previousDayTotalPrice: 0, // Initialize as 0 if not provided
+ lastSavedPrice: {
+ num: 0,
+ timestamp: '',
+ }, // Initialize as 0 if not provided
+ latestPrice: {
+ num: 0,
+ timestamp: '',
+ }, // Initialize as 0 if not provided
+ dailyPriceChange: 0, // Initialize as 0 if not provided
+ priceDifference: 0, // Initialize as 0 if not provided
+ priceChange: 0, // Initialize as 0 if not provided
+ allCardPrices: [], // Initialize as empty array if not provided
+ cards: [], // Initialize as empty array if not provided
+ currentChartDataSets2: [], // Initialize as empty array if not provided
+ xys: [], // Use defaultXyData or initialize as empty if not provided
+ chartData: {
+ name: '', // Initialize as empty string if not provided
+ userId: '', // Assuming this is an ObjectId string
+ datasets: [], // Initialize as empty array if not provided
+ xys: [], // Initialize as empty array if not provided
+ allXYValues: [], // Initialize as empty array if not provided
+ },
+};
+/**
+ * Filters out duplicate Y values from an array of datasets.
+ * @param {Array} datasets - An array of dataset objects.
+ * @returns {Array} Filtered datasets.
+ */
+const filterOutDuplicateYValues = (datasets) => {
+ const seenYValues = new Set();
+ return datasets?.filter((data) => {
+ const yValue = data?.y;
+ if (seenYValues.has(yValue)) {
+ return false;
+ }
+ seenYValues.add(yValue);
+ return true;
+ });
+};
+
+/**
+ * Transforms the given chartData into an array of x-y points.
+ * @param {Object} chartData - Chart data object.
+ * @returns {Array} Transformed data.
+ */
+const transformChartData = (chartData) => {
+ const pointsArray = [];
+
+ if (Array.isArray(chartData?.datasets)) {
+ chartData?.datasets?.forEach((dataset) => {
+ dataset.data?.forEach((dataEntry) => {
+ dataEntry.xys?.forEach((xyEntry) => {
+ const { x, y } = xyEntry.data;
+ if (x && y !== undefined) {
+ pointsArray.push({ x, y });
+ }
+ });
+ });
+ });
+ }
+
+ return pointsArray;
+};
+
+/**
+ * Converts the given original data into a Nivo-compatible format.
+ * @param {Object} originalData - Original data object.
+ * @returns {Object} Converted data.
+ */
+const convertData = (originalData) => {
+ const finalDataForChart = [];
+ const { datasets } = originalData;
+
+ if (Array.isArray(datasets) && datasets.length > 0) {
+ datasets.forEach((dataset, index) => {
+ if (Array.isArray(dataset.data) && dataset.data.length > 0) {
+ dataset.data.forEach((dataEntry) => {
+ dataEntry.xys?.forEach((xyEntry) => {
+ const { label, data } = xyEntry;
+ finalDataForChart[label] = finalDataForChart[label] || [];
+
+ data.forEach(({ x, y }) => {
+ if (x && y !== undefined) {
+ finalDataForChart[label].push({ x, y });
+ }
+ });
+ });
+ });
+ }
+ });
+ }
+
+ const nivoData = Object.keys(finalDataForChart).map((label) => ({
+ id: label,
+ data: finalDataForChart[label],
+ }));
+
+ return {
+ ...originalData,
+ finalDataForChart: nivoData,
+ };
+};
+
+/**
+ * Checks if the given object is empty.
+ * @param {Object} obj - Object to check.
+ * @returns {Boolean} True if the object is empty, false otherwise.
+ */
+const isEmpty = (obj) => {
+ return (
+ [Object, Array].includes((obj || {}).constructor) &&
+ !Object.entries(obj || {}).length
+ );
+};
+
+/**
+ * Validates the given data based on its type and emptiness.
+ * @param {any} data - Data to validate.
+ * @param {String} eventName - Event that triggered the function.
+ * @param {String} functionName - Name of the function.
+ * @returns {Boolean} True if the data is valid, false otherwise.
+ */
+const validateData = (data, eventName, functionName) => {
+ if (typeof data === 'object') {
+ if (isEmpty(data)) {
+ console.error(
+ `The data passed to ${functionName} from ${eventName} is empty.`
+ );
+ return false;
+ }
+ return true;
+ }
+ console.error(
+ `The data passed to ${functionName} from ${eventName} is not an object.`
+ );
+ return false;
+};
+
+/**
+ * Handles the addition of a new card to the collection.
+ * @param {Array} currentCards - Current array of cards.
+ * @param {Object} cardToAdd - Card object to add.
+ * @returns {Array} Updated array of cards.
+ */
+const handleCardAddition = (currentCards, cardToAdd) => {
+ // Initialize currentCards to an empty array if it's not defined
+ currentCards = currentCards || [];
+
+ console.log('CURRENT CARDS:', currentCards);
+ console.log('CARD TO ADD:', cardToAdd);
+ const cardToAddId =
+ typeof cardToAdd.id === 'number' ? String(cardToAdd.id) : cardToAdd.id;
+ const matchingCard = currentCards.find((c) => c.id === cardToAddId);
+
+ if (matchingCard) {
+ matchingCard.quantity++;
+ return [...currentCards];
+ }
+
+ return [...currentCards, { ...cardToAdd, id: cardToAddId, quantity: 1 }];
+};
+
+/**
+ * Handles the removal of a card from the collection.
+ * @param {Array} currentCards - Current array of cards.
+ * @param {Object} cardToRemove - Card object to remove.
+ * @returns {Array} Updated array of cards.
+ */
+const handleCardRemoval = (currentCards, cardToRemove) => {
+ // Initialize currentCards to an empty array if it's not defined
+ currentCards = currentCards || [];
+
+ console.log('CURRENT CARDS:', currentCards);
+ console.log('CARD TO REMOVE:', cardToRemove);
+
+ const cardToRemoveId =
+ typeof cardToRemove.id === 'number'
+ ? String(cardToRemove.id)
+ : cardToRemove.id;
+
+ // Find the card to remove in the current cards array
+ const cardIndex = currentCards.findIndex((c) => c.id === cardToRemoveId);
+
+ if (cardIndex === -1) {
+ console.error('Card not found in the collection.');
+ return [...currentCards];
+ }
+
+ const matchingCard = currentCards[cardIndex];
+
+ // If the card has a quantity greater than 1, decrement it
+ if (matchingCard.quantity > 1) {
+ matchingCard.quantity--;
+ return [...currentCards];
+ } else {
+ // Remove the card from the collection if quantity is 1 or less
+ return currentCards.filter((card) => card.id !== cardToRemoveId);
+ }
+};
+
+/**
+ * Filters unique XY values with Y not equal to 0.
+ * @param {Array} allXYValues - Array of XY value objects.
+ * @returns {Array} Filtered array of XY value objects.
+ */
+const getUniqueFilteredXYValues = (allXYValues) => {
+ // Check if the input is an array and is not null/undefined
+ if (!Array.isArray(allXYValues)) {
+ // You can throw an error, return an empty array, or handle it as needed
+ console.error('Invalid input: allXYValues should be an array');
+ return [];
+ }
+
+ const uniqueXValues = new Set();
+ return allXYValues
+ .filter((entry) => {
+ // Check if entry is an object and has property y with a number value
+ return (
+ entry &&
+ typeof entry === 'object' &&
+ typeof entry.y === 'number' &&
+ entry.y !== 0
+ );
+ })
+ .filter((entry) => {
+ // Check if entry has property x with a valid value (not null/undefined)
+ const hasValidX =
+ entry && 'x' in entry && entry.x !== null && entry.x !== undefined;
+ if (hasValidX && !uniqueXValues.has(entry.x)) {
+ uniqueXValues.add(entry.x);
+ return true;
+ }
+ return false;
+ });
+};
+
+// Example usage:
+// console.log(getUniqueFilteredXYValues(null)); // Should return []
+// console.log(getUniqueFilteredXYValues(undefined)); // Should return []
+// console.log(getUniqueFilteredXYValues([{ x: 1, y: 0 }, { x: 2, y: 3 }])); // Should return [{ x: 2, y: 3 }]
+
+/**
+ * Calculates the total price from an array of card prices.
+ * @param {Array} allCardPrices - Array of card prices.
+ * @returns {Number} Total price.
+ */
+const calculateTotalFromAllCardPrices = (allCardPrices) => {
+ if (!Array.isArray(allCardPrices)) return 0;
+ return allCardPrices.reduce(
+ (total, price) => total + ensureNumber(price, 0),
+ 0
+ );
+};
+
+/**
+ * Ensures a value is a number, providing a default if not.
+ * @param {any} value - Value to check.
+ * @param {Number} defaultValue - Default value if check fails.
+ * @returns {Number} Ensured number.
+ */
+const ensureNumber = (value, defaultValue = 0) => {
+ const num = parseFloat(value);
+ return isNaN(num) ? defaultValue : num;
+};
+
+/**
+ * Finds the index of a collection by its ID.
+ * @param {Array} collections - Array of collections.
+ * @param {String} id - ID of the collection to find.
+ * @returns {Number} Index of the collection, or -1 if not found.
+ */
+const findCollectionIndex = (collections, id) =>
+ collections?.findIndex((collection) => collection?._id === id) ?? -1;
+
+/**
+ * Creates a full API URL from a given path.
+ * @param {String} path - API path.
+ * @returns {String} Full API URL.
+ */
+const createApiUrl = (path) => `${BASE_API_URL}/${path}`;
+
+// To prevent making the same type of request within 10 seconds
+const lastRequestTime = {
+ POST: 0,
+ PUT: 0,
+ DELETE: 0,
+ GET: 0,
+ // Add other methods if needed
+};
+
+/**
+ * Checks whether a new request can be made based on the last request's timestamp.
+ * @param {String} method - The HTTP method for the request.
+ */
+const canMakeRequest = (method) => {
+ const currentTime = Date.now();
+ // The comment mentioned 10 seconds, but the code was checking for 5 seconds. Adjust as necessary.
+ return currentTime - lastRequestTime[method] > 1000; // Now it's 10 seconds
+};
+
+/**
+ * Updates the last request timestamp for a given method.
+ * @param {String} method - The HTTP method for the request.
+ */
+const updateLastRequestTime = (method) => {
+ lastRequestTime[method] = Date.now();
+};
+/**
+ * Wraps fetch API calls and implements a rate limit for each HTTP method type.
+ * @param {String} url - The API URL to make the request to.
+ * @param {String} method - The HTTP method for the request.
+ * @param {Object} [body=null] - The request payload if needed.
+ * @returns {Promise