*/}
diff --git a/src/components/other/dataDisplay/chart/PieChartStats.jsx b/src/components/other/dataDisplay/chart/PieChartStats.jsx
new file mode 100644
index 0000000..4437ebf
--- /dev/null
+++ b/src/components/other/dataDisplay/chart/PieChartStats.jsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { Box, Grid, Paper, Typography, useTheme } from '@mui/material';
+import { styled } from '@mui/system';
+import { useMode } from '../../../../context';
+import { PieChart } from '@mui/x-charts';
+
+// Define styled components
+const StatisticPaper = styled(Paper)(({ theme }) => ({
+ padding: theme.spacing(2),
+ color: theme.palette.text.secondary,
+ background: theme.palette.background.paper,
+ boxShadow: theme.shadows[2],
+ borderRadius: theme.shape.borderRadius,
+ transition: 'box-shadow 0.3s',
+ '&:hover': {
+ boxShadow: theme.shadows[4],
+ },
+}));
+
+const StatisticHeader = styled(Typography)(({ theme }) => ({
+ fontWeight: 'bold',
+ color: theme.palette.primary.main,
+ marginBottom: theme.spacing(1),
+}));
+
+const PieChartStats = ({ chartData }) => {
+ const { theme } = useMode();
+
+ return (
+
+
+
+ Collection Statistics
+
+
+
+
+
+
+ );
+};
+
+export default PieChartStats;
diff --git a/src/components/other/dataDisplay/commonStyles.jsx b/src/components/other/dataDisplay/commonStyles.jsx
new file mode 100644
index 0000000..1cacd17
--- /dev/null
+++ b/src/components/other/dataDisplay/commonStyles.jsx
@@ -0,0 +1,17 @@
+export const commonStatisticPaperStyle = {
+ padding: 2,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ boxShadow: 3,
+};
+
+export const commonStatisticHeaderStyle = {
+ marginBottom: 1,
+ fontWeight: 'bold',
+ color: 'secondary.main',
+};
+
+// Use these styles in your components below
diff --git a/src/components/other/search/DeckSearch.js b/src/components/other/search/DeckSearch.js
new file mode 100644
index 0000000..de422a2
--- /dev/null
+++ b/src/components/other/search/DeckSearch.js
@@ -0,0 +1,140 @@
+import React, { useEffect, useState, useMemo } from 'react';
+import { Box, Fade } from '@mui/material';
+import { useCardStore } from '../../../context/CardContext/CardStore';
+import DeckSearchCardGrid from '../../grids/searchResultsGrids/DeckSearchCardGrid';
+import CustomPagination from '../../reusable/CustomPagination';
+import SearchBar from './SearchBar';
+
+const DeckSearch = ({ userDecks }) => {
+ const [page, setPage] = useState(1);
+ const { searchData, setSlicedAndMergedSearchData } = useCardStore();
+
+ // Pagination Control
+ const itemsPerPage = 36;
+ const handlePagination = (event, value) => setPage(value);
+ const paginatedData = useMemo(() => {
+ const start = (page - 1) * itemsPerPage;
+ return searchData?.slice(start, start + itemsPerPage);
+ }, [searchData, page]);
+
+ useEffect(() => {
+ setSlicedAndMergedSearchData(paginatedData);
+ }, [paginatedData]);
+
+ return (
+
+
+ {/* Already contains all needed functionality */}
+
+
+
+
+ );
+};
+
+export default DeckSearch;
+
+// import React, { useEffect, useMemo, useRef, useState } from 'react';
+// import { Box, Fade } from '@mui/material';
+// import { useCardStore } from '../../../context/CardContext/CardStore';
+// import SearchForm from '../../forms/SearchForm';
+// import DeckSearchCardGrid from '../../grids/searchResultsGrids/DeckSearchCardGrid';
+// import CustomPagination from '../../reusable/CustomPagination';
+// import SearchBar from './SearchBar';
+
+// const DeckSearch = ({ userDecks }) => {
+// const [searchTerm, setSearchTerm] = useState('');
+// const [page, setPage] = useState(1);
+// const {
+// // deckSearchData,
+// searchData,
+// handleRequest,
+// setSlicedAndMergedSearchData,
+// } = useCardStore();
+
+// const handleChange = (event) => setSearchTerm(event.target.value);
+// const handleSubmit = () => {
+// // event.preventDefault();
+// handleRequest({ name: searchTerm });
+// };
+// const handlePagination = (event, value) => setPage(value);
+
+// const itemsPerPage = 36;
+// const start = (page - 1) * itemsPerPage;
+// const end = start + itemsPerPage;
+// // const currentDeckSearchData = deckSearchData?.slice(start, end);
+// const currentStoreSearchData = useMemo(
+// () => searchData?.slice(start, end),
+// [searchData, page]
+// );
+
+// useEffect(() => {
+// setSlicedAndMergedSearchData(currentStoreSearchData);
+// }, [currentStoreSearchData]);
+
+// return (
+//
+//
+//
+// {/* */}
+//
+//
+//
+//
+//
+//
+// );
+// };
+
+// export default DeckSearch;
diff --git a/src/components/other/search/SearchBar.js b/src/components/other/search/SearchBar.js
new file mode 100644
index 0000000..8cdcb47
--- /dev/null
+++ b/src/components/other/search/SearchBar.js
@@ -0,0 +1,159 @@
+// commonStyles.js (or use theme.js)
+export const commonPaperStyles = (theme) => ({
+ padding: 3,
+ borderRadius: 2,
+ background: theme.palette.success.dark,
+ boxShadow: 3,
+ margin: 'auto',
+ width: '100%',
+ maxWidth: 'md',
+ '&:hover': {
+ boxShadow: 6,
+ },
+});
+
+// SearchBar.jsx
+import React, { useState, useEffect } from 'react';
+import SettingsIcon from '@mui/icons-material/Settings';
+
+import {
+ Container,
+ Paper,
+ Typography,
+ Box,
+ IconButton,
+ Menu,
+ MenuItem,
+} from '@mui/material';
+import {
+ useCardStore,
+ useDeckStore,
+ useFormContext,
+ useMode,
+} from '../../../context';
+import SearchForm from '../../forms/SearchForm';
+import SearchSettings from './SearchSettings';
+
+const SearchBar = ({ onSearchFocus, onSearchBlur }) => {
+ const { theme } = useMode();
+ const { handleRequest } = useCardStore();
+ const { forms, handleChange, handleSubmit } = useFormContext(); // Consume context
+ useEffect(
+ () => {
+ console.log('SearchBar forms', forms);
+ if (!forms?.searchForm?.searchTerm) return;
+ handleRequest(forms?.searchForm?.searchTerm);
+ },
+ [forms?.searchForm?.searchTerm],
+ handleSubmit
+ ); // Watch for changes in the form data
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+ return (
+
+
+
+
+ Search Cards
+
+
+
+
+
+
+
+ {/* eslint-disable-next-line max-len */}
+ {
+ e.preventDefault();
+ handleSubmit('searchForm')(e); // Pass the event to the handleSubmit function
+ }}
+ handleKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleSubmit('searchForm')(e);
+ }
+ }}
+ onFocus={onSearchFocus}
+ onBlur={onSearchBlur}
+ />
+
+
+
+ );
+};
+
+export default SearchBar;
+
+// // // SearchForm.jsx
+// // import React from 'react';
+// // import { Button, TextField, Paper } from '@mui/material';
+
+// const SearchForm = ({
+// searchTerm,
+// handleChange,
+// handleSubmit,
+// handleKeyPress,
+// }) => {
+// const { theme } = useMode();
+// return (
+//
+//
+//
+// );
+// };
+
+// // 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 20cebd4..f7856e1 100644
--- a/src/components/reusable/PrivateRoute.jsx
+++ b/src/components/reusable/PrivateRoute.jsx
@@ -1,18 +1,15 @@
import React, { useContext } from 'react';
import { Navigate } from 'react-router-dom';
-import { useCookies } from 'react-cookie';
-import { useAuthContext } from '../../context/hooks/auth';
+import { useAuthContext } from '../../context';
const PrivateRoute = ({ children }) => {
- const authContext = useAuthContext();
+ const { isLoggedIn } = useAuthContext();
- // Use react-cookie's useCookies hook to read the 'isLoggedIn' cookie
- const [cookies] = useCookies(['isLoggedIn']);
+ if (!isLoggedIn) {
+ return
;
+ }
- const isLoggedIn = authContext.isLoggedIn || cookies.isLoggedIn;
-
- // If isLoggedIn from either cookie or context is true, proceed to the route
- return isLoggedIn ? children :
;
+ return children;
};
export default PrivateRoute;
diff --git a/src/components/search/DeckSearch.js b/src/components/search/DeckSearch.js
deleted file mode 100644
index c10c10f..0000000
--- a/src/components/search/DeckSearch.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react';
-import { Box, Fade } from '@mui/material';
-import { useCardStore } from '../../context/CardContext/CardStore';
-import SearchForm from '../forms/SearchForm';
-import DeckSearchCardGrid from '../grids/searchResultGrids/DeckSearchCardGrid';
-import CustomPagination from '../reusable/CustomPagination';
-
-const DeckSearch = ({ userDecks }) => {
- const [searchTerm, setSearchTerm] = useState('');
- const [page, setPage] = useState(1);
- const {
- // deckSearchData,
- searchData,
- handleRequest,
- setSlicedAndMergedSearchData,
- } = useCardStore();
-
- const handleChange = (event) => setSearchTerm(event.target.value);
- const handleSubmit = () => {
- // event.preventDefault();
- handleRequest({ name: searchTerm });
- };
- const handlePagination = (event, value) => setPage(value);
-
- const itemsPerPage = 36;
- const start = (page - 1) * itemsPerPage;
- const end = start + itemsPerPage;
- // const currentDeckSearchData = deckSearchData?.slice(start, end);
- const currentStoreSearchData = useMemo(
- () => searchData?.slice(start, end),
- [searchData, page]
- );
-
- useEffect(() => {
- setSlicedAndMergedSearchData(currentStoreSearchData);
- }, [currentStoreSearchData]);
-
- return (
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default DeckSearch;
diff --git a/src/components/search/SearchBar.js b/src/components/search/SearchBar.js
deleted file mode 100644
index 8d70dcb..0000000
--- a/src/components/search/SearchBar.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import React, { useState } from 'react';
-import { Grid, Box, Typography, Container, Paper } from '@mui/material';
-import { useCardStore } from '../../context/CardContext/CardStore';
-import SearchButton from '../buttons/other/SearchButton';
-import CardNameInput from '../other/InputComponents/CardNameInput';
-import CustomSelector from '../other/InputComponents/CustomSelector';
-import search from './search.json';
-import { useMode } from '../../context/hooks/colormode';
-import SearchForm from '../forms/SearchForm';
-
-const SearchBar = ({ onSearchFocus, onSearchBlur }) => {
- const { theme } = useMode();
- const { initialState, filters } = search;
- const [searchParams, setSearchParams] = useState({
- name: '',
- type: '',
- attribute: '',
- race: '',
- });
- const { handleRequest } = useCardStore();
- const handleChange = (name, newValue) => {
- setSearchParams((prev) => ({ ...prev, [name]: newValue }));
- };
- const handleSubmit = () => {
- // e.preventDefault(); // Prevent default form submission
- handleRequest(searchParams);
- };
- return (
-
-
-
- Search Cards
-
-
-
- {/* handleChange('name', event.target.value)}
- /> */}
- handleChange('name', e.target.value)}
- handleSubmit={handleSubmit}
- onFocus={onSearchFocus}
- onBlur={onSearchBlur}
- />
-
-
- {filters?.map((filter) => (
-
-
- handleChange(filter.label, event.target.value)
- }
- setSearchParams={setSearchParams}
- values={filter?.values}
- />
-
- ))}
- {/*
-
- */}
-
-
-
- );
-};
-
-export default SearchBar;
diff --git a/src/containers/CardDetailsContainer.jsx b/src/containers/CardDetailsContainer.jsx
deleted file mode 100644
index 452ea69..0000000
--- a/src/containers/CardDetailsContainer.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-// // Import necessary modules from MUI or other libraries
-// import React from 'react';
-// import { Typography, Grid } from '@mui/material';
-// import { useStyles } from '../components/cards/cardStyles';
-
-// const CardDetailsContainer = ({ card }) => {
-// const classes = useStyles();
-// return (
-//
-//
-//
-// {card?.name}
-//
-//
-// {card?.desc || 'No description available.'}
-//
-//
-//
-// );
-// };
-
-// export default CardDetailsContainer;
diff --git a/src/containers/collectionPageContainers/CollectionPortfolioChartContainer.jsx b/src/containers/collectionPageContainers/CollectionPortfolioChartContainer.jsx
index 155a9f9..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/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/containers/collectionPageContainers/CollectionPortfolioListContainer.jsx b/src/containers/collectionPageContainers/CollectionPortfolioListContainer.jsx
index d556f78..cd63b7f 100644
--- a/src/containers/collectionPageContainers/CollectionPortfolioListContainer.jsx
+++ b/src/containers/collectionPageContainers/CollectionPortfolioListContainer.jsx
@@ -3,39 +3,17 @@ import { Box, Grid, Paper } from '@mui/material';
import CardList from '../../components/grids/collectionGrids/CardList';
import { useTheme } from '@mui/material/styles';
import { useMode } from '../../context/hooks/colormode';
+import usePortfolioStyles from '../../context/hooks/usePortfolioStyles';
const CollectionPortfolioListContainer = ({ selectedCards, removeCard }) => {
const theme2 = useTheme();
const { theme } = useMode();
+ const classes = usePortfolioStyles(theme);
return (
-
-
-
-
-
+
+
+
);
diff --git a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js
deleted file mode 100644
index 14ee2e4..0000000
--- a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import { Grid } from '@mui/material';
-import DeckDisplay from '../../layout/DeckDisplay';
-import DeckSearch from '../../components/search/DeckSearch';
-import { styled } from '@mui/styles';
-import { useMode } from '../../context/hooks/colormode';
-
-const SearchGrid = styled(Grid)(({ theme }) => ({
- padding: theme.spacing(2),
- [theme.breakpoints.down('md')]: {
- width: '50%', // Half width on small and medium screens
- },
-}));
-
-const DisplayGrid = styled(Grid)(({ theme }) => ({
- padding: theme.spacing(2),
- [theme.breakpoints.down('md')]: {
- width: '50%', // Half width on small and medium screens
- },
-}));
-
-const RootGrid = styled(Grid)(({ theme }) => ({
- overflow: 'auto',
- display: 'flex',
- flexDirection: 'row',
- flexWrap: 'wrap', // Ensure wrapping on smaller screens
- backgroundColor: theme.palette.background.secondary,
- padding: theme.spacing(3),
- borderRadius: theme.shape.borderRadius,
- boxShadow: theme.shadows[5],
- width: '100%',
-}));
-
-const DeckBuilderContainer = () => {
- const { theme } = useMode();
-
- return (
-
-
-
-
-
-
-
-
- );
-};
-
-export default DeckBuilderContainer;
diff --git a/src/context/AuthContext/authContext.js b/src/context/AuthContext/authContext.js
index a1cc68f..f1859ec 100644
--- a/src/context/AuthContext/authContext.js
+++ b/src/context/AuthContext/authContext.js
@@ -1,215 +1,504 @@
+/* eslint-disable @typescript-eslint/no-empty-function */
import React, {
useState,
useEffect,
- useRef,
useCallback,
+ useRef,
useContext,
+ createContext,
+ useMemo,
} from 'react';
import axios from 'axios';
import { useCookies } from 'react-cookie';
-import { debounce } from 'lodash';
-import {
- AUTH_COOKIE,
- LOGGED_IN_COOKIE,
- USER_COOKIE,
- processResponseData,
-} from './helpers';
-
-export const AuthContext = React.createContext();
+import { processResponseData } from './helpers';
+import { usePageContext } from '../PageContext/PageContext';
+
+// 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([
- LOGGED_IN_COOKIE,
- AUTH_COOKIE,
- USER_COOKIE,
+ 'basicData',
+ 'securityData',
+ 'user',
+ 'isLoggedIn',
+ 'userId',
+ 'authUser',
+ 'authToken',
+ 'lastLogin',
+ 'lastLogout',
]);
- const [isLoading, setIsLoading] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
-
- const [user, setUser] = useState({
- username: '',
- email: '',
- id: '',
- role: '',
- cart: [],
- decks: [],
- collections: [],
+ 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 [updatedUser, setUpdatedUser] = useState({});
- const [token, setToken] = useState(null);
- const [error, setError] = useState(null);
- // const logoutTimerRef = useRef(null);
-
const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER;
const executeAuthAction = async (actionType, url, requestData) => {
- setIsLoading(true);
+ setLoading('isPageLoading', true);
try {
const response = await axios.post(
`${REACT_APP_SERVER}/api/users/${url}`,
requestData
);
- const processedData = processResponseData(response.data, actionType);
- if (processedData) {
- const { token, user, newUser } = processedData;
- setCookie(AUTH_COOKIE, token, { path: '/' });
- setCookie(USER_COOKIE, user || newUser, { path: '/' });
- setCookie(LOGGED_IN_COOKIE, true, { path: '/' });
- setIsLoggedIn(true);
+ console.log('Response:', response);
+ const processedData = processResponseData(response, actionType);
+ if (response.status === 200 || response.status === 201) {
+ 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(user || newUser);
+ 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('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 (err) {
- setError(err.message);
+ } catch (error) {
+ console.error('Auth error:', error);
} finally {
- setIsLoading(false);
+ 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');
- axios.interceptors.request.use(
- (config) => {
- const token = cookies[AUTH_COOKIE];
- if (token) {
- config.headers.Authorization = `Bearer ${token}`;
- }
- return config;
- },
- (error) => Promise.reject(error)
- );
-
- // Login function
- const login = async (username, password) => {
- await executeAuthAction('Login', 'signin', { username, password });
- };
-
- // Signup function
- const signup = async (loginData, basicInfo, otherInfo) => {
- await executeAuthAction('Signup', 'signup', {
- login_data: loginData,
- basic_info: basicInfo,
- ...otherInfo,
- });
- };
-
- // Logout function
- const logout = () => {
- removeCookie(AUTH_COOKIE);
setIsLoggedIn(false);
+ setLoginStatus({
+ isLoggedIn: isLoggedIn,
+ lastLogin: lastLogin.current,
+ lastLogout: logoutTimerRef.current,
+ });
+ setUser(null);
setToken(null);
- setUser({});
- };
-
- const logoutTimerRef = useRef(null);
-
- // Function to start the logout timer
- const startLogoutTimer = () => {
- // Clear existing timer if any
if (logoutTimerRef.current) clearTimeout(logoutTimerRef.current);
-
- // Set a new timer for 30 minutes (1800000 milliseconds)
- logoutTimerRef.current = setTimeout(() => {
- logout(); // Call your logout function
- }, 1800000);
- };
-
- const debouncedLogout = useCallback(
- debounce(() => {
- if (logoutTimerRef.current) {
- clearTimeout(logoutTimerRef.current);
- }
- logoutTimerRef.current = setTimeout(logout, 1800000); // 30 minutes
- }, 500),
- [logout] // Dependency for useCallback
- );
-
+ }, [removeCookie]);
const resetLogoutTimer = useCallback(() => {
- debouncedLogout();
- }, [debouncedLogout]);
-
- // Attach debounced event listeners for user activity
+ clearTimeout(logoutTimerRef.current);
+ logoutTimerRef.current = setTimeout(logout, 2700000); // 45 minutes
+ }, [logout]);
useEffect(() => {
- const events = ['mousemove', 'keypress', 'scroll', 'click'];
- events.forEach((event) => window.addEventListener(event, resetLogoutTimer));
+ if (token) {
+ console.log('Token found, resetting logout timer...');
+ resetLogoutTimer();
+ }
+ }, [token, resetLogoutTimer]);
+ useEffect(() => {
+ const interceptorId = axios.interceptors.request.use(
+ (config) => {
+ if (token) config.headers.Authorization = `Bearer ${token}`;
+ return config;
+ },
+ (error) => Promise.reject(error)
+ );
- return () => {
- clearTimeout(logoutTimerRef.current);
- events.forEach((event) =>
- window.removeEventListener(event, resetLogoutTimer)
- );
- };
- }, [resetLogoutTimer]);
+ return () => axios.interceptors.request.eject(interceptorId);
+ }, [token]);
- // Attach event listeners for user activity
- useEffect(() => {
- startLogoutTimer();
-
- window.addEventListener('mousemove', resetLogoutTimer);
- window.addEventListener('keypress', resetLogoutTimer);
- window.addEventListener('scroll', resetLogoutTimer);
- window.addEventListener('click', resetLogoutTimer);
-
- return () => {
- clearTimeout(logoutTimerRef.current);
- window.removeEventListener('mousemove', resetLogoutTimer);
- window.removeEventListener('keypress', resetLogoutTimer);
- window.removeEventListener('scroll', resetLogoutTimer);
- window.removeEventListener('click', resetLogoutTimer);
- };
- }, []);
+ // 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(() => {
- const storedToken = cookies[AUTH_COOKIE];
- const storedUser = cookies[USER_COOKIE];
+ 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);
- } else {
- setIsLoggedIn(false);
+ setLoginTimes();
+ setLoginStatus({
+ isLoggedIn: isLoggedIn,
+ lastLogin: lastLogin.current,
+ lastLogout: logoutTimerRef.current,
+ authUser: authUser,
+ token: token,
+ user: user,
+ });
+ resetLogoutTimer();
}
- }, [cookies]);
+ }, [
+ 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}
);
}
-// // Custom hook to use the AuthContext
+export const useAuthContext = () => useContext(AuthContext);
+
+// import React, {
+// useState,
+// useEffect,
+// useRef,
+// useCallback,
+// useContext,
+// } from 'react';
+// import axios from 'axios';
+// import { useCookies } from 'react-cookie';
+// import { debounce } from 'lodash';
+// import {
+// AUTH_COOKIE,
+// AUTH_USER_COOKIE,
+// LOGGED_IN_COOKIE,
+// USER_COOKIE,
+// USER_ID_COOKIE,
+// processResponseData,
+// } from './helpers';
+// import { usePageContext } from '../PageContext/PageContext';
+
+// export const AuthContext = React.createContext();
+
+// export default function AuthProvider({ children, serverUrl }) {
+// const { setLoading, loadingStatus } = usePageContext();
+// const [cookies, setCookie, removeCookie] = useCookies([
+// LOGGED_IN_COOKIE,
+// AUTH_COOKIE,
+// USER_COOKIE,
+// USER_ID_COOKIE,
+// AUTH_USER_COOKIE,
+// ]);
+// // const [isLoading, setIsLoading] = useState(false);
+// const [isLoggedIn, setIsLoggedIn] = useState(false);
+// const [user, setUser] = useState({
+// username: '',
+// userBasicData: {
+// firstName: '',
+// lastName: '',
+// userId: '',
+// },
+// userSecurityData: {
+// username: '',
+// password: '',
+// email: '',
+// phone: '',
+// role_data: {
+// name: '',
+// capabilities: [],
+// },
+// },
+// allDecks: [],
+// allCollections: [],
+// cart: {},
+// });
+// const [updatedUser, setUpdatedUser] = useState({});
+// const [token, setToken] = useState(null);
+// const [error, setError] = useState(null);
+// const [minutes, setMinutes] = useState(0);
+// const [logoutExpires, setLogoutExpires] = useState();
+// const logoutTimerRef = useRef(null);
+// const expires = new Date();
+// const REACT_APP_SERVER = serverUrl || process.env.REACT_APP_SERVER;
+
+// // Logout Function
+// const logout = useCallback(() => {
+// console.log('User is logged out');
+// removeCookie(AUTH_COOKIE);
+// removeCookie(USER_COOKIE);
+// removeCookie(AUTH_USER_COOKIE);
+// removeCookie(LOGGED_IN_COOKIE);
+// removeCookie(USER_ID_COOKIE);
+// setIsLoggedIn(false);
+// setUser({});
+// setToken(null);
+// if (logoutTimerRef.current) clearTimeout(logoutTimerRef.current);
+// }, [removeCookie]);
+
+// // Reset Logout Timer
+// const resetLogoutTimer = useCallback(() => {
+// if (logoutTimerRef.current) clearTimeout(logoutTimerRef.current);
+// logoutTimerRef.current = setTimeout(logout, 45 * 60 * 1000); // 45 minutes
+// }, [logout]);
+
+// // Execute Authentication Action
+// const executeAuthAction = async (actionType, url, requestData) => {
+// try {
+// const response = await axios.post(
+// `${serverUrl}/api/users/${url}`,
+// requestData
+// );
+// const processedData = processResponseData(response, actionType);
+
+// console.log('Processed Data: ', processedData);
+// if (response.status === 200 || response.status === 201) {
+// const { token, authData } = processedData; // Make sure this data is correctly retrieved
+// setCookie(AUTH_COOKIE, token, { path: '/' });
+// setCookie(USER_COOKIE, authData, { path: '/' });
+// setCookie(AUTH_USER_COOKIE, authData.userSecurityData, { path: '/' });
+// setCookie(LOGGED_IN_COOKIE, true, { path: '/' });
+// setCookie(USER_ID_COOKIE, authData.userSecurityData.userId, {
+// path: '/',
+// });
+// setIsLoggedIn(true);
+// setUser(authData);
+// setToken(token);
+// resetLogoutTimer();
+// }
+// return processedData;
+// } catch (error) {
+// setError(error);
+// }
+// };
+// // Set axios interceptors
+// axios.interceptors.request.use(
+// (config) => {
+// const authToken = cookies[AUTH_COOKIE];
+// if (authToken) config.headers.Authorization = `Bearer ${authToken}`;
+// return config;
+// },
+// (error) => Promise.reject(error)
+// );
+
+// useEffect(() => {
+// if (isLoggedIn) {
+// resetLogoutTimer();
+// }
+// }, [isLoggedIn, resetLogoutTimer]);
+
+// const signup = async (securityData, basicData) => {
+// const data = await executeAuthAction('signup', 'signup', {
+// userSecurityData: securityData,
+// userBasicData: basicData,
+// });
+// // resetLogoutTimer();
+// return data;
+// };
+// const login = async (username, password) => {
+// const data = await executeAuthAction('signin', 'signin', {
+// userSecurityData: { username: username, password: password },
+// });
+// console.log('User ' + username + ' is logged in');
+// // resetLogoutTimer();
+// return data;
+// };
+
+// useEffect(() => {
+// const handleUserActivity = debounce(resetLogoutTimer, 1000);
+// const events = ['mousemove', 'keypress', 'scroll', 'click'];
+
+// events.forEach((event) =>
+// window.addEventListener(event, handleUserActivity)
+// );
+
+// return () => {
+// events.forEach((event) =>
+// window.removeEventListener(event, handleUserActivity)
+// );
+// handleUserActivity.cancel();
+// if (logoutTimerRef?.current) clearTimeout(logoutTimerRef?.current);
+// };
+// }, [resetLogoutTimer, logoutTimerRef]);
+// // Check for stored tokens and user data in cookies
+// // Initialize user and token from cookies
+// useEffect(() => {
+// const storedToken = cookies[AUTH_COOKIE];
+// const storedUser = cookies[USER_COOKIE];
+// if (storedToken && storedUser) {
+// setToken(storedToken);
+// setUser(storedUser);
+// setIsLoggedIn(true);
+// resetLogoutTimer();
+// }
+// }, [cookies, resetLogoutTimer]);
+
+// return (
+//
+// {children}
+//
+// );
+// }
+
// export const useAuthContext = () => {
-// const context = React.useContext(AuthContext);
-// if (context === undefined) {
-// throw new Error('useAuthContext must be used within a AuthProvider');
+// const context = useContext(AuthContext);
+
+// if (!context) {
+// throw new Error('useAuth must be used within an AuthProvider');
// }
+
// return context;
// };
-export const useAuthContext = () => {
- const context = useContext(AuthContext);
+// // Attach event listeners for user activity
+// // useEffect(() => {
+// // startLogoutTimer();
- if (!context) {
- throw new Error('useAuth must be used within an AuthProvider');
- }
+// // window.addEventListener('mousemove', resetLogoutTimer);
+// // window.addEventListener('keypress', resetLogoutTimer);
+// // window.addEventListener('scroll', resetLogoutTimer);
+// // window.addEventListener('click', resetLogoutTimer);
- return context;
-};
+// // return () => {
+// // clearTimeout(logoutTimerRef.current);
+// // window.removeEventListener('mousemove', resetLogoutTimer);
+// // window.removeEventListener('keypress', resetLogoutTimer);
+// // window.removeEventListener('scroll', resetLogoutTimer);
+// // window.removeEventListener('click', resetLogoutTimer);
+// // };
+// // }, []);
diff --git a/src/context/AuthContext/helpers.jsx b/src/context/AuthContext/helpers.jsx
index 0865ad5..c83604f 100644
--- a/src/context/AuthContext/helpers.jsx
+++ b/src/context/AuthContext/helpers.jsx
@@ -1,9 +1,19 @@
import jwt_decode from 'jwt-decode';
-export const LOGGED_IN_COOKIE = 'loggedIn';
+// login status
+export const LOGGED_IN_COOKIE = false;
+// token
export const AUTH_COOKIE = 'authToken';
+// 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) => {
if (!data || Object.keys(data).length === 0) {
@@ -14,21 +24,23 @@ export const validateData = (data, eventName, functionName) => {
};
// Process the server response based on the action type (Login/Signup)
-export const processResponseData = (data, type) => {
- if (!validateData(data, `${type} Response`, `process${type}Data`))
- return null;
-
- if (type === 'Login') {
- const token = data?.data?.token;
- if (!token) return null;
- const user = jwt_decode(token);
- return { token, user };
- }
-
- if (type === 'Signup') {
- const { success, newUser } = data;
- if (success && newUser) return { success, newUser };
- }
+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),
+ basicData: data?.userBasicData,
+ securityData: data?.userSecurityData,
+ data: data,
+ message: message,
+ };
+ console.log('processedData --------------->', processedData);
- return null;
+ return processedData;
};
diff --git a/src/context/CardContext/CardStore.js b/src/context/CardContext/CardStore.js
index 908912b..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(['cart'], ['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 b610d8a..fd29785 100644
--- a/src/context/CartContext/CartContext.js
+++ b/src/context/CartContext/CartContext.js
@@ -10,8 +10,8 @@ import React, {
} from 'react';
import { useCookies } from 'react-cookie';
import { useUserContext } from '../UserContext/UserContext';
-import { fetchWrapper } from '../Helpers';
import { getCardQuantity } from './helpers';
+import useFetchWrapper from '../hooks/useFetchWrapper';
export const CartContext = createContext({
cartData: {
@@ -33,19 +33,20 @@ export const CartContext = createContext({
export const CartProvider = ({ children }) => {
// const { user, setUser } = useUserContext();
// const userId = user?.id;
+ const fetchWrapper = useFetchWrapper();
const [cartData, setCartData] = useState({
_id: '',
cart: [],
quantity: 0, // Total quantity of items
totalPrice: 0, // Total price of items
});
- const [cookies, setCookies] = useCookies(['user', 'cart']);
- const userId = cookies?.user?.id;
+ const [cookies, setCookie] = useCookies(['authUser', 'cart', 'cartData']);
+ const userId = cookies?.authUser?.userId;
const [totalQuantity, setTotalQuantity] = useState(0);
const [totalPrice, setTotalPrice] = useState(0);
const getTotalCost = () => {
- return cartData.cart.reduce(
+ return cartData?.cart?.reduce(
(acc, card) => acc + card.card_prices[0].tcgplayer_price * card.quantity,
0
);
@@ -53,7 +54,7 @@ export const CartProvider = ({ children }) => {
const totalCost = useMemo(
() =>
- cartData.cart.reduce(
+ cartData?.cart?.reduce(
(total, item) =>
total + item.quantity * item.card_prices[0].tcgplayer_price,
0
@@ -66,12 +67,13 @@ export const CartProvider = ({ children }) => {
const newCartData = await fetchWrapper(
// `${process.env.REACT_APP_SERVER}/api/carts/createEmptyCart`,
`${process.env.REACT_APP_SERVER}/api/users/${userId}/cart/createCart`,
-
'POST',
JSON.stringify({ userId })
);
-
- 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);
@@ -92,6 +94,7 @@ export const CartProvider = ({ children }) => {
`${process.env.REACT_APP_SERVER}${`/api/users/${userId}/cart`}`,
'GET'
);
+ console.log('FETCHED USER CART:', response);
setCartDataAndCookie(response);
} catch (error) {
console.error('Error fetching user cart:', error);
@@ -99,21 +102,26 @@ export const CartProvider = ({ children }) => {
await createUserCart();
}
}
- }, [userId, setCookies]);
+ }, [userId]);
// Set cart data and cookie
const setCartDataAndCookie = useCallback(
(newCartData) => {
- if (newCartData && Array.isArray(newCartData.cart)) {
+ if (newCartData && Array.isArray(newCartData?.cart)) {
setCartData(newCartData);
- setCookies('cart', newCartData.cart, {
+ setCookie('cartData', newCartData, {
+ path: '/',
+ secure: true,
+ sameSite: 'none',
+ });
+ setCookie('cart', newCartData?.cart, {
path: '/',
secure: true,
sameSite: 'none',
});
}
},
- [setCookies]
+ [setCookie]
);
// useEffect to fetch and set cart data
@@ -125,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) => {
@@ -219,7 +294,7 @@ export const CartProvider = ({ children }) => {
(acc, card) => acc + card.quantity,
0
),
- cartCardCount: cartData.cart?.length,
+ cartCardCount: cartData?.cart?.length,
cartValue: cartData?.cart?.reduce(
(acc, card) =>
acc + card.card_prices[0].tcgplayer_price * card.quantity,
@@ -230,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 7d2f2dd..28e7c8c 100644
--- a/src/context/CollectionContext/CollectionContext.jsx
+++ b/src/context/CollectionContext/CollectionContext.jsx
@@ -10,7 +10,7 @@ import React, {
useRef,
} from 'react';
import { useCookies } from 'react-cookie';
-import { fetchWrapper, createApiUrl } from '../Helpers.jsx';
+import { createApiUrl } from '../Helpers.jsx';
import {
handleError,
removeDuplicatesFromCollection,
@@ -35,13 +35,16 @@ import {
// convertToXYLabelData,
} from './helpers.jsx';
import { isEqual } from 'lodash';
+import useFetchWrapper from '../hooks/useFetchWrapper.jsx';
export const CollectionContext = createContext(defaultContextValue);
export const CollectionProvider = ({ children }) => {
- const [cookies] = useCookies(['user']);
- const user = cookies?.user;
- const userId = user?.id;
+ const [cookies] = useCookies(['authUser']);
+ const fetchWrapper = useFetchWrapper();
+
+ const user = cookies?.authUser;
+ const userId = user?.userId;
// state for the collection context
const [allCollections, setAllCollections] = useState([]);
const [collectionData, setCollectionData] = useState(initialCollectionState);
@@ -120,7 +123,23 @@ export const CollectionProvider = ({ children }) => {
console.error(`Failed to fetch collections: ${error}`, error.message);
}
}, [userId]);
-
+ /**
+ * Handles the updating of a collection.
+ * @param {Object} collection - The collection to update.
+ * @param {Object} cardUpdate - The card update object.
+ * @param {String} operation - The operation to perform.
+ * @returns {Promise