diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/src/Main.jsx b/src/Main.jsx index c008ed2..9613800 100644 --- a/src/Main.jsx +++ b/src/Main.jsx @@ -23,15 +23,15 @@ import { LoginPage, } from './pages'; import { - useUserContext, useCollectionStore, useDeckStore, useAuthContext, usePageContext, - useMode, } from './context'; import { AppContainer } from './pages/pageStyles/StyledComponents'; import { useCookies } from 'react-cookie'; +import useDialog from './context/hooks/useDialog'; +import useSnackBar from './context/hooks/useSnackBar'; const Main = () => { const [cookies] = useCookies(['authUser']); @@ -40,57 +40,60 @@ const Main = () => { const { loadingStatus, returnDisplay, setLoading } = usePageContext(); const { fetchAllCollectionsForUser } = useCollectionStore(); const { fetchAllDecksForUser } = useDeckStore(); - // const navigate = useNavigate(); // Use the useNavigate hook + const handleSnackBar = useSnackBar()[1]; - const [showLoginDialog, setShowLoginDialog] = useState(!isLoggedIn); + const { isLoginDialogOpen, openLoginDialog, closeLoginDialog } = + useDialog(handleSnackBar); // Assuming false represents the logged-in state - useEffect(() => { - if (!isLoggedIn) return; - if (!authUser) return; - - const fetchData = async () => { - if (isLoggedIn && authUser) { - setLoading('isPageLoading', true); - try { - await Promise.all([ - fetchAllCollectionsForUser(), - fetchAllDecksForUser(), - ]); - } catch (error) { - console.error('Error fetching data:', error); - } finally { - setLoading('isPageLoading', false); - } - } - }; - fetchData(); - }, [isLoggedIn, authUser, fetchAllCollectionsForUser, fetchAllDecksForUser]); + const routeConfig = [ + { path: '/', component: HomePage, isPrivate: false }, + { path: '/home', component: HomePage, isPrivate: false }, + { path: '/store', component: StorePage, isPrivate: false }, + { path: '/cart', component: CartPage, isPrivate: true }, + { path: '/userprofile', component: ProfilePage, isPrivate: true }, + { path: '/collection', component: CollectionPage, isPrivate: true }, + { path: '/deckbuilder', component: DeckBuilderPage, isPrivate: true }, + { path: '/profile', component: ProfilePage, isPrivate: false }, + { path: '/login', component: LoginPage, isPrivate: false }, + { path: '*', component: NotFoundPage, isPrivate: false }, + ]; + // const [showLoginDialog, setShowLoginDialog] = useState(!isLoggedIn); + const fetchData = async () => { + setLoading('isPageLoading', true); + try { + await Promise.all([fetchAllCollectionsForUser(), fetchAllDecksForUser()]); + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading('isPageLoading', false); + } + }; + const renderHelmet = () => { + console.log('Rendering helmet...'); + return ( + + {/* Basic */} + Your Website Title + + + - // Manage logout timer reset - useEffect(() => { - if (isLoggedIn) resetLogoutTimer(); - }, [isLoggedIn, resetLogoutTimer]); + {/* SEO */} + + {/* Social Media */} + + + + + - // Manage login dialog visibility - useEffect(() => { - if (isLoggedIn) { - setShowLoginDialog(false); - } - if (!isLoggedIn) { - setShowLoginDialog(true); - } - setShowLoginDialog(window.location.pathname === '/login' || !isLoggedIn); - }, [isLoggedIn]); - // useEffect(() => { - // // Redirects user to the homepage if they are already logged in - // if (isLoggedIn) { - // navigate('/'); - // } - // }, [isLoggedIn, navigate]); // Add navigate to the dependency array + {/* Responsive and mobile */} + - return ( - <> - + {/* Additional links and styles */} { href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap" rel="stylesheet" /> + + {/* Specify language and character set */} + + + + {/* Scripts */} + {/* Example: Add a script needed for a service or functionality */} + {/* */} - {/* {loadingStatus?.isPageLoading && returnDisplay()} */} - {!authUser ? ( - setShowLoginDialog(false)} - onLogin={() => setShowLoginDialog(false)} // This might need to be updated based on the login logic + ); + }; + const renderRoutes = () => ( + + {routeConfig.map(({ path, component: Component, isPrivate }, index) => ( + + + + ) : ( + + ) + } /> + ))} + + ); + const renderLoginDialog = () => { + console.log('Auth user not found, rendering login dialog...', authUser); + return ; + }; + // useEffect(() => { + // if (isLoggedIn) { + // resetLogoutTimer(); + // fetchData(); // Fetch data when the user is logged in + // } + // }, [isLoggedIn, resetLogoutTimer, fetchData]); // Remove authUser from dependencies + + // useEffect(() => { + // // Manage login dialog based on isLoggedIn status + // if (isLoggedIn && isLoginDialogOpen) { + // closeLoginDialog(); + // } else if (!isLoggedIn && !isLoginDialogOpen) { + // openLoginDialog(); + // } + // }, [isLoggedIn, isLoginDialogOpen, openLoginDialog, closeLoginDialog]); + + return ( + <> + {renderHelmet()} + {!authUser ? ( + renderLoginDialog() ) : ( + {/* {authUser} */} +
- - } /> - } /> - } /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> - } /> - {/* } /> */} - {/* } /> */} - } />{' '} - + {/* {returnDisplay()} */} + {renderRoutes()} + {/* + {renderRoutes(routeConfig)} {/* Use the renderRoutes function */} + {/* */} + {/* {snackbar} */} )} @@ -168,3 +184,46 @@ export default Main; // if (logoutTimerRef.current) clearTimeout(logoutTimerRef.current); // }; // }, [userId, resetLogoutTimer]); +// // Manage loading display +// useEffect(() => { +// if (!isLoggedIn) return; +// if (!authUser) return; + +// const fetchData = async () => { +// if (isLoggedIn && authUser) { +// setLoading('isPageLoading', true); +// try { +// await Promise.all([ +// fetchAllCollectionsForUser(), +// fetchAllDecksForUser(), +// ]); +// } catch (error) { +// console.error('Error fetching data:', error); +// } finally { +// setLoading('isPageLoading', false); +// } +// } +// }; +// fetchData(); +// }, [isLoggedIn, authUser, fetchAllCollectionsForUser, fetchAllDecksForUser]); +// // Manage logout timer reset +// useEffect(() => { +// if (isLoggedIn) resetLogoutTimer(); +// }, [isLoggedIn, resetLogoutTimer]); +// // Manage login dialog visibility +// useEffect(() => { +// if (isLoggedIn && isLoginDialogOpen) { +// console.log('Closing login dialog...'); +// closeLoginDialog(); +// } +// if (!isLoggedIn && !isLoginDialogOpen) { +// console.log('Opening login dialog...'); +// openLoginDialog(); +// } +// // if (!isLoggedIn) { +// // } +// // setShowLoginDialog( +// // // window.location.pathname === '/login' || +// // !isLoggedIn +// // ); +// }, [isLoggedIn]); diff --git a/src/assets/animations/LoadingCardAnimation.jsx b/src/assets/animations/LoadingCardAnimation.jsx index 12a4f02..3fd0647 100644 --- a/src/assets/animations/LoadingCardAnimation.jsx +++ b/src/assets/animations/LoadingCardAnimation.jsx @@ -1,228 +1,93 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import * as THREE from 'three'; -import { useCardImages } from '../../context/CardImagesContext/CardImagesContext'; -import placeholderImage from '../images/placeholder.jpeg'; // Adjust the path as necessary -function LoadingCardAnimation() { - const { cards, isLoading, error } = useCardImages(); - const [randomCardImage, setRandomCardImage] = useState(); - useEffect(() => { - if (cards && cards.length > 0) { - const randomCard = cards[Math.floor(Math.random() * cards.length)]; - if (randomCard.card_images && randomCard.card_images.length > 0) { - // Constructing the local file name - const name = randomCard.name.replace(/[/\\?%*:|"<>]/g, ''); - console.log('name', name); - const folder = 'images'; - console.log('folder', folder); - const folder2 = 'cards'; - const folder3 = `${ - randomCard?.type === 'Spell Card' || randomCard?.type === 'Trap Card' - ? 'spell_trap' - : 'monster' - }`; - console.log('folder2', folder2); - const extension = 'jpg'; // Assuming all images are jpg - console.log('naextensionme', extension); - const fullName = `${name}_${randomCard.race}_${randomCard.type}${ - randomCard.level ? '_lvl' + randomCard.level : '' - }${ - randomCard.attribute ? '_' + randomCard.attribute : '' - }.${extension}`; - console.log('fullName', fullName); - const filePath = `../${folder}/${folder2}/${folder3}/${fullName}`; - console.log('filePath', filePath); - console.log('placeholderImage', placeholderImage); - setRandomCardImage(filePath); - } - } - console.log('cards', cards); - console.log('randomCardImage', randomCardImage); - }, [cards]); +const sizes = { + extraSmall: { width: 0.3, height: 0.4 }, + small: { width: 0.5, height: 0.7 }, + medium: { width: 1, height: 1.4 }, + large: { width: 2, height: 2.8 }, + extraLarge: { width: 3, height: 4.2 }, +}; + +function LoadingCardAnimation({ selected, size = 'medium' }) { + const mount = useRef(null); + const placeholderImage = '../../assets/images/placeholder.png'; // Replace with your placeholder image path + const placeholderTexture = new THREE.TextureLoader().load(placeholderImage); useEffect(() => { - if (!randomCardImage) return; + if (!selected) return; + + const currentMount = mount.current; + + // Get dimensions from the sizes object + const { width, height } = sizes[size]; + // Scene, Camera, and Renderer setup const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 - ); + // Smaller FOV can make objects appear larger within the same canvas size + const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ alpha: true }); - renderer.setSize(window.innerWidth, window.innerHeight); - - const loadingIconContainer = document.getElementById('loadingIcon'); - if (!loadingIconContainer) return; - - loadingIconContainer.appendChild(renderer.domElement); - - // Load the placeholder texture - const placeholderTexture = new THREE.TextureLoader().load(placeholderImage); - - // Load the card image texture - new THREE.TextureLoader().load( - randomCardImage, - (texture) => { - const cardMaterialFront = new THREE.MeshBasicMaterial({ map: texture }); - const cardMaterialBack = new THREE.MeshBasicMaterial({ - map: placeholderTexture, - }); - - const cardGeometry = new THREE.BoxGeometry(1, 1.4, 0.01); - const card = new THREE.Mesh(cardGeometry, [ - cardMaterialBack, // Left side - cardMaterialBack, // Right side - cardMaterialBack, // Top side - cardMaterialBack, // Bottom side - cardMaterialFront, // Front side - cardMaterialBack, // Back side - ]); - scene.add(card); - camera.position.z = 5; - - const animate = () => { - requestAnimationFrame(animate); - card.rotation.y += 0.05; - renderer.render(scene, camera); - }; - - animate(); - }, - undefined, // onProgress callback not supported - (err) => { - console.error('An error occurred while loading the texture:', err); - } - ); + // Keep the canvas size the same but make the content appear larger + renderer.setSize(width * 100, height * 100); // Keeping canvas size + + currentMount.appendChild(renderer.domElement); + + // Geometry and Material setup + const cardGeometry = new THREE.BoxGeometry(1, 1.4, 0.01); // Standard size for the geometry + const cardMaterial = new THREE.MeshBasicMaterial({ + map: placeholderTexture, + }); + + const card = new THREE.Mesh(cardGeometry, [ + cardMaterial, + cardMaterial, + cardMaterial, + cardMaterial, + cardMaterial, + cardMaterial, + ]); + + // Scale the card based on size prop + card.scale.set(width, height, 1); // Scaling the card + + scene.add(card); + // Adjust the camera position closer to make the object appear larger + camera.position.z = 2; + + const animate = function () { + requestAnimationFrame(animate); + card.rotation.y += 0.01; // Speed of rotation + renderer.render(scene, camera); + }; + + animate(); + + // Clean up return () => { - if (loadingIconContainer.contains(renderer.domElement)) { - loadingIconContainer.removeChild(renderer.domElement); - } + currentMount.removeChild(renderer.domElement); + scene.remove(card); + cardMaterial.dispose(); + cardGeometry.dispose(); }; - }, [randomCardImage]); + }, [selected, size]); // Rerun effect if selected or size changes - if (isLoading) return
Loading...
; - if (error) return
Error: {error}
; + if (!selected) { + return null; + } return ( -
- Three.js animation should appear here -
+
); } export default LoadingCardAnimation; - -// import * as THREE from 'three'; -// import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -// import placeholderImage from '../images/placeholder.jpeg'; -// import { useCardImages } from '../../context/CardImagesContext/CardImagesContext'; - -// function LoadingCardAnimation() { -// const { selectedCollection, allCollections } = useCollectionStore(); -// const [randomCardImage, setRandomCardImage] = useState(null); -// const { images, isLoading, error, downloadCardImages, getRandomCardImages } = -// useCardImages(); - -// if (isLoading) { -// console.log('Loading...'); -// return
Loading...
; -// } - -// if (error) { -// console.error(error); -// return
Error: {error}
; -// } - -// if (images && images.length > 0) { -// return ( -//
-// {images.map((image, index) => ( -// card -// ))} -//
-// ); -// } - -// useEffect(() => { -// const selectRandomCardImage = (collection) => { -// const filteredCards = collection.cards.filter( -// (card) => card.card_images && card.card_images.length > 0 -// ); -// if (filteredCards.length > 0) { -// const randomCard = -// filteredCards[Math.floor(Math.random() * filteredCards.length)]; -// return randomCard.card_images[0].image_url; -// } -// return placeholderImage; -// }; - -// const collection = -// selectedCollection || -// (allCollections.length > 0 -// ? allCollections[Math.floor(Math.random() * allCollections.length)] -// : null); -// if (collection) { -// setRandomCardImage(selectRandomCardImage(collection)); -// } else { -// setRandomCardImage(placeholderImage); -// } -// }, [selectedCollection, allCollections]); - -// useEffect(() => { -// if (!randomCardImage) return; - -// const loadingIconContainer = document.getElementById('loadingIcon'); -// if (!loadingIconContainer) { -// console.error('Loading icon container not found'); -// return; -// } - -// const scene = new THREE.Scene(); -// const camera = new THREE.PerspectiveCamera(75, 400 / 400, 0.1, 1000); -// const renderer = new THREE.WebGLRenderer({ alpha: true }); -// renderer.setSize(400, 400); - -// loadingIconContainer.appendChild(renderer.domElement); - -// const placeholderTexture = new THREE.TextureLoader().load(placeholderImage); -// const randomCardTexture = new THREE.TextureLoader().load(randomCardImage); -// const cardMaterialFront = new THREE.MeshBasicMaterial({ -// map: placeholderTexture, -// }); -// const cardMaterialBack = new THREE.MeshBasicMaterial({ -// map: randomCardTexture, -// }); - -// const cardGeometry = new THREE.BoxGeometry(1, 1.4, 0.01); -// const card = new THREE.Mesh(cardGeometry, [ -// cardMaterialBack, -// cardMaterialBack, -// cardMaterialBack, -// cardMaterialBack, -// cardMaterialFront, -// cardMaterialBack, -// ]); -// scene.add(card); - -// camera.position.z = 5; - -// const animate = function () { -// requestAnimationFrame(animate); -// card.rotation.y += 0.05; -// renderer.render(scene, camera); -// }; - -// animate(); - -// return () => { -// loadingIconContainer.removeChild(renderer.domElement); -// }; -// }, [randomCardImage]); - -// return
; -// } - -// export default LoadingCardAnimation; diff --git a/src/components/cards/CarouselCard.jsx b/src/assets/cleanup/CarouselCard.jsx similarity index 98% rename from src/components/cards/CarouselCard.jsx rename to src/assets/cleanup/CarouselCard.jsx index 75000f7..08ddd93 100644 --- a/src/components/cards/CarouselCard.jsx +++ b/src/assets/cleanup/CarouselCard.jsx @@ -11,7 +11,7 @@ import { import { useMode } from '../../context/hooks/colormode'; import { makeStyles, styled } from '@mui/styles'; import { ModalContext } from '../../context/ModalContext/ModalContext'; -import GenericCard from './GenericCard'; +import GenericCard from '../../components/cards/GenericCard'; import { MainContainer2, MainContainer, diff --git a/src/assets/cleanup/PortfolioChart.jsx b/src/assets/cleanup/PortfolioChart.jsx new file mode 100644 index 0000000..517354e --- /dev/null +++ b/src/assets/cleanup/PortfolioChart.jsx @@ -0,0 +1,193 @@ +// import React, { +// useCallback, +// useEffect, +// useLayoutEffect, +// useMemo, +// useRef, +// useState, +// } from 'react'; +// import { +// Box, +// Container, +// Grid, +// Paper, +// styled, +// useMediaQuery, +// useTheme, +// } from '@mui/material'; +// import LinearChart from './LinearChart'; +// import { useChartContext } from '../../../../context/ChartContext/ChartContext'; +// import ErrorBoundary from '../../../../context/ErrorBoundary'; +// import { useCollectionStore } from '../../../../context/CollectionContext/CollectionContext'; +// import debounce from 'lodash/debounce'; +// import { getUniqueValidData } from '../../../../context/CollectionContext/helpers'; +// import { usePageContext } from '../../../../context'; +// import LoadingIndicator from '../../../reusable/indicators/LoadingIndicator'; + +// const ChartPaper = styled(Paper)(({ theme }) => ({ +// borderRadius: theme.shape.borderRadius, +// boxShadow: theme.shadows[5], +// backgroundColor: theme.palette.background.paper, +// color: theme.palette.text.secondary, +// padding: theme.spacing(2), +// display: 'flex', +// flexDirection: 'column', +// alignItems: 'center', +// justifyContent: 'center', +// width: '100%', +// minHeight: '400px', +// overflow: 'hidden', +// margin: theme.spacing(2, 0), +// })); + +// const ResponsiveSquare = styled(Box)(({ theme }) => ({ +// width: '100%', +// paddingTop: '100%', +// backgroundColor: theme.palette.background.paper, +// borderRadius: theme.shape.borderRadius, +// boxShadow: theme.shadows[5], +// display: 'flex', +// alignItems: 'center', +// justifyContent: 'center', +// })); + +// function handleThresholdUpdate(lastUpdateTime, setLastUpdateTime) { +// const currentTime = new Date().getTime(); +// if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { +// setLastUpdateTime(currentTime); +// return currentTime; +// } +// return lastUpdateTime; +// } + +// const PortfolioChart = () => { +// const { selectedCollection, allXYValues } = useCollectionStore(); +// console.log('allXYValues', allXYValues); +// const { latestData, timeRange, groupAndAverageData, convertDataForNivo2 } = +// useChartContext(); +// const { isLoading, setIsLoading, displayLoadingIndicator } = usePageContext(); +// const theme = useTheme(); +// const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + +// // const [lastUpdateTime, setLastUpdateTime] = useState(null); +// const lastUpdateTimeRef = useRef(null); + +// const chartContainerRef = useRef(null); +// const [chartDimensions, setChartDimensions] = useState({ +// width: 0, +// height: 0, +// }); +// const HEIGHT_TO_WIDTH_RATIO = 7 / 10; +// const threshold = useMemo( +// () => handleThresholdUpdate(lastUpdateTimeRef), +// [lastUpdateTimeRef] +// ); +// const [dataReady, setDataReady] = useState(false); +// const calculateChartDimensions = useCallback(() => { +// if (chartContainerRef.current) { +// const width = chartContainerRef.current.offsetWidth; +// const height = width * HEIGHT_TO_WIDTH_RATIO; +// setChartDimensions({ width, height }); +// } +// }, []); + +// // Effect: Update chart dimensions on window resize +// useEffect(() => { +// const handleResize = debounce(calculateChartDimensions, 100); +// window.addEventListener('resize', handleResize); +// return () => { +// handleResize.cancel(); +// window.removeEventListener('resize', handleResize); +// }; +// }, [calculateChartDimensions]); + +// // Effect: Set data ready state based on allXYValues +// useEffect(() => { +// if (Array.isArray(allXYValues) && allXYValues?.length > 0) { +// setDataReady(true); +// } +// }, [allXYValues]); +// useLayoutEffect(() => { +// if (dataReady) { +// calculateChartDimensions(); +// } +// }, [dataReady]); +// // const getFilteredData2 = useCallback((values) => { +// // if (!values || !Array.isArray(values) || !values?.length > 0) return []; +// // return getUniqueValidData(values || []); +// // }, []); +// // const filteredChartData2 = useMemo( +// // () => getFilteredData2(allXYValues), +// // [allXYValues, timeRange, getFilteredData2] +// // ); +// const isEnoughData = allXYValues?.length >= 5; +// const rawData2 = useMemo(() => { +// return groupAndAverageData(allXYValues, threshold, timeRange); +// }, [allXYValues, threshold, timeRange]); + +// const nivoReadyData2 = useMemo(() => { +// return rawData2?.length > 0 ? convertDataForNivo2(rawData2) : []; +// }, [rawData2]); + +// useLayoutEffect(() => { +// if (dataReady) calculateChartDimensions(); +// }, [dataReady, calculateChartDimensions]); + +// // Render loading indicator, error message or chart based on conditions +// if (!dataReady) { +// return ; +// } + +// if (!isEnoughData || !rawData2?.length) { +// return ( +// +//
Not enough data points
+//
+// ); +// } + +// return ( +// +// +// +// +// +// {allXYValues?.length > 0 && rawData2?.length > 0 ? ( +// +// ) : ( +//
No data available
+// )} +//
+//
+//
+//
+//
+// ); +// }; + +// export default PortfolioChart; diff --git a/src/assets/data/card_sample.json b/src/assets/data/card_sample.json new file mode 100644 index 0000000..3a43dea --- /dev/null +++ b/src/assets/data/card_sample.json @@ -0,0 +1,295 @@ +{ + "data": [ + { + "id": 83764719, + "name": "Monster Reborn", + "type": "Spell Card", + "frameType": "spell", + "desc": "Target 1 monster in either GY; Special Summon it.", + "race": "Normal", + "ygoprodeck_url": "https://ygoprodeck.com/card/monster-reborn-7027", + "card_sets": [ + { + "set_name": "2019 Gold Sarcophagus Tin", + "set_code": "TN19-EN011", + "set_rarity": "Prismatic Secret Rare", + "set_rarity_code": "(PScR)", + "set_price": "4.44" + }, + { + "set_name": "Battle Pack 2: War of the Giants", + "set_code": "BP02-EN128", + "set_rarity": "Mosaic Rare", + "set_rarity_code": "(MSR)", + "set_price": "2.1" + }, + { + "set_name": "Battle Pack 2: War of the Giants", + "set_code": "BP02-EN128", + "set_rarity": "Rare", + "set_rarity_code": "(R)", + "set_price": "2.2" + }, + { + "set_name": "Battles of Legend: Relentless Revenge", + "set_code": "BLRR-EN046", + "set_rarity": "Secret Rare", + "set_rarity_code": "(ScR)", + "set_price": "5.49" + }, + { + "set_name": "Dark Beginning 1", + "set_code": "DB1-EN133", + "set_rarity": "Ultra Rare", + "set_rarity_code": "(UR)", + "set_price": "23.1" + }, + { + "set_name": "Dark Legends", + "set_code": "DLG1-EN017", + "set_rarity": "Super Rare", + "set_rarity_code": "(SR)", + "set_price": "14.92" + }, + { + "set_name": "Duelist Pack: Yugi", + "set_code": "DPYG-EN019", + "set_rarity": "Rare", + "set_rarity_code": "(R)", + "set_price": "1.91" + }, + { + "set_name": "Egyptian God Deck: Obelisk the Tormentor", + "set_code": "EGO1-EN024", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.44" + }, + { + "set_name": "Egyptian God Deck: Slifer the Sky Dragon", + "set_code": "EGS1-EN023", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.51" + }, + { + "set_name": "Hobby League 7 participation card A", + "set_code": "HL07-EN001", + "set_rarity": "Ultra Parallel Rare", + "set_rarity_code": "(UPR)", + "set_price": "43.71" + }, + { + "set_name": "Legend of Blue Eyes White Dragon", + "set_code": "LOB-118", + "set_rarity": "Ultra Rare", + "set_rarity_code": "(UR)", + "set_price": "27.29" + }, + { + "set_name": "Legend of Blue Eyes White Dragon", + "set_code": "LOB-E096", + "set_rarity": "Ultra Rare", + "set_rarity_code": "(UR)", + "set_price": "27.53" + }, + { + "set_name": "Legend of Blue Eyes White Dragon", + "set_code": "LOB-EN118", + "set_rarity": "Ultra Rare", + "set_rarity_code": "(UR)", + "set_price": "49.86" + }, + { + "set_name": "Legendary Collection 3: Yugi's World Mega Pack", + "set_code": "LCYW-EN058", + "set_rarity": "Ultra Rare", + "set_rarity_code": "(UR)", + "set_price": "6.68" + }, + { + "set_name": "Legendary Collection 4: Joey's World Mega Pack", + "set_code": "LCJW-EN060", + "set_rarity": "Ultra Rare", + "set_rarity_code": "(UR)", + "set_price": "4.63" + }, + { + "set_name": "Legendary Duelists: Rage of Ra", + "set_code": "LED7-EN012", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.57" + }, + { + "set_name": "Legendary Hero Decks", + "set_code": "LEHD-ENA23", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.58" + }, + { + "set_name": "Legendary Hero Decks", + "set_code": "LEHD-ENB19", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.4" + }, + { + "set_name": "Legendary Hero Decks", + "set_code": "LEHD-ENC16", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.42" + }, + { + "set_name": "Maximum Gold", + "set_code": "MAGO-EN045", + "set_rarity": "Premium Gold Rare", + "set_rarity_code": "(PG)", + "set_price": "2.97" + }, + { + "set_name": "Retro Pack", + "set_code": "RP01-EN016", + "set_rarity": "Super Rare", + "set_rarity_code": "(SR)", + "set_price": "50.61" + }, + { + "set_name": "Saga of Blue-Eyes White Dragon Structure Deck", + "set_code": "SDBE-EN028", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.81" + }, + { + "set_name": "Starter Deck: Codebreaker", + "set_code": "YS18-EN024", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.77" + }, + { + "set_name": "Starter Deck: Joey", + "set_code": "SDJ-035", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "2.19" + }, + { + "set_name": "Starter Deck: Kaiba", + "set_code": "SDK-036", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.77" + }, + { + "set_name": "Starter Deck: Kaiba", + "set_code": "SDK-E033", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "16.52" + }, + { + "set_name": "Starter Deck: Kaiba Evolution", + "set_code": "SKE-029", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "2.28" + }, + { + "set_name": "Starter Deck: Pegasus", + "set_code": "SDP-035", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "2.07" + }, + { + "set_name": "Starter Deck: Yugi", + "set_code": "SDY-030", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "2.68" + }, + { + "set_name": "Starter Deck: Yugi", + "set_code": "SDY-E028", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "10.09" + }, + { + "set_name": "Starter Deck: Yugi Evolution", + "set_code": "SYE-029", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "2.02" + }, + { + "set_name": "The Lost Art Promotion (series)", + "set_code": "LART-EN001", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "0" + }, + { + "set_name": "The Lost Art Promotion A", + "set_code": "LART-EN001", + "set_rarity": "Ultra Rare", + "set_rarity_code": "(UR)", + "set_price": "62.69" + }, + { + "set_name": "Yugi's Legendary Decks", + "set_code": "YGLD-ENA23", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.78" + }, + { + "set_name": "Yugi's Legendary Decks", + "set_code": "YGLD-ENB16", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.84" + }, + { + "set_name": "Yugi's Legendary Decks", + "set_code": "YGLD-ENC24", + "set_rarity": "Common", + "set_rarity_code": "(C)", + "set_price": "1.73" + } + ], + "banlist_info": { + "ban_tcg": "Limited", + "ban_ocg": "Limited", + "ban_goat": "Banned" + }, + "card_images": [ + { + "id": 83764719, + "image_url": "https://images.ygoprodeck.com/images/cards/83764719.jpg", + "image_url_small": "https://images.ygoprodeck.com/images/cards_small/83764719.jpg", + "image_url_cropped": "https://images.ygoprodeck.com/images/cards_cropped/83764719.jpg" + }, + { + "id": 83764718, + "image_url": "https://images.ygoprodeck.com/images/cards/83764718.jpg", + "image_url_small": "https://images.ygoprodeck.com/images/cards_small/83764718.jpg", + "image_url_cropped": "https://images.ygoprodeck.com/images/cards_cropped/83764718.jpg" + } + ], + "card_prices": [ + { + "cardmarket_price": "0.16", + "tcgplayer_price": "0.27", + "ebay_price": "0.99", + "amazon_price": "0.79", + "coolstuffinc_price": "0.79" + } + ] + } + ] +} diff --git a/src/assets/themeSettings.jsx b/src/assets/themeSettings.jsx index e541304..554551e 100644 --- a/src/assets/themeSettings.jsx +++ b/src/assets/themeSettings.jsx @@ -105,6 +105,7 @@ export const themeSettings = (mode) => { lightest: '#dbf5ee', contrastTextA: '#FBFAF2', contrastTextB: '#333', + contrastTextC: '#555', }, backgroundB: { darkest: '#111', diff --git a/src/components/cards/CardToolTip.jsx b/src/components/cards/CardToolTip.jsx index a830b8c..3028329 100644 --- a/src/components/cards/CardToolTip.jsx +++ b/src/components/cards/CardToolTip.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { makeStyles } from '@mui/styles'; import { Tooltip } from '@mui/material'; -import { useStyles } from './cardStyles'; +import { useStyles } from './styles/cardStyles'; const formatKey = (key) => { return key diff --git a/src/components/cards/GenericCard.jsx b/src/components/cards/GenericCard.jsx index 5822413..f73a44e 100644 --- a/src/components/cards/GenericCard.jsx +++ b/src/components/cards/GenericCard.jsx @@ -16,7 +16,8 @@ import { QuantityLine, StyledCard, StyledCardContent, -} from './cardStyledComponents'; +} from './styles/cardStyledComponents'; +import { getQuantity } from '../componentHelpers'; const GenericCard = React.forwardRef((props, ref) => { const { card, context, page } = props; @@ -27,7 +28,8 @@ const GenericCard = React.forwardRef((props, ref) => { const { cartData } = useCartStore(); const { selectedCollection, allCollections } = useCollectionStore(); const { selectedDeck, allDecks } = useDeckStore(); - const { openModalWithCard, setModalOpen, setClickedCard } = useModalContext(); + const { openModalWithCard, setModalOpen, setClickedCard, isModalOpen } = + useModalContext(); const { setHoveredCard, setIsPopoverOpen, hoveredCard } = useContext(PopoverContext); @@ -80,80 +82,51 @@ const GenericCard = React.forwardRef((props, ref) => { 'N/A' }`; // Get the quantity of card across all contexts - const getQuantity = useCallback(() => { - const findCardQuantity = (collectionsOrDecks) => - collectionsOrDecks?.reduce( - (acc, item) => - acc + (item?.cards?.find((c) => c.id === card.id)?.quantity ?? 0), - 0 - ) ?? 0; - - const cartQuantity = isInContext - ? cartData?.cart?.find((c) => c.id === card.id)?.quantity ?? 0 - : 0; - const collectionQuantity = - isInContext && selectedCollection - ? selectedCollection?.cards?.find((c) => c.id === card.id)?.quantity ?? - 0 - : findCardQuantity(allCollections); - const deckQuantity = - isInContext && selectedDeck - ? selectedDeck?.cards?.find((c) => c.id === card.id)?.quantity ?? 0 - : findCardQuantity(allDecks); - - return Math.max(cartQuantity, collectionQuantity, deckQuantity); - }, [ - card, - cartData, - selectedCollection, - allCollections, - selectedDeck, - allDecks, - ]); - - // let cartQuantity = isInContext - // ? cartData?.cart?.find((c) => c?.id === card?.id)?.quantity - // : 0; - // let storeQuantity = isInContext - // ? cartData?.cart?.find((c) => c?.id === card?.id)?.quantity - // : 0; - - // // Modify the logic to calculate quantity across all decks or collections if none is selected - // let deckQuantity = selectedDeck - // ? selectedDeck?.cards?.find((c) => c.id === card.id)?.quantity - // : allDecks?.reduce((acc, deck) => { - // const foundCard = deck?.cards?.find((c) => c.id === card.id); - // return foundCard ? acc + foundCard.quantity : acc; - // }, 0); - - // let collectionQuantity = selectedCollection - // ? selectedCollection?.cards?.find((c) => c.id === card.id)?.quantity - // : allCollections?.reduce((acc, collection) => { - // const foundCard = collection?.cards?.find((c) => c.id === card.id); - // return foundCard ? acc + foundCard.quantity : acc; - // }, 0); - // // Simplify the method to retrieve the relevant quantity - // let relevantQuantity = 0; - // switch (page) { - // case 'Store': - // case 'StorePage': - // relevantQuantity = storeQuantity; - // break; - // case 'Cart': - // case 'CartPage': - // relevantQuantity = cartQuantity; - // break; - // case 'Deck': - // case 'DeckPage': - // case 'DeckBuilder': - // relevantQuantity = deckQuantity; - // break; - // case 'Collection': - // relevantQuantity = collectionQuantity; - // break; - // default: - // relevantQuantity = card?.quantity; - // } + // const getQuantity = useCallback(() => { + // const findCardQuantity = (collectionsOrDecks) => + // collectionsOrDecks?.reduce( + // (acc, item) => + // acc + (item?.cards?.find((c) => c.id === card.id)?.quantity ?? 0), + // 0 + // ) ?? 0; + + // const cartQuantity = isInContext + // ? cartData?.cart?.find((c) => c.id === card.id)?.quantity ?? 0 + // : 0; + // const collectionQuantity = + // isInContext && selectedCollection + // ? selectedCollection?.cards?.find((c) => c.id === card.id)?.quantity ?? + // 0 + // : findCardQuantity(allCollections); + // const deckQuantity = + // isInContext && selectedDeck + // ? selectedDeck?.cards?.find((c) => c.id === card.id)?.quantity ?? 0 + // : findCardQuantity(allDecks); + + // return Math.max(cartQuantity, collectionQuantity, deckQuantity); + // }, [ + // card, + // cartData, + // selectedCollection, + // allCollections, + // selectedDeck, + // allDecks, + // ]); + // Use the getQuantity helper function + // const quantity = getQuantity({ + // card: card, + // cartData: useCartStore().cartData, // assuming useCartStore returns cartData + // selectedCollection: useCollectionStore().selectedCollection, + // allCollections: useCollectionStore().allCollections, + // selectedDeck: useDeckStore().selectedDeck, + // allDecks: useDeckStore().allDecks, + // }); + const cartQuantity = getQuantity({ card: card, cartData: cartData }); + const collectionQuantity = getQuantity({ + card: card, + collectionData: selectedCollection, + }); + const deckQuantity = getQuantity({ card: card, deckData: selectedDeck }); // Function to render the card's media section const renderCardMediaSection = () => ( @@ -164,10 +137,11 @@ const GenericCard = React.forwardRef((props, ref) => { card={card} context={context} page={page} - quantity={getQuantity()} + quantity={cartQuantity} isHovered={hoveredCard === card} handleInteraction={handleInteraction} handleClick={handleClick} + isModalOpen={isModalOpen} ref={cardRef} /> @@ -181,19 +155,11 @@ const GenericCard = React.forwardRef((props, ref) => { {price} - {`Cart: ${ - cartData?.cart?.find((c) => c.id === card.id)?.quantity ?? 0 - }`} - - - {`Collection: ${ - selectedCollection?.cards?.find((c) => c.id === card.id) - ?.quantity ?? 0 - }`} - - {`Deck: ${ - selectedDeck?.cards?.find((c) => c.id === card.id)?.quantity ?? 0 - }`} + {`Cart: ${cartQuantity}`} + {`Collection: ${collectionQuantity}`} + {`Deck: ${deckQuantity}`} ); }; diff --git a/src/components/cards/media/CardMediaSection.js b/src/components/cards/media/CardMediaSection.js index c5aef8b..f3af08d 100644 --- a/src/components/cards/media/CardMediaSection.js +++ b/src/components/cards/media/CardMediaSection.js @@ -1,4 +1,4 @@ -import React, { useEffect, forwardRef } from 'react'; +import React, { useEffect, forwardRef, useState } from 'react'; import { CardMedia, Popover, Popper } from '@mui/material'; import CardToolTip from '../CardToolTip'; import { makeStyles } from '@mui/styles'; @@ -52,33 +52,31 @@ const transformOrigin = { const CardMediaSection = forwardRef( ( - { imgUrl, card, isHovered, handleInteraction, handleClick, isRequired }, + { + imgUrl, + card, + isHovered, + handleInteraction, + handleClick, + isRequired, + isModalOpen, + }, ref ) => { const classes = useStyles(); - const { setModalImgUrl, clickedCard, setClickedCard } = useModalContext(); - const { isPopoverOpen } = usePopoverContext(); - const [anchorEl, setAnchorEl] = React.useState(ref?.current || null); - const eventHandlers = isRequired - ? { - onMouseEnter: () => handleInteraction?.(true), - onMouseLeave: () => handleInteraction?.(false), - onClick: () => { - handleClick?.(); - setClickedCard?.(card); - }, - } - : {}; + const [anchorEl, setAnchorEl] = useState(null); - useEffect(() => { - if (imgUrl && clickedCard) { - setModalImgUrl(imgUrl); - } - }, [imgUrl, clickedCard, setModalImgUrl]); + // Handle overlay state and interaction + const [overlay, setOverlay] = useState(null); + + const handleOverlayChange = (newOverlay) => { + // Logic to change overlay based on rarity or other property + setOverlay(newOverlay); + }; useEffect(() => { if (isHovered && ref?.current) { - setAnchorEl(ref?.current); + setAnchorEl(ref.current); } else { setAnchorEl(null); } @@ -88,8 +86,14 @@ const CardMediaSection = forwardRef(
handleClick?.()} + {...(isRequired && { + onMouseEnter: () => handleInteraction(!isModalOpen ? true : false), + onMouseLeave: () => handleInteraction(false), + onClick: () => { + handleClick?.(); + handleOverlayChange('newOverlayValue'); // Replace with actual value or function call + }, + })} > + + {/* Potentially additional elements for overlays and interactivity */} + {overlay && ( +
+ {' '} + {/* Add styles and logic for overlay */} + {overlay} +
+ )} + {anchorEl && isHovered && ( handleInteraction(false)} - // anchorOrigin={anchorOrigin} - // transformOrigin={transformOrigin} + open={isHovered} + anchorEl={anchorEl} placement="right-start" - // disableRestoreFocus > diff --git a/src/components/cards/cardStyledComponents.jsx b/src/components/cards/styles/cardStyledComponents.jsx similarity index 100% rename from src/components/cards/cardStyledComponents.jsx rename to src/components/cards/styles/cardStyledComponents.jsx diff --git a/src/components/cards/cardStyles.jsx b/src/components/cards/styles/cardStyles.jsx similarity index 100% rename from src/components/cards/cardStyles.jsx rename to src/components/cards/styles/cardStyles.jsx diff --git a/src/components/componentHelpers.jsx b/src/components/componentHelpers.jsx new file mode 100644 index 0000000..cfd16b5 --- /dev/null +++ b/src/components/componentHelpers.jsx @@ -0,0 +1,27 @@ +export const getQuantity = ({ + card, + cartData, + selectedCollection, + allCollections, + selectedDeck, + allDecks, +}) => { + // Helper function to find the quantity of a card in a list of collections or decks + const findCardQuantity = (collectionsOrDecks) => + collectionsOrDecks?.reduce( + (acc, item) => + acc + (item?.cards?.find((c) => c.id === card.id)?.quantity ?? 0), + 0 + ) ?? 0; + + const cartQuantity = + cartData?.cart?.find((c) => c.id === card.id)?.quantity ?? 0; + const collectionQuantity = selectedCollection + ? selectedCollection?.cards?.find((c) => c.id === card.id)?.quantity ?? 0 + : findCardQuantity(allCollections); + const deckQuantity = selectedDeck + ? selectedDeck?.cards?.find((c) => c.id === card.id)?.quantity ?? 0 + : findCardQuantity(allDecks); + + return Math.max(cartQuantity, collectionQuantity, deckQuantity); +}; diff --git a/src/components/dialogs/CollectionDialog.jsx b/src/components/dialogs/CollectionDialog.jsx new file mode 100644 index 0000000..3a997c4 --- /dev/null +++ b/src/components/dialogs/CollectionDialog.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Dialog, DialogContent } from '@mui/material'; +import CollectionForm from '../forms/CollectionForm'; + +const CollectionDialog = ({ + isDialogOpen, + closeDialog, + onSave, + isNew, + initialName = '', + initialDescription = '', +}) => { + return ( + + + + + + ); +}; + +CollectionDialog.propTypes = { + isDialogOpen: PropTypes.bool.isRequired, + closeDialog: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired, + isNew: PropTypes.bool, + initialName: PropTypes.string, + initialDescription: PropTypes.string, +}; + +export default CollectionDialog; diff --git a/src/components/dialogs/CreateOrEditCollectionDialog.jsx b/src/components/dialogs/CreateOrEditCollectionDialog.jsx deleted file mode 100644 index ab884ba..0000000 --- a/src/components/dialogs/CreateOrEditCollectionDialog.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import { Dialog, DialogContent, Typography, Alert } from '@mui/material'; -import { useCookies } from 'react-cookie'; -import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -import { StyledButton, StyledTextField } from '../forms/styled'; -import { useMode } from '../../context'; - -const CreateOrEditCollectionDialog = ({ - isDialogOpen, - closeDialog, - onSave, - isNew, - initialName = '', - initialDescription = '', - // editedName, - // setEditedName, - // editedDescription, - // setEditedDescription, -}) => { - const { theme } = useMode(); - const [error, setError] = useState(''); - const [editedName, setEditedName] = useState(initialName); - const [editedDescription, setEditedDescription] = - useState(initialDescription); - - const { - createUserCollection, - removeCollection, - selectedCollection, - updateCollectionDetails, - } = useCollectionStore(); - const [cookies] = useCookies(['authUser']); - const userId = cookies?.authUser?.id; - - useEffect(() => { - setError(''); - }, [isDialogOpen]); - useEffect(() => { - // Update internal state when dialog opens - setEditedName(initialName); - setEditedDescription(initialDescription); - }, [initialName, initialDescription, isDialogOpen]); - - const handleSave = () => { - if (!editedName.trim() || !editedDescription.trim()) { - setError('Please fill in all fields.'); - return; - } - - const newCollectionInfo = { - name: editedName, - description: editedDescription, - userId: userId, - tag: 'new', - }; - - if (isNew) { - createUserCollection(userId, newCollectionInfo, 'create'); - } else { - updateCollectionDetails(newCollectionInfo, selectedCollection._id); - } - - onSave(newCollectionInfo); - closeDialog(); - }; - - const handleRemove = (e) => { - e.preventDefault(); - removeCollection(selectedCollection); - closeDialog(); - }; - - return ( - - - - {isNew ? 'Create a new collection' : 'Edit your collection'} - - {error && ( - - {error} - - )} - setEditedName(e.target.value)} - variant="outlined" - fullWidth - margin="normal" - theme={theme} - /> - setEditedDescription(e.target.value)} - variant="outlined" - multiline - rows={4} - fullWidth - margin="normal" - theme={theme} - /> -
- - {isNew ? 'Create Collection' : 'Save Changes'} - - {!isNew && ( - - Delete - - )} -
-
-
- ); -}; - -CreateOrEditCollectionDialog.propTypes = { - isDialogOpen: PropTypes.bool.isRequired, - closeDialog: PropTypes.func.isRequired, - onSave: PropTypes.func.isRequired, - isNew: PropTypes.bool, - editedName: PropTypes.string, - setEditedName: PropTypes.func, - editedDescription: PropTypes.string, - setEditedDescription: PropTypes.func, -}; - -export default CreateOrEditCollectionDialog; diff --git a/src/components/dialogs/LoginDialog.jsx b/src/components/dialogs/LoginDialog.jsx index f6bc4de..d5b4305 100644 --- a/src/components/dialogs/LoginDialog.jsx +++ b/src/components/dialogs/LoginDialog.jsx @@ -18,10 +18,22 @@ import { useMode, usePageContext, } from '../../context'; +import { + StyledDialog, + StyledDialogContent, + StyledDialogTitle, +} from '../forms/styled'; +import useDialog from '../../context/hooks/useDialog'; +import useSnackBar from '../../context/hooks/useSnackBar'; -function LoginDialog({ open, onClose, onLogin }) { +function LoginDialog() { + const { theme } = useMode(); // Ensures theme is applied correctly const { toggleColorMode, mode } = useMode(); const { logout, isLoggedIn } = useAuthContext(); + const handleSnackBar = useSnackBar()[1]; + const { isLoginDialogOpen, openLoginDialog, closeLoginDialog } = + useDialog(handleSnackBar); + const { returnDisplay, loadingStatus, setLoading } = usePageContext(); // Access loading display or error status const { forms } = useFormContext(); const loginValues = forms?.loginForm; @@ -29,36 +41,57 @@ function LoginDialog({ open, onClose, onLogin }) { const signupMode = signupValues?.signupMode; const currentValues = signupMode ? signupValues : loginValues; - // Close dialog on successful login + // console.log('LoginDialog', { currentValues }); + // console.log('LoginDialog', { loadingStatus }); + // console.log('LoginDialog', { isLoginDialogOpen }); + // console.log('LoginDialog', { isLoggedIn }); + // console.log('LoginDialog', { signupMode }); + // useEffect for checking dialog status useEffect(() => { - if (isLoggedIn && open) { - onClose(); + if (!isLoggedIn && !isLoginDialogOpen) { + openLoginDialog(); + // closeLoginDialog(); + } + if (isLoggedIn && isLoginDialogOpen) { + closeLoginDialog(); } - }, [isLoggedIn, open, onClose]); // Make sure dependencies are accurate + }, [isLoggedIn, isLoginDialogOpen, closeLoginDialog]); // Make sure dependencies are accurate + // console.log('LoginDialog', { theme }); + // console.log('LoginDialog', { mode }); - // Update pagecontext error state if authcontext error state changes // useEffect(() => { - // if (formErrors) { - // setLoading(formErrors, false); + // if (isLoggedIn && isLoginDialogOpen) { + // closeLoginDialog(); // } - // }, [formErrors]); + // }, [isLoggedIn, isLoginDialogOpen, closeLoginDialog]); // Make sure dependencies are accurate return ( - - + + - {currentValues === signupMode ? 'Sign Up' : 'Login'} + + {signupMode ? 'Sign Up' : 'Login'} + {mode === 'dark' ? : } - - + + {isLoggedIn ? ( + + ); } diff --git a/src/components/dialogs/cardDialog/GenericCardDialog.jsx b/src/components/dialogs/cardDialog/GenericCardDialog.jsx index b1d92da..074373b 100644 --- a/src/components/dialogs/cardDialog/GenericCardDialog.jsx +++ b/src/components/dialogs/cardDialog/GenericCardDialog.jsx @@ -37,7 +37,7 @@ import CloseIcon from '@mui/icons-material/Close'; import useResponsiveStyles from '../../../context/hooks/useResponsiveStyles'; import CardDetail from '../../cards/CardDetail'; import { FaRegCopy } from 'react-icons/fa'; -import CardDetailsContainer from '../../cards/CardDetailsContainer'; +import CardDetailsContainer from '../../../layout/CardDetailsContainer'; import { FormControl } from 'react-bootstrap'; import CardDetails from '../../cards/CardDetails'; @@ -166,6 +166,8 @@ const GenericCardDialog = (props) => { imgUrl={imageUrl} /> + + {/* these two grid items are for the card details */} ({ -// flexCenter: { -// display: 'flex', -// alignItems: 'center', -// }, -// actionButtons: { -// justifyContent: 'center', -// gap: '1.5rem', -// }, -// media: { -// objectFit: 'cover', -// borderRadius: '4px', -// boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', -// }, -// details: { -// gap: '1.5rem', -// marginBottom: '1.5rem', -// }, -// dialogTitle: { -// fontSize: '1.5rem', -// fontWeight: 600, -// color: theme.palette.primary.dark, -// }, -// dialogContent: { -// padding: '2rem', -// }, -// listItem: { -// 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), -// }, -// // flexCenter: { -// // display: 'flex', -// // alignItems: 'center', -// // justifyContent: 'center', -// // }, -// // actionButtons: { -// // order: { xs: 2, md: 3 }, -// // }, -// mediaAndDetails: { -// order: { xs: 1, md: 1 }, -// }, -// carousel: { -// order: { xs: 3, md: 2 }, -// display: 'flex', -// justifyContent: 'center', -// }, -// swiperSlide: { -// '& .swiper-slide': { -// opacity: 0.5, -// transform: 'scale(0.85)', -// transition: 'transform 0.3s, opacity 0.3s', -// '&-active': { -// opacity: 1, -// transform: 'scale(1.2)', -// }, -// '&-next, &-prev': { -// transform: 'scale(0.8)', -// }, -// }, -// }, -// carouselContainer: { -// bottom: theme.spacing(2), -// right: theme.spacing(2), -// width: '100%', -// height: 180, -// perspective: '1200px', -// }, -// })); - -// // 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/forms/CollectionForm.jsx b/src/components/forms/CollectionForm.jsx new file mode 100644 index 0000000..36148c8 --- /dev/null +++ b/src/components/forms/CollectionForm.jsx @@ -0,0 +1,74 @@ +import React, { useState, useEffect, useContext } from 'react'; +import PropTypes from 'prop-types'; +import { Typography, Alert } from '@mui/material'; +import { useFormContext, useMode } from '../../context'; +import { FormWrapper, StyledButton, StyledTextField } from './styled'; + +const CollectionForm = ({ onSave, isNew }) => { + const { theme } = useMode(); + const { forms, handleChange, handleSubmit } = useFormContext(); + const [formValues, setFormValues] = useState({ name: '', description: '' }); + const formType = isNew ? 'addCollectionForm' : 'updateCollectionForm'; + + useEffect(() => { + setFormValues( + isNew ? forms?.addCollectionForm || {} : forms?.updateCollectionForm || {} + ); + }, [isNew, forms.addCollectionForm, forms.updateCollectionForm]); + + const handleValueChange = (field) => (event) => { + setFormValues((prev) => ({ ...prev, [field]: event.target.value })); + }; + + const handleSave = async (e) => { + e.preventDefault(); + // You might need to ensure onSave is properly defined and used, or use the provided handleSubmit for form submission + onSave(formValues); // Replace or use along with your context's handleSubmit + }; + return ( + + + {isNew ? 'Create a new collection' : 'Edit your collection'} + + {/* {error && {error}} */} + + + + {isNew ? 'Create Collection' : 'Save Changes'} + + + ); +}; + +CollectionForm.propTypes = { + onSave: PropTypes.func.isRequired, + isNew: PropTypes.bool, + // initialName: PropTypes.string, + // initialDescription: PropTypes.string, +}; + +export default CollectionForm; diff --git a/src/components/forms/ProfileForm.jsx b/src/components/forms/ProfileForm.jsx index 3684bf0..9dbe179 100644 --- a/src/components/forms/ProfileForm.jsx +++ b/src/components/forms/ProfileForm.jsx @@ -1,79 +1,98 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { TextField, Button } from '@mui/material'; +import { FormWrapper } from './styled'; +import { useFormContext } from '../../context'; const ProfileForm = ({ userName, name, age, status, onSave }) => { - const [formData, setFormData] = useState({ - userName: userName || '', // default to empty string if undefined - name: name || '', - age: age || '', - status: status || '', - }); - const handleChange = (e) => { - setFormData({ - ...formData, - [e.target.id]: e.target.value, - }); - }; + const { forms, handleChange, handleSubmit } = useFormContext(); + const profileValues = forms?.updateUserDataForm || {}; + const formType = 'updateUserDataForm'; + // const [formData, setFormData] = useState({ + // userName: userName || '', // default to empty string if undefined + // name: name || '', + // age: age || '', + // status: status || '', + // }); + // const handleChange = (e) => { + // setFormData({ + // ...formData, + // [e.target.id]: e.target.value, + // }); + // }; - const handleSubmit = (e) => { - e.preventDefault(); - onSave(formData); - }; + // const handleSubmit = (e) => { + // e.preventDefault(); + // onSave(formData); + // }; - useEffect(() => { - setFormData({ - userName: userName || '', - name: name || '', - age: age || '', - status: status || '', - }); - }, [userName, name, age, status]); + // useEffect(() => { + // setFormData({ + // userName: userName || '', + // name: name || '', + // age: age || '', + // status: status || '', + // }); + // }, [userName, name, age, status]); + const handleFormSubmit = (event) => { + event.preventDefault(); // Prevent the default form submission behavior + handleSubmit(formType)(event); // Pass the event to your form handler + }; return ( -
+ + - +
); }; ProfileForm.propTypes = { - userName: PropTypes.string, - name: PropTypes.string, + username: PropTypes.string, + firstName: PropTypes.string, + lastName: PropTypes.string, + age: PropTypes.string, status: PropTypes.string, onSave: PropTypes.func.isRequired, diff --git a/src/components/forms/SearchForm.jsx b/src/components/forms/SearchForm.jsx index 3aed087..1a7d59d 100644 --- a/src/components/forms/SearchForm.jsx +++ b/src/components/forms/SearchForm.jsx @@ -1,27 +1,35 @@ import React from 'react'; import { useMode } from '../../context/hooks/colormode'; -import { StyledButton, StyledPaper, StyledTextField } from './styled'; +import { + FormWrapper, + StyledButton, + StyledPaper, + StyledTextField, +} from './styled'; import { Button, Grid } from '@mui/material'; const SearchForm = ({ searchTerm, handleChange, - onFocus, - onBlur, handleSubmit, handleKeyPress, + onFocus, + onBlur, }) => { const { theme } = useMode(); return ( -
@@ -36,27 +44,21 @@ const SearchForm = ({ onKeyDown={handleKeyPress} // Add keydown listener here theme={theme} /> - - - -
+ +
); }; diff --git a/src/components/forms/styled.jsx b/src/components/forms/styled.jsx index 903ba53..423ae3c 100644 --- a/src/components/forms/styled.jsx +++ b/src/components/forms/styled.jsx @@ -1,5 +1,37 @@ import styled, { keyframes, css } from 'styled-components'; -import { Box, Button, Paper, TextField } from '@mui/material'; +import { + Box, + Button, + Dialog, + DialogContent, + DialogTitle, + Paper, + TextField, +} from '@mui/material'; + +export const StyledDialog = styled(Dialog)(({ theme }) => ({ + '& .MuiDialog-paper': { + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(4), + backgroundColor: theme.palette.background.default, // Choose a subtle color + color: theme.palette.text.primary, + boxShadow: theme.shadows[5], + '@media (max-width:600px)': { + margin: theme.spacing(2), + }, + }, + '& .MuiDialog-paperScrollPaper': { + maxHeight: '90vh', // Slightly more space + }, +})); + +export const StyledDialogContent = styled(DialogContent)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + padding: theme.spacing(3), + backgroundColor: theme.palette.background.paper, +})); export const FormWrapper = styled('form')` display: 'flex'; @@ -79,3 +111,19 @@ export const StyledBox = styled(Box)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, color: theme.palette.text.primary, })); + +export const StyledDialogTitle = styled(DialogTitle)(({ theme }) => ({ + background: theme.palette.primary.main, // Or any other appropriate color + color: theme.palette.primary.contrastText, + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + borderRadius: `${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0 0`, // Only round the top corners + '& .MuiTypography-root': { + fontWeight: 'bold', + }, + '& .MuiIconButton-root': { + color: theme.palette.primary.contrastText, // Ensure it stands out or matches + }, +})); diff --git a/src/components/grids/collectionGrids/CardList.jsx b/src/components/grids/collectionGrids/CardList.jsx index 40efe60..fa07073 100644 --- a/src/components/grids/collectionGrids/CardList.jsx +++ b/src/components/grids/collectionGrids/CardList.jsx @@ -166,16 +166,32 @@ const CardList = () => { }; const renderTotalPriceBox = () => { + const renderTotalPrice = () => { + return ( + + {`$${roundToNearestTenth(getTotalPrice())}`} + + ); + }; return ( - - Total: ${roundToNearestTenth(getTotalPrice())} + + Total: + {renderTotalPrice()} ); }; diff --git a/src/components/grids/collectionGrids/CollectionListItem.jsx b/src/components/grids/collectionGrids/CollectionListItem.jsx new file mode 100644 index 0000000..8efbd86 --- /dev/null +++ b/src/components/grids/collectionGrids/CollectionListItem.jsx @@ -0,0 +1,121 @@ +/* eslint-disable react/display-name */ +import React, { memo, useState } from 'react'; +import { + Box, + Card, + CardActionArea, + CardContent, + Grid, + Typography, + useTheme, + IconButton, + Divider, + Tooltip, +} from '@mui/material'; +import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; +import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import { styled } from '@mui/system'; +import PropTypes from 'prop-types'; +import { useMode } from '../../../context'; +import useSelectCollectionListStyles from '../../../context/hooks/useSelectCollectionListStyles'; +import LongMenu from '../../reusable/LongMenu'; + +const StyledCard = styled(Card)(({ theme }) => ({ + margin: theme.spacing(1), + transition: '0.3s', + boxShadow: '0 8px 40px -12px rgba(0,0,0,0.3)', + '&:hover': { + boxShadow: '0 16px 70px -12.125px rgba(0,0,0,0.3)', + }, +})); + +const StyledCardContent = styled(CardContent)(({ theme }) => ({ + textAlign: 'left', + padding: theme.spacing(2), +})); +const StatisticTypography = styled(Typography)(({ theme }) => ({ + fontWeight: 'bold', + color: theme.palette.text.secondary, +})); + +const CollectionListItem = memo( + ({ + collection, + handleSelect, + statsByCollectionId, + handleOpenDialog, + roundToNearestTenth, + }) => { + const { theme } = useMode(); + const classes = useSelectCollectionListStyles(theme); + const [showOptions, setShowOptions] = useState(false); + const twentyFourHourChange = statsByCollectionId?.twentyFourHourAverage; + + return ( + + handleSelect(collection._id)}> + + + + Name: {collection?.name} + + + + Value: ${roundToNearestTenth(collection?.totalPrice)} + + + + + Performance:{' '} + {twentyFourHourChange?.priceChange > 0 ? ( + + ) : ( + + )} + {twentyFourHourChange?.percentageChange} + + + + Cards: {collection?.totalQuantity} + + {/* Add more grid items for other details like Performance */} + + + + + setShowOptions(!showOptions)} + style={{ position: 'absolute', top: 5, right: 5 }} + > + + + + {showOptions && ( + handleOpenDialog(collection)} + onStats={() => console.log('Stats:', collection)} + onView={() => console.log('View:', collection)} + onClose={() => setShowOptions(false)} + /> + )} + + ); + }, + (prevProps, nextProps) => + prevProps.collection._id === nextProps.collection._id && + prevProps.isSelected === nextProps.isSelected +); + +CollectionListItem.propTypes = { + collection: PropTypes.object.isRequired, + handleSelect: PropTypes.func.isRequired, + isSelected: PropTypes.bool, +}; + +CollectionListItem.defaultProps = { + isSelected: false, +}; + +export default CollectionListItem; diff --git a/src/components/grids/collectionGrids/SelectCollectionList.jsx b/src/components/grids/collectionGrids/SelectCollectionList.jsx index 6131b4e..81495d8 100644 --- a/src/components/grids/collectionGrids/SelectCollectionList.jsx +++ b/src/components/grids/collectionGrids/SelectCollectionList.jsx @@ -21,7 +21,8 @@ import LongMenu from '../../reusable/LongMenu'; import { roundToNearestTenth } from '../../../context/Helpers'; import useSelectCollectionListStyles from '../../../context/hooks/useSelectCollectionListStyles'; import { styled } from '@mui/styles'; -import { useMode } from '../../../context'; +import { useMode, usePageContext } from '../../../context'; +import CollectionListItem from './CollectionListItem'; const StyledSkeletonCard = styled(Card)(({ theme }) => ({ // Use the same styles as in StyledCard @@ -46,39 +47,6 @@ const AspectRatioBoxSkeleton = styled('div')(({ theme }) => ({ paddingTop: '56.25%', // 16:9 aspect ratio })); -// eslint-disable-next-line react/display-name -// const ListItemSkeleton = memo( -// () => { -// const classes = useSelectCollectionListStyles(); -// return ( -// -// -// -// -// -// -// -// ); -// }, -// () => true -// ); -// eslint-disable-next-line react/display-name -// const ListItemSkeleton = memo( -// () => { -// const { theme } = useMode(); - -// const classes = useSelectCollectionListStyles(theme); - -// return ( -// -// -// -// -// -// ); -// }, -// () => true -// ); // eslint-disable-next-line react/display-name const ListItemSkeleton = memo( () => { @@ -98,89 +66,88 @@ const ListItemSkeleton = memo( ); // eslint-disable-next-line react/display-name -const CollectionListItem = memo( - ({ - collection, - handleSelect, - handleOpenDialog, - isSelected, - statsByCollectionId, - isPlaceholder, - }) => { - const { theme } = useMode(); - const classes = useSelectCollectionListStyles(theme); - - const twentyFourHourChange = - statsByCollectionId[collection?._id]?.twentyFourHourAverage; - return ( - - - !isPlaceholder && handleSelect(collection?._id)} - > - - - Name: - {collection?.name} - - - Value: - - ${roundToNearestTenth(collection?.totalPrice)} - - - - - Performance: - - - {twentyFourHourChange?.priceChange > 0 ? ( - - ) : ( - - )} - {twentyFourHourChange?.percentageChange} - - - - Cards: - {collection?.totalQuantity} - - - -
- handleOpenDialog(collection)} - onStats={() => console.log('Stats:', collection)} - onView={() => console.log('View:', collection)} - /> -
-
- -
- ); - }, - (prevProps, nextProps) => { - // Only re-render if specific props have changed - return ( - prevProps.collection._id === nextProps.collection._id && - prevProps.isSelected === nextProps.isSelected - ); - } -); +// const CollectionListItem = memo( +// ({ +// collection, +// handleSelect, +// handleOpenDialog, +// isSelected, +// statsByCollectionId, +// isPlaceholder, +// }) => { +// const { theme } = useMode(); +// const classes = useSelectCollectionListStyles(theme); +// console.log('statsByCollectionId', statsByCollectionId); +// const twentyFourHourChange = statsByCollectionId?.twentyFourHourAverage; +// return ( +// +// +// !isPlaceholder && handleSelect(collection?._id)} +// > +// +// +// Name: +// {collection?.name} +// +// +// Value: +// +// ${roundToNearestTenth(collection?.totalPrice)} +// +// +// +// +// Performance: +// +// +// {twentyFourHourChange?.priceChange > 0 ? ( +// +// ) : ( +// +// )} +// {twentyFourHourChange?.percentageChange} +// +// +// +// Cards: +// {collection?.totalQuantity} +// +// +// +//
+// handleOpenDialog(collection)} +// onStats={() => console.log('Stats:', collection)} +// onView={() => console.log('View:', collection)} +// /> +//
+//
+// +//
+// ); +// }, +// (prevProps, nextProps) => { +// // Only re-render if specific props have changed +// return ( +// prevProps.collection._id === nextProps.collection._id && +// prevProps.isSelected === nextProps.isSelected +// ); +// } +// ); const SelectCollectionList = ({ onSave, @@ -188,14 +155,14 @@ const SelectCollectionList = ({ handleSelectCollection, isLoadingNewCollection, }) => { - const classes = useSelectCollectionListStyles(); + const { theme } = useMode(); + const classes = useSelectCollectionListStyles(theme); const [selectedCollectionId, setSelectedCollectionId] = useState(null); const [loadingCollectionIds, setLoadingCollectionIds] = useState([]); - const { allCollections, setSelectedCollection, fetchAllCollectionsForUser } = useCollectionStore(); + const { setLoading } = usePageContext(); const { statsByCollectionId } = useStatisticsStore(); - const [isLoading, setIsLoading] = useState(false); const handleSelect = useCallback( (selectedId) => { @@ -221,21 +188,13 @@ const SelectCollectionList = ({ }, [openDialog, setSelectedCollection] ); - // useEffect(() => { - // if (isLoadingNewCollection) { - // // Assume new collection is the last one added - // const newCollectionId = allCollections[allCollections.length - 1]?._id; - // if (newCollectionId) { - // setLoadingCollectionIds((prev) => [...prev, newCollectionId]); - // } - // } - // }, [isLoadingNewCollection, allCollections]); useEffect(() => { if (isLoadingNewCollection) { - // Assume new collection is the last one added - const newCollectionId = allCollections[allCollections.length - 1]?._id; + setLoading('isLoading', true); + const newCollectionId = allCollections[allCollections?.length - 1]?._id; if (newCollectionId) { setLoadingCollectionIds((prev) => [...prev, newCollectionId]); + setLoading('isLoading', false); } } }, [isLoadingNewCollection, allCollections]); @@ -247,18 +206,21 @@ const SelectCollectionList = ({ prev.filter((id) => id !== collectionId) ); }; - - // Simulate loading each collection after a certain time - loadingCollectionIds.forEach((collectionId) => { + loadingCollectionIds?.forEach((collectionId) => { setTimeout(() => loadCollection(collectionId), 1000); // simulate delay }); }, [loadingCollectionIds]); + // useEffect(() => { + // fetchAllCollectionsForUser().catch((err) => + // console.error('Failed to get all collections:', err) + // ); + // }. [fetchAllCollectionsForUser]); + return ( - {allCollections.map((collection) => { - const isSelected = collection._id === selectedCollectionId; - const isLoading = loadingCollectionIds.includes(collection._id); - + {allCollections?.map((collection) => { + const isSelected = collection?._id === selectedCollectionId; + const isLoading = loadingCollectionIds?.includes(collection?._id); return isLoading ? ( // Render skeleton if the collection is still loading @@ -269,6 +231,7 @@ const SelectCollectionList = ({ collection={collection} handleSelect={handleSelect} handleOpenDialog={handleOpenDialog} + roundToNearestTenth={roundToNearestTenth} isSelected={isSelected} classes={classes} statsByCollectionId={statsByCollectionId} @@ -284,6 +247,8 @@ SelectCollectionList.propTypes = { onSave: PropTypes.func.isRequired, openDialog: PropTypes.func.isRequired, handleSelectCollection: PropTypes.func.isRequired, + isLoadingNewCollection: PropTypes.bool, + allCollections: PropTypes.array.isRequired, // Ensure this is passed or obtained from context }; export default SelectCollectionList; diff --git a/src/components/grids/deckBuilderGrids/CardsGrid.js b/src/components/grids/deckBuilderGrids/CardsGrid.js index 4e488af..f8c697e 100644 --- a/src/components/grids/deckBuilderGrids/CardsGrid.js +++ b/src/components/grids/deckBuilderGrids/CardsGrid.js @@ -1,9 +1,10 @@ import React, { useMemo, useState } from 'react'; import { Grid, Typography } from '@mui/material'; -import { CSSTransition, TransitionGroup } from 'react-transition-group'; +import { CSSTransition } from 'react-transition-group'; import DeckItem from '../gridItems/DeckItem'; import { useDeckStore } from '../../../context/DeckContext/DeckContext'; import SkeletonDeckItem from '../gridItems/SkeletonDeckItem'; +import GridLayout from '../searchResultsGrids/GridLayout'; const CardsGrid = ({ isLoading }) => { const { selectedCards } = useDeckStore(); const [error, setError] = useState(''); @@ -35,34 +36,37 @@ const CardsGrid = ({ isLoading }) => { console.log('LOADING', isLoading); return ( - - - {isLoading - ? Array.from({ length: skeletonCount }).map((_, index) => ( - - - - - - )) - : flattenSelectedCards?.map((card, index) => ( - - - - - - ))} - - + + {isLoading + ? Array.from({ length: skeletonCount }).map((_, index) => ( + + + + + + )) + : flattenSelectedCards?.map((card, index) => ( + + + + + + ))} + ); }; diff --git a/src/components/grids/gridItems/ReusableSkeletonItem.jsx b/src/components/grids/gridItems/ReusableSkeletonItem.jsx new file mode 100644 index 0000000..158318d --- /dev/null +++ b/src/components/grids/gridItems/ReusableSkeletonItem.jsx @@ -0,0 +1,18 @@ +// ReusableSkeletonItem.jsx +import React from 'react'; +import { Grid } from '@mui/material'; +import SkeletonStoreItem from './SkeletonStoreItem'; // Or your Skeleton Deck Item + +const ReusableSkeletonItem = ({ itemType, count, gridItemProps }) => ( + <> + {Array(count) + .fill(0) + .map((_, index) => ( + + {/* Use your specific skeleton item */} + + ))} + +); + +export default ReusableSkeletonItem; diff --git a/src/components/grids/searchResultsGrids/DeckSearchCardGrid.jsx b/src/components/grids/searchResultsGrids/DeckSearchCardGrid.jsx index af2f5c8..3861ac0 100644 --- a/src/components/grids/searchResultsGrids/DeckSearchCardGrid.jsx +++ b/src/components/grids/searchResultsGrids/DeckSearchCardGrid.jsx @@ -6,14 +6,11 @@ import { styled } from '@mui/system'; import SkeletonDeckItem from '../gridItems/SkeletonDeckItem'; import DeckItem from '../gridItems/DeckItem'; import useResponsiveStyles from '../../../context/hooks/useResponsiveStyles'; +import GridLayout from './GridLayout'; +import ReusableSkeletonItem from '../gridItems/ReusableSkeletonItem'; const DeckSearchCardGrid = ({ cards, userDecks }) => { - const { theme } = useMode(); const [isLoading, setIsLoading] = useState(true); - const { getStyledGridStyle, getStyledGridItemStyle } = - useResponsiveStyles(theme); - const StyledGrid = getStyledGridStyle(theme); - const StyledGridItem = getStyledGridItemStyle(theme); useEffect(() => { const timer = setTimeout(() => setIsLoading(false), 1000); @@ -25,55 +22,34 @@ const DeckSearchCardGrid = ({ cards, userDecks }) => { [cards] ); - if (isLoading) { - return ( - - {Array.from({ length: 9 }).map((_, index) => ( - - - - ))} - - ); - } - return ( - - {uniqueCards.map((card) => ( - - - - {/* */} - - - ))} - + + {/* Define the grid item size for 3 columns layout */} + {isLoading ? ( + + ) : ( + uniqueCards.map((card) => ( + + + + + + )) + )} + ); }; diff --git a/src/components/grids/searchResultsGrids/GridLayout.jsx b/src/components/grids/searchResultsGrids/GridLayout.jsx new file mode 100644 index 0000000..21144f5 --- /dev/null +++ b/src/components/grids/searchResultsGrids/GridLayout.jsx @@ -0,0 +1,36 @@ +// GridLayoutComponent.jsx +import React from 'react'; +import { Grid, Container } from '@mui/material'; +import { CSSTransition, TransitionGroup } from 'react-transition-group'; + +const GridLayout = ({ + children, + containerStyles, + isLoading, + skeletonCount, + gridItemProps, +}) => ( + + + + {/* Ensure children are properly formatted for transition */} + {React.Children.map(children, (child, index) => ( + + {React.cloneElement(child, gridItemProps)} + + ))} + {/* {isLoading + ? Array.from({ length: skeletonCount }).map((_, index) => ( + + + {React.cloneElement(children, { key: index })} + + + )) + : children} */} + + + +); + +export default GridLayout; diff --git a/src/components/grids/searchResultsGrids/ProductGrid.js b/src/components/grids/searchResultsGrids/ProductGrid.js index 34ed5f3..3e388af 100644 --- a/src/components/grids/searchResultsGrids/ProductGrid.js +++ b/src/components/grids/searchResultsGrids/ProductGrid.js @@ -1,15 +1,17 @@ import React, { useState, useEffect, useMemo, useRef } from 'react'; import { Grid, Container } from '@mui/material'; -import StoreItem from '../gridItems/StoreItem'; // Ensure StoreItem is wrapped with React.memo import CustomPagination from '../../reusable/CustomPagination'; import { useCardStore, useMode } from '../../../context'; import SkeletonStoreItem from '../gridItems/SkeletonStoreItem'; // A new component for skeleton screens import useResponsiveStyles from '../../../context/hooks/useResponsiveStyles'; +import GridLayout from './GridLayout'; +import ReusableSkeletonItem from '../gridItems/ReusableSkeletonItem'; +import StoreItem from '../gridItems/StoreItem'; const ProductGrid = ({ updateHeight }) => { const { theme } = useMode(); const { getProductGridContainerStyle } = useResponsiveStyles(theme); // Use the hook - const containerStyles = getProductGridContainerStyle(); // Get the styles + const containerStyles = getProductGridContainerStyle(theme); // Get the styles const { searchData, setSlicedAndMergedSearchData, isCardDataValid } = useCardStore(); const [page, setPage] = useState(1); @@ -33,37 +35,27 @@ const ProductGrid = ({ updateHeight }) => { }, [currentStoreSearchData, updateHeight]); return ( - - - {isCardDataValid - ? currentStoreSearchData?.map((card, index) => ( - - - - )) - : Array(itemsPerPage) - .fill(0) - .map((_, index) => ( - - - - ))} - + + {/* Define the grid item size for 3 columns layout */} + {isCardDataValid ? ( + currentStoreSearchData?.map((card, index) => ( + + + + )) + ) : ( + + )} - + ); }; diff --git a/src/components/headings/collection/SelectCollectionHeader.jsx b/src/components/headings/collection/SelectCollectionHeader.jsx index 3e3bf0d..3a7b52a 100644 --- a/src/components/headings/collection/SelectCollectionHeader.jsx +++ b/src/components/headings/collection/SelectCollectionHeader.jsx @@ -5,11 +5,11 @@ import { useMode } from '../../../context'; const SelectCollectionHeader = ({ openNewDialog }) => { const { theme } = useMode(); return ( - + Choose a Collection @@ -23,7 +23,7 @@ const SelectCollectionHeader = ({ openNewDialog }) => { - - - ); -}; +// const SearchForm = ({ +// searchTerm, +// handleChange, +// handleSubmit, +// handleKeyPress, +// }) => { +// const { theme } = useMode(); +// return ( +// +//
{ +// e.preventDefault(); +// handleSubmit(); +// }} +// > +// +// +// +//
+// ); +// }; -// export default SearchForm; +// // export default SearchForm; diff --git a/src/components/other/search/SearchFormB.jsx b/src/components/other/search/SearchFormB.jsx new file mode 100644 index 0000000..7f0e0b5 --- /dev/null +++ b/src/components/other/search/SearchFormB.jsx @@ -0,0 +1,58 @@ +// SearchForm.jsx +import React from 'react'; +import { + TextField, + MenuItem, + FormControl, + Select, + InputLabel, +} from '@mui/material'; +import { styled } from '@mui/system'; + +const StyledFormControl = styled(FormControl)(({ theme }) => ({ + minWidth: 120, +})); + +const SearchFormB = ({ + searchSettings, + handleSearch, + handleSearchBy, + handleSortBy, +}) => { + return ( + <> + + + Search by + + + + Sort by + + + + ); +}; + +export default SearchFormB; diff --git a/src/components/other/search/SearchSettings.jsx b/src/components/other/search/SearchSettings.jsx new file mode 100644 index 0000000..a7fa97c --- /dev/null +++ b/src/components/other/search/SearchSettings.jsx @@ -0,0 +1,86 @@ +// SearchSettings.jsx +import React, { useState } from 'react'; +import { Paper, Box, Container, Typography } from '@mui/material'; +import { styled } from '@mui/system'; +import SearchFormB from './SearchFormB'; // Ensure path is correct +import { useMode } from '../../../context'; + +export const commonPaperStyles = (theme) => ({ + padding: theme.spacing(3), + borderRadius: theme.shape.borderRadius, + background: theme.palette.success.dark, + boxShadow: theme.shadows[3], + margin: 'auto', + width: '100%', + maxWidth: 'md', + '&:hover': { + boxShadow: theme.shadows[6], + }, +}); + +const SearchSettingsBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), +})); + +const SearchSettings = () => { + const [searchSettings, setSearchSettings] = useState({ + search: '', + searchBy: 'title', + sortBy: 'release_date', + }); + + const { theme } = useMode(); + + const handleSearch = (e) => { + setSearchSettings({ + ...searchSettings, + search: e.target.value, + }); + }; + + const handleSearchBy = (e) => { + setSearchSettings({ + ...searchSettings, + searchBy: e.target.value, + }); + }; + + const handleSortBy = (e) => { + setSearchSettings({ + ...searchSettings, + sortBy: e.target.value, + }); + }; + + return ( + + + {' '} + {/* + Search Settings + */} + + + + + + ); +}; + +export default SearchSettings; diff --git a/src/components/reusable/PrivateRoute.jsx b/src/components/reusable/PrivateRoute.jsx index c4c3dba..f7856e1 100644 --- a/src/components/reusable/PrivateRoute.jsx +++ b/src/components/reusable/PrivateRoute.jsx @@ -4,9 +4,11 @@ import { useAuthContext } from '../../context'; const PrivateRoute = ({ children }) => { const { isLoggedIn } = useAuthContext(); - if (!isLoggedIn) return ; - // If isLoggedIn from either cookie or context is true, proceed to the route + if (!isLoggedIn) { + return ; + } + return children; }; diff --git a/src/containers/collectionPageContainers/CollectionPortfolioChartContainer.jsx b/src/containers/collectionPageContainers/CollectionPortfolioChartContainer.jsx index 99ac52c..0f17ec7 100644 --- a/src/containers/collectionPageContainers/CollectionPortfolioChartContainer.jsx +++ b/src/containers/collectionPageContainers/CollectionPortfolioChartContainer.jsx @@ -1,6 +1,20 @@ -import React from 'react'; -import { Box, Grid, useTheme, useMediaQuery } from '@mui/material'; -import PortfolioChart from '../../components/other/dataDisplay/chart/PortfolioChart'; +import React, { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { + Box, + Grid, + useTheme, + useMediaQuery, + Paper, + Container, +} from '@mui/material'; +import PortfolioChart from '../../assets/cleanup/PortfolioChart'; import TimeRangeSelector from '../../components/other/InputComponents/TimeRangeSelector'; import UpdateStatusBox2 from '../../components/other/InputComponents/UpdateStatusBox2'; import TopCardsDisplay from '../../components/other/dataDisplay/TopCardsDisplay'; @@ -11,16 +25,116 @@ import { useCombinedContext, useStatisticsStore, useMode, + ErrorBoundary, + usePageContext, } from '../../context'; +import { styled } from '@mui/styles'; +import { debounce } from 'lodash'; +import LoadingIndicator from '../../components/reusable/indicators/LoadingIndicator'; +import LinearChart from '../../components/other/dataDisplay/chart/LinearChart'; + +const ChartPaper = styled(Paper)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.secondary, + padding: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + minHeight: '400px', + overflow: 'hidden', + margin: theme.spacing(2, 0), + flexGrow: 1, +})); + +const ResponsiveSquare = styled(Box)(({ theme }) => ({ + width: '100%', + paddingTop: '100%', + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + display: 'flex', + flexGrow: 1, + alignItems: 'center', + justifyContent: 'center', +})); +function handleThresholdUpdate(lastUpdateTime, setLastUpdateTime) { + const currentTime = new Date().getTime(); + if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { + setLastUpdateTime(currentTime); + return currentTime; + } + return lastUpdateTime; +} const CollectionPortfolioChartContainer = ({ selectedCards, removeCard }) => { const { theme } = useMode(); const theme2 = useTheme(); const isMobile = useMediaQuery(theme2.breakpoints.down('sm')); - const { timeRange } = useChartContext(); + const [dataReady, setDataReady] = useState(false); + + const { latestData, timeRange, groupAndAverageData, convertDataForNivo2 } = + useChartContext(); const { allCollections } = useCollectionStore(); const { socket } = useCombinedContext(); - const { stats, statsByCollectionId } = useStatisticsStore(); + const { stats, statsByCollectionId, markers } = useStatisticsStore(); + const { selectedCollection, allXYValues } = useCollectionStore(); + const lastUpdateTimeRef = useRef(null); + + const chartRef = useRef(null); + const [chartDimensions, setChartDimensions] = useState({ + width: 0, + height: 0, + }); + const HEIGHT_TO_WIDTH_RATIO = 7 / 10; + const threshold = useMemo( + () => handleThresholdUpdate(lastUpdateTimeRef), + [lastUpdateTimeRef] + ); + // Dynamically calculate and set chart dimensions + const calculateChartDimensions = useCallback(() => { + if (chartRef.current) { + const width = chartRef.current.offsetWidth; + const height = width * HEIGHT_TO_WIDTH_RATIO; // Maintain aspect ratio + setChartDimensions({ width, height }); + } + }, []); + + useEffect(() => { + calculateChartDimensions(); // Calculate on initial load + const handleResize = debounce(calculateChartDimensions, 100); // Debounce resize event + window.addEventListener('resize', handleResize); + return () => { + handleResize.cancel(); + window.removeEventListener('resize', handleResize); + }; + }, [calculateChartDimensions]); + + if (!allXYValues?.length) { + return ; // Show loading indicator while data is being fetched + } + + const rawData = groupAndAverageData(allXYValues, threshold, timeRange); + const nivoReadyData = convertDataForNivo2(rawData); + + if (!nivoReadyData.length) { + return No data available; + } + + // Effect: Set data ready state based on allXYValues + useEffect(() => { + if (Array.isArray(allXYValues) && allXYValues?.length > 0) { + setDataReady(true); + } + }, [allXYValues]); + useLayoutEffect(() => { + if (dataReady) { + calculateChartDimensions(); + } + }, [dataReady]); return ( { {/* Portfolio Chart Row */} - + + + {/* Portfolio Chart */} + + + + + + + + {/* + + + + {allXYValues?.length > 0 && rawData2?.length > 0 ? ( + + ) : ( +
No data available
+ )} +
+
+
+
*/} +
+
diff --git a/src/context/AuthContext/authContext.js b/src/context/AuthContext/authContext.js index e0964d5..f1859ec 100644 --- a/src/context/AuthContext/authContext.js +++ b/src/context/AuthContext/authContext.js @@ -1,33 +1,70 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ import React, { useState, useEffect, useCallback, useRef, useContext, + createContext, + useMemo, } from 'react'; import axios from 'axios'; import { useCookies } from 'react-cookie'; import { processResponseData } from './helpers'; import { usePageContext } from '../PageContext/PageContext'; -export const AuthContext = React.createContext(); - +// export const AuthContext = React.createContext(); +export const AuthContext = createContext({ + isLoggedIn: false, + authUser: null, + token: null, + user: null, + responseData: null, + login: () => {}, + signup: () => {}, + logout: () => {}, + resetLogoutTimer: () => {}, + setUser: () => {}, + setIsLoggedIn: () => {}, + setAuthUser: () => {}, +}); export default function AuthProvider({ children, serverUrl }) { const { setLoading } = usePageContext(); const [cookies, setCookie, removeCookie] = useCookies([ + 'basicData', + 'securityData', 'user', 'isLoggedIn', 'userId', 'authUser', 'authToken', + 'lastLogin', + 'lastLogout', ]); const [isLoggedIn, setIsLoggedIn] = useState(false); - const [user, setUser] = useState(null); - const [authUser, setAuthUser] = useState(null); - const [responseData, setResponseData] = useState(null); + const [basicData, setBasicData] = useState(cookies.basicData); + const [securityData, setSecurityData] = useState(cookies.securityData); + const [user, setUser] = useState(cookies.user); + const [authUser, setAuthUser] = useState(cookies.authUser); const [token, setToken] = useState(cookies.authToken); + const [responseData, setResponseData] = useState(null); + const lastLogin = useRef(null); const logoutTimerRef = useRef(null); - + // function to set login times for tracking + const setLoginTimes = useCallback(() => { + lastLogin.current = new Date(); + setCookie('lastLogin', lastLogin.current, { path: '/' }); + setCookie('lastLogout', logoutTimerRef.current, { path: '/' }); + }, [setCookie, logoutTimerRef]); + // value for tracking changes in login status + const [loginStatus, setLoginStatus] = useState({ + isLoggedIn: isLoggedIn, + lastLogin: lastLogin.current, + lastLogout: logoutTimerRef.current, + authUser: authUser, + token: token, + user: user, + }); const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER; const executeAuthAction = async (actionType, url, requestData) => { @@ -40,19 +77,48 @@ export default function AuthProvider({ children, serverUrl }) { console.log('Response:', response); const processedData = processResponseData(response, actionType); if (response.status === 200 || response.status === 201) { - const { token, userData, authData, message, data } = processedData; + const { + token, + userData, + authData, + message, + data, + securityData, + basicData, + } = processedData; + // console.log('Processed Data: ', data); console.log('Processed Data Message: ', message); console.log('Processed User Data: ', userData); + console.log('Processed Auth Data', authData); + console.log('Processed Token: ', token); + console.log('Processed Security Data: ', securityData); + console.log('Processed Basic Data: ', basicData); setToken(token); - setUser(userData); + setBasicData(basicData); + setSecurityData(securityData); + setUser(data); setAuthUser(authData); setResponseData(data); setIsLoggedIn(true); + setLoginTimes(); + setLoginStatus({ + isLoggedIn: isLoggedIn, + lastLogin: lastLogin.current, + lastLogout: logoutTimerRef.current, + authUser: authUser, + token: token, + user: user, + }); + setCookie('authToken', token, { path: '/' }); - setCookie('authUser', authData, { path: '/' }); + setCookie('basicData', basicData, { path: '/' }); + setCookie('securityData', securityData, { path: '/' }); setCookie('user', userData, { path: '/' }); + setCookie('authUser', authData, { path: '/' }); setCookie('isLoggedIn', true, { path: '/' }); setCookie('userId', authData?.id, { path: '/' }); + setCookie('lastLogin', lastLogin.current, { path: '/' }); + setCookie('lastLogout', logoutTimerRef.current, { path: '/' }); } } catch (error) { console.error('Auth error:', error); @@ -60,86 +126,150 @@ export default function AuthProvider({ children, serverUrl }) { setLoading('isPageLoading', false); } }; - + // LOGIC FOR LOGOUT, TOKEN EXPIRATION, AND USER ACTIVITY const logout = useCallback(() => { removeCookie('authToken'); removeCookie('user'); removeCookie('authUser'); removeCookie('isLoggedIn'); removeCookie('userId'); + removeCookie('lastLogin'); + removeCookie('lastLogout'); setIsLoggedIn(false); + setLoginStatus({ + isLoggedIn: isLoggedIn, + lastLogin: lastLogin.current, + lastLogout: logoutTimerRef.current, + }); setUser(null); setToken(null); if (logoutTimerRef.current) clearTimeout(logoutTimerRef.current); }, [removeCookie]); - const resetLogoutTimer = useCallback(() => { clearTimeout(logoutTimerRef.current); logoutTimerRef.current = setTimeout(logout, 2700000); // 45 minutes }, [logout]); - useEffect(() => { if (token) { + console.log('Token found, resetting logout timer...'); resetLogoutTimer(); } }, [token, resetLogoutTimer]); - useEffect(() => { - axios.interceptors.request.use( + const interceptorId = axios.interceptors.request.use( (config) => { if (token) config.headers.Authorization = `Bearer ${token}`; return config; }, (error) => Promise.reject(error) ); + + return () => axios.interceptors.request.eject(interceptorId); }, [token]); + // LOGIC FOR LOGIN AND SIGNUP const login = async (username, password) => { await executeAuthAction('signin', 'signin', { userSecurityData: { username, password }, }); }; - const signup = async (securityData, basicData) => { await executeAuthAction('signup', 'signup', { userSecurityData: securityData, userBasicData: basicData, }); }; - useEffect(() => { + console.log('Auth Context: '); const storedToken = cookies['authToken']; + // const storedUserId = cookies['userId']; + const storedUserBasicData = cookies['basicData']; + const storedUserSecurityData = cookies['securityData']; const storedUser = cookies['user']; const storedAuthUser = cookies['authUser']; if (storedToken && storedUser) { setToken(storedToken); + setBasicData(storedUserBasicData); + setSecurityData(storedUserSecurityData); setUser(storedUser); + setAuthUser(storedAuthUser); setIsLoggedIn(true); + setLoginTimes(); + setLoginStatus({ + isLoggedIn: isLoggedIn, + lastLogin: lastLogin.current, + lastLogout: logoutTimerRef.current, + authUser: authUser, + token: token, + user: user, + }); resetLogoutTimer(); } - }, [cookies, resetLogoutTimer]); - + }, [ + cookies['authToken'], + cookies['basicData'], + cookies['securityData'], + cookies['user'], + cookies['authUser'], + resetLogoutTimer, + ]); + const contextValue = useMemo( + () => ({ + isLoggedIn, + authUser, + token, + user, + basicData, + securityData, + responseData, + loginStatus, + + login, + signup, + logout, + resetLogoutTimer, + setUser, + setIsLoggedIn, + setAuthUser, + }), + [ + loginStatus, + isLoggedIn, + authUser, + token, + user, + responseData, + login, + signup, + logout, + resetLogoutTimer, + setUser, + setIsLoggedIn, + setAuthUser, + ] + ); + useEffect(() => { + console.log('AUTH CONTEXT:', { + isLoggedIn, + authUser, + token, + user, + responseData, + loginStatus, + }); + }, [ + // login, + // signup, + // logout, + // resetLogoutTimer, + setUser, + setIsLoggedIn, + setLoginStatus, + setAuthUser, + ]); return ( - - {children} - + {children} ); } diff --git a/src/context/AuthContext/helpers.jsx b/src/context/AuthContext/helpers.jsx index 39ebcb6..c83604f 100644 --- a/src/context/AuthContext/helpers.jsx +++ b/src/context/AuthContext/helpers.jsx @@ -1,9 +1,18 @@ import jwt_decode from 'jwt-decode'; +// login status export const LOGGED_IN_COOKIE = false; +// token export const AUTH_COOKIE = 'authToken'; -export const USER_COOKIE = 'user'; +// user login token + data export const AUTH_USER_COOKIE = 'authUser'; +// user basic data +export const USER_BASIC_DATA_COOKIE = 'userBasicData'; +// user security data +export const USER_SECURITY_DATA_COOKIE = 'userSecurityData'; +// all user data +export const USER_COOKIE = 'user'; +// user id export const USER_ID_COOKIE = 'userId'; // Validator function export const validateData = (data, eventName, functionName) => { @@ -19,13 +28,15 @@ export const processResponseData = (response, type) => { // if (!validateData(data, `${type} Response`, `process${type}Data`)) // return null; const { message, data } = response.data; + console.log('message --------------->', message); const token = data?.token; console.log('token --------------->', token); if (!token) return null; const processedData = { token: token, authData: jwt_decode(token), - userData: data?.user, + basicData: data?.userBasicData, + securityData: data?.userSecurityData, data: data, message: message, }; diff --git a/src/context/CardContext/CardStore.js b/src/context/CardContext/CardStore.js index 493cd53..a18576b 100644 --- a/src/context/CardContext/CardStore.js +++ b/src/context/CardContext/CardStore.js @@ -8,7 +8,6 @@ import { useLayoutEffect, } from 'react'; import { useCookies } from 'react-cookie'; -import { useCombinedContext } from '../CombinedContext/CombinedProvider'; import { useCollectionStore } from '../CollectionContext/CollectionContext'; import { useUserContext } from '../UserContext/UserContext'; @@ -17,10 +16,11 @@ const CardContext = createContext(); export const CardProvider = ({ children }) => { const { user } = useUserContext(); - const [cookies, setCookie] = useCookies(['deckData']); + const [cookies, setCookie] = useCookies(['deckData', 'searchHistory']); const initialStore = cookies.store || []; const [cardsArray, setCardsArray] = useState(initialStore); const { allCollections, setAllCollections } = useCollectionStore(); + const [image, setImage] = useState(null); // const [searchParam, setSearchParam] = useState(''); // const [searchParams, setSearchParams] = useState([]); // const [searchParams, setSearchParams] = useState({ @@ -42,18 +42,29 @@ export const CardProvider = ({ children }) => { const currentDeckData = cookies.deck || []; const [savedDeckData, setSavedDeckData] = useState(currentDeckData); const [deckSearchData, setDeckSearchData] = useState([]); + const [searchHistory, setSearchHistory] = useState([]); if (!currenCartArray || !savedDeckData) { return
Loading...
; } - const handleRequest = async (searchParamss) => { + const handleRequest = async (searchParams) => { try { + console.log('SEARCH PARAMS: ', searchParams); // setSearchParams([searchParams]); + // Adding a unique dummy parameter or timestamp to the request + const uniqueParam = { requestTimestamp: new Date().toISOString() }; + const requestBody = { + ...searchParams, + ...uniqueParam, // Including the unique or dummy parameter + }; + // update history array with new search params + setSearchHistory([...searchHistory, searchParams]); + setCookie('searchHistory', searchParams, { path: '/' }); const response = await axios.post( `${process.env.REACT_APP_SERVER}/api/cards/ygopro`, - searchParamss + requestBody ); if (response.data.data) { @@ -94,6 +105,7 @@ export const CardProvider = ({ children }) => { setCookie('deckSearchData', uniqueCards, { path: '/' }); setCookie('rawSearchData', uniqueCards, { path: '/' }); // setCookie('organizedSearchData', limitedCardsToRender, { path: '/' }); + return response?.data?.data; } else { setSearchData([]); setDeckSearchData([]); @@ -104,52 +116,102 @@ export const CardProvider = ({ children }) => { console.error(err); } }; + // Example client-side function to fetch an image + // Function to request the card's image + // const fetchCardImage = async (cardId) => { + // try { + // // Ensure there's a cardId to fetch + // if (!cardId) { + // console.error('No card ID provided for image fetch'); + // return; + // } - const handlePatch = async () => { - let needsUpdate = false; - - for (const collection of allCollections) { - for (const card of collection.cards) { - if ( - !user.id || - !card.card_images || - !card.card_sets || - !card.card_prices - ) { - needsUpdate = true; - const response = await axios.patch( - `${process.env.REACT_APP_SERVER}/api/cards/ygopro/${card.name}`, - { id: card.id, user: user.id }, - { withCredentials: true } - ); - if (response.data && response.data.data) { - const updatedCard = response.data.data; - const cardIndex = collection.cards.findIndex( - (c) => c.id === updatedCard.id - ); - if (cardIndex !== -1) { - collection.cards[cardIndex] = updatedCard; - } - } - } - } - } + // // Constructing the query URL + // const url = `${process.env.REACT_APP_SERVER}/api/card/image/${cardId}`; - if (needsUpdate) { - setAllCollections([...allCollections]); - } - }; + // const response = await axios.get(url); + // if (response.status === 200 && response.data) { + // // Assuming response.data contains the URL of the image + // const imageUrl = response.data.imageUrl; + + // // Update your state or perform actions with the image URL + // setImage(imageUrl); + // } else { + // throw new Error('Failed to fetch image'); + // } + // } catch (error) { + // console.error('Error fetching the card image:', error); + // } + // }; + // Example client-side function to fetch a card's image through the server + // const fetchCardImage = async (cardData) => { + // try { + // const response = await axios.get( + // `${process.env.REACT_APP_SERVER}/api/card/ygopro`, + // { + // params: { name: cardData.name }, + // } + // ); + // if (response.data) { + // // Assuming the server returns the image as a base64 string + // const imageBase64 = response.data; + // console.log('IMAGE BASE 64: ', imageBase64); + // setImage(imageBase64); + // return imageBase64; + // } + // throw new Error('Failed to fetch image.'); + // } catch (error) { + // console.error('Error fetching the image:', error); + // throw error; + // } + // }; + + // const handlePatch = async () => { + // let needsUpdate = false; + + // for (const collection of allCollections) { + // for (const card of collection.cards) { + // if ( + // !user.id || + // !card.card_images || + // !card.card_sets || + // !card.card_prices + // ) { + // needsUpdate = true; + // console.log('PATCHING CARD: ', card); + // const response = await axios.patch( + // `${process.env.REACT_APP_SERVER}/api/cards/ygopro/${card.name}`, + // { id: card.id, user: user.id }, + // { withCredentials: true } + // ); + // if (response.data && response.data.data) { + // const updatedCard = response.data.data; + // const cardIndex = collection.cards.findIndex( + // (c) => c.id === updatedCard.id + // ); + // if (cardIndex !== -1) { + // collection.cards[cardIndex] = updatedCard; + // } + // } + // } + // } + // } + + // if (needsUpdate) { + // setAllCollections([...allCollections]); + // } + // }; useLayoutEffect(() => { // Check if there's any collection that requires an update const hasMissingData = allCollections?.some((collection) => collection.cards?.some( - (card) => !card.card_images || !card.card_sets || !card.card_prices + (card) => !card?.card_images || !card?.card_sets || !card?.card_prices ) ); if (hasMissingData) { - handlePatch(); + // handlePatch(); } }, [allCollections]); // Keep the dependency array, but now it only triggers when necessary @@ -184,8 +246,11 @@ export const CardProvider = ({ children }) => { organizedSearchData, isCardDataValid, slicedAndMergedSearchData, + image, + // fetchCardImage, + setImage, - handlePatch, + // handlePatch, setSlicedAndMergedSearchData, setOrganizedSearchData, setRawSearchData, diff --git a/src/context/CardImagesContext/CardImagesContext.jsx b/src/context/CardImagesContext/CardImagesContext.jsx index aa1bd03..6277838 100644 --- a/src/context/CardImagesContext/CardImagesContext.jsx +++ b/src/context/CardImagesContext/CardImagesContext.jsx @@ -34,26 +34,29 @@ export const CardImagesProvider = ({ children }) => { const [cards, setCards] = useState([]); const [images, setImages] = useState([]); // [ const [randomCardImage, setRandomCardImage] = useState(null); + const [imageSrc, setImageSrc] = 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`; + // Function to download card images 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); - } - }); + const fetchedCards = response.data; + const imageUrls = fetchedCards + .map((card) => { + if (card.card_images && card.card_images.length > 0) { + return card.card_images[0].image_url + '?dummy=' + Date.now(); + } + return null; // or some placeholder image URL + }) + .filter(Boolean); // Remove any nulls + + setCards(fetchedCards); + setImages(imageUrls); // Set all image URLs at once } catch (error) { console.error('Error in downloadCardImages:', error); setError(error.message); @@ -62,21 +65,24 @@ export const CardImagesProvider = ({ children }) => { } }; - useEffect(() => { - downloadCardImages(); - }, []); + // useEffect(() => { + // downloadCardImages(); + // }, []); + // Handle random card image selection 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); + const randomCardIndex = Math.floor(Math.random() * cards.length); + const randomCard = cards[randomCardIndex]; + if ( + randomCard && + randomCard.card_images && + randomCard.card_images.length > 0 + ) { + setRandomCardImage(randomCard.card_images[0].image_url); } } - }, [cards]); + }, [cards]); // Dependency on cards means this will rerun when cards are fetched/updated useEffect(() => { if (images && images.length > 0) { @@ -95,6 +101,9 @@ export const CardImagesProvider = ({ children }) => { randomCardImage, isLoading, error, + imageSrc, + + setImageSrc, downloadCardImages, setImages, }} @@ -103,5 +112,3 @@ export const CardImagesProvider = ({ children }) => { ); }; - -export default CardImagesProvider; diff --git a/src/context/CardImagesContext/useCardManager.jsx b/src/context/CardImagesContext/useCardManager.jsx new file mode 100644 index 0000000..131b9fe --- /dev/null +++ b/src/context/CardImagesContext/useCardManager.jsx @@ -0,0 +1,39 @@ +import { useState } from 'react'; + +const useCardManager = (initialCards = []) => { + const [cards, setCards] = useState(initialCards); + + // General function to update a card + const updateCard = (cardId, updatedData) => { + setCards((currentCards) => + currentCards.map((card) => + card.id === cardId ? { ...card, ...updatedData } : card + ) + ); + }; + + // Add a new card + const addCard = (newCardData) => { + setCards([...cards, newCardData]); + }; + + // General utility to update a specific field of a card + const updateField = (cardId, field, value) => { + updateCard(cardId, { [field]: value }); + }; + + // Update a nested field like priceHistory or card_images + const updateNestedField = (cardId, field, newValue) => { + const card = cards.find((card) => card.id === cardId); + updateCard(cardId, { [field]: [...card[field], newValue] }); + }; + + return { + cards, + addCard, + updateCard: updateField, // for updating single fields + updateNestedField, // for updating nested fields like arrays + }; +}; + +export default useCardManager; diff --git a/src/context/CardImagesContext/useCardVariantManager.jsx b/src/context/CardImagesContext/useCardVariantManager.jsx new file mode 100644 index 0000000..b5762fb --- /dev/null +++ b/src/context/CardImagesContext/useCardVariantManager.jsx @@ -0,0 +1,73 @@ +import { useState } from 'react'; + +const useCardVariantManager = (initialCards = []) => { + const [cards, setCards] = useState(initialCards); + + // Function to identify the variant index + const findVariantIndex = (cardId, setCode) => + cards.findIndex( + (card) => card.id === cardId && card.card_set.set_code === setCode + ); + + // Function to determine overlay based on rarity + const getOverlayByRarity = (rarity) => { + // Define overlays for different rarities + const overlays = { + Common: 'commonOverlay', + Rare: 'rareOverlay', + 'Ultra Rare': 'ultraRareOverlay', + // ... other rarities + }; + return overlays[rarity] || 'defaultOverlay'; + }; + + // Function to update a specific variant field + const updateVariantField = (cardId, setCode, field, value) => { + const index = findVariantIndex(cardId, setCode); + if (index < 0) return; // Variant not found + + const updatedCards = [...cards]; + updatedCards[index] = { + ...updatedCards[index], + [field]: value, + // Update overlay based on rarity + overlay: getOverlayByRarity(updatedCards[index].card_set.set_rarity), + }; + setCards(updatedCards); + }; + + // Function to add a new variant + const addVariant = (cardId, newVariantData) => { + const updatedCards = [...cards]; + updatedCards.push({ + ...newVariantData, + id: cardId, + // Assign overlay based on rarity + overlay: getOverlayByRarity(newVariantData.card_set.set_rarity), + }); + setCards(updatedCards); + }; + + // Function to update nested fields like priceHistory for a specific variant + const updateNestedField = (cardId, setCode, field, newValue) => { + const index = findVariantIndex(cardId, setCode); + if (index < 0) return; // Variant not found + + const updatedCards = [...cards]; + const variant = updatedCards[index]; + updatedCards[index] = { + ...variant, + [field]: [...variant[field], newValue], + }; + setCards(updatedCards); + }; + + return { + cards, + addVariant, + updateVariantField, + updateNestedField, + }; +}; + +export default useCardVariantManager; diff --git a/src/context/CartContext/CartContext.js b/src/context/CartContext/CartContext.js index 83ea2ba..fd29785 100644 --- a/src/context/CartContext/CartContext.js +++ b/src/context/CartContext/CartContext.js @@ -41,7 +41,7 @@ export const CartProvider = ({ children }) => { totalPrice: 0, // Total price of items }); const [cookies, setCookie] = useCookies(['authUser', 'cart', 'cartData']); - const userId = cookies?.authUser?.id; + const userId = cookies?.authUser?.userId; const [totalQuantity, setTotalQuantity] = useState(0); const [totalPrice, setTotalPrice] = useState(0); @@ -70,8 +70,10 @@ export const CartProvider = ({ children }) => { 'POST', JSON.stringify({ userId }) ); - console.log('NEW CART DATA:', newCartData); - setCartDataAndCookie(newCartData); + const { message, data } = newCartData; + console.log('CREATE CART: -----> response message', message); + console.log('CART DATA: -----> response data', data); + setCartDataAndCookie(data); } catch (error) { console.error('Error creating cart:', error); @@ -100,7 +102,7 @@ export const CartProvider = ({ children }) => { await createUserCart(); } } - }, [userId, setCookie]); + }, [userId]); // Set cart data and cookie const setCartDataAndCookie = useCallback( @@ -131,78 +133,145 @@ export const CartProvider = ({ children }) => { } }, [userId, fetchUserCart]); useEffect(() => { - const newTotalQuantity = cartData.cart.reduce( - (total, item) => total + item.quantity, + const newTotalQuantity = cartData?.cart?.reduce( + (total, item) => total + item?.quantity, 0 ); setTotalQuantity(newTotalQuantity); setTotalPrice(totalCost); }, [cartData.cart, totalCost]); + const updateCart = useCallback( - async (cartId, updatedCart) => { - if (!userId) return; - if (!cartId) return; + async (cartId, updatedCart, method) => { + if (!userId || !cartId) return; + const formattedCartData = { - cartItems: updatedCart.map((item) => ({ - ...item, - id: item.id, - quantity: item.quantity, - })), userId: userId, + cart: updatedCart.map((item) => ({ + id: item.id, // assuming id is the unique identifier for each cart item + quantity: item.quantity, // ensure this is the current quantity to be updated in the cart + price: item.price, // ensure this is the current price of the item + // Include other necessary fields as per your cart item structure + })), + method: method, // 'POST' for adding items, 'DELETE' for removing items, 'PUT' for updating items + // Calculating total quantity and total price outside of the cart array + quantity: updatedCart.reduce((total, item) => total + item.quantity, 0), + totalPrice: updatedCart.reduce( + (total, item) => total + item.quantity * item.price, + 0 + ), }; - // const response = await fetchWrapper( - // `${process.env.REACT_APP_SERVER}${`/api/carts/userCart/${userId}`}`, - // 'GET' - // ); - const data = await fetchWrapper( - `${process.env.REACT_APP_SERVER}/api/users/${userId}/cart/${cartId}/update`, - 'PUT', - formattedCartData - // JSON.stringify(formattedCartData) - ); - setCartDataAndCookie(data); - return data; + + try { + const response = await fetch( + `${process.env.REACT_APP_SERVER}/api/users/${userId}/cart/${cartId}/update`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formattedCartData), + } + ); + + const { message, data } = await response.json(); + console.log('PUT: /cart -----> response message', message); + if (response.ok) { + console.log('PUT: /cart -----> response data', data); + setCartDataAndCookie(data); // Update your cart state and cookie here + } else { + console.error( + 'Failed to update cart: ', + data?.error || 'Error occurred' + ); + // Handle errors appropriately (e.g., show an error message to the user) + } + } catch (error) { + console.error('Error updating cart: ', error); + // Handle errors appropriately (e.g., show an error message to the user) + } }, - [fetchWrapper, setCartDataAndCookie] + [userId, setCartDataAndCookie] // dependencies array ); const addOneToCart = useCallback( async (cardInfo) => { if (!cartData._id) return; - console.log('Adding one to cart', cardInfo); - const { quantityOfSameId, totalItems } = getCardQuantity( - cartData, - cardInfo.id - ); - if (quantityOfSameId >= 3) return; - let updatedCart = cartData.cart.map((item) => - item.id === cardInfo.id - ? { ...item, quantity: item.quantity + 1 } - : item + + const existingItem = cartData?.cart?.find( + (item) => item.id === cardInfo.id ); - if (!cartData.cart.some((item) => item.id === cardInfo.id)) { - updatedCart = [...updatedCart, { ...cardInfo, quantity: 1 }]; + const updatedExistingItem = { + ...cardInfo, + quantity: existingItem ? existingItem.quantity + 1 : 1, + totalPrice: existingItem + ? existingItem.totalPrice + existingItem.price + : 1, + }; + const updatedCart = cartData?.cart?.map((item) => { + return item.id === cardInfo.id ? updatedExistingItem : item; + }); + + let newItem = { + ...cardInfo, + quantity: 1, + totalPrice: cardInfo.price, + }; + if (!existingItem) { + updatedCart.push(newItem); // New item } - const updatedCartData = await updateCart(cartData._id, updatedCart); - console.log('UPDATED CART DATA:', updatedCartData); + + const method = existingItem ? 'PUT' : 'POST'; // Decide method based on whether the item exists + const updatedCartData = await updateCart( + cartData._id, + updatedCart, + method + // updatedExistingItem ? updatedExistingItem : newItem + ); if (updatedCartData) setCartData(updatedCartData); }, - [cartData._id, cartData.cart, getCardQuantity, updateCart] + [cartData, updateCart, setCartData] ); const removeOneFromCart = useCallback( async (cardInfo) => { - if (cartData.cart.some((item) => item.id === cardInfo.id)) { - const updatedCart = cartData.cart - .map((item) => - item.id === cardInfo.id && item.quantity > 0 - ? { ...item, quantity: item.quantity - 1 } - : item - ) - .filter((item) => item.quantity > 0); - const updatedCartData = await updateCart(cartData._id, updatedCart); - if (updatedCartData) setCartData(updatedCartData); + const existingItemIndex = cartData.cart.findIndex( + (item) => item.id === cardInfo.id + ); + + if (existingItemIndex === -1) { + console.error('Item not found in cart'); + return; // Item not found in cart + } + + const existingItem = cartData.cart[existingItemIndex]; + + // Decrement quantity or remove item from cart + let updatedCart; + let method; + if (existingItem.quantity > 1) { + // Decrement quantity by 1 + updatedCart = cartData?.cart?.map((item, index) => + index === existingItemIndex + ? { ...item, quantity: item.quantity - 1 } + : item + ); + method = 'PUT'; // Update the item quantity + } else { + // Remove item from cart as its quantity will be 0 + updatedCart = cartData.cart.filter( + (item, index) => index !== existingItemIndex + ); + method = 'DELETE'; // Remove the item from the cart } + + // Update the cart with new data + const updatedCartData = await updateCart( + cartData._id, + updatedCart, + method + ); + if (updatedCartData) setCartData(updatedCartData); }, - [cartData._id, cartData.cart, updateCart] + [cartData, updateCart, setCartData] // dependencies array ); const deleteFromCart = useCallback( async (cardInfo) => { @@ -236,7 +305,7 @@ export const CartProvider = ({ children }) => { addOneToCart, removeOneFromCart, deleteFromCart, - fetchUserCart, + fetchCartForUser: fetchUserCart, createUserCart, }), [ diff --git a/src/context/ChartContext/ChartContext.jsx b/src/context/ChartContext/ChartContext.jsx index 598219e..d50d3ac 100644 --- a/src/context/ChartContext/ChartContext.jsx +++ b/src/context/ChartContext/ChartContext.jsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState } from 'react'; +import { createContext, useContext, useMemo, useState } from 'react'; import { groupAndAverageData, convertDataForNivo2, @@ -34,6 +34,33 @@ export const ChartProvider = ({ children }) => { const handleChange = (e) => { setTimeRange(e.target.value); // Update timeRange based on selection }; + + const { tickValues, xFormat } = useMemo(() => { + let format, ticks; + switch (timeRange) { + case '2 hours': + format = '%H:%M'; + ticks = 'every 15 minutes'; + break; + case '24 hours': + format = '%H:%M'; + ticks = 'every hour'; + break; + case '7 days': + format = '%b %d'; + ticks = 'every day'; + break; + case '1 month': + format = '%b %d'; + ticks = 'every 3 days'; + break; + default: + format = '%b %d'; + ticks = 'every day'; + } + return { tickValues: ticks, xFormat: `time:${format}` }; + }, [timeRange]); + return ( { latestData, timeRange, timeRanges, + tickValues, + xFormat, groupAndAverageData, convertDataForNivo2, diff --git a/src/context/ChartContext/helpers.jsx b/src/context/ChartContext/helpers.jsx index 2850d27..c3097ce 100644 --- a/src/context/ChartContext/helpers.jsx +++ b/src/context/ChartContext/helpers.jsx @@ -1,4 +1,6 @@ +import { makeStyles } from '@mui/styles'; import { roundToNearestTenth } from '../Helpers'; +import { Tooltip, Typography } from '@mui/material'; export const groupAndAverageData = (data, threshold = 600000, timeRange) => { if (!data || data.length === 0) return []; @@ -8,6 +10,7 @@ export const groupAndAverageData = (data, threshold = 600000, timeRange) => { // console.log('Initial cluster with first data point: ', currentCluster); + // loop for each data point 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(); @@ -78,25 +81,72 @@ export const getTickValues = (timeRange) => { 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: ', rawData2); +// // console.log('rawData2.data: ', rawData2[0]); +// // rawData is assumed to be an array of objects with 'label', 'x', and 'y' properties +// switch (rawData2) { +// case rawData2[0].x instanceof Date: +// return rawData2.map((dataPoint) => ({ +// x: dataPoint[0]?.x, // x value is already an ISO date string +// y: dataPoint.y, // y value +// })); +// // equals array +// case typeof rawData2[0] === Array.isArray: +// return rawData2.map((dataPoint) => ({ +// x: new Date(dataPoint.x).toISOString(), // x value is already an ISO date string +// y: dataPoint.y, // y value +// })); +// default: +// console.error( +// 'Invalid rawData2 provided. Expected an array of objects with "x" and "y" properties', +// rawData2 +// ); +// return []; +// } +// const nivoData = rawData2?.map((dataPoint) => ({ +// x: dataPoint[0]?.x, // x value is already an ISO date string +// y: dataPoint[0]?.y, // y value +// })); + +// // Wrapping the data for a single series. You can add more series similarly +// return [ +// { +// id: 'Averaged Data', +// color: '#4cceac', +// data: nivoData, +// }, +// ]; +// }; export const convertDataForNivo2 = (rawData2) => { - if (!Array.isArray(rawData2) || rawData2?.length === 0) { + 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 - })); + // Assuming rawData2 is an array of objects with 'x' and 'y' properties + const nivoData = rawData2?.map((dataPoint) => { + // Ensure the 'x' value is in ISO date string format + const xValue = + dataPoint[0]?.x instanceof Date + ? dataPoint[0]?.x?.toISOString() + : dataPoint[0]?.x; + const yValue = dataPoint[0]?.y; // Assuming y value is directly usable + + return { x: xValue, y: yValue }; + }); // Wrapping the data for a single series. You can add more series similarly return [ { - id: 'Averaged Data', - color: '#4cceac', + id: 'Your Data', // Replace with a meaningful id + color: 'hsl(252, 70%, 50%)', // Replace with color of your choice or logic for dynamic colors data: nivoData, }, ]; @@ -128,3 +178,36 @@ export const formatDateToString = (date) => { .toString() .padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; }; + +const useStyles = makeStyles((theme) => ({ + tooltipTarget: { + cursor: 'pointer', // Change the cursor to indicate it's hoverable + }, +})); + +export const ChartTooltip = ({ point, lastData, hoveredData, latestData }) => { + const classes = useStyles(); + if (!point) return null; + const formattedTime = hoveredData + ? new Date(hoveredData.x).toLocaleString() + : new Date((latestData || lastData).x).toLocaleString(); + + const formattedValue = `$${ + hoveredData ? hoveredData.y : (latestData || lastData)?.y + }`; + + return ( + <> + + + {formattedTime} + + + + + {formattedValue} + + + + ); +}; diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index d87d1b8..28e7c8c 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -44,7 +44,7 @@ export const CollectionProvider = ({ children }) => { const fetchWrapper = useFetchWrapper(); const user = cookies?.authUser; - const userId = user?.id; + const userId = user?.userId; // state for the collection context const [allCollections, setAllCollections] = useState([]); const [collectionData, setCollectionData] = useState(initialCollectionState); @@ -600,10 +600,10 @@ export const CollectionProvider = ({ children }) => { }, [selectedCollection?.chartData?.allXYValues]); // This useEffect is for fetching collections when the user ID changes useEffect(() => { - if (userId) { + if (userId && !allCollections?.length) { fetchAndSetCollections(); } - }, [userId, fetchAndSetCollections]); + }, [userId, fetchAndSetCollections, allCollections]); const contextValue = useMemo( () => ({ allCollections, diff --git a/src/context/CollectionContext/helpers.jsx b/src/context/CollectionContext/helpers.jsx index 6ed660b..5c09506 100644 --- a/src/context/CollectionContext/helpers.jsx +++ b/src/context/CollectionContext/helpers.jsx @@ -326,6 +326,10 @@ export const handleCardAddition = (currentCards, cardToAdd) => { const existingCard = currentCards[existingCardIndex]; existingCard.quantity = (existingCard.quantity || 0) + 1; existingCard.totalPrice = existingCard.price * existingCard.quantity; + existingCard.chart_datasets = [ + ...existingCard.chart_datasets, + createChartDataEntry(existingCard.totalPrice), + ]; // Update the card in the currentCards array currentCards[existingCardIndex] = existingCard; diff --git a/src/context/CollectionContext/useCollectionManager.jsx b/src/context/CollectionContext/useCollectionManager.jsx new file mode 100644 index 0000000..461f07e --- /dev/null +++ b/src/context/CollectionContext/useCollectionManager.jsx @@ -0,0 +1,39 @@ +import { useState, useEffect, useCallback } from 'react'; +import axios from 'axios'; // Make sure to install axios for HTTP requests + +// Custom hook for managing the collection +function useCollectionManager(userId) { + const [collection, setCollection] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + // Fetch collection by user ID + const fetchCollection = useCallback(async () => { + setLoading(true); + try { + const response = await axios.get(`/api/collections/${userId}`); + setCollection(response.data); // Assume the data is the collection object + setLoading(false); + } catch (err) { + setError(err); + setLoading(false); + } + }, [userId]); + + // Call fetchCollection when the component using this hook mounts + useEffect(() => { + fetchCollection(); + }, [fetchCollection]); + + // Define other CRUD operations similarly, e.g., addCard, removeCard, updateCollection, etc. + + return { + collection, + loading, + error, + fetchCollection, + // add other methods here as you define them + }; +} + +export default useCollectionManager; diff --git a/src/context/CombinedContext/CombinedProvider.jsx b/src/context/CombinedContext/CombinedProvider.jsx index 79888dc..ce0fac6 100644 --- a/src/context/CombinedContext/CombinedProvider.jsx +++ b/src/context/CombinedContext/CombinedProvider.jsx @@ -22,7 +22,7 @@ export const CombinedProvider = ({ children }) => { const [cookies] = useCookies(['authUser']); const [state, setState] = useState(initialState); const user = cookies?.authUser; - const userId = user?.id; + const userId = user?.userId; const { selectedCollection, updateCollection, diff --git a/src/context/DeckContext/DeckContext.js b/src/context/DeckContext/DeckContext.js index 5c60ea7..33075ef 100644 --- a/src/context/DeckContext/DeckContext.js +++ b/src/context/DeckContext/DeckContext.js @@ -23,13 +23,13 @@ export const DeckContext = createContext(defaultContextValue); export const DeckProvider = ({ children }) => { const [cookies] = useCookies(['authUser']); + const userId = cookies?.authUser?.userId; const fetchWrapper = useFetchWrapper(); - + const [prevUserId, setPrevUserId] = useState(null); const [deckData, setDeckData] = useState({}); const [allDecks, setAllDecks] = useState([]); const [selectedDeck, setSelectedDeck] = useState({}); const [selectedCards, setSelectedCards] = useState(selectedDeck?.cards || []); - const userId = cookies?.authUser?.id; const fetchDecksForUser = useCallback(async () => { if (!userId) { @@ -46,15 +46,17 @@ export const DeckProvider = ({ children }) => { }, [userId]); const fetchAndSetDecks = useCallback(async () => { try { - const userDecks = await fetchDecksForUser(); + const { message, data } = await fetchDecksForUser(); + console.log('Response from server for fetch decks:', message, data); - if (userDecks.data && userDecks.data.length > 0) { - const uniqueDecks = removeDuplicateDecks(userDecks.data); + if (data && data?.length > 0) { + const uniqueDecks = removeDuplicateDecks(data); setAllDecks((prevDecks) => removeDuplicateDecks([...prevDecks, ...uniqueDecks]) ); setDeckData(uniqueDecks[0] || {}); } else { + console.log('No decks found for user.', data.data); // No decks found for user const shouldCreateDeck = window.confirm( 'No decks found. Would you like to create a new one?' @@ -130,7 +132,6 @@ export const DeckProvider = ({ children }) => { console.error(`Failed to update deck in backend: ${error.message}`); } }; - const createUserDeck = async (userId, newDeckInfo) => { try { const url = `${BASE_API_URL}/${userId}/decks/createDeck`; @@ -141,10 +142,10 @@ export const DeckProvider = ({ children }) => { const data = await fetchWrapper(url, 'POST', { cards: [], totalPrice: 0, - description: newDeckInfo.updatedInfo.description || '', - name: newDeckInfo.updatedInfo.name || '', - tags: newDeckInfo.updatedInfo.tags || [], - color: newDeckInfo.updatedInfo.color || '', + description: newDeckInfo?.description || '', + name: newDeckInfo?.name || '', + tags: newDeckInfo?.tags || [], + color: newDeckInfo?.color || '', }); console.log('NEW DECK DATA:', data); setDeckData(data.data); @@ -185,7 +186,7 @@ export const DeckProvider = ({ children }) => { if (!deckId) { setSelectedDeck(allDecks[0]); console.warn('No deck ID provided. Adding card to first deck in list.'); - deckId = allDecks[0]._id; + deckId = allDecks[0]?._id; } try { // Update deck data locally @@ -227,7 +228,7 @@ export const DeckProvider = ({ children }) => { const updatedDeck = responseData.allDecks.find( (deck) => deck._id === deckId ).cards; - const updatedCards = updatedDeck.cards; + const updatedCards = updatedDeck?.cards; setSelectedDeck({ ...selectedDeck, cards: updatedCards }); } } catch (error) { @@ -246,7 +247,7 @@ export const DeckProvider = ({ children }) => { throw new Error('Deck not found locally'); } - const originalCard = currentDeck.cards.find( + const originalCard = currentDeck?.cards?.find( (card) => card.id === updatedCard.id ); if (originalCard && originalCard.quantity === updatedCard.quantity) { @@ -312,9 +313,14 @@ export const DeckProvider = ({ children }) => { } }; const getCardQuantity = (cardId) => { - const foundCard = selectedDeck?.cards?.find((item) => item.id === cardId); + const foundCard = selectedDeck?.cards.find((item) => item.id === cardId); return foundCard?.quantity || 0; }; + const shouldFetchDecks = (prevUserId, currentUserId) => { + // Fetch decks if there was no previous user and now there is one + // or if the user has changed + return (!prevUserId && currentUserId) || prevUserId !== currentUserId; + }; const contextValue = { deckData, @@ -349,8 +355,16 @@ export const DeckProvider = ({ children }) => { useEffect(() => { console.log('DECKCONTEXT:', contextValue); - if (userId && typeof userId === 'string') { + }, [ + contextValue.deckData, + contextValue.allDecks, + contextValue.selectedDeck, + contextValue.selectedCards, + ]); + useEffect(() => { + if (shouldFetchDecks(prevUserId, userId)) { fetchAndSetDecks(); + setPrevUserId(userId); // Update the previous userId } }, [userId, fetchAndSetDecks]); diff --git a/src/context/FormContext/FormContext.jsx b/src/context/FormContext/FormContext.jsx index 8c9406c..cd6399f 100644 --- a/src/context/FormContext/FormContext.jsx +++ b/src/context/FormContext/FormContext.jsx @@ -1,6 +1,10 @@ -import React, { createContext, useState, useContext } from 'react'; +import React, { createContext, useState, useContext, useEffect } from 'react'; import { useAuthContext } from '../AuthContext/authContext'; import { usePageContext } from '../PageContext/PageContext'; +import { useCardStore } from '../CardContext/CardStore'; +// import { params: initialState } from './search.json'; +// import as dif name from ./search.json +import { initialState } from './search.json'; import { defaultContextValue } from './helpers'; // Define the context @@ -32,6 +36,14 @@ const formValidations = { // ... more validations return errors; }, + updateCollectionForm: (values) => { + let errors = {}; + // Example: Add validations specific to collection update form + if (!values.name) errors.name = 'Collection name is required'; + if (!values.description) errors.description = 'Description is required'; + // ... more validations + return errors; + }, // ...other form-specific validations }; // Initial state for different forms @@ -79,8 +91,21 @@ const initialFormStates = { name: '', description: '', }, + addCollectionForm: { + // New form with same initial values as updateCollectionForm + name: '', + description: '', + }, searchForm: { searchTerm: '', + searchParams: { + name: '', + type: '', + race: '', + archetype: '', + attribute: '', + level: '', + }, }, customerInfoFields: { firstName: '', @@ -108,6 +133,7 @@ export const FormProvider = ({ children }) => { setLoading, // Use setLoading instead of individual state setters returnDisplay, } = usePageContext(); + // const { handleRequest } = useCardStore(); const [forms, setForms] = useState(initialFormStates); const [currentForm, setCurrentForm] = useState({}); // For multiple forms const [formErrors, setFormErrors] = useState(null); // For a single form @@ -192,6 +218,12 @@ export const FormProvider = ({ children }) => { setLoading('isFormDataLoading', false); // indicate form submission is done }; + // useEffect(() => { + // if (initialFormStates?.searchForm?.searchTerm) { + // handleRequest(initialFormStates?.searchForm?.searchTerm); + // } + // }, [returnDisplay]); + // Provide each form's data, handleChange, and handleSubmit through context const contextValue = { forms, diff --git a/src/components/other/search/search.json b/src/context/FormContext/search.json similarity index 100% rename from src/components/other/search/search.json rename to src/context/FormContext/search.json diff --git a/src/context/Providers.jsx b/src/context/Providers.jsx index cd20e95..1f84520 100644 --- a/src/context/Providers.jsx +++ b/src/context/Providers.jsx @@ -26,6 +26,7 @@ import { useMode, PageProvider, ErrorBoundary, + CardImagesProvider, // CardImagesProvider, // Uncomment if CardImagesProvider is used } from '.'; // Ensure this path is correctly pointing to where your context providers are defined @@ -40,11 +41,11 @@ const Providers = ({ children }) => { - - - - - + + + + + @@ -52,24 +53,26 @@ const Providers = ({ children }) => { - {/* */} - - - - - - {/* */} - - - {children}{' '} - - {/* */} - - - - - - {/* */} + + + + + + + {/* */} + + + {children}{' '} + + {/* */} + + + + + + @@ -77,11 +80,11 @@ const Providers = ({ children }) => { - - - - - + + + + + diff --git a/src/context/SideBarContext/SideBarProvider.jsx b/src/context/SideBarContext/SideBarProvider.jsx index c12f22b..34fa916 100644 --- a/src/context/SideBarContext/SideBarProvider.jsx +++ b/src/context/SideBarContext/SideBarProvider.jsx @@ -1,10 +1,11 @@ import React, { createContext, useContext, useState } from 'react'; +import { useAuthContext } from '../AuthContext/authContext'; const SidebarContext = createContext(); export const SidebarProvider = ({ children }) => { + const { login, logout, isLoggedIn } = useAuthContext(); const [isOpen, setIsOpen] = useState(false); - const [isLoggedIn, setisLoggedIn] = useState(false); const [sidebarBackgroundColor, setSidebarBackgroundColor] = useState('#FFFFFF'); const [sidebarImage, setSidebarImage] = useState(null); @@ -13,14 +14,6 @@ export const SidebarProvider = ({ children }) => { setIsOpen(!isOpen); }; - const login = () => { - setisLoggedIn(true); - }; - - const logout = () => { - setisLoggedIn(false); - }; - return ( useContext(StatisticsContext); export const StatisticsProvider = ({ children }) => { const { timeRange } = useChartContext(); const { allCollections, allXYValues } = useCollectionStore(); + const [selectedStat, setSelectedStat] = useState(''); + // memoized status regarding the price history of a user's collection const stats = useMemo( () => calculateStatistics({ data: allXYValues }, timeRange, allCollections), [allXYValues, timeRange] ); + // memoized stats regarding the price history of all of a user's collections const statsByCollectionId = useMemo(() => { - return allCollections.reduce((acc, collection) => { + return allCollections?.reduce((acc, collection) => { // Assuming each collection has its own 'currentChartDataSets2' const data = collection?.chartData?.allXYValues; acc[collection._id] = calculateStatistics({ data }, timeRange); return acc; }, {}); }, [allCollections, timeRange]); + // Prepare markers for high and low points + const markers = [ + { + axis: 'y', + value: statsByCollectionId?.highPoint, + lineStyle: { stroke: '#b0413e', strokeWidth: 2 }, + legend: 'High Point', + legendOrientation: 'vertical', + }, + { + axis: 'y', + value: statsByCollectionId?.lowPoint, + lineStyle: { stroke: '#b0413e', strokeWidth: 2 }, + legend: 'Low Point', + legendOrientation: 'vertical', + }, + { + axis: 'y', + value: statsByCollectionId?.average, + lineStyle: { stroke: '#b0413e', strokeWidth: 2 }, + legend: 'Average', + legendOrientation: 'vertical', + }, + ]; + + // Calculate the total value of all collections + const totalValue = allCollections?.reduce( + (acc, collection) => acc + collection.totalPrice, + 0 + ); + const topFiveCards = allCollections + .flatMap((collection) => collection.cards) // Flatten all cards into one array + .sort((a, b) => b.price - a.price) // Sort by price in descending order + .slice(0, 5); + const chartData = allCollections?.map((collection) => ({ + id: collection.id, + value: collection.totalPrice, + label: collection.name, + })); return ( - + {children} ); diff --git a/src/context/UserContext/UserContext.js b/src/context/UserContext/UserContext.js index 663e56b..c8204e6 100644 --- a/src/context/UserContext/UserContext.js +++ b/src/context/UserContext/UserContext.js @@ -12,18 +12,23 @@ import useFetchWrapper from '../hooks/useFetchWrapper'; export const UserContext = createContext(); export const UserProvider = ({ children }) => { - const { authUser, user, setUser, isLoggedIn } = useAuthContext(); // Use the useAuthContext hook + const { setUser, isLoggedIn } = useAuthContext(); // Use the useAuthContext hook + const [cookies, setCookie] = useCookies(['authUser', 'user']); const fetchWrapper = useFetchWrapper(); - const userId = authUser?.id; - - // const fetchUserData = useCallback(async () => { - // // Get request to fetch user data - // const endpoint = `/users/${user.id}/userData`; - // const url = createUrl(endpoint); - // const response = await useFetchWrapper(url); - // // const response = await fetchWrapper.get(`/api/users/${userId}`); - // // const userData = response.data; - // }, []); + const authUser = cookies?.authUser; + const user = cookies?.user; + const userId = cookies?.authUser?.userId; + const fetchUserData = useCallback(async () => { + // Get request to fetch user data + const endpoint = `/users/${userId}/userData`; + const url = createUrl(endpoint); + const response = await fetchWrapper(url, 'GET'); + const { message, data } = response; + console.log('Response from server for fetch user:', message, data); + // setUser(data.userDoc); + // const response = await fetchWrapper.get(`/api/users/${userId}`); + // const userData = response.data; + }, []); const updateUser = async (updatedUser) => { try { @@ -31,8 +36,9 @@ export const UserProvider = ({ children }) => { const endpoint = `users/${userId}/userData/update`; const url = createUrl(endpoint); const response = await fetchWrapper(url, 'PUT', updatedUser); - const updatedUserResponse = response?.data?.user; - setUser(updatedUserResponse); + const { message, data } = response; + console.log('Response from server for update user:', message, data); + setUser(data.updatedUserDoc); console.log('User Data Sent to Server and Cookie Updated:', updatedUser); } catch (error) { console.error('Error updating user data:', error); @@ -41,6 +47,7 @@ export const UserProvider = ({ children }) => { useEffect(() => { if (userId) { + console.log('User ID found, fetching user data...', cookies.user); const updatedUser = { ...user, userSecurityData: { @@ -59,10 +66,11 @@ export const UserProvider = ({ children }) => { return ( {children} diff --git a/src/context/WithPageProvider.jsx b/src/context/WithPageProvider.jsx index 526e307..cece8d3 100644 --- a/src/context/WithPageProvider.jsx +++ b/src/context/WithPageProvider.jsx @@ -1,13 +1,15 @@ import React from 'react'; -import { PageProvider } from '.'; +import { FormProvider, PageProvider } from '.'; // Higher Order Component for PageProvider const WithPageProvider = (WrappedComponent) => { const WithPageProvider = (props) => { return ( - - - + + + + + ); }; return WithPageProvider; diff --git a/src/context/hooks/useDialog.jsx b/src/context/hooks/useDialog.jsx index 7f808f4..a0a27c8 100644 --- a/src/context/hooks/useDialog.jsx +++ b/src/context/hooks/useDialog.jsx @@ -1,32 +1,197 @@ -// useDialog.js -import { useState, useCallback } from 'react'; - -const useDialog = (handleSnackBar, onClose) => { - const [isOpen, setIsOpen] = useState(false); - - const openDialog = useCallback(() => { - setIsOpen(true); - }, []); - - const closeDialog = useCallback(() => { - setIsOpen(false); - onClose?.(); - }, [onClose]); - - const handleCloseDialog = useCallback( - (event, reason) => { - if ( - reason && - (reason === 'backdropClick' || reason === 'escapeKeyDown') - ) { - handleSnackBar('Operation cancelled, no changes were made.'); +import { useState, useCallback, useEffect } from 'react'; + +const useDialog = (handleSnackBar) => { + const [isLoginDialogOpen, setLoginDialogOpen] = useState(false); + const [dialogStatus, setDialogStatus] = useState({ + isOpen: false, + event: '', + }); + const [dialogs, setDialogs] = useState({}); + + // Function to open a dialog + const openDialog = useCallback( + (dialogName, action) => { + setDialogs((prevDialogs) => ({ + ...prevDialogs, + [dialogName]: true, + })); + setLoginDialogOpen(true); + console.log(`${dialogName} Dialog attempting ${action}`); + handleSnackBar(`${dialogName} Dialog attempting ${action}`, 'info', 6000); + // console.log(`${dialogName} Dialog Status: Open`); + }, + [handleSnackBar] + ); + + // Function to close a dialog + const closeDialog = useCallback( + (dialogName, action, reason) => { + if (reason === 'backdropClick' || reason === 'escapeKeyDown') { + handleSnackBar( + 'Operation cancelled, no changes were made.', + 'warning', + 6000 + ); + } else { + setLoginDialogOpen(false); + console.log(`${dialogName} Dialog attempting ${action}`); + handleSnackBar( + `${dialogName} Dialog attempting ${action}`, + 'info', + 6000 + ); + // console.log(`${dialogName} Dialog Status: Closed`); } - closeDialog(); + setDialogs((prevDialogs) => ({ + ...prevDialogs, + [dialogName]: false, + })); }, - [closeDialog, handleSnackBar] + [handleSnackBar] ); - return { isOpen, openDialog, handleCloseDialog, closeDialog }; + // Track dialog status changes + useEffect(() => { + Object.entries(dialogs).forEach(([dialogName, isOpen]) => { + console.log(`${dialogName} Dialog Status: ${isOpen ? 'Open' : 'Closed'}`); + }); + }, [dialogs]); + + return { + isLoginDialogOpen, + openLoginDialog: () => openDialog('Login', 'Open'), + closeLoginDialog: (event, reason) => closeDialog('Login', 'Close', reason), + }; }; export default useDialog; +// // console.log('isLoggedIn:', isLoggedIn); +// console.log('isLoginDialogOpen:', isLoginDialogOpen); +// // console.log('handleSnackBar:', handleSnackBar); +// // useEffect(() => { +// // if (!isLoggedIn && isLoginDialogOpen) { +// // handleSnackBar('Please log in to continue', 'info'); +// // } +// // }, [isLoggedIn, isLoginDialogOpen, handleSnackBar]); + +// // const handleToggleLoginDialog = useCallback( +// // (event) => { +// // if (event === 'Open') { +// // setLoginDialogOpen(true); +// // handleSnackBar('Opening login dialog...', 'info'); +// // } else if (event === 'Close') { +// // setLoginDialogOpen(false); +// // handleSnackBar('Closing login dialog...', 'info'); +// // } else { +// // // Handle backdrop click or escape key down, or toggle dialog for other cases +// // if (event === 'backdropClick' || event === 'escapeKeyDown') { +// // handleSnackBar( +// // 'Operation cancelled, no changes were made.', +// // 'warning' +// // ); +// // } else { +// // setLoginDialogOpen((prev) => { +// // const newStatus = !prev; +// // handleSnackBar( +// // newStatus ? 'Dialog opened' : 'Dialog closed', +// // 'info' +// // ); +// // return newStatus; +// // }); +// // } +// // } +// // }, +// // [handleSnackBar] +// // ); +// const openLoginDialog = useCallback(() => { +// setLoginDialogOpen(true); +// handleSnackBar('Login Dialog opened', 'info', 6000); // Notify on dialog open +// }, [handleSnackBar]); + +// const closeLoginDialog = useCallback( +// (event, reason) => { +// if ( +// reason && +// (reason === 'backdropClick' || reason === 'escapeKeyDown') +// ) { +// handleSnackBar( +// 'Operation cancelled, no changes were made.', +// 'warning', +// 6000 +// ); +// } else { +// handleSnackBar('Login Dialog closed', 'info', 6000); // Notify on dialog close +// } +// setLoginDialogOpen(false); +// }, +// [handleSnackBar] +// ); + +// useEffect(() => { +// if (isLoginDialogOpen) { +// setDialogStatus({ isOpen: true, event: 'Open' }); +// console.log('Dialog Successfully Opened'); +// } +// if (!isLoginDialogOpen) { +// setDialogStatus({ isOpen: false, event: 'Close' }); +// console.log('Dialog Successfully Closed'); +// } + +// if (dialogStatus.event === 'Open') { +// handleSnackBar('Login Dialog opened', 'info', 6000); // Notify on dialog open +// } +// if (dialogStatus.event === 'Close') { +// handleSnackBar('Login Dialog closed', 'info', 6000); // Notify on dialog close +// } +// }); +// const [isOpen, setIsOpen] = useState(false); + +// const openDialog = useCallback(() => { +// setIsOpen(true); +// }, []); + +// const closeDialog = useCallback(() => { +// setIsOpen(false); +// onClose?.(); +// }, [onClose]); + +// const openLoginDialog = useCallback(() => { +// setLoginDialogOpen(true); +// }, []); + +// const closeLoginDialog = useCallback(() => { +// setLoginDialogOpen(false); +// }, []); + +// toggleLoginDialog combines openLoginDialog and closeLoginDialog +// const handleCloseDialog = useCallback( +// (event, reason) => { +// if ( +// reason && +// (reason === 'backdropClick' || reason === 'escapeKeyDown') +// ) { +// handleSnackBar('Operation cancelled, no changes were made.'); +// } +// closeDialog(); +// }, +// [closeDialog, handleSnackBar] +// ); + +// example usage of handleToggleLoginDialog: +// +// const handleCloseLoginDialog = useCallback( +// (event, reason) => { +// if ( +// reason && +// (reason === 'backdropClick' || reason === 'escapeKeyDown') +// ) { +// handleSnackBar('Operation cancelled, no changes were made.'); +// } +// toggleLoginDialog(); +// }, +// [toggleLoginDialog, handleSnackBar] +// ); diff --git a/src/context/hooks/useFetchAndDisplayImage.jsx b/src/context/hooks/useFetchAndDisplayImage.jsx new file mode 100644 index 0000000..7770f36 --- /dev/null +++ b/src/context/hooks/useFetchAndDisplayImage.jsx @@ -0,0 +1,29 @@ +import { useState, useEffect } from 'react'; +import { useCardImages } from '../CardImagesContext/CardImagesContext'; + +function useFetchAndDisplayImage(imageUrl) { + const { downloadCardImage } = useCardImages(); + const [imageSrc, setImageSrc] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (!imageUrl) return; + + setLoading(true); // Indicate the start of an image download + downloadCardImage(imageUrl) // This function should handle the API request + .then((downloadedImgUrl) => { + setImageSrc(downloadedImgUrl); // Update state with the new image URL + setLoading(false); // Indicate that the image download is complete + }) + .catch((error) => { + console.error('Error fetching image:', error); + setError(error); + setLoading(false); // Indicate that the image download is complete even if there was an error + }); + }, [imageUrl, downloadCardImage]); // Dependency array includes downloadCardImage to handle updates to the function + + return { imageSrc, error, loading }; +} + +export default useFetchAndDisplayImage; diff --git a/src/context/hooks/useResponsiveStyles.jsx b/src/context/hooks/useResponsiveStyles.jsx index 415f953..6840597 100644 --- a/src/context/hooks/useResponsiveStyles.jsx +++ b/src/context/hooks/useResponsiveStyles.jsx @@ -45,7 +45,7 @@ const useResponsiveStyles = (theme) => { } }; - const getProductGridContainerStyle = () => ({ + const getProductGridContainerStyle = (theme) => ({ maxWidth: 'lg', maxHeight: '100%', display: 'flex', diff --git a/src/context/index.js b/src/context/index.js index ddb2b1d..28f2546 100644 --- a/src/context/index.js +++ b/src/context/index.js @@ -37,6 +37,6 @@ export { AppContextProvider } from './AppContext/AppContextProvider'; export { PopoverProvider } from './PopoverContext/PopoverContext'; export { CronJobProvider } from './CronJobContext/CronJobContext'; export { StatisticsProvider } from './StatisticsContext/StatisticsContext'; -export { CardImagesProvider } from './CardImagesContext/CardImagesContext'; export { FormProvider } from './FormContext/FormContext'; export { PageProvider } from './PageContext/PageContext'; +export { CardImagesProvider } from './CardImagesContext/CardImagesContext'; diff --git a/src/components/cards/CardDetailsContainer.jsx b/src/layout/CardDetailsContainer.jsx similarity index 95% rename from src/components/cards/CardDetailsContainer.jsx rename to src/layout/CardDetailsContainer.jsx index 651a464..2587f16 100644 --- a/src/components/cards/CardDetailsContainer.jsx +++ b/src/layout/CardDetailsContainer.jsx @@ -8,9 +8,9 @@ import { FaVenusMars, } from 'react-icons/fa'; import { GiAxeSword } from 'react-icons/gi'; -import CardDetail from './CardDetail'; +import CardDetail from '../components/cards/CardDetail'; import { styled } from '@mui/system'; -import { useMode } from '../../context'; +import { useMode } from '../context'; const IconWrapper = styled('div')(({ theme }) => ({ display: 'flex', diff --git a/src/components/other/dataDisplay/UserStats.jsx b/src/layout/UserStats.jsx similarity index 76% rename from src/components/other/dataDisplay/UserStats.jsx rename to src/layout/UserStats.jsx index 0cb073a..4943611 100644 --- a/src/components/other/dataDisplay/UserStats.jsx +++ b/src/layout/UserStats.jsx @@ -1,8 +1,8 @@ import React from 'react'; import { Box, Typography } from '@mui/material'; -import { useDeckStore } from '../../../context/DeckContext/DeckContext'; -import { useCartStore } from '../../../context/CartContext/CartContext'; -import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; +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/layout/cart/CartContent.js b/src/layout/cart/CartContent.js index 6061312..c4a97d7 100644 --- a/src/layout/cart/CartContent.js +++ b/src/layout/cart/CartContent.js @@ -11,7 +11,7 @@ import { useMode } from '../../context'; const CartContent = () => { const { theme } = useMode(); const { getProductGridContainerStyle } = useResponsiveStyles(theme); - const containerStyles = getProductGridContainerStyle(); + const containerStyles = getProductGridContainerStyle(theme); const { cartData, isLoading } = useCartStore(); const renderCartItems = () => { diff --git a/src/layout/collection/SelectCollection.jsx b/src/layout/collection/SelectCollection.jsx index dc2f5c1..65e6c67 100644 --- a/src/layout/collection/SelectCollection.jsx +++ b/src/layout/collection/SelectCollection.jsx @@ -1,16 +1,14 @@ -// SelectCollection.jsx import React, { useState, useCallback } from 'react'; -import { Box, Button, Grid, Paper, Typography } from '@mui/material'; +import { Box, Grid } from '@mui/material'; import SelectCollectionList from '../../components/grids/collectionGrids/SelectCollectionList'; -import CreateOrEditCollectionDialog from '../../components/dialogs/CreateOrEditCollectionDialog'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; import usePortfolioStyles from '../../context/hooks/usePortfolioStyles'; import { useMode, useStatisticsStore } from '../../context'; -import { PieChart } from '@mui/x-charts/PieChart'; import SelectCollectionHeader from '../../components/headings/collection/SelectCollectionHeader'; import PieChartStats from '../../components/other/dataDisplay/chart/PieChartStats'; import TotalValueOfCollectionsDisplay from '../../components/other/dataDisplay/TotalValueOfCollectionsDisplay'; import TopFiveExpensiveCards from '../../components/other/dataDisplay/TopFiveExpensiveCards'; +import CollectionDialog from '../../components/dialogs/CollectionDialog'; const SelectCollection = ({ handleSelectCollection }) => { const { theme } = useMode(); @@ -19,75 +17,77 @@ const SelectCollection = ({ handleSelectCollection }) => { const [isNew, setIsNew] = useState(false); const { setSelectedCollection, selectedCollection, allCollections } = useCollectionStore(); - const { statsByCollectionId } = useStatisticsStore(); const [isLoadingNewCollection, setIsLoadingNewCollection] = useState(false); + const { topFiveCards, totalValue, chartData } = useStatisticsStore(); const handleDialogToggle = useCallback(() => { - setDialogOpen(!isDialogOpen); - }, [isDialogOpen]); + setDialogOpen((prev) => !prev); + }, []); const handleSave = useCallback( (collection) => { setSelectedCollection(collection); - setIsLoadingNewCollection(true); - - // Simulate a delay for adding collection (replace with actual API call or logic) - setTimeout(() => { - setSelectedCollection(collection); - setIsLoadingNewCollection(false); // Set loading state false once added - setDialogOpen(false); - }, 1000); // Simulate network or processing delay + setDialogOpen(false); + handleSelectCollection(collection); }, - [setSelectedCollection] + [setSelectedCollection, handleSelectCollection] ); + const openNewDialog = () => { setIsNew(true); setDialogOpen(true); }; - // Calculate the total value of all collections - const totalValue = allCollections.reduce( - (acc, collection) => acc + collection.totalPrice, - 0 + + // Function to render the header + const renderHeader = () => ( + ); - const topFiveCards = allCollections - .flatMap((collection) => collection.cards) // Flatten all cards into one array - .sort((a, b) => b.price - a.price) // Sort by price in descending order - .slice(0, 5); - const chartData = allCollections.map((collection) => ({ - id: collection.id, - value: collection.totalPrice, - label: collection.name, - })); - return ( - - - - - - - - - - - ( + + + + + + ); + + // Function to render the collection list + const renderCollectionList = () => ( + + ); + + // Function to render the dialog for collection + const renderCollectionDialog = () => ( + + ); + + return ( + + {renderHeader()} + {renderStatistics()} + {renderCollectionList()} + {renderCollectionDialog()} + + ); }; export default SelectCollection; diff --git a/src/pages/CartPage.js b/src/pages/CartPage.js index c993f8a..10b36fe 100644 --- a/src/pages/CartPage.js +++ b/src/pages/CartPage.js @@ -16,18 +16,20 @@ const CartPage = () => { cartData, addOneToCart, removeOneFromCart, - fetchUserCart, + fetchCartForUser, getTotalCost, cartCardQuantity, totalCost, } = useCartStore(); const { loadingStatus, returnDisplay, setLoading } = usePageContext(); + const calculateTotalPrice = getTotalCost(); + // useEffect hook to fetch cart data for user useEffect(() => { const fetchData = async () => { setLoading('isPageLoading', true); try { - await fetchUserCart(); // Assuming fetchUserCart updates cartData + await fetchCartForUser(); // Assuming fetchUserCart updates cartData } catch (error) { console.error('Error fetching cart data:', error); } finally { @@ -39,8 +41,7 @@ const CartPage = () => { if (!cartData) { fetchData(); } - }, [cartData, fetchUserCart, setLoading]); - + }, [cartData, fetchCartForUser]); // Modify this function based on how your cart store manages items const handleModifyItemInCart = async (cardId, operation) => { try { @@ -49,12 +50,63 @@ const CartPage = () => { console.error('Failed to adjust quantity in cart:', e); } }; + // Function to render the cart content grid + const renderCartContent = () => ( + + + + ); + // Function to render the checkout and summary section + const renderCheckoutAndSummary = () => ( + + + + + + + ); + // Function to render the overall cart layout + const renderCartLayout = () => ( + + + + {renderCartContent()} + {renderCheckoutAndSummary()} + + + + ); - const calculateTotalPrice = getTotalCost(); return ( {loadingStatus?.isPageLoading && returnDisplay()} - + {loadingStatus?.isLoading && returnDisplay()} { backgroundColor: theme.palette.background.paper, }} > - - - - - - - - - - - - - - - + {renderCartLayout()} ); }; - export default CartPage; diff --git a/src/pages/CollectionPage.js b/src/pages/CollectionPage.js index 4816f5c..5a5e086 100644 --- a/src/pages/CollectionPage.js +++ b/src/pages/CollectionPage.js @@ -34,36 +34,42 @@ const CollectionPage = () => { } }, []); - // if (isLoading) displayLoadingIndicator(); - // if (pageError) displayErrorIndicator(); + // Function to render the hero center + const renderHeroCenter = () => + !isCollectionView && ( + + ); + + // Function to render the collection portfolio + const renderCollectionPortfolio = () => ( + + ); + + // Function to render the card dialog + const renderCardDialog = () => + isModalOpen && ( + + ); - // const handleCollectionSelected = (selected) => { - // setIsCollectionSelected(!!selected); - // }; return ( {loadingStatus?.isPageLoading && returnDisplay()} - - {!isCollectionView && ( - - )} - - {isModalOpen && ( - - )} + {renderHeroCenter()} + {renderCollectionPortfolio()} + {renderCardDialog()} ); }; diff --git a/src/pages/DeckBuilderPage.js b/src/pages/DeckBuilderPage.js index e809312..072df15 100644 --- a/src/pages/DeckBuilderPage.js +++ b/src/pages/DeckBuilderPage.js @@ -69,39 +69,64 @@ const DeckBuilderPage = () => { } }, [userId]); + // Function to render the Hero banner + const renderHeroBanner = () => ( + + + + + + ); + + // Function to render the deck search section + const renderDeckSearch = () => ( + + + + ); + + // Function to render the deck display section + const renderDeckDisplay = () => ( + + + + ); + + // Function to render the deck builder's main content + const renderDeckBuilderContent = () => ( + + + + {renderDeckSearch()} + {renderDeckDisplay()} + + + + ); + + // Function to render the card dialog + const renderCardDialog = () => { + return ( + isModalOpen && ( + + ) + ); + }; + return ( {loadingStatus?.isPageLoading && returnDisplay()} - - - - - - - - - - - - - - - - - - - - {isModalOpen && ( - - )} + {renderHeroBanner()} + {renderDeckBuilderContent()} + {renderCardDialog()} ); diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js index 76eddbf..512190c 100644 --- a/src/pages/HomePage.js +++ b/src/pages/HomePage.js @@ -8,6 +8,8 @@ import { Grid, Box, useMediaQuery, + Paper, + Stack, } from '@mui/material'; import { useMode } from '../context/hooks/colormode'; import { @@ -35,9 +37,9 @@ import { useTheme } from '@emotion/react'; import SingleCardAnimation from '../assets/animations/SingleCardAnimation'; import { useCollectionStore } from '../context'; import CardChart from '../tests/CardChart'; -import CardComponent from '../tests/CardComponent'; import useCardCronJob from '../tests/useCardCronJob'; -import initialCardData from '../tests/initialCardData'; +// import initialCardData from '../tests/initialCardData'; +import { styled } from '@mui/styles'; const AnimatedBox = animated(Box); @@ -45,8 +47,11 @@ const HomePage = () => { const { theme } = useMode(); const theme2 = useTheme(); const isSmUp = useMediaQuery(theme.breakpoints.up('sm')); - const { cardData, startUpdates, pauseUpdates, resetData } = - useCardCronJob(initialCardData); + const isMdUp = useMediaQuery(theme.breakpoints.up('md')); + const isLgUp = useMediaQuery(theme.breakpoints.up('lg')); + const { allCollections } = useCollectionStore(); + const initialCardData = allCollections[0]?.cards[0]; + const { cardData } = useCardCronJob(initialCardData); const { isModalOpen, modalContent } = useContext(ModalContext); const { selectedCollection } = useCollectionStore(); @@ -76,228 +81,199 @@ const HomePage = () => { } }, []); - const titleStyles = { - padding: theme.spacing(2), // consistent padding with the theme - textAlign: 'center', - color: theme.palette.backgroundD.contrastText, - background: theme.palette.background.dark, - borderRadius: theme.shape.borderRadius, - margin: theme.spacing(2), - // Add more styles as per the theme or design requirements - }; - - return ( - - -
- -
- - - - {introText.mainTitle} - - - {introText.description} - - - - - - ( +
+ +
+ ); + // Function to render tertiary content + const renderTertiaryContent = () => ( + + + - - Top Performing Cards - - -
- + - {cardData && cardData?.dailyPriceHistory && ( - - )} + {introText.description} + + + + ); + // Function to render secondary content + const renderSecondaryContent = () => ( + + + {isMdUp && ( - - {' '} - + {/* + Top Performing Cards + */} + +
+ )} + + {/* Chart and Card Components */} - {/* This is your animation component */} - - - - {tiers.map((tier, index) => { - const [tiltAnimation, setTiltAnimation] = useSpring(() => ({ + > + +
+ + + ); + // Function to render main content + const renderMainContent = () => ( + + + {tiers.map((tier, index) => { + const [tiltAnimation, setTiltAnimation] = useSpring(() => ({ + transform: + 'perspective(600px) rotateX(0deg) rotateY(0deg) scale(1)', + })); + const handleMouseEnter = () => + setTiltAnimation({ transform: - 'perspective(600px) rotateX(0deg) rotateY(0deg) scale(1)', - })); - const handleMouseEnter = () => - setTiltAnimation({ - transform: - 'perspective(600px) rotateX(5deg) rotateY(5deg) scale(1.05)', - }); + 'perspective(600px) rotateX(5deg) rotateY(5deg) scale(1.05)', + }); - const handleMouseLeave = () => - setTiltAnimation({ - transform: - 'perspective(600px) rotateX(0deg) rotateY(0deg) scale(1)', - }); - return ( - + setTiltAnimation({ + transform: + 'perspective(600px) rotateX(0deg) rotateY(0deg) scale(1)', + }); + return ( + + - - - - - - {tier.description.map((line, index) => ( - - {line} - - ))} - - - - handleOpenModal(tier.title)} - > - {tier.buttonText} - - - - - - ); - })} - - - {isModalOpen && ( - - )} - {detailsModalShow && } + backgroundColor: theme.palette.backgroundD.dark, + }} // Apply the background color here + /> + + + {tier.description.map((line, index) => ( + + {line} + + ))} + + + + handleOpenModal(tier.title)} + > + {tier.buttonText} + + + + +
+ ); + })} +
+ + ); + // Function to render dialogs (GenericCardDialog and DetailsModal) + const renderDialogs = () => { + return ( + <> + {isModalOpen && ( + + )} + {detailsModalShow && } + + ); + }; + + return ( + + + {/* Main Splash Page */} + {renderSplashPage()} + + {/* Tertiary Content */} + {renderTertiaryContent()} + + {/* Secondary Content */} + {renderSecondaryContent()} + + {/* Main Content */} + {renderMainContent()} + + {/* Dialogs */} + {renderDialogs()} ); }; diff --git a/src/pages/StorePage.js b/src/pages/StorePage.js index d53c777..faf3727 100644 --- a/src/pages/StorePage.js +++ b/src/pages/StorePage.js @@ -16,83 +16,86 @@ const StorePage = () => { const [containerHeight, setContainerHeight] = useState(0); const [searchBarFocused, setSearchBarFocused] = useState(false); const { isModalOpen, modalContent } = useModalContext(); - - const updateContainerHeight = (height) => { - setContainerHeight(height); - }; + // Function to render the Hero section + const renderHero = () => ( + + ); + // Function to render the search bar and product grid + const renderSearchAndProducts = () => ( + + + setSearchBarFocused(true)} + onSearchBlur={() => setSearchBarFocused(false)} + /> + + + + ); + // Function to render the bottom background box (for UI effects) + const renderBackgroundBox = () => ( + + ); return ( - - - - - setSearchBarFocused(true)} - onSearchBlur={() => setSearchBarFocused(false)} - /> - - - + {/* Main content rendering */} + {renderHero()} + {renderSearchAndProducts()} + + {/* Modal for card details */} + {isModalOpen && ( + + )} + + {/* Background box for additional UI */} + {renderBackgroundBox()} - {isModalOpen && ( - - )} - ); }; diff --git a/src/pages/otherPages/LoginPage.jsx b/src/pages/otherPages/LoginPage.jsx index 43a2a6b..40a4dde 100644 --- a/src/pages/otherPages/LoginPage.jsx +++ b/src/pages/otherPages/LoginPage.jsx @@ -34,9 +34,10 @@ const defaultTheme = createTheme(); export default function LoginPage() { const { theme } = useMode(); - const { isLoggedIn } = useAuthContext(); + const [cookie, setCookie] = React.useState('isLoggedIn'); + // const { isLoggedIn } = useAuthContext(); - if (isLoggedIn) return ; + if (cookie.isLoggedIn) return ; return ( diff --git a/src/pages/otherPages/ProfilePage.js b/src/pages/otherPages/ProfilePage.js index 9aec8df..343b31e 100644 --- a/src/pages/otherPages/ProfilePage.js +++ b/src/pages/otherPages/ProfilePage.js @@ -12,7 +12,7 @@ import { } from '@mui/material'; import { Edit as EditIcon } from '@mui/icons-material'; import placeholder from '../../assets/images/placeholder.jpeg'; -import UserStats from '../../components/other/dataDisplay/UserStats'; +import UserStats from '../../layout/UserStats'; import { useUserContext } from '../../context/UserContext/UserContext'; import { useCookies } from 'react-cookie'; import ThemeToggleButton from '../../components/buttons/other/ThemeToggleButton'; diff --git a/src/pages/pageStyles/StyledComponents.jsx b/src/pages/pageStyles/StyledComponents.jsx index d206770..9a2e441 100644 --- a/src/pages/pageStyles/StyledComponents.jsx +++ b/src/pages/pageStyles/StyledComponents.jsx @@ -18,33 +18,33 @@ export const AppContainer = styled('div')(({ theme }) => ({ height: '100vh', // background: '#222', })); -const StyledContainer = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - // alignSelf: 'start', - alignItems: 'center', - justifyContent: 'center', - // marginLeft: theme.spacing(2), - borderRadius: theme.shape.borderRadiusLarge, - // marginRight: theme.spacing(2), - // minHeight: '250vh', - flexGrow: 1, - // minHeight: '100%', - background: '#333', - // backgroundColor: '#f1f1f1', - padding: { - xs: 0, - sm: theme.spacing(1), - md: theme.spacing(2.5), - lg: theme.spacing(2.5), - }, - height: '100%', - width: '100%', -})); +// const StyledContainer = styled(Box)(({ theme }) => ({ +// display: 'flex', +// flexDirection: 'column', +// // alignSelf: 'start', +// alignItems: 'center', +// justifyContent: 'center', +// // marginLeft: theme.spacing(2), +// borderRadius: theme.shape.borderRadiusLarge, +// // marginRight: theme.spacing(2), +// // minHeight: '250vh', +// flexGrow: 1, +// // minHeight: '100%', +// background: '#333', +// // backgroundColor: '#f1f1f1', +// padding: { +// xs: 0, +// sm: theme.spacing(1), +// md: theme.spacing(2.5), +// lg: theme.spacing(2.5), +// }, +// height: '100%', +// width: '100%', +// })); -export const CollectionContainer = ({ children }) => { - return {children}; -}; +// export const AppContainer = ({ children }) => { +// return {children}; +// }; const StyledContainer2 = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'column', @@ -457,24 +457,39 @@ export const MainContentContainer = styled(Container)(({ theme }) => ({ margin: theme.spacing(4, 0), // added vertical spacing })); -export const SecondaryContentContainer = styled('div')(({ theme }) => ({ - background: theme.palette.backgroundD.dark, - marginBottom: theme.spacing(2), - width: '100%', +export const SecondaryContentContainer = styled(Box)(({ theme }) => ({ + display: 'flex', flexDirection: 'row', - boxShadow: theme.shadows[3], + justifyContent: 'center', + alignItems: 'center', + width: '100%', + padding: theme.spacing(2), + background: theme.palette.backgroundD.dark, borderRadius: theme.shape.borderRadius, + // justifyContent: 'center', + // // marginBottom: theme.spacing(2), + // width: '100%', + // height: '100%', + // flexDirection: 'row', + // boxShadow: theme.shadows[3], + // borderRadius: theme.shape.borderRadius, transition: 'background-color 0.3s', - position: 'relative', - paddingTop: '0', // Remove top padding to align the animation at the top - paddingBottom: '0', // Remove bottom padding - minHeight: '70vh', // Adjust the height as needed to fit the animation - display: 'flex', - justifyContent: 'space-evenly', // Center the animation horizontally - alignItems: 'center', // Center the animation vertically - alignContent: 'center', // Center the animation vertically - overflow: 'hidden', // Prevent overflow + // position: 'relative', + // paddingTop: theme.spacing(10), // Add top padding + // paddingBottom: theme.spacing(20), // Add bottom padding + // // marginBottom: theme.spacing(20), + // // paddingLeft: theme.spacing(10), // Add left padding + // // paddingRight: theme.spacing(10), // Add right padding + // minHeight: '70vh', // Adjust the height as needed to fit the animation + // maxHeight: '80vh', + // // alignItems: 'flex-start', // Center the animation vertically + // alignContent: 'center', // Center the animation vertically + // overflow: 'hidden', // Prevent overflow + // display: 'flex', + // alignItems: 'center', // Align children to the center horizontally + // justifyContent: 'space-between', // Distribute space between children })); + export const TertiaryContentContainer = styled('div')(({ theme }) => ({ padding: theme.spacing(3), borderRadius: theme.shape.borderRadius, diff --git a/src/tests/CardChart.jsx b/src/tests/CardChart.jsx index b8ee511..818d850 100644 --- a/src/tests/CardChart.jsx +++ b/src/tests/CardChart.jsx @@ -8,136 +8,426 @@ import React, { } from 'react'; import { Box, + Button, + Card, + CardActions, + CardContent, + CardHeader, Container, Grid, + List, + ListItem, Paper, + Typography, styled, useMediaQuery, useTheme, } from '@mui/material'; import CardLinearChart from './CardLinearChart'; -import { ErrorBoundary, useMode, usePageContext } from '../context'; -const ChartPaper = styled(Paper)(({ theme }) => ({ - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[5], - backgroundColor: theme.palette.background.paper, - color: theme.palette.text.secondary, +import { + ErrorBoundary, + useCollectionStore, + useMode, + usePageContext, +} from '../context'; +import useCardCronJob from './useCardCronJob'; +import initialCardData from './initialCardData'; +import { format } from 'date-fns'; +import SingleCardAnimation from '../assets/animations/SingleCardAnimation'; +import LoadingCardAnimation from '../assets/animations/LoadingCardAnimation'; +import ImageDisplayFromHook from '../components/other/dataDisplay/ImageDisplayFromHook'; + +// Adjust the padding, margin, and other styles as needed +const ChartArea = styled(Box)(({ theme }) => ({ + width: '100%', + height: '100%', padding: theme.spacing(2), display: 'flex', - flexDirection: 'column', alignItems: 'center', justifyContent: 'center', - width: '100%', - minHeight: '400px', + border: '1px solid #000', + borderRadius: '5px', +})); +// A square, responsive container for the chart +const SquareChartContainer = styled(Box)(({ theme }) => ({ + position: 'relative', + width: '100%', // 100% of the parent width + paddingTop: '100%', // Maintain aspect ratio (1:1) overflow: 'hidden', - margin: theme.spacing(2, 0), + '& > *': { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + }, })); -const ResponsiveSquare = styled(Box)(({ theme }) => ({ - width: '100%', - // paddingTop: '100%', - backgroundColor: theme.palette.backgroundA.lightest, - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[5], - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - // width: isSmallScreen ? '100%' : '80%', // Adjust width based on screen size - height: 'auto', // Adjust height as needed - padding: theme.spacing(2), // Consistent padding - overflow: 'auto', // Adds scroll to inner content if it overflows -})); +const CardChart = ({ cardData = initialCardData }) => { + // STYLING AND MEDIA QUERY HOOKS + const { theme } = useMode(); + const theme2 = useTheme(); + const isLgUp = useMediaQuery(theme.breakpoints.up('lg')); + const { selectedCollection, allCollections } = useCollectionStore(); + const [imageUrl, setImageUrl] = useState(null); + const [error, setError] = useState(null); -function handleThresholdUpdate(lastUpdateTime, setLastUpdateTime) { - const currentTime = new Date().getTime(); - if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { - setLastUpdateTime(currentTime); - return currentTime; - } - return lastUpdateTime; -} + const { startUpdates, pauseUpdates, resetData } = + useCardCronJob(initialCardData); + const formatTimestamp = (timestamp) => + format(new Date(timestamp), "MMM do, yyyy 'at' HH:mm"); -const CardChart = ({ cardData }) => { + const [chartDimensions, setChartDimensions] = useState({ + width: 0, + height: 0, + }); const { setLoading, loadingStatus, returnDisplay } = usePageContext(); if (!cardData || !cardData?.dailyPriceHistory) { setLoading('isLoading', true); } const safeCardData = cardData || { dailyPriceHistory: [] }; const dailyPriceHistory = safeCardData?.dailyPriceHistory; - if (!dailyPriceHistory?.length) { - setLoading('isLoading', true); - } useEffect(() => { - if (dailyPriceHistory?.length) { - setLoading('isLoading', false); + const shouldLoad = + !cardData || + !cardData?.dailyPriceHistory || + !cardData.dailyPriceHistory.length; + if (shouldLoad !== loadingStatus.isLoading) { + setLoading('isLoading', shouldLoad); } - }, [dailyPriceHistory]); - // STYLING AND MEDIA QUERY HOOKS - const { theme } = useMode(); - const theme2 = useTheme(); - const isMobile = useMediaQuery(theme2.breakpoints.down('sm')); - const chartContainerRef = useRef(null); - const [chartDimensions, setChartDimensions] = useState({ - width: 0, - height: 0, - }); - - const calculateChartDimensions = useCallback(() => { - if (chartContainerRef.current) { - const width = chartContainerRef.current.offsetWidth; - const minWidth = isMobile ? 300 : 400; - const height = width * 0.7; // 70% of width - const minHeight = minWidth * 0.7; - setChartDimensions({ width, height, minWidth, minHeight }); - } - }, []); + }, [cardData, loadingStatus.isLoading, setLoading]); // Add loadingStatus.isLoading to dependencies + // useEffect to set image state when cardData changes useEffect(() => { - const handleResize = () => calculateChartDimensions(); - window.addEventListener('resize', handleResize); - calculateChartDimensions(); - return () => window.removeEventListener('resize', handleResize); - }, [calculateChartDimensions]); + if (cardData?.imageUrl) { + setImageUrl(cardData?.image); + } + }, [cardData?.imageUrl]); const chartData = useMemo( () => - cardData?.dailyPriceHistory?.map((priceEntry) => ({ + dailyPriceHistory?.map((priceEntry) => ({ x: priceEntry?.timestamp, y: priceEntry?.num, })), - [cardData.dailyPriceHistory] + [dailyPriceHistory] // dependency array ); - + // const chartData = useMemo( + // () => + // cardData?.dailyPriceHistory?.map((entry) => ({ + // x: format(new Date(entry.timestamp), "MMM do, yyyy 'at' HH:mm"), + // y: entry.num, + // })) || [], + // [cardData?.dailyPriceHistory] + // ); const nivoReadyData = useMemo( - () => [{ id: cardData?.name, data: chartData }], + () => [ + { + id: cardData?.name || 'default', // Fallback ID if cardData.name is not available + data: chartData, + }, + ], [chartData, cardData?.name] ); + // Ensure this effect doesn't set loading when not necessary + useEffect(() => { + if (nivoReadyData && nivoReadyData.length > 0 && loadingStatus.isLoading) { + setLoading('isLoading', false); + } + }, [nivoReadyData, setLoading, loadingStatus.isLoading]); + // Add responsive chart dimension handling + useEffect(() => { + // Example of setting dynamic chart dimensions (could be more complex based on container size) + const updateDimensions = () => { + const width = window.innerWidth < 500 ? window.innerWidth : 500; // or some other logic + const height = 300; // Fixed height or based on aspect ratio + setChartDimensions({ width, height }); + }; - // // Return loading indicator, error message, or chart based on data status - // if (!dailyPriceHistory?.length) { - // return ; // or some placeholder text - // } + window.addEventListener('resize', updateDimensions); + updateDimensions(); // Initial call + + return () => { + window.removeEventListener('resize', updateDimensions); // Cleanup listener + }; + }, []); // Empty array ensures this effect runs only once after initial render + // Simplified for demonstration + const renderLoadingAnimation = () => { + return ( + + ); + }; + const renderHeaderWithAnimation = () => { + return ( + + + {/* Conditionally render animation based on size or other conditions */} + {isLgUp && renderLoadingAnimation()} + + ); + }; + // const renderImageLoadTestWithErrorHandling = () => { + // const handleImageLoad = () => { + // console.log('Image loaded'); + // }; + // const handleImageError = () => { + // console.log('Image error'); + // }; + // return ( + // + // + // + // ); + // }; + // const renderImageLoadTest = () => { + // if (!imageUrl && allCollections?.length > 0) return null; + // // if (!imageUrl && allCollections?.length > 0) { + // // setImageUrl(allCollections[0]?.cards[0]?.image); + // // } + // const handleImageLoad = () => { + // console.log('Image loaded'); + // }; + // const handleImageError = () => { + // console.log('Image error'); + // }; + // return ( + // + // ); + // }; + // const renderLoadingAnimation = () => { + // if (isLgUp) { + // console.log('Loading animation'); + // return ( + // + // {/* Use LoadingCardAnimation with the correct props */} + // + // + // ); + // } + // return null; + // }; return ( - - {loadingStatus?.isLoading && returnDisplay()} - - - - - + + + + {renderHeaderWithAnimation()} + + {/* + {renderImageLoadTest()} + */} + + + {loadingStatus?.isLoading ? ( + returnDisplay() + ) : ( + - - - - - - + + )} + + + + + + {/* Card displaying data with responsive typography */} + + + {/* Responsive Button Styling */} + {/* Iterate through buttons to reduce redundancy */} + {['Start Updates', 'Pause Updates', 'Reset Data'].map( + (text, index) => ( + + ) + )} + + + + + + {cardData?.dailyPriceHistory?.map((entry, index) => ( + + + Quantity: {cardData?.quantity} + + + Price: ${entry?.num} + + + {formatTimestamp(entry?.timestamp)} + + + ))} + + + + + ); }; diff --git a/src/tests/CardComponent.jsx b/src/tests/CardComponent.jsx deleted file mode 100644 index 291e08f..0000000 --- a/src/tests/CardComponent.jsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react'; -import useCardCronJob from './useCardCronJob'; // path to your hook -import initialCardData from './initialCardData'; -import { - Button, - Card, - CardContent, - Typography, - Box, - List, - ListItem, -} from '@mui/material'; -import { useMode } from '../context'; -import { format } from 'date-fns'; - -const CardComponent = () => { - const { theme } = useMode(); - const { cardData, startUpdates, pauseUpdates, resetData } = - useCardCronJob(initialCardData); - // Format timestamp to be more human readable - const formatTimestamp = (timestamp) => - format(new Date(timestamp), "MMM do, yyyy 'at' HH:mm"); - - return ( - - - - - Card Cron Job Simulator - - - - - - - - {/* Displaying the card data for demonstration */} - - - {/* Card Data */} - {/* - Card Data: - */} - - - Daily Price History: - - - {cardData?.dailyPriceHistory?.map((entry, index) => ( - - - Quantity: {cardData?.quantity} - - - Price: ${entry?.num} - - - {formatTimestamp(entry?.timestamp)} - - - ))} - - - - - - - ); -}; - -export default CardComponent; diff --git a/src/tests/CardLinearChart.jsx b/src/tests/CardLinearChart.jsx index 2b7413c..4e5ba41 100644 --- a/src/tests/CardLinearChart.jsx +++ b/src/tests/CardLinearChart.jsx @@ -1,4 +1,8 @@ -// ... (imports) +import { Box, Tooltip, Typography, useMediaQuery } from '@mui/material'; +import { makeStyles, useTheme } from '@mui/styles'; +import { ResponsiveLine } from '@nivo/line'; +import { useCallback, useMemo, useState } from 'react'; +import { useMode } from '../context'; const useStyles = makeStyles((theme) => ({ axisLabel: { position: 'absolute', @@ -48,7 +52,14 @@ const CustomTooltip = ({ point }) => { ); }; - +const parseDate = (dateString) => { + const date = new Date(dateString); + if (isNaN(date.getTime())) { + console.error(`Invalid date: ${dateString}`); + return null; // or a sensible default, or throw an error, depending on your needs + } + return date; +}; export const useEventHandlers = () => { const [hoveredData, setHoveredData] = useState(null); const handleMouseMove = useCallback((point) => { @@ -57,62 +68,74 @@ export const useEventHandlers = () => { const handleMouseLeave = useCallback(() => setHoveredData(null), []); return { hoveredData, handleMouseMove, handleMouseLeave }; }; -import { Box, Tooltip, Typography, useMediaQuery } from '@mui/material'; -import { makeStyles, useTheme } from '@mui/styles'; -import { ResponsiveLine } from '@nivo/line'; -import { useCallback, useMemo, useState } from 'react'; -import { useMode } from '../context'; const CardLinearChart = ({ nivoReadyData, dimensions }) => { - console.log('nivoReadyData', nivoReadyData); - const { theme } = useMode(); // or useTheme() based on your context setup + const { theme } = useMode(); const classes = useStyles(theme); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - const [isZoomed, setIsZoomed] = useState(false); // If you need zoom functionality - const { handleMouseMove, handleMouseLeave } = useEventHandlers(); + // Ensure all data points have valid dates + const processedData = useMemo(() => { + return nivoReadyData?.map((series) => ({ + ...series, + data: series?.data?.map((point) => ({ + ...point, + x: parseDate(point?.x) || point?.x, // Use the parsed date or fallback to the original value + })), + })); + }, [nivoReadyData]); + // const { theme } = useMode(); + // const classes = useStyles(theme); + // const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + // const [isZoomed, setIsZoomed] = useState(false); + // const { handleMouseMove, handleMouseLeave } = useEventHandlers(); - // Error handling for empty or invalid data - if ( - !nivoReadyData || - nivoReadyData?.length === 0 || - !Array.isArray(nivoReadyData) - ) { - return No data available; - } + // if ( + // !nivoReadyData || + // nivoReadyData.length === 0 || + // !Array.isArray(nivoReadyData) + // ) { + // return No data available; + // } - // Ensure all data points have valid dates - nivoReadyData?.forEach((series) => { - series?.data?.forEach((point) => { - if (!(point?.x instanceof Date)) { - // Convert to date or handle error - console.warn('Invalid date found in chart data'); + // // Correct the date parsing logic + // const processedData = nivoReadyData.map((series) => ({ + // ...series, + // data: series.data.map((point) => ({ + // ...point, + // x: parseDate(point.x) || point.x, // Use the parsed date or fallback to the original value + // })), + // })); + + // // Ensure all data points have valid dates + // nivoReadyData?.forEach((series) => { + // series?.data?.forEach((point) => { + // const date = parseDate(point?.x); + // if (!(date instanceof Date)) { + // // Convert to date or handle error + // console.warn('Invalid date found in chart data', date); - // Example of converting to date - const date = new Date(point?.x); - if (date instanceof Date && !isNaN(date)) { - point.x = date; - } - } - }); - }); + // // Example of converting to date + // const date = new Date(date); + // if (date instanceof Date && !isNaN(date)) { + // point.x = date; + // } + // } + // }); + // }); // Calculate chart properties based on nivoReadyData and dimensions + // Minimal chart properties for testing const chartProps = useMemo( () => ({ - data: nivoReadyData, - margin: { top: 50, right: 110, bottom: 50, left: 60 }, + data: processedData, + margin: { top: 20, right: 20, bottom: 20, left: 35 }, xScale: { type: 'time', format: 'time:%Y-%m-%dT%H:%M:%S.%LZ', useUTC: false, precision: 'second', }, - xFormat: 'time:%Y-%m-%d %H:%M:%S', - yScale: { type: 'linear', min: 'auto', max: 'auto' }, - animate: true, - motionStiffness: 90, - motionDamping: 15, axisBottom: { tickRotation: 0, legend: 'Time', @@ -123,28 +146,67 @@ const CardLinearChart = ({ nivoReadyData, dimensions }) => { tickValues: 'every 2 days', format: '%b %d', }, - axisLeft: { - orient: 'left', - legend: 'Price', - legendOffset: -40, - legendPosition: 'middle', - tickSize: 5, - tickPadding: 5, - }, - pointSize: 10, - pointColor: { theme: 'background' }, - pointBorderWidth: 2, - pointBorderColor: { from: 'serieColor' }, - useMesh: true, + // animate: true, + // motionStiffness: 90, + // motionDamping: 15, + // axisLeft: { + // orient: 'left', + // legend: 'Price', + // legendOffset: -40, + // legendPosition: 'middle', + // tickSize: 5, + // tickPadding: 5, + // }, + // pointSize: 10, + // pointColor: { theme: 'background' }, + // pointBorderWidth: 2, + // pointBorderColor: { from: 'serieColor' }, + // useMesh: true, enableSlices: 'x', + yScale: { type: 'linear', min: 'auto', max: 'auto' }, }), - [nivoReadyData, theme, isMobile] - ); // Add other dependencies as needed + [nivoReadyData, processedData] + ); + // const chartProps = useMemo( + // () => ({ + // data: processedData, + // margin: { top: 50, right: 110, bottom: 50, left: 60 }, + // xScale: { + // type: 'time', + // format: 'time:%Y-%m-%dT%H:%M:%S.%LZ', + // useUTC: false, + // precision: 'second', + // }, + // xFormat: 'time:%Y-%m-%d %H:%M:%S', + // yScale: { type: 'linear', min: 'auto', max: 'auto' }, + // animate: true, + // motionStiffness: 90, + // motionDamping: 15, + // axisLeft: { + // orient: 'left', + // legend: 'Price', + // legendOffset: -40, + // legendPosition: 'middle', + // tickSize: 5, + // tickPadding: 5, + // }, + // pointSize: 10, + // pointColor: { theme: 'background' }, + // pointBorderWidth: 2, + // pointBorderColor: { from: 'serieColor' }, + // useMesh: true, + // enableSlices: 'x', + // }), + // [processedData, theme, isMobile] // Add other dependencies as needed + // ); // Add other dependencies as needed - // Responsive container - const containerHeight = isMobile ? '200px' : dimensions.height || '300px'; - const containerWidth = '100%'; // Always take the full width of the parent + // console.log('Nivo Ready Data:', nivoReadyData); + // console.log('Processed Data:', processedData); + // console.log('Chart Dimensions:', dimensions); + if (!processedData || !processedData?.length) { + return No data available; + } return ( { const [cardData, setCardData] = useState(initialCardData); const [intervalId, setIntervalId] = useState(null); - const updateCardData = useCallback(() => { - setCardData((currentCard) => { - console.log('Updating card data:', cardData); - console.log('Current card data:', currentCard); - const newPriceHistory = { - num: (Math.random() * 10).toFixed(2), // or however you calculate new price - timestamp: new Date().toISOString(), - }; + // Function to update the card's daily price history + const updatePriceHistory = useCallback(() => { + setCardData((currentCardData) => { + if (!currentCardData) return null; + const lastPrice = + currentCardData?.dailyPriceHistory.slice(-1)[0]?.num || + currentCardData?.price; // Get last price or default to initial price + const newDailyPriceHistory = [...currentCardData.dailyPriceHistory]; + + // Generate 10 new daily prices + for (let i = 0; i < 10; i++) { + const newPrice = generateNewPrice(lastPrice); + newDailyPriceHistory.push({ + num: newPrice, + timestamp: new Date().toISOString(), + }); + } + + // Ensure the dailyPriceHistory doesn't exceed 10 entries + const slicedHistory = newDailyPriceHistory.slice(-10); return { - ...currentCard, - quantity: currentCard.quantity + 1, // Increment quantity or however you update - dailyPriceHistory: [...currentCard.dailyPriceHistory, newPriceHistory], + ...currentCardData, + dailyPriceHistory: slicedHistory, }; }); }, []); + // const updateCardData = useCallback(() => { + // setCardData((currentCard) => { + // console.log('Updating card data:', cardData); + // console.log('Current card data:', currentCard); + // const newPriceHistory = { + // num: (Math.random() * 10).toFixed(2), // or however you calculate new price + // timestamp: new Date().toISOString(), + // }; + + // return { + // ...currentCard, + // quantity: currentCard.quantity + 1, // Increment quantity or however you update + // dailyPriceHistory: [...currentCard.dailyPriceHistory, newPriceHistory], + // }; + // }); + // }, []); + + // Simulate a cron job with useEffect and setInterval + useEffect(() => { + const intervalId = setInterval(() => { + updatePriceHistory(); // Update price history every interval + }, 120000); // Update every 5 seconds for example, adjust as needed + + // Cleanup function to clear interval when component unmounts or updates + return () => clearInterval(intervalId); + }, [updatePriceHistory]); + const startUpdates = useCallback(() => { console.log('Starting updates'); if (!intervalId) { - const id = setInterval(updateCardData, 120000); // Update every 2 minutes + const id = setInterval(updatePriceHistory, 120000); // Update every 2 minutes, adjust as needed setIntervalId(id); } - }, [updateCardData, intervalId]); + }, [updatePriceHistory, intervalId]); const pauseUpdates = useCallback(() => { console.log('Pausing updates'); @@ -38,20 +80,21 @@ const useCardCronJob = (initialCardData) => { }, [intervalId]); const resetData = useCallback(() => { - console.log('Resetting data'); + console.log('Resetting data to initial state'); setCardData(initialCardData); pauseUpdates(); }, [initialCardData, pauseUpdates]); - useEffect(() => { - return () => { - // Cleanup interval on component unmount - if (intervalId) clearInterval(intervalId); - }; - }, [intervalId]); + // useEffect(() => { + // return () => { + // // Cleanup interval on component unmount + // if (intervalId) clearInterval(intervalId); + // }; + // }, [intervalId]); return { cardData, + updatePriceHistory, startUpdates, pauseUpdates, resetData,