From d077c4e5ea60d0931ee542390b554b312945002c Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Mon, 6 May 2024 18:55:13 -0700 Subject: [PATCH] [test(global)][index/app]: setup initial testing environment DESCRIPTION: This commit sets up the initial testing environment for the application. BREAKING_CHANGE: Error caused by react/swiper import not being found causes breaking change for tests. --- .eslintrc.json | 7 +- .github/workflows/main.yml | 4 +- __mocks__/fileMock.js | 1 + __mocks__/swiper/react.js | 9 + package.json | 104 +++--- setupTests.js | 2 + src/App.js | 3 +- src/__tests__/App.test.js | 63 ++++ src/context/ColorModeProvider.jsx | 2 +- src/context/hooks/index.jsx | 4 - src/context/hooks/useBreakPoint.jsx | 1 - src/context/hooks/useDebounce.jsx | 29 -- src/context/hooks/useDialogState.jsx | 1 - src/context/hooks/useFetchWrapper.jsx | 3 +- src/context/hooks/useFormManagement.jsx | 1 + src/context/hooks/useGridItems.jsx | 7 +- src/context/hooks/useLocalStorage.jsx | 4 - src/context/hooks/useLogger.jsx | 62 ---- src/context/hooks/useManageCookies.jsx | 5 - src/context/hooks/useSelectedContext.jsx | 26 -- src/context/hooks/useSelectorActions.jsx | 20 +- src/context/index.js | 4 - src/context/state/useAuthManager.jsx | 2 +- src/context/state/useCardStore.jsx | 34 +- src/context/state/useManager.jsx | 342 ++++++------------ src/context/state/useMode.jsx | 2 +- src/context/state/useUserData.jsx | 19 +- src/data/baseMenuItems.jsx | 55 --- src/data/formsConfig.jsx | 9 +- src/data/index.jsx | 6 +- .../RC_FORMS/RCDynamicForm.jsx | 6 +- .../SpecificStyledComponents.jsx | 2 +- .../REUSABLE_STYLED_COMPONENTS/index.jsx | 4 +- src/layout/cards/CardDetailsContainer.jsx | 30 +- src/layout/cards/CardToolTip.jsx | 4 +- src/layout/cards/GenericCard.jsx | 96 +---- src/layout/cart/index.jsx | 5 +- .../CollectionsViewLayout/stats/PriceList.jsx | 25 +- .../MyPortfolioLineChart.jsx | 69 +++- .../PortfolioViewLayout/TopCardsSwiper.jsx | 47 ++- .../collection/PortfolioViewLayout/index.jsx | 2 +- src/layout/collection/index.jsx | 1 - src/layout/deck/index.jsx | 6 - src/layout/home/FeatureCardsSection.jsx | 31 +- src/layout/home/HeroChartSection.jsx | 1 - src/layout/home/HeroSwiper.jsx | 11 +- src/layout/home/index.jsx | 4 +- src/layout/store/index.jsx | 1 - src/tests/app.test.js | 7 - src/tests/index.test.js | 6 - 50 files changed, 475 insertions(+), 714 deletions(-) create mode 100644 __mocks__/fileMock.js create mode 100644 __mocks__/swiper/react.js create mode 100644 setupTests.js create mode 100644 src/__tests__/App.test.js delete mode 100644 src/context/hooks/useDebounce.jsx delete mode 100644 src/context/hooks/useLogger.jsx delete mode 100644 src/context/hooks/useSelectedContext.jsx delete mode 100644 src/data/baseMenuItems.jsx delete mode 100644 src/tests/app.test.js delete mode 100644 src/tests/index.test.js diff --git a/.eslintrc.json b/.eslintrc.json index 0451e9c..cc9a03d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -20,12 +20,7 @@ "ecmaVersion": 2021, "sourceType": "module" }, - "plugins": [ - "react", - "react-hooks", - "@typescript-eslint", - "prettier" - ], + "plugins": ["react", "react-hooks", "@typescript-eslint", "prettier"], "rules": { "no-console": "off", "@typescript-eslint/indent": ["error", 2], diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6306cac..e676fe3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: CI +name: CI/CD on: push: @@ -31,7 +31,7 @@ jobs: run: npm install netlify-cli -g - name: Deploy to Netlify - run: npx netlify deploy --dir=build --prod + run: npx netlify deploy --dir=src --prod env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js new file mode 100644 index 0000000..86059f3 --- /dev/null +++ b/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = 'test-file-stub'; diff --git a/__mocks__/swiper/react.js b/__mocks__/swiper/react.js new file mode 100644 index 0000000..9a3f907 --- /dev/null +++ b/__mocks__/swiper/react.js @@ -0,0 +1,9 @@ +// /__mocks__/swiper/react.js +import React from 'react'; + +export const Swiper = ({ children }) => ( +
{children}
+); +export const SwiperSlide = ({ children }) => ( +
{children}
+); diff --git a/package.json b/package.json index 4450d12..1bb9293 100644 --- a/package.json +++ b/package.json @@ -26,41 +26,43 @@ "url": "https://github.com/reedvogt/enhanced-card-store/issues" }, "dependencies": { - "@emotion/react": "^11.11.3", - "@emotion/styled": "^11.11.0", - "@floating-ui/react": "^0.26.9", - "@fortawesome/fontawesome-free": "^6.4.0", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@floating-ui/react": "^0.26.13", + "@fortawesome/fontawesome-free": "^6.5.2", "@hookform/resolvers": "^3.3.4", - "@mui/icons-material": "^5.14.5", - "@mui/joy": "^5.0.0-beta.16", - "@mui/lab": "^5.0.0-alpha.162", - "@mui/material": "^5.15.6", - "@mui/styled-engine-sc": "^6.0.0-alpha.12", - "@mui/system": "^5.15.5", - "@mui/x-charts": "^6.18.4", - "@mui/x-data-grid": "^6.16.2", - "@mui/x-date-pickers": "^6.10.1", - "@nivo/line": "^0.83.0", - "@nivo/tooltip": "^0.86.0", - "@stripe/react-stripe-js": "^2.1.1", - "@stripe/stripe-js": "^1.54.2", + "@mui/icons-material": "^5.15.16", + "@mui/joy": "^5.0.0-beta.36", + "@mui/lab": "^5.0.0-alpha.170", + "@mui/material": "^5.15.16", + "@mui/styled-engine-sc": "^6.0.0-alpha.18", + "@mui/system": "^5.15.15", + "@mui/x-charts": "^7.3.2", + "@mui/x-data-grid": "^7.3.2", + "@mui/x-date-pickers": "^7.3.2", + "@nivo/line": "^0.86.0", + "@stripe/react-stripe-js": "^2.7.1", + "@stripe/stripe-js": "^3.4.0", + "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.6", - "axios": "^1.4.0", + "@testing-library/user-event": "^14.5.2", + "axios": "^1.6.8", "chroma-js": "^2.4.2", + "dotenv": "^16.4.5", "eslint-config-react-app": "^7.0.1", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", - "mathjs": "^12.4.1", + "mathjs": "^12.4.2", "nanoid": "^5.0.7", "notistack": "^3.0.1", "react": "^18.3.1", - "react-cookie": "^4.1.1", + "react-cookie": "^7.1.4", "react-device-detect": "^2.2.3", "react-dom": "^18.3.1", "react-error-boundary": "^4.0.13", "react-helmet-async": "^2.0.4", - "react-hook-form": "^7.50.0", - "react-icons": "^4.10.1", + "react-hook-form": "^7.51.4", + "react-icons": "^5.2.1", "react-perfect-scrollbar": "^1.5.8", "react-router-dom": "^6.23.0", "react-scroll-parallax": "^3.4.5", @@ -68,14 +70,42 @@ "react-stripe-checkout": "^2.6.3", "react-table": "^7.8.0", "react-transition-group": "^4.4.5", - "recharts": "^2.12.3", - "sass": "^1.69.7", - "stripe": "^12.14.0", - "styled-components": "^6.1.8", - "swiper": "^11.0.6", - "three": "^0.156.1", - "web-vitals": "^2.1.4", - "zod": "^3.22.4" + "recharts": "^2.12.6", + "sass": "^1.77.0", + "stripe": "^15.5.0", + "styled-components": "^6.1.9", + "swiper": "^11.1.1", + "three": "^0.164.1", + "web-vitals": "^3.5.2", + "zod": "^3.23.6" + }, + "devDependencies": { + "@babel/core": "^7.24.5", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@babel/preset-env": "^7.24.5", + "@babel/preset-react": "^7.24.1", + "babel-jest": "^29.7.0", + "depcheck": "^1.4.7", + "eslint": "^8.0.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react-hooks": "^4.6.2", + "http-proxy-middleware": "^3.0.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^29.7.0", + "prettier": "^3.2.5", + "react-scripts": "5.0.1" + }, + "babel": { + "presets": [ + "@babel/preset-env", + "@babel/preset-react" + ], + "plugins": [ + "@babel/plugin-proposal-optional-chaining", + "@babel/plugin-proposal-private-property-in-object" + ] }, "eslintConfig": { "extends": [ @@ -104,17 +134,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "devDependencies": { - "@babel/plugin-proposal-optional-chaining": "^7.21.0", - "@babel/plugin-proposal-private-property-in-object": "^7.21.11", - "depcheck": "^1.4.7", - "eslint": "^8.46.0", - "eslint-config-prettier": "^8.9.0", - "eslint-plugin-prettier": "^5.0.0", - "eslint-plugin-react-hooks": "^4.6.2", - "http-proxy-middleware": "^2.0.6", - "prettier": "^3.0.0", - "react-scripts": "5.0.1" } } diff --git a/setupTests.js b/setupTests.js new file mode 100644 index 0000000..47fa4b5 --- /dev/null +++ b/setupTests.js @@ -0,0 +1,2 @@ +import '@testing-library/jest-dom/extend-expect'; +// Directly in your test file or in src/setupTests.js diff --git a/src/App.js b/src/App.js index da5cc25..10567ca 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ // App.js -import React, { Suspense, lazy, useEffect } from 'react'; +import React, { Suspense, useEffect } from 'react'; import { Route, Routes, useNavigate } from 'react-router-dom'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import LoginDialog from 'layout/dialogs/LoginDialog'; @@ -11,7 +11,6 @@ import { PageLayout, } from 'layout/REUSABLE_COMPONENTS'; import { - useUserData, useConfigurator, useManageCookies, useMode, diff --git a/src/__tests__/App.test.js b/src/__tests__/App.test.js new file mode 100644 index 0000000..e5aabb4 --- /dev/null +++ b/src/__tests__/App.test.js @@ -0,0 +1,63 @@ +// App.test.js +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import App from 'App'; + +// Mocking modules and context +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => jest.fn(), +})); +jest.mock('context', () => ({ + useManageCookies: () => ({ + getCookie: jest.fn().mockReturnValue({ authUser: null, isLoggedIn: false }), + }), +})); +jest.mock('context', () => ({ + useMode: () => ({ theme: {} }), +})); +jest.mock('context', () => ({ + useAuthManager: () => ({ + logout: jest.fn(), + }), +})); +jest.mock('context', () => ({ + useConfigurator: () => ({ + isConfiguratorOpen: false, + }), +})); +jest.mock('layout/dialogs/LoginDialog', () => () =>
LoginDialog
); +jest.mock('layout/navigation', () => () =>
Navigation
); +jest.mock('layout/REUSABLE_COMPONENTS/Configurator', () => () => ( +
Configurator
+)); +jest.mock('layout/REUSABLE_COMPONENTS', () => () => ( +
LoadingOverlay
+)); +jest.mock('layout/REUSABLE_COMPONENTS', () => () => ( +
PageLayout
+)); + +// Helper to wrap component with router context +const renderWithRouter = (ui, { route = '/' } = {}) => { + window.history.pushState({}, 'Test page', route); + return render(ui, { wrapper: Router }); +}; + +describe('App Component', () => { + it('should redirect to login if not authenticated', async () => { + renderWithRouter(); + await waitFor(() => + expect(screen.getByText('LoginDialog')).toBeInTheDocument() + ); + }); + + it('should display the navigation and page layout', () => { + renderWithRouter(); + expect(screen.getByText('Navigation')).toBeInTheDocument(); + expect(screen.getByText('PageLayout')).toBeInTheDocument(); + }); + + // Add more tests as needed for different scenarios +}); diff --git a/src/context/ColorModeProvider.jsx b/src/context/ColorModeProvider.jsx index cafba2e..33b1f45 100644 --- a/src/context/ColorModeProvider.jsx +++ b/src/context/ColorModeProvider.jsx @@ -1,7 +1,7 @@ import { useState, useMemo, createContext, useEffect } from 'react'; import { createTheme } from '@mui/material'; import { themeSettings } from 'assets/themeSettings'; -import { useManageCookies } from './hooks'; +import { useManageCookies } from 'context'; export const ColorModeContext = createContext({ mode: 'dark', diff --git a/src/context/hooks/index.jsx b/src/context/hooks/index.jsx index 9460963..6a5e405 100644 --- a/src/context/hooks/index.jsx +++ b/src/context/hooks/index.jsx @@ -6,10 +6,8 @@ import useEventHandlers from './useEventHandlers'; import useFetchWrapper from './useFetchWrapper'; import useGridItems from './useGridItems'; import useLoading from './useLoading'; -import useLogger from './useLogger'; import useManageCookies from './useManageCookies'; import usePagination from './usePagination'; -import useSelectedContext from './useSelectedContext'; import usePopover from './usePopover'; import useSelectorActions from './useSelectorActions'; import useFormManagement from './useFormManagement'; @@ -25,10 +23,8 @@ export { useFetchWrapper, useGridItems, useLoading, - useLogger, useManageCookies, usePagination, - useSelectedContext, usePopover, useSelectorActions, useFormManagement, diff --git a/src/context/hooks/useBreakPoint.jsx b/src/context/hooks/useBreakPoint.jsx index 2eb1f01..fc8438c 100644 --- a/src/context/hooks/useBreakPoint.jsx +++ b/src/context/hooks/useBreakPoint.jsx @@ -1,4 +1,3 @@ -import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; import { useMode } from 'context'; diff --git a/src/context/hooks/useDebounce.jsx b/src/context/hooks/useDebounce.jsx deleted file mode 100644 index e79ba2b..0000000 --- a/src/context/hooks/useDebounce.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useState, useEffect } from 'react'; -/** - * useDebounce hook - * - * @param {any} value - The value to be debounced. - * @param {number} delay - The number of milliseconds to delay. - * @returns {any} - The debounced value after the specified delay. - */ -function useDebounce(value, delay) { - // State and setters for debounced value - const [debouncedValue, setDebouncedValue] = useState(value); - const [delayTime, setDelayTime] = useState(delay || 500); - - useEffect(() => { - // Update debounced value after the specified delay - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - // Cleanup function to cancel the timeout if value or delay changes - return () => { - clearTimeout(handler); - }; - }, [value, delay]); // Only re-run effect if value or delay changes - - return debouncedValue; -} - -export default useDebounce; diff --git a/src/context/hooks/useDialogState.jsx b/src/context/hooks/useDialogState.jsx index 6cf6a18..60fd074 100644 --- a/src/context/hooks/useDialogState.jsx +++ b/src/context/hooks/useDialogState.jsx @@ -1,4 +1,3 @@ -// hooks/useDialogState.js import { useState, useCallback } from 'react'; const useDialogState = () => { diff --git a/src/context/hooks/useFetchWrapper.jsx b/src/context/hooks/useFetchWrapper.jsx index 02bd686..ec4e2b8 100644 --- a/src/context/hooks/useFetchWrapper.jsx +++ b/src/context/hooks/useFetchWrapper.jsx @@ -1,5 +1,4 @@ -import { useState, useCallback, useEffect } from 'react'; -import useLogger from './useLogger'; +import { useState, useCallback } from 'react'; import useLocalStorage from './useLocalStorage'; import useLoading from './useLoading'; import { useSnackbar } from 'notistack'; diff --git a/src/context/hooks/useFormManagement.jsx b/src/context/hooks/useFormManagement.jsx index d996e3a..ff4e2cf 100644 --- a/src/context/hooks/useFormManagement.jsx +++ b/src/context/hooks/useFormManagement.jsx @@ -1,4 +1,5 @@ import { useState, useCallback } from 'react'; + const useFormManagement = (initialFormKey, formSchemas) => { const [activeForm, setActiveForm] = useState(initialFormKey); diff --git a/src/context/hooks/useGridItems.jsx b/src/context/hooks/useGridItems.jsx index a196675..d6d9784 100644 --- a/src/context/hooks/useGridItems.jsx +++ b/src/context/hooks/useGridItems.jsx @@ -1,9 +1,12 @@ import React, { useMemo } from 'react'; import { Grid, Grow, IconButton, Tooltip } from '@mui/material'; -import GenericCard from 'layout/cards/GenericCard'; import HighlightOffRoundedIcon from '@mui/icons-material/HighlightOffRounded'; -import { useCardStore, useManager, useMode } from 'context'; + import { MDBox, SkeletonCard } from 'layout/REUSABLE_COMPONENTS'; +import GenericCard from 'layout/cards/GenericCard'; + +import { useCardStore, useManager, useMode } from 'context'; + const useGridItems = ({ itemsPerPage, cards, diff --git a/src/context/hooks/useLocalStorage.jsx b/src/context/hooks/useLocalStorage.jsx index 865582d..8fc4654 100644 --- a/src/context/hooks/useLocalStorage.jsx +++ b/src/context/hooks/useLocalStorage.jsx @@ -72,10 +72,6 @@ function useLocalStorage(key, initialValue) { url: window.location.href, }); window.dispatchEvent(storageEvent); - // Optionally, for cross-tab communication, you might want to trigger a storage event manually - // window.dispatchEvent( - // new Event('storage', { key, newValue: JSON.stringify(valueToStore) }) - // ); } catch (error) { console.error(`Error setting localStorage key "${key}":`, error); } diff --git a/src/context/hooks/useLogger.jsx b/src/context/hooks/useLogger.jsx deleted file mode 100644 index cf09cb1..0000000 --- a/src/context/hooks/useLogger.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; - -const useLogger = (componentName, options = {}) => { - const mountedRef = useRef(false); - const [state, setState] = useState({}); - const specialEvents = ['apiResponse', 'stateChange']; - - // Function to log custom events - const logEvent = (eventName, additionalData = {}) => { - // Check if the event is special - if (specialEvents.includes(eventName)) { - // Handle special event - if (eventName === 'apiResponse') { - const { message, data, source } = additionalData; - console.log( - `[${componentName}] API Response:`, - `Message: ${message}`, - `Data: ${data}`, - `Source: ${source || 'Unknown'}` - ); - } - // Add additional special event handling here if needed - } else { - // Log general events - console.log( - `[${componentName}] Event: ${eventName}`, - // field ? `[${field}]` : '', - additionalData - ); - } - }; - - // Function to log errors - const logError = (error) => { - console.error(`[${componentName}] Error:`, error); - }; - - useEffect(() => { - // console.log(`[${componentName}] Mounted`, options); - mountedRef.current = true; - - return () => { - // console.log(`[${componentName}] Unmounted`, options); - mountedRef.current = false; - }; - }, []); - - useEffect(() => { - if (mountedRef.current) { - // console.log(`[${componentName}] Updated`, state); - } - }, [state]); - - const setStateAndLog = (newState) => { - setState(newState); - logEvent('State Change', newState); - }; - - return { logEvent, setStateAndLog, logError }; -}; - -export default useLogger; diff --git a/src/context/hooks/useManageCookies.jsx b/src/context/hooks/useManageCookies.jsx index bea34f7..a954a38 100644 --- a/src/context/hooks/useManageCookies.jsx +++ b/src/context/hooks/useManageCookies.jsx @@ -35,15 +35,10 @@ function useManageCookies() { return acc; }, {}); } else { - // Handling a single cookie name - // return { - // [nameOrNames]: parseCookieValue(cookies[nameOrNames]), - // }; return parseCookieValue(cookies[nameOrNames]); } }; - // Function to remove a cookie const deleteCookie = (name) => { removeCookie(name); }; diff --git a/src/context/hooks/useSelectedContext.jsx b/src/context/hooks/useSelectedContext.jsx deleted file mode 100644 index 45f9c9f..0000000 --- a/src/context/hooks/useSelectedContext.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useState, useCallback, useEffect } from 'react'; - -const useSelectedContext = () => { - const [selectedContext, setSelectedContext] = useState(null); - const [isContextSelected, setIsContextSelected] = useState(false); - - const setContext = useCallback((newContext) => { - console.log('Context selected:', newContext); - setSelectedContext(newContext); - }, []); - - // Method to clear the context - const clearContext = useCallback(() => { - setSelectedContext(null); - }, []); - - return { - selectedContext, - setContext, - isContextSelected, - setIsContextSelected, - clearContext, - }; -}; - -export default useSelectedContext; diff --git a/src/context/hooks/useSelectorActions.jsx b/src/context/hooks/useSelectorActions.jsx index f946b2a..a630df3 100644 --- a/src/context/hooks/useSelectorActions.jsx +++ b/src/context/hooks/useSelectorActions.jsx @@ -1,8 +1,8 @@ /* eslint-disable no-case-declarations */ import { useState } from 'react'; -import useLocalStorage from './useLocalStorage'; -import useManager from '../state/useManager'; import { nanoid } from 'nanoid'; +import useLocalStorage from './useLocalStorage'; +import { useManager } from 'context'; function useSelectorActions() { const { @@ -37,7 +37,7 @@ function useSelectorActions() { switch (selectorName) { case 'timeRange': setTime(e.target.value); - updateEntityField( + await updateEntityField( 'collections', selectedCollectionId, ['selectedChartDataKey', 'selectedChartData'], @@ -55,7 +55,7 @@ function useSelectorActions() { break; case 'statRange': setStat(e.target.value); - updateEntityField( + await updateEntityField( 'collections', selectedCollectionId, ['selectedStatDataKey', 'selectedStatData'], @@ -76,7 +76,7 @@ function useSelectorActions() { break; case 'themeRange': setTheme(e.target.value); - updateEntityField( + await updateEntityField( 'collections', selectedCollectionId, ['selectedThemeDataKey', 'selectedThemeData'], @@ -98,28 +98,24 @@ function useSelectorActions() { if (e.type === 'add') { const newTag = { id: nanoid(), label: e.target.value }; if (!updatedTags.some((tag) => tag.label === newTag.label)) { - // Check if tag already exists by label updatedTags.push(newTag); // Add new tag if not present } } else if (e.type === 'delete') { updatedTags = updatedTags.filter( (tag) => tag.id !== e.target.value.id - ); // Remove tag by id + ); } - - // Update tags in state and backend - setTags(updatedTags); // Update local state + setTags(updatedTags); await updateEntityField( 'decks', selectedDeckId, ['tags', 'name', 'description', 'color'], [updatedTags, name, description, color] - ); // Persist tags update + ); const updatedDeck = await fetchDeckById(selectedDeckId); console.log('UPDATED DECK', updatedDeck); handleSelectDeck(updatedDeck); - // e.target.value = ''; // Clear the input after adding a tag break; case 'deck': default: diff --git a/src/context/index.js b/src/context/index.js index 9c9b628..8962e9f 100644 --- a/src/context/index.js +++ b/src/context/index.js @@ -14,10 +14,8 @@ import { useFetchWrapper, useGridItems, useLoading, - useLogger, useManageCookies, usePagination, - useSelectedContext, usePopover, useSelectorActions, useFormManagement, @@ -41,10 +39,8 @@ export { useFetchWrapper, useGridItems, useLoading, - useLogger, useManageCookies, usePagination, - useSelectedContext, usePopover, useSelectorActions, useFormManagement, diff --git a/src/context/state/useAuthManager.jsx b/src/context/state/useAuthManager.jsx index bb869a8..6d9ef67 100644 --- a/src/context/state/useAuthManager.jsx +++ b/src/context/state/useAuthManager.jsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import jwt_decode from 'jwt-decode'; +import { jwtDecode as jwt_decode } from 'jwt-decode'; import { useFetchWrapper, useManageCookies, useUserData } from 'context'; function useAuthManager() { diff --git a/src/context/state/useCardStore.jsx b/src/context/state/useCardStore.jsx index 4793cd6..28c9141 100644 --- a/src/context/state/useCardStore.jsx +++ b/src/context/state/useCardStore.jsx @@ -1,10 +1,10 @@ import { useState, useEffect, useCallback } from 'react'; -import axios from 'axios'; -import useLogger from '../hooks/useLogger'; -import useLocalStorage from '../hooks/useLocalStorage'; // Ensure this is the correct path to your hook -import useLoading from '../hooks/useLoading'; -import useManageCookies from '../hooks/useManageCookies'; -import { useFetchWrapper } from 'context'; +import { + useFetchWrapper, + useLoading, + useLocalStorage, + useManageCookies, +} from 'context'; function debounce(func, wait) { let timeout; @@ -22,7 +22,6 @@ const useCardStore = () => { const { getCookie } = useManageCookies(); const { fetchWrapper, status } = useFetchWrapper(); const { userId } = getCookie(['userId']); - const logger = useLogger('CardProvider'); const [previousSearchData, setPreviousSearchData] = useLocalStorage( 'previousSearchData', [] @@ -46,34 +45,37 @@ const useCardStore = () => { const clearSearchData = () => { setSearchData([]); setIsDataValid(false); - logger.logEvent('Search Data Cleared'); + console.log('Search Data Cleared'); }; const handleRequest = useCallback( debounce(async (searchParams) => { startLoading('isSearchLoading'); try { - logger.logEvent('handleRequest start', { searchParams, userId }); + console.log('handleRequest start', { searchParams, userId }); const requestBody = { searchParams, user: userId, searchTerm: searchParams, }; - const response = await axios.post( + const response = await fetchWrapper( `${process.env.REACT_APP_SERVER}/api/cards/ygopro`, - requestBody + 'POST', + requestBody, + 'FETCH_SEARCH_DATA' ); - if (response?.data?.data?.length > 0) { - logger.logEvent('Data Fetched Successfully', { - dataLength: response?.data.data.length, + console.log('handleRequest response', response); + if (response?.data?.length > 0) { + console.log('Data Fetched Successfully', { + dataLength: response?.data.length, }); - const limitedData = response?.data.data.slice(0, 30); // Limit to 30 cards + const limitedData = response?.data.slice(0, 30); // Limit to 30 cards setIsDataValid(true); setSearchData(limitedData); // Directly set the new searchData } else { clearSearchData(); } } catch (err) { - logger.logEvent('Error fetching card data', err); + console.log('Error fetching card data', err); clearSearchData(); } finally { stopLoading('isSearchLoading'); // Set loading to false once the request is complete diff --git a/src/context/state/useManager.jsx b/src/context/state/useManager.jsx index b695800..bf5bb2c 100644 --- a/src/context/state/useManager.jsx +++ b/src/context/state/useManager.jsx @@ -1,53 +1,46 @@ /* eslint-disable no-case-declarations */ import { useCallback, useMemo, useState } from 'react'; -import { - useFetchWrapper, - useLocalStorage, - useLogger, - useManageCookies, - useSelectedContext, -} from '../hooks'; - +import { useFetchWrapper, useLocalStorage, useManageCookies } from 'context'; +// const useCleanId = (id) => encodeURIComponent(id.replace(/"/g, '')); const useManager = () => { const { fetchWrapper, status } = useFetchWrapper(); const { getCookie } = useManageCookies(); const { isLoggedIn, userId } = getCookie(['isLoggedIn', 'userId']); - const logger = useLogger('CollectionManager'); const [customError, setCustomError] = useState(''); const baseUrl = `${process.env.REACT_APP_SERVER}/api/users/${userId}`; - const { selectedContext } = useSelectedContext(); const createApiUrl = (entity, action) => `${baseUrl}/${entity}/${action}`; + const cleanId = (id) => encodeURIComponent(id.replace(/"/g, '')); - // DECKS + // State management for all entities using useState const [decks, setDecks] = useState([]); - const [selectedDeck, setSelectedDeck] = useLocalStorage('selectedDeck', {}); - const [selectedDeckId, setSelectedDeckId] = useLocalStorage( - 'selectedDeckId', - null - ); - const [hasFetchedDecks, setHasFetchedDecks] = useState(false); - const [hasUpdatedDecks, setHasUpdatedDecks] = useState(false); - const [hasUpdatedSelectedDeck, setHasUpdatedSelectedDeck] = useState(false); - - // COLLECTIONS const [collections, setCollections] = useState([]); + const [cart, setCart] = useState([]); + + // State management for selected entities using useLocalStorage + const [selectedDeck, setSelectedDeck] = useLocalStorage('selectedDeck', {}); const [selectedCollection, setSelectedCollection] = useLocalStorage( 'selectedCollection', {} ); + const [selectedDeckId, setSelectedDeckId] = useLocalStorage( + 'selectedDeckId', + null + ); const [selectedCollectionId, setSelectedCollectionId] = useLocalStorage( 'selectedCollectionId', null ); + // State management for operational flags + const [hasFetchedDecks, setHasFetchedDecks] = useState(false); const [hasFetchedCollections, setHasFetchedCollections] = useState(false); - - // CART - const [cart, setCart] = useLocalStorage('cart', null); const [hasFetchedCart, setHasFetchedCart] = useState(false); - - // CARDS const [hasFetchedCards, setHasFetchedCards] = useState(false); + + const [hasUpdatedDecks, setHasUpdatedDecks] = useState(false); + const [hasUpdatedCollections, setHasUpdatedCollections] = useState(false); + const [hasUpdatedSelectedDeck, setHasUpdatedSelectedDeck] = useState(false); const [hasUpdatedCards, setHasUpdatedCards] = useState(false); + const [hasFetched, setHasFetched] = useLocalStorage('hasFetched', { initFetch: false, cards: false, @@ -65,10 +58,6 @@ const useManager = () => { 'collectionMetaData', [] ); - const [cardsWithQuantities, setCardsWithQuantities] = useLocalStorage( - 'cardsWithQuantities', - {} - ); const compileCollectionMetaData = useCallback( (allCollections) => { if (!allCollections || allCollections.length === 0) return; @@ -115,66 +104,6 @@ const useManager = () => { }, [collections, setCollectionMetaData] ); - const compileCardsWithQuantities = useCallback( - (col, dec, car) => { - if (!col && !dec && !car) return []; - const cards = []; - if (!dec !== null) { - dec?.forEach((d) => { - if (!d?.cards) return; - cards.push(...(d?.cards || [])); - }); - } - if (col !== null) { - col?.forEach((c) => { - if (!c?.cards) return; - cards.push(...(c?.cards || [])); - }); - } - if (car !== null) { - if (!car?.items) return; - cards.push(...(car?.items || [])); - } - // MAP CARDS BY ID AND SET VALUE AS STRING --> '[card.variant.modelName][card.name]: card quantity' - const cardsWithQuantities = cards?.reduce((acc, card) => { - if (!card.id) return acc; - if (!acc[card.id]) { - acc[card.id] = { - CardInCart: '', - CardInCollection: '', - CardInDeck: '', - }; - } - const modelKey = card?.variant?.cardModel; - if (modelKey && !acc[card.id][modelKey]) { - acc[card.id][modelKey] = `${card.name}: ${card.quantity}`; - } else { - // Handle updating quantities if card already exists in the accumulator - const currentQty = parseInt( - acc[card.id][modelKey].split(': ')[1], - 10 - ); - acc[card.id][modelKey] = - `${card.name}: ${currentQty + card.quantity}`; - } - return acc; - }, {}); - setCardsWithQuantities((state) => ({ ...state, ...cardsWithQuantities })); - return cards; - }, - [setCardsWithQuantities] - ); - const isCardInContext = useCallback( - (card) => { - const cardsList = { - Collection: selectedCollection?.cards, - Deck: selectedDeck?.cards, - Cart: cart?.items, - }; - return !!cardsList[selectedContext]?.find((c) => c?.id === card?.id); - }, - [selectedContext, selectedCollection, selectedDeck, cart] - ); // FETCHING ENTITIES const handleSelectEntity = useCallback( (entityName, entityData) => { @@ -200,34 +129,30 @@ const useManager = () => { const fetchEntities = useCallback( async (entity) => { const loadingID = `FETCH_ALL_${entity.toUpperCase()}`; - console.log('LOADING ID:', loadingID); + const url = createApiUrl(entity, 'all'); try { - const response = await fetchWrapper( - createApiUrl(entity, 'all'), - 'GET', - null, - loadingID - ); - if (entity === 'cart') { - setCart(response.data); - setHasFetchedCart(true); - setHasFetched((state) => ({ ...state, cart: true })); - compileCardsWithQuantities(null, null, response.data); - return response.data; - } else if (entity === 'collections') { - setCollections(response.data); - compileCollectionMetaData(response.data); - setHasFetchedCollections(true); - setHasFetched((state) => ({ ...state, collections: true })); - compileCardsWithQuantities(response.data, null, null); - return response.data; - } else { - setDecks(response.data); - setHasFetchedDecks(true); - setHasFetched((state) => ({ ...state, decks: true })); - compileCardsWithQuantities(null, response.data, null); - return response.data; + const response = await fetchWrapper(url, 'GET', null, loadingID); + switch (entity) { + case 'decks': + setDecks(response.data); + setHasFetched((state) => ({ ...state, decks: true })); + setHasFetchedDecks(true); + break; + case 'collections': + setCollections(response.data); + compileCollectionMetaData(response.data); + setHasFetched((state) => ({ ...state, collections: true })); + setHasFetchedCollections(true); + break; + case 'cart': + setCart(response.data); + setHasFetched((state) => ({ ...state, cart: true })); + setHasFetchedCart(true); + break; + default: + console.error(`Unhandled entity type: ${entity}`); } + return response.data; } catch (error) { console.error('ERROR FETCHING ENTITIES', error); setCustomError('Failed to fetch data'); @@ -235,7 +160,6 @@ const useManager = () => { }, [ fetchWrapper, - logger, setCollections, setDecks, setHasFetchedCollections, @@ -244,44 +168,30 @@ const useManager = () => { setHasFetched, setCart, compileCollectionMetaData, - compileCardsWithQuantities, ] ); const fetchSingleEntity = useCallback( async (entity, id) => { - try { - const cleanedId = encodeURIComponent(id.replace(/"/g, '')); - const response = await fetchWrapper( - createApiUrl(entity, `get/${cleanedId}`), - 'GET', - null, - `fetch${entity}`.toLocaleUpperCase() - ); - handleSelectEntity(entity, response.data); - return response.data; - } catch (error) { - logger.logError('Fetch Error:', error); - setCustomError('Failed to fetch data'); - } + const response = await fetchWrapper( + createApiUrl(entity, `get/${cleanId(id)}`), + 'GET', + null, + `fetch${entity}`.toLocaleUpperCase() + ); + handleSelectEntity(entity, response.data); + return response.data; }, - [fetchWrapper, logger] + [fetchWrapper, handleSelectEntity] ); // UPDATING ENTITIES const handleEntityOperation = useCallback( - async (entity, endpoint, action, data) => { + async (entity, endpoint, action, data, method, idValue) => { if (!isLoggedIn) { setCustomError('User is not logged in'); return; } const loadingID = `${action}${entity}`.toUpperCase(); - console.log(loadingID); const url = createApiUrl(entity, endpoint); - const method = endpoint.includes('delete') - ? 'DELETE' - : endpoint.includes('create') || endpoint.includes('add') - ? 'POST' - : 'PUT'; - try { const response = await fetchWrapper(url, method, data, loadingID); if (response && response.data) { @@ -294,14 +204,19 @@ const useManager = () => { ); break; case 'decks': - const prevDecks = [...decks]; - const updatedDecks = prevDecks.map((deck) => - deck._id === response?.data?.data?._id - ? response?.data?.data - : deck + // const prevDecks = [...decks]; + // const updatedDecks = prevDecks.map((deck) => + // deck._id === response?.data?.data?._id + // ? response?.data?.data + // : deck + // ); + setDecks((prev) => + prev.map((deck) => + deck._id === idValue ? { ...deck, ...response.data } : deck + ) ); - setDecks(updatedDecks); - handleSelectEntity('deck', response.data.data); + // setDecks(updatedDecks); + handleSelectEntity('deck', response.data); setHasUpdatedCards(true); setHasUpdatedDecks(true); break; @@ -314,91 +229,49 @@ const useManager = () => { } fetchEntities(entity); } catch (error) { - logger.logError(`Error performing ${endpoint} on ${entity}:`, error); + console.log(`Error performing ${endpoint} on ${entity}:`, error); setCustomError(`Failed to ${endpoint} ${entity}`); } }, [ isLoggedIn, fetchWrapper, - logger, setCart, setCollections, setDecks, setHasUpdatedCards, ] ); - /** - * Updates the fields of an entity with the specified values. - * - * @param {string} entity - The type of entity to update. - * @param {string} id - The ID of the entity to update. - * @param {string|string[]} fields - The field(s) to update. - * @param {any|any[]} values - The value(s) to set for the field(s). - * @returns {Promise} - A promise that resolves when the update is complete. - * @example updateEntityFields('collections', '6158888888888', ['name', 'description'], ['New Collection Name', 'New Collection Description']); - */ const updateEntityField = useCallback( async (entity, id, fields, values) => { if (!isLoggedIn) { setCustomError('User is not logged in'); return; } - const cleanedId = encodeURIComponent(id.replace(/"/g, '')); - const url = createApiUrl(entity, `update/${cleanedId}`); - let data; - // Handle updating multiple fields - if (Array.isArray(fields)) { - data = fields.reduce((acc, field, index) => { - acc[field] = values[index]; - return acc; - }, {}); + // Prepare data object from fields and values + let data = {}; + if (Array.isArray(fields) && Array.isArray(values)) { + fields.forEach((field, index) => { + data[field] = values[index]; + }); } else { - // If fields is not an array, treat it as a single field with a single value - data = { [fields]: values }; + data[fields] = values; } - try { - const response = await fetchWrapper( - url, - 'PUT', - data, - `update${entity}` - ); - if (response && response.data) { - // Assuming the server returns the updated entity - switch (entity) { - case 'collections': - const selectedId = localStorage.getItem('selectedCollectionId'); - setCollections(response.data); - break; - case 'decks': - setDecks((prev) => - prev.map((deck) => - deck._id === id ? { ...deck, ...response.data } : deck - ) - ); - break; - case 'cart': - if (id === cart._id) { - setCart({ ...cart, ...response.data }); - } - break; - default: - throw new Error(`Unhandled entity type: ${entity}`); - } - } - // return response.data; - } catch (error) { - console.error(`Error updating ${entity} fields:`, error); - setCustomError(`Failed to update ${entity} fields`); - } + // Construct endpoint and action + const endpoint = `update/${cleanId(id)}`; + const action = `update_${entity}`; + + // Use handleEntityOperation to perform the update + handleEntityOperation(entity, endpoint, action, data, 'PUT', id); }, - [isLoggedIn, fetchWrapper, logger, setCart, setCollections, setDecks, cart] + [handleEntityOperation, isLoggedIn, setCustomError] ); const addEntity = useCallback( - (entity, data) => handleEntityOperation(entity, 'create', 'add_', data), - [handleEntityOperation] + (entity, data) => + handleEntityOperation(entity, 'create', 'add_', data, 'POST')[ + handleEntityOperation + ] ); const updateEntity = useCallback( async (entity, data) => { @@ -406,15 +279,27 @@ const useManager = () => { const id = localStorage.getItem('selected' + localEntity + 'Id'); const cleanedId = encodeURIComponent(id.replace(/"/g, '')); console.log('UPDAtING', entity, data); - handleEntityOperation(entity, `update/${cleanedId}`, 'update_', data); + handleEntityOperation( + entity, + `update/${cleanedId}`, + 'update_', + data, + 'PUT' + ); }, [handleEntityOperation] ); const deleteEntity = useCallback( - (entity, id) => - handleEntityOperation(entity, `delete/${id}`, 'delete_', { - id: id, - }), + async (entity, id) => + handleEntityOperation( + entity, + `delete/${id}`, + 'delete_', + { + id: id, + }, + 'DELETE' + ), [handleEntityOperation] ); const addCardToEntity = useCallback( @@ -427,10 +312,16 @@ const useManager = () => { } else { type = 'addNew'; } - return handleEntityOperation(entity, 'cards/add', 'add_cards_to_', { - cards: [card], - type: type, - }); + return handleEntityOperation( + entity, + 'cards/add', + 'add_cards_to_', + { + cards: [card], + type: type, + }, + 'POST' + ); } const selectEntVal = entity === 'collections' ? selectedCollection?._id : selectedDeck?._id; @@ -450,7 +341,8 @@ const useManager = () => { { cards: [card], type: type, - } + }, + 'POST' ); }, [handleEntityOperation] @@ -477,7 +369,8 @@ const useManager = () => { { cards: [item.id], type: type, - } + }, + 'DELETE' ); } const selectEntVal = @@ -506,13 +399,13 @@ const useManager = () => { { cards: [existingCard?.id], type: type, - } + }, + 'DELETE' ); }, [handleEntityOperation] ); - // Memoized return values to prevent unnecessary re-renders return useMemo( () => ({ customError, @@ -543,7 +436,6 @@ const useManager = () => { removeCardFromEntity('collections', item), addDeck: (data) => addEntity('decks', data), - updateDeck: (data) => updateEntity('decks', data), deleteDeck: (id) => deleteEntity('decks', id), addItemToDeck: (item) => addCardToEntity('decks', item), removeItemFromDeck: (item) => removeCardFromEntity('decks', item), @@ -559,8 +451,6 @@ const useManager = () => { setHasFetchedCollections, setHasFetchedDecks, setHasFetchedCart, - checkForCardInContext: isCardInContext, - compileCardsWithQuantities, fetchEntities, updateEntityField, setHasUpdatedCards, @@ -599,8 +489,6 @@ const useManager = () => { addCardToEntity, removeCardFromEntity, handleSelectEntity, - isCardInContext, - compileCardsWithQuantities, ] ); }; diff --git a/src/context/state/useMode.jsx b/src/context/state/useMode.jsx index 7749471..18faba0 100644 --- a/src/context/state/useMode.jsx +++ b/src/context/state/useMode.jsx @@ -1,5 +1,5 @@ +import { ColorModeContext } from 'context/ColorModeProvider'; import { useContext } from 'react'; -import { ColorModeContext } from '../ColorModeProvider'; const useMode = () => { const { mode, colorMode, theme, toggleColorMode } = diff --git a/src/context/state/useUserData.jsx b/src/context/state/useUserData.jsx index 5da9fc1..e8ed77e 100644 --- a/src/context/state/useUserData.jsx +++ b/src/context/state/useUserData.jsx @@ -1,10 +1,4 @@ -// hooks/useUserData.js -import { - useFetchWrapper, - useLocalStorage, - useLogger, - useManageCookies, -} from 'context'; +import { useFetchWrapper, useLocalStorage, useManageCookies } from 'context'; import { useCallback, useState, useEffect } from 'react'; function useUserData() { @@ -31,7 +25,6 @@ function useUserData() { totalPrice: 0, }, }); - const logger = useLogger('useUserData'); const { fetchWrapper } = useFetchWrapper(); const [hasFetchedUser, setHasFetchedUser] = useState(false); const [error, setError] = useState(null); @@ -68,7 +61,7 @@ function useUserData() { }, }); setHasFetchedUser(false); - }, [setUser, setHasFetchedUser, logger]); + }, [setUser, setHasFetchedUser]); const fetchUserData = useCallback(async () => { if (!userId || !isLoggedIn) return; @@ -83,9 +76,9 @@ function useUserData() { } catch (error) { console.error('Error fetching user data:', error); setError(error.message || 'Failed to fetch user data'); - logger.logEvent('Failed to fetch user data', error.message); + console.log('Failed to fetch user data', error.message); } - }, [userId, isLoggedIn, user, fetchWrapper, setUser, logger]); + }, [userId, isLoggedIn, user, fetchWrapper, setUser]); const updateUser = useCallback( async (updatedUserData) => { @@ -107,10 +100,10 @@ function useUserData() { } catch (error) { console.error('Error updating user data:', error); setError('Failed to update user data'); - logger.logError('Error updating user data:', error); + console.log('Error updating user data:', error); } }, - [userId, isLoggedIn, fetchWrapper, fetchUserData, logger] + [userId, isLoggedIn, fetchWrapper, fetchUserData] ); useEffect(() => { diff --git a/src/data/baseMenuItems.jsx b/src/data/baseMenuItems.jsx deleted file mode 100644 index 3f63050..0000000 --- a/src/data/baseMenuItems.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { - Store as StoreIcon, - ShoppingCart as CartIcon, - Assessment as CollectionIcon, -} from '@mui/icons-material'; -import { Badge } from '@mui/material'; -import DeckBuilderIcon from './DeckBuilderIcon'; - -const baseMenuItems = ({ cartCardQuantity }) => [ - { - name: 'Deck', - icon: , - to: '/deckbuilder', - requiresLogin: false, - }, - { - name: 'Collection', - icon: , - to: '/collection', - requiresLogin: true, - }, - { - name: 'Store', - icon: , - to: '/store', - requiresLogin: true, - }, - { - name: 'Cart', - icon: ( - - - - ), - to: '/cart', - requiresLogin: true, - }, -]; - -export default baseMenuItems; diff --git a/src/data/formsConfig.jsx b/src/data/formsConfig.jsx index c91c827..7997a17 100644 --- a/src/data/formsConfig.jsx +++ b/src/data/formsConfig.jsx @@ -25,7 +25,7 @@ import { useAuthManager, useCardStore, useManager } from 'context'; // ---------------------------- FORM FIELD HANDLERS ---------------------------- // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- -const getFormFieldHandlers = () => { +const useGetFormFieldHandlers = () => { const { signup, login } = useAuthManager(); const { handleRequest } = useCardStore(); const { @@ -33,8 +33,7 @@ const getFormFieldHandlers = () => { updateCollection, deleteDeck, addDeck, - updateDeck: updateDeckDetails, - // selectedCollection, + updateEntityField, } = useManager(); const formHandlers = { @@ -59,7 +58,7 @@ const getFormFieldHandlers = () => { }, updateDeckForm: (formData) => { console.log('Update Deck Form Data:', formData); - updateDeckDetails(formData); + // updateEntityField('', formData); }, addDeckForm: (formData, additionalData) => { console.log('Add Deck Form Data:', formData, additionalData); @@ -642,5 +641,5 @@ export { configOptions, handleSchemaValidation, handleFieldValidation, - getFormFieldHandlers, + useGetFormFieldHandlers, }; diff --git a/src/data/index.jsx b/src/data/index.jsx index e8c67e6..d16b335 100644 --- a/src/data/index.jsx +++ b/src/data/index.jsx @@ -1,6 +1,5 @@ import heroData from './heroData'; import collectionPortfolioHeaderItems from './collectionPortfolioHeaderItems'; -import baseMenuItems from './baseMenuItems'; import prepareTableData from './prepareTableData'; import HelmetMetaData from './HelmetMetaData'; import DeckBuilderIcon from './DeckBuilderIcon'; @@ -9,7 +8,7 @@ import { zodSchemas, configOptions, handleSchemaValidation, - getFormFieldHandlers, + useGetFormFieldHandlers, } from './formsConfig'; import getRoutes from './route-config.jsx'; export { @@ -18,7 +17,6 @@ export { // ROUTE_CONFIG, HelmetMetaData, collectionPortfolioHeaderItems, - baseMenuItems, prepareTableData, // FORMS formFields, @@ -26,5 +24,5 @@ export { configOptions, DeckBuilderIcon, handleSchemaValidation, - getFormFieldHandlers, + useGetFormFieldHandlers, }; diff --git a/src/layout/REUSABLE_COMPONENTS/RC_FORMS/RCDynamicForm.jsx b/src/layout/REUSABLE_COMPONENTS/RC_FORMS/RCDynamicForm.jsx index ad9782e..8c09184 100644 --- a/src/layout/REUSABLE_COMPONENTS/RC_FORMS/RCDynamicForm.jsx +++ b/src/layout/REUSABLE_COMPONENTS/RC_FORMS/RCDynamicForm.jsx @@ -13,9 +13,8 @@ import { useMode, useManager, } from 'context'; -import { getFormFieldHandlers, handleValidation } from 'data'; +import { useGetFormFieldHandlers } from 'data'; import { RCInput, RCLoadingButton } from '..'; -import { handleFieldValidation } from 'data/formsConfig'; const RCDynamicForm = ({ formKey, @@ -28,9 +27,8 @@ const RCDynamicForm = ({ const { theme } = useMode(); const { status } = useManager(); const { isMobile } = useBreakpoint(); - // const dataRef = React.useRef(initialData || {}); const methods = useRCFormHook(formKey); - const { onSubmit } = useFormSubmission(getFormFieldHandlers(), formKey); + const { onSubmit } = useFormSubmission(useGetFormFieldHandlers(), formKey); const [isMounted, setIsMounted] = useState(false); useEffect(() => { setIsMounted(true); diff --git a/src/layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx b/src/layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx index 201cc20..f8e5208 100644 --- a/src/layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx +++ b/src/layout/REUSABLE_STYLED_COMPONENTS/SpecificStyledComponents.jsx @@ -1,4 +1,4 @@ -import { RCMainCard } from 'layout/REUSABLE_COMPONENTS'; +import { RCMainCard } from 'layout/REUSABLE_COMPONENTS/RC_OTHER/RCMainCard'; import styled from 'styled-components'; const CardWrapper = styled(RCMainCard)(({ theme }) => ({ diff --git a/src/layout/REUSABLE_STYLED_COMPONENTS/index.jsx b/src/layout/REUSABLE_STYLED_COMPONENTS/index.jsx index d0e2766..d176de2 100644 --- a/src/layout/REUSABLE_STYLED_COMPONENTS/index.jsx +++ b/src/layout/REUSABLE_STYLED_COMPONENTS/index.jsx @@ -13,7 +13,7 @@ import { StyledCardContent, DialogPaper, } from './ReusableStyledComponents'; -import { CardWrapper } from './SpecificStyledComponents'; +// import { CardWrapper } from './SpecificStyledComponents'; export { StyledContainerBox, @@ -29,5 +29,5 @@ export { FeatureCard, StyledCardContent, DialogPaper, - CardWrapper, + // CardWrapper, }; diff --git a/src/layout/cards/CardDetailsContainer.jsx b/src/layout/cards/CardDetailsContainer.jsx index 9787fca..a03596b 100644 --- a/src/layout/cards/CardDetailsContainer.jsx +++ b/src/layout/cards/CardDetailsContainer.jsx @@ -13,7 +13,7 @@ import { import { useMode } from 'context'; import styled from 'styled-components'; import RCTypography from 'layout/REUSABLE_COMPONENTS/RCTYPOGRAPHY'; -import { CardWrapper } from 'layout/REUSABLE_STYLED_COMPONENTS'; +import { RCMainCard } from 'layout/REUSABLE_COMPONENTS'; const textDetails = [ { title: 'Description', key: 'desc' }, @@ -141,7 +141,7 @@ const RenderDetailsSection = ({ details, card, className, handleAction }) => { {details?.map((detail, index) => ( - { display: 'flex', flexDirection: 'column', alignItems: 'flex-start', + backgroundColor: theme.palette.success.darkest, + color: theme.palette.primary.light, + overflow: 'hidden', + position: 'relative', + '&:before': { + content: '""', + position: 'absolute', + // eslint-disable-next-line max-len + background: `linear-gradient(140.9deg, ${theme.palette.primary[200]} -14.02%, rgba(144, 202, 249, 0) 77.58%)`, + borderRadius: '50%', + width: 210, + height: 210, + top: -160, + right: -130, + }, + '&:after': { + content: '""', + position: 'absolute', + borderRadius: '50%', + width: 210, + height: 210, + top: -30, + right: -180, + }, }} > { )} - + ))} diff --git a/src/layout/cards/CardToolTip.jsx b/src/layout/cards/CardToolTip.jsx index e6ca136..1becfa0 100644 --- a/src/layout/cards/CardToolTip.jsx +++ b/src/layout/cards/CardToolTip.jsx @@ -30,7 +30,7 @@ export const StyledDescriptionSpan = styled('span')(({ theme }) => ({ marginTop: theme.spacing(1), flexGrow: 1, })); -const createTooltipContent = (card) => { +const useCreateTooltipContent = (card) => { const { theme } = useMode(); const attributes = { ...card.attributes, @@ -90,7 +90,7 @@ const CardToolTip = ({ card }) => { variant="solid" color="primary" > - {createTooltipContent(card)} + {useCreateTooltipContent(card)} ); }; diff --git a/src/layout/cards/GenericCard.jsx b/src/layout/cards/GenericCard.jsx index de2e623..0868c3d 100644 --- a/src/layout/cards/GenericCard.jsx +++ b/src/layout/cards/GenericCard.jsx @@ -2,63 +2,17 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Box, Card, CardActions } from '@mui/material'; import CardMediaSection from './CardMediaSection'; import placeholder from 'assets/images/placeholder.jpeg'; -import { - useMode, - usePopover, - useManager, - useDialogState, - useSelectedContext, -} from 'context'; +import { useMode, usePopover, useDialogState } from 'context'; import { StyledCardContent } from 'layout/REUSABLE_STYLED_COMPONENTS'; import { GenericActionButtons, RCTypography } from 'layout/REUSABLE_COMPONENTS'; -const getQuantity = ({ - card, - cart, - selectedCollection, - allCollections, - selectedDeck, - allDecks, -}) => { - const findCardQuantity = (collectionsOrDecks, cardId) => - collectionsOrDecks?.reduce( - (acc, item) => - acc + (item?.cards?.find((c) => c.id === cardId)?.quantity ?? 0), - 0 - ) ?? 0; - - const cartQuantity = - cart?.items?.find((c) => c.id === card.id)?.quantity ?? 0; - const collectionQuantity = selectedCollection - ? selectedCollection?.cards?.find((c) => c.id === card.id)?.quantity ?? 0 - : findCardQuantity(allCollections, card.id); - const deckQuantity = selectedDeck - ? selectedDeck?.cards?.find((c) => c.id === card.id)?.quantity ?? 0 - : findCardQuantity(allDecks, card.id); - - return { - cartQuantity, - collectionQuantity, - deckQuantity, - }; -}; const GenericCard = React.forwardRef((props, ref) => { - const { - collections: allCollections, - decks: allDecks, - selectedDeck, - selectedCollection, - cart, - checkForCardInContext, - } = useManager(); - const { setContext, setIsContextSelected } = useSelectedContext(); const { dialogState, openDialog } = useDialogState(); const { setHoveredCard, setIsPopoverOpen, hoveredCard } = usePopover(); + const { theme } = useMode(); const { card, context, page, isDeckCard } = props; - const effectiveContext = typeof context === 'object' ? context.pageContext : context; - const { theme } = useMode(); const cardRef = useRef(null); const [cardSize, setCardSize] = useState('md'); useEffect(() => { @@ -84,59 +38,19 @@ const GenericCard = React.forwardRef((props, ref) => { }, [setHoveredCard, setIsPopoverOpen, card] ); - const handleContextSelect = useCallback( - (newContext) => { - setContext(newContext); - setIsContextSelected(true); - }, - [setContext, setIsContextSelected] - ); useEffect(() => { setIsPopoverOpen(hoveredCard === card); }, [hoveredCard, card, setIsPopoverOpen]); - const isInContext = checkForCardInContext(card); - const name = card?.name; - const imgUrl = card?.card_images?.[0]?.image_url || placeholder; - const price = `Price: ${ - card?.price || - card?.latestPrice?.num || - card?.card_prices?.[0]?.tcgplayer_price || - 'N/A' - }`; - const { cartQuantity, collectionQuantity, deckQuantity } = getQuantity({ - card: card, - cart: cart, - selectedCollection: selectedCollection, - allCollections: allCollections, - selectedDeck: selectedDeck, - allDecks: allDecks, - }); let cardContent = null; if (cardSize !== 'xs' && !isDeckCard) { cardContent = ( - {name} + {`${card?.name || 'N/A'}`} - {price} + {`Price: ${card?.price || 'N/A'}`} - {cardSize !== 'sm' && ( - <> - {`Cart: ${isInContext ? cartQuantity : 'N/A'}`} - {`Collection: ${isInContext ? collectionQuantity : 'N/A'}`} - {`Deck: ${isInContext ? deckQuantity : 'N/A'}`} - - )} ); } @@ -153,7 +67,7 @@ const GenericCard = React.forwardRef((props, ref) => { > { alt={card.name} /> - {/* - {`$${card.price}`} - */} { const { theme } = useMode(); + const { collectionMetaData } = useManager(); const colors = theme.palette; const grey = colors.grey.darkest; const lightGrey = colors.grey.lightest; const primary = colors.primary.dark; const greenAccent = colors.success.main_light; - const { collectionMetaData } = useManager(); - const { data, columns } = useMemo( - () => prepareTableData(collectionMetaData?.topFiveCards, 'topCards'), - [collectionMetaData?.topFiveCards] - ); + // const { data, columns } = useMemo( + // () => prepareTableData(collectionMetaData?.topFiveCards, 'topCards'), + // [collectionMetaData?.topFiveCards] + // ); + const dataAndColumns = useMemo(() => { + if (!collectionMetaData?.topFiveCards) { + return { data: [], columns: [] }; // Provide default empty structures + } + return prepareTableData(collectionMetaData?.topFiveCards, 'topCards'); + }, [collectionMetaData?.topFiveCards]); + const { data, columns } = dataAndColumns; return ( { useSX={true} titleVariant="h5" paddingVariant={theme.spacing(2)} - sx={{ - color: greenAccent, - borderRadius: theme.borders.borderRadius.md, - }} + // sx={{ + // color: greenAccent, + // borderRadius: theme.borders.borderRadius.md, + // }} /> diff --git a/src/layout/collection/PortfolioViewLayout/MyPortfolioLineChart.jsx b/src/layout/collection/PortfolioViewLayout/MyPortfolioLineChart.jsx index c7bff37..3cecec7 100644 --- a/src/layout/collection/PortfolioViewLayout/MyPortfolioLineChart.jsx +++ b/src/layout/collection/PortfolioViewLayout/MyPortfolioLineChart.jsx @@ -2,27 +2,62 @@ import { ResponsiveLine } from '@nivo/line'; import { Tooltip } from '@mui/material'; import PropTypes from 'prop-types'; import { useEventHandlers } from 'context'; -const TooltipLayer = ({ points }) => { +// const TooltipLayer = ({ points }) => { +// return ( +// <> +// {points?.map((point, index) => ( +// +// ))} +// +// ); +// }; +/** + * Tooltip component to display details for points in the line chart. + * @param {object} point - The data point for which the tooltip is shown. + */ +const CustomTooltip = ({ point }) => { + const { data } = point; + const date = new Date(data.xFormatted); + const valueFormatted = data.yFormatted; // Assuming yFormatted is already formatted as currency + return ( - <> - {points?.map((point, index) => ( - - ))} - +
+ Date:{' '} + {`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`} +
+ Value: {valueFormatted} +
); }; +/** + * A layer component for rendering tooltips in the line chart. + * This will be used as a custom layer in the Nivo line chart's `layers` prop. + */ +const TooltipLayer = ({ points, xScale, yScale, margin }) => { + return points.map((point, index) => ( + + )); +}; const MyPortfolioLineChart = ({ data = [], // tickValues, diff --git a/src/layout/collection/PortfolioViewLayout/TopCardsSwiper.jsx b/src/layout/collection/PortfolioViewLayout/TopCardsSwiper.jsx index f452a5c..055e568 100644 --- a/src/layout/collection/PortfolioViewLayout/TopCardsSwiper.jsx +++ b/src/layout/collection/PortfolioViewLayout/TopCardsSwiper.jsx @@ -1,5 +1,11 @@ import React, { useState, useRef, useEffect } from 'react'; import { CardMedia, CardContent, Icon } from '@mui/material'; +// import { Swiper, SwiperSlide } from 'swiper/react'; +// import { Autoplay, EffectCoverflow, Navigation, Pagination } from 'swiper'; +// import 'swiper/swiper.scss'; // core Swiper +// import 'swiper/modules/navigation/navigation.scss'; // Navigation module +// import 'swiper/modules/pagination/pagination.scss'; // Pagination module + import { Swiper, SwiperSlide } from 'swiper/react'; import { Autoplay, Pagination, Navigation } from 'swiper/modules'; import 'swiper/css'; @@ -13,12 +19,13 @@ import { DashboardBox, FlexBetween, MDBox, + RCMainCard, } from 'layout/REUSABLE_COMPONENTS'; import CardDetailsContainer from 'layout/cards/CardDetailsContainer'; import { useMode, useBreakpoint, useManager } from 'context'; -import { CardWrapper } from 'layout/REUSABLE_STYLED_COMPONENTS'; +// import { CardWrapper } from 'layout/REUSABLE_STYLED_COMPONENTS'; const TopCardsSwiper = () => { const { theme } = useMode(); @@ -87,7 +94,41 @@ const TopCardsSwiper = () => { }} // ref={swiperRef} > - + + {/* */} { />
- + ))} diff --git a/src/layout/collection/PortfolioViewLayout/index.jsx b/src/layout/collection/PortfolioViewLayout/index.jsx index 7f00a78..fa61f9e 100644 --- a/src/layout/collection/PortfolioViewLayout/index.jsx +++ b/src/layout/collection/PortfolioViewLayout/index.jsx @@ -353,7 +353,7 @@ const CollectionCardList = React.memo(({ data, columns, theme }) => { useEffect(() => { setPageSize(entriesPerPage.defaultValue); - }, [collection]); + }, []); return ( { diff --git a/src/layout/deck/index.jsx b/src/layout/deck/index.jsx index 34ff7b8..0e20f15 100644 --- a/src/layout/deck/index.jsx +++ b/src/layout/deck/index.jsx @@ -38,7 +38,6 @@ const DeckBuilder = () => { fetchDeckById, hasFetchedDecks, setHasUpdatedDecks, - updateDeck, setHasFetchedDecks, decks: allDecks, } = useManager(); @@ -278,11 +277,6 @@ const DeckBuilder = () => { }} > {deckListItems[activeTab]} - {/* {loadingState.global === true ? ( - - ) : ( - deckListItems[activeTab] - )} */} diff --git a/src/layout/home/FeatureCardsSection.jsx b/src/layout/home/FeatureCardsSection.jsx index f88ef81..c41697f 100644 --- a/src/layout/home/FeatureCardsSection.jsx +++ b/src/layout/home/FeatureCardsSection.jsx @@ -9,41 +9,27 @@ import { Grid, List, ListItem, - AppBar, - Card, } from '@mui/material'; import featureCardData from 'data/json-data/featureCardData.json'; // Adjust the path as necessary import { FeatureCard, StyledContainerBox, StyledPaper, -} from '../REUSABLE_STYLED_COMPONENTS/ReusableStyledComponents'; +} from 'layout/REUSABLE_STYLED_COMPONENTS'; +import { RCButton } from 'layout/REUSABLE_COMPONENTS'; import DetailsModal from 'layout/dialogs/DetailsModal'; -import { useMode, useDialogState, useBreakpoint } from 'context'; -import { RCButton } from 'layout/REUSABLE_COMPONENTS'; +import { useMode, useDialogState, useBreakpoint } from 'context'; const AnimatedBox = animated(Box); export const AnimatedFeatureCard = ({ cardData }) => { const { theme } = useMode(); const { dialogState, openDialog, closeDialog } = useDialogState(); - const handleOpenModal = (itemTitle) => { - const selectedItem = featureCardData.find( - (item) => item.title === itemTitle - ); - if (selectedItem) { - openDialog('isDetailsDialogOpen'); - } - }; - const handleCloseDialog = () => { - closeDialog('isDetailsDialogOpen'); - }; const [tiltAnimation, api] = useSpring(() => ({ transform: 'perspective(600px) rotateX(0deg) rotateY(0deg) scale(1)', })); - const handleMouseEnter = () => api.start({ transform: 'perspective(600px) rotateX(5deg) rotateY(5deg) scale(1.05)', @@ -112,14 +98,21 @@ export const AnimatedFeatureCard = ({ cardData }) => { size="large" variant="holo" withContainer={false} - onClick={() => handleOpenModal(cardData.title)} + onClick={() => { + const selectedItem = featureCardData.find( + (item) => item.title === cardData.title + ); + if (selectedItem) { + openDialog('isDetailsDialogOpen'); + } + }} > {cardData.title} {dialogState.isDetailsDialogOpen && ( closeDialog('isDetailsDialogOpen')} /> )} diff --git a/src/layout/home/HeroChartSection.jsx b/src/layout/home/HeroChartSection.jsx index 0810a44..dbe3e6c 100644 --- a/src/layout/home/HeroChartSection.jsx +++ b/src/layout/home/HeroChartSection.jsx @@ -1,4 +1,3 @@ -import placeHolder from 'assets/images/placeholder.jpeg'; import { Card, CardContent, Zoom } from '@mui/material'; import { ResponsiveContainer, diff --git a/src/layout/home/HeroSwiper.jsx b/src/layout/home/HeroSwiper.jsx index 5905d4b..5ea3195 100644 --- a/src/layout/home/HeroSwiper.jsx +++ b/src/layout/home/HeroSwiper.jsx @@ -1,19 +1,18 @@ import React, { useEffect, useRef } from 'react'; +import placeHolder from 'assets/images/placeholder.jpeg'; +import { useBreakpoint } from 'context'; +import { MDBox, RCTypography } from 'layout/REUSABLE_COMPONENTS'; import { Swiper, SwiperSlide } from 'swiper/react'; import { + Autoplay, EffectCoverflow, - Pagination, Navigation, - Autoplay, + Pagination, } from 'swiper/modules'; import 'swiper/css'; import 'swiper/css/effect-coverflow'; import 'swiper/css/pagination'; import 'swiper/css/navigation'; -import placeHolder from 'assets/images/placeholder.jpeg'; -import { useBreakpoint } from 'context'; -import { MDBox, RCTypography } from 'layout/REUSABLE_COMPONENTS'; - const HeroSwiper = ({ cards, handleSlideChange, activeCardIndex }) => { const { isMd } = useBreakpoint(); diff --git a/src/layout/home/index.jsx b/src/layout/home/index.jsx index 4a22b9d..c14ed12 100644 --- a/src/layout/home/index.jsx +++ b/src/layout/home/index.jsx @@ -23,9 +23,9 @@ const HeroSection = () => { image: placeHolder, })); const [hasFetchedCards, setHasFetchedCards] = useState(false); - const [cards, setCards] = useState([]); + // const [cards, setCards] = useState([...defaultCards]); - // const [cards, setCards] = useState([...randomCards, ...defaultCards]); + const [cards, setCards] = useState([...randomCards, ...defaultCards]); useEffect(() => { setShouldShow(true); const fetchData = async () => { diff --git a/src/layout/store/index.jsx b/src/layout/store/index.jsx index 073ff67..bf992c9 100644 --- a/src/layout/store/index.jsx +++ b/src/layout/store/index.jsx @@ -21,7 +21,6 @@ const StoreSearch = () => { sx={{ flexGrow: 1, boxShadow: theme.shadows[5], - [theme.breakpoints.down('sm')]: {}, }} > diff --git a/src/tests/app.test.js b/src/tests/app.test.js deleted file mode 100644 index b5edf3f..0000000 --- a/src/tests/app.test.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import App from '../App.js'; - -test('renders without crashing', () => { - render(); -}); diff --git a/src/tests/index.test.js b/src/tests/index.test.js deleted file mode 100644 index ca57545..0000000 --- a/src/tests/index.test.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import Index from '../indexjs'; -test('renders without crashing', () => { - render(); -});