Skip to content

Priyam/dark mode enable#214

Open
priyam731 wants to merge 9 commits intowednesday-solutions:masterfrom
priyam731:priyam/dark_mode-enable
Open

Priyam/dark mode enable#214
priyam731 wants to merge 9 commits intowednesday-solutions:masterfrom
priyam731:priyam/dark_mode-enable

Conversation

@priyam731
Copy link

@priyam731 priyam731 commented Feb 4, 2026

Description

Added dark mode toggle functionality to the application. Users can now switch between light and dark themes.

Changes:

  • Created DarkModeToggle component
  • Implemented theme persistence using localStorage
  • Added theme context/state management
  • Updated styling to support dark mode

Steps to Reproduce / Test

  1. Run the application locally
  2. Click on the dark mode toggle button
  3. Verify theme switches between light and dark
  4. Refresh the page - theme preference should persist
  5. Check all components render correctly in both themes

GIF's

darkmode


Live Link

http://wednesday-react-template.s3-website.ap-south-1.amazonaws.com/priyam/dark_mode-enable


Summary by CodeRabbit

  • New Features

    • Persistent dark/light mode toggle with animated icon and contextual tooltip; theme applies app-wide.
  • Refactor

    • App now uses a centralized ThemeProvider and expanded color tokens for consistent light/dark styling.
  • Style

    • Global transitions, smoother theme-switch animation and improved dark-mode visuals (including scrollbar styling).
  • Tests

    • Added tests for toggle behavior, persistence, positioning, and tooltip interactions.
  • Documentation

    • README updated with dark mode usage and examples.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

Warning

Rate limit exceeded

@priyam731 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds a ThemeContext (ThemeProvider + useTheme), global light/dark CSS, expanded color tokens, a DarkModeToggle FAB component (with tests), MUI theme wiring in App, theming updates in HomeContainer and tests, Storybook compatibility shim, and several defensive property-access fixes.

Changes

Cohort / File(s) Summary
Theme System Foundation
app/contexts/themeContext.js
Adds ThemeContext, ThemeProvider, and useTheme. Initializes from localStorage or system preference, persists changes, updates document root classes, and exposes memoized theme tokens and toggleTheme.
Dark Mode Toggle
app/components/DarkModeToggle/index.js, app/components/DarkModeToggle/tests/index.test.js
New FAB-like toggle component with animated icon and tooltip; fixed bottom-right positioning; triggers toggleTheme and brief animation; tests cover rendering, toggle behavior, localStorage persistence, positioning, and hover/tooltip scaffolding.
App Integration & Test Helpers
app/containers/App/index.js, app/utils/testUtils.js
Wraps application with ThemeProvider and MUI ThemeProvider derived from theme context; mounts DarkModeToggle inside theme provider; updates test utilities to wrap renders with the new theme providers.
Container Theming
app/containers/HomeContainer/index.js
Replaces direct color imports with useTheme; styled components accept theme-driven props (bgcolor, bordercolor, shadowcolor, titlecolor, etc.) and gain theme-based transitions and hover effects.
Color Tokens & Tests
app/themes/colors.js, app/themes/tests/colors.test.js
Expands light/dark palettes into explicit hex/rgb token objects (background, surface, text, textSecondary, border, shadow, hover, accent, success, error, warning); updates tests to assert explicit token values.
Global Styles
app/global-styles.js
Adds CSS variable for transition speed, .light-mode/.dark-mode root styles, global transition rules, custom dark-mode scrollbar styling, and a fadeIn keyframe for theme transitions.
Minor Safety Fixes
app/components/ProtectedRoute/index.js, app/components/T/index.js, app/i18n.js
Adds defensive property-access checks and safer dynamic lookups (hasOwnProperty and guarded access) for route filtering, font style retrieval, and i18n message fallback logic.
Storybook Compatibility
.storybook/preview.js
Adds React 18/19 compatibility polyfill for Storybook 6.x by adapting ReactDOM.render/unmount to use createRoot when available.
Docs
README.md
Adds "Dark Mode Support" documentation: usage, persistence, integration points, and example.
CI
.github/workflows/ci.yml
Checks out PR head SHA, adds step to clear Storybook cache, and minor SonarQube variable indentation adjustments.

Sequence Diagram

sequenceDiagram
    actor User
    participant Toggle as "DarkModeToggle\n(FAB)"
    participant ThemeCtx as "ThemeProvider\n(useTheme)"
    participant MUI as "MUI ThemeProvider\n(createTheme)"
    participant LocalStorage as "localStorage"
    participant Document as "Document DOM"

    User->>Toggle: click
    Toggle->>ThemeCtx: call toggleTheme()
    ThemeCtx->>ThemeCtx: update isDarkMode state
    ThemeCtx->>LocalStorage: persist preference
    ThemeCtx->>Document: update root class (dark-mode/light-mode)
    ThemeCtx->>MUI: provide updated color tokens (createTheme)
    MUI->>Document: re-render themed components
    Document->>User: UI updates with new theme
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I tap the moon, I tap the sun,
A tiny spin — the toggle's done.
localStorage nods, the classes sway,
Colors bloom and softly play.
Hop, hop, the UI finds its day/night fun.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Priyam/dark mode enable' is vague and uses non-descriptive formatting (author name prefix and generic phrasing) that doesn't clearly convey the main change. Revise the title to be more descriptive and clear, such as 'Add dark mode toggle functionality' or 'Implement dark/light theme switching with persistence'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description includes all key sections from the template: clear description of changes, detailed steps to reproduce/test, GIF demonstration, and live link. The description is complete and informative.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @priyam731, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust dark mode feature, significantly enhancing the user experience by allowing theme customization and persistence. It establishes a scalable theme management system using React Context, ensuring that future UI elements can easily adapt to the chosen theme. The changes span new component development, state management, global styling, and updates to existing components to fully integrate the new theming capabilities.

Highlights

  • Dark Mode Toggle Component: Introduced a new DarkModeToggle component (app/components/DarkModeToggle/index.js) allowing users to switch between light and dark themes with visual feedback and animation.
  • Theme Context and Persistence: Implemented a ThemeContext (app/contexts/themeContext.js) for managing the application's theme state, including logic to persist the user's theme preference in localStorage and respect system-level dark mode settings.
  • Dynamic Styling Integration: Updated global styles (app/global-styles.js) and key components (HomeContainer, App) to dynamically apply theme-specific colors and styles, ensuring a consistent look across the application.
  • Comprehensive Theme Palette: Expanded the colors theme definitions (app/themes/colors.js) to include a full range of colors for both light and dark modes, enhancing the visual consistency of the application.
  • Test Coverage and Snapshot Updates: Added dedicated tests for the DarkModeToggle component and updated existing snapshot tests (snapshots/*.js.snap) to reflect the new theme-aware styling and component structure.
  • Dependency Updates: Several core dependencies, including react, react-dom, and react-redux, were updated, leading to changes in yarn.lock.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • app/components/DarkModeToggle/index.js
    • Added a new React component for the dark mode toggle button, featuring styled-components for visual presentation, Material-UI icons, and animation on toggle.
  • app/components/DarkModeToggle/tests/index.test.js
    • Added unit tests for the DarkModeToggle component, verifying its rendering, icon changes based on theme, theme toggling functionality, fixed positioning, and tooltip behavior.
  • app/components/ProtectedRoute/tests/snapshots/index.test.js.snap
    • Updated UI snapshots to reflect the new theme-aware styling properties applied to components within the ProtectedRoute.
  • app/containers/App/index.js
    • Integrated the new ThemeProvider at the root of the application and rendered the DarkModeToggle component globally.
  • app/containers/App/tests/snapshots/index.test.js.snap
    • Updated UI snapshot to include the newly added DarkModeToggle component in the application's main layout.
  • app/containers/HomeContainer/index.js
    • Refactored to consume theme colors from the useTheme hook, applying dynamic bgcolor, color, bordercolor, shadowcolor, inputbg, inputcolor, hovercolor, accentcolor, and placeholdercolor props to styled components like CustomCard, CustomCardHeader, StyledT, and StyledOutlinedInput.
  • app/containers/HomeContainer/tests/snapshots/index.test.js.snap
    • Updated UI snapshots to reflect the dynamic styling changes in HomeContainer based on the active theme.
  • app/contexts/themeContext.js
    • Created a new React Context (ThemeContext) and ThemeProvider component to manage the isDarkMode state, toggleTheme function, and a colors object containing theme-specific palettes. It initializes the theme from localStorage or system preference and updates document.documentElement classes.
  • app/global-styles.js
    • Introduced global CSS variables for transition speed and defined light-mode and dark-mode classes on the html element to apply base background and text colors, along with smooth transitions and custom scrollbar styles for dark mode.
  • app/themes/colors.js
    • Modified the colors object to define distinct and comprehensive color palettes for lightMode and darkMode, including primary, secondary, background, surface, text, border, shadow, hover, accent, success, error, and warning colors.
  • app/themes/tests/colors.test.js
    • Updated tests to assert the correctness of the newly defined light and dark mode color palettes in app/themes/colors.js.
  • app/utils/testUtils.js
    • Modified renderProvider to include the CustomThemeProvider wrapper, ensuring that components rendered in tests have access to the theme context.
  • yarn.lock
    • Updated dependency versions for @babel/runtime, @testing-library/dom, @testing-library/react, @types/react-dom, hoist-non-react-statics, loose-envify, react-dom, react-is, react-redux, react, scheduler, and use-sync-external-store.
Activity
  • The author, priyam731, has initiated this pull request with a clear objective to implement dark mode functionality.
  • The description includes a detailed breakdown of changes, steps to reproduce/test, a GIF demonstrating the feature, and a live link for direct interaction.
  • This indicates a thorough and well-documented contribution, ready for review and testing.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a dark mode feature, which is a great enhancement. The implementation uses a React Context for theme management and persists the user's preference in localStorage, which is well done. However, there are a few areas for improvement. Most critically, the Material-UI theme is created statically and does not update when the theme is toggled, which will lead to UI inconsistencies. I've also identified opportunities to improve code maintainability by removing duplicated color definitions, enhance component logic for animations, and strengthen the test suite for the new components. Additionally, there are some minor issues in the global styles that could affect performance and code clarity.

</StyledEngineProvider>
<ThemeProvider>
<StyledEngineProvider injectFirst>
<MUIThemeProvider theme={theme}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The MUIThemeProvider is receiving a static theme object that is created outside the component. This means the Material-UI components will not react to theme changes (light/dark mode), as the theme is only created once with the default light mode colors. This will cause visual inconsistencies when switching to dark mode.

To fix this, the MUI theme should be created dynamically based on the current theme from themeContext. This can be achieved by creating the theme inside a component that has access to the theme context (using useTheme()) and memoizing it with useMemo. This ensures the MUI theme updates whenever the application's theme changes.

Comment on lines +51 to +81
colors: isDarkMode
? {
primary: '#2d3748',
secondary: '#4a5568',
background: '#1a202c',
surface: '#2d3748',
text: '#f7fafc',
textSecondary: '#cbd5e0',
border: '#4a5568',
shadow: 'rgba(0, 0, 0, 0.3)',
hover: '#374151',
accent: '#60a5fa',
success: '#48bb78',
error: '#f56565',
warning: '#ed8936'
}
: {
primary: '#fcedda',
secondary: '#f8c49c',
background: '#ffffff',
surface: '#f8f9fa',
text: '#212529',
textSecondary: '#6c757d',
border: '#e0e0e0',
shadow: 'rgba(0, 0, 0, 0.1)',
hover: '#f1f3f5',
accent: '#1890ff',
success: '#28a745',
error: '#dc3545',
warning: '#ffc107'
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The color palettes for light and dark modes are hardcoded here, but they are also defined in app/themes/colors.js. This duplication makes maintenance harder as you have to update colors in two places. You should import the colors from app/themes/colors.js to have a single source of truth.

First, add the import:

import colors from '@app/themes/colors';

Then, use the imported colors as suggested.

Suggested change
colors: isDarkMode
? {
primary: '#2d3748',
secondary: '#4a5568',
background: '#1a202c',
surface: '#2d3748',
text: '#f7fafc',
textSecondary: '#cbd5e0',
border: '#4a5568',
shadow: 'rgba(0, 0, 0, 0.3)',
hover: '#374151',
accent: '#60a5fa',
success: '#48bb78',
error: '#f56565',
warning: '#ed8936'
}
: {
primary: '#fcedda',
secondary: '#f8c49c',
background: '#ffffff',
surface: '#f8f9fa',
text: '#212529',
textSecondary: '#6c757d',
border: '#e0e0e0',
shadow: 'rgba(0, 0, 0, 0.1)',
hover: '#f1f3f5',
accent: '#1890ff',
success: '#28a745',
error: '#dc3545',
warning: '#ffc107'
}
colors: isDarkMode ? colors.theme.darkMode : colors.theme.lightMode

Comment on lines +62 to +66
const handleToggle = () => {
setIsAnimating(true);
toggleTheme();
setTimeout(() => setIsAnimating(false), 500);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using setTimeout with a hardcoded duration to reset the animation state is fragile. If the animation duration in the CSS (0.5s) is changed, this setTimeout duration (500ms) will also need to be updated manually. A more robust approach is to use the onAnimationEnd event on the animated element to reset the state.

You can update handleToggle as suggested, and then add the onAnimationEnd handler to your IconWrapper to reset the animation state:

<IconWrapper animate={isAnimating} onAnimationEnd={() => setIsAnimating(false)}>
  <Icon fontSize="large" />
</IconWrapper>
  const handleToggle = () => {
    setIsAnimating(true);
    toggleTheme();
  };

Comment on lines +32 to +42
it('should show sun icon in dark mode', () => {
renderWithTheme('dark');
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
});

it('should show moon icon in light mode', () => {
renderWithTheme('light');
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These tests only check for the presence of the button, not the specific icon for each theme mode. To make these tests more meaningful, you should assert that the correct icon is rendered for each mode. Material-UI icons have a data-testid you can use for this.

Suggested change
it('should show sun icon in dark mode', () => {
renderWithTheme('dark');
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
});
it('should show moon icon in light mode', () => {
renderWithTheme('light');
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
});
it('should show sun icon in dark mode', () => {
renderWithTheme('dark');
expect(screen.getByTestId('Brightness7Icon')).toBeInTheDocument();
expect(screen.queryByTestId('Brightness4Icon')).not.toBeInTheDocument();
});
it('should show moon icon in light mode', () => {
renderWithTheme('light');
expect(screen.getByTestId('Brightness4Icon')).toBeInTheDocument();
expect(screen.queryByTestId('Brightness7Icon')).not.toBeInTheDocument();
});

Comment on lines +64 to +69
it('should show tooltip on hover', () => {
renderWithTheme();
const button = screen.getByRole('button');
fireEvent.mouseOver(button);
// Material-UI tooltips appear after a delay
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test for the tooltip is incomplete. Since Material-UI tooltips appear asynchronously after a delay, you need to use an async test with await and a findBy* query to correctly test for the tooltip's appearance.

  it('should show tooltip on hover', async () => {
    renderWithTheme();
    const button = screen.getByRole('button');
    fireEvent.mouseOver(button);
    const tooltip = await screen.findByRole('tooltip');
    expect(tooltip).toBeInTheDocument();
    expect(tooltip).toHaveTextContent('Switch to Dark Mode');
  });

Comment on lines +47 to +50
* {
transition: background-color var(--transition-speed) ease, color var(--transition-speed) ease,
border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the universal selector (*) to apply transitions to all elements can negatively impact performance, especially on pages with many elements. It forces the browser to track transition properties for everything, which can be costly. It's better to apply transitions more selectively only to the elements that need to be animated during the theme change.

Comment on lines +82 to +92
/* Smooth animations for theme switch */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The @keyframes fadeIn is defined here but does not appear to be used anywhere in the application. This is dead code and should be removed to keep the codebase clean.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/global-styles.js (1)

14-50: ⚠️ Potential issue | 🟠 Major

Respect prefers-reduced-motion for global transitions/animations.

Global transitions and animations can be problematic for motion-sensitive users. Gate them behind prefers-reduced-motion or disable them when reduced motion is requested.

🧩 Suggested accessibility guard
   html,
   body {
     height: 100vh;
     width: 100vw;
     margin: 0;
     padding: 0;
     transition: background-color var(--transition-speed) ease, color var(--transition-speed) ease;
   }

+  `@media` (prefers-reduced-motion: reduce) {
+    html,
+    body,
+    html.light-mode `#app`,
+    html.dark-mode `#app`,
+    html.dark-mode * {
+      transition: none !important;
+      animation: none !important;
+    }
+  }

Also applies to: 82-91

app/containers/HomeContainer/index.js (1)

24-47: ⚠️ Potential issue | 🟠 Major

Prop‑driven card styling isn’t applied everywhere (dark mode gaps).

CustomCard / CustomCardHeader now rely on theme props, but renderRepoList and renderErrorState still render them without those props, which can leave parts of the UI un-themed (or generate invalid CSS like border: 1px solid undefined) in dark mode. Please thread themeColors into those helpers or provide defaults.

✅ Suggested fix: pass theme colors into all CustomCard usages
-      {renderRepoList(reposData, loading, repoName)}
-      {renderErrorState(repoName, loading, reposError)}
+      {renderRepoList(reposData, loading, repoName, themeColors)}
+      {renderErrorState(repoName, loading, reposError, themeColors)}
-const renderRepoList = (reposData, loading, repoName) => {
+const renderRepoList = (reposData, loading, repoName, themeColors) => {
   const items = get(reposData, 'items', []);
   const totalCount = get(reposData, 'totalCount', 0);
   return (
     <If condition={!isEmpty(items) || loading}>
-      <CustomCard>
+      <CustomCard
+        bgcolor={themeColors.surface}
+        color={themeColors.text}
+        bordercolor={themeColors.border}
+        shadowcolor={themeColors.shadow}
+      >
         <If condition={!loading} otherwise={renderSkeleton()}>
-const renderErrorState = (repoName, loading, reposError) => {
+const renderErrorState = (repoName, loading, reposError, themeColors) => {
   let repoError;
   let messageId;
   if (reposError) {
     repoError = reposError;
     messageId = 'error-message';
   } else if (isEmpty(repoName)) {
     repoError = 'repo_search_default';
     messageId = 'default-message';
   }

   return (
     <If condition={!loading && repoError}>
-      <CustomCard color={reposError ? 'red' : 'grey'}>
-        <CustomCardHeader title={translate('repo_list')} />
-        <Divider sx={{ mb: 1.25 }} light />
+      <CustomCard
+        bgcolor={themeColors.surface}
+        color={reposError ? 'red' : themeColors.textSecondary}
+        bordercolor={themeColors.border}
+        shadowcolor={themeColors.shadow}
+      >
+        <CustomCardHeader title={translate('repo_list')} titlecolor={themeColors.text} />
+        <Divider sx={{ mb: 1.25, borderColor: themeColors.border }} />
         <T data-testid={messageId} id={repoError} text={repoError} />
       </CustomCard>
     </If>
   );
 };
🤖 Fix all issues with AI agents
In `@app/components/DarkModeToggle/index.js`:
- Around line 62-66: The timeout started in handleToggle (setTimeout(() =>
setIsAnimating(false), 500)) can call setState after the component unmounts;
store the timer id (e.g., using a ref like animationTimerRef) when you call
setTimeout, and clear it in a useEffect cleanup (and before setting a new
timeout when handleToggle runs again) using
clearTimeout(animationTimerRef.current) to avoid calling setIsAnimating after
unmount; update handleToggle to save the returned id and add a useEffect that
returns a cleanup which clears the timer.

In `@app/components/DarkModeToggle/tests/index.test.js`:
- Around line 64-69: The test "should show tooltip on hover" currently has no
assertion; after calling renderWithTheme() and fireEvent.mouseOver(button)
(where button is obtained via screen.getByRole('button')), make the test async
and await an assertion that the tooltip appears — e.g. await
screen.findByRole('tooltip') or await screen.findByText('<expected tooltip
text>') — to assert the Material-UI tooltip is present in the DOM; update the
test function signature to async and add the awaited assertion after
fireEvent.mouseOver(button).
- Around line 44-55: The test's assertions read localStorage synchronously but
the component writes it inside a useEffect, causing flakiness; update the test
in index.test.js (the "should toggle theme when clicked" spec that uses
renderWithTheme and the button from getByRole) to wrap the post-click
localStorage assertions in async waitFor calls (await waitFor(() => {
expect(localStorage.getItem('theme')).toBe('dark') }) and similarly for 'light')
so the test waits for the effect to run after fireEvent.click.
🧹 Nitpick comments (4)
app/contexts/themeContext.js (2)

47-83: Centralize theme tokens to avoid drift between ThemeContext and colors.js.

The color palette is duplicated here and in app/themes/colors.js. Reuse the shared tokens to prevent divergence.

♻️ Suggested consolidation
-import React, { createContext, useState, useEffect, useContext, useMemo } from 'react';
+import React, { createContext, useState, useEffect, useContext, useMemo } from 'react';
+import themeColors from '@app/themes/colors';
 ...
   const theme = useMemo(
     () => ({
       isDarkMode,
       toggleTheme,
-      colors: isDarkMode
-        ? {
-            primary: '#2d3748',
-            secondary: '#4a5568',
-            background: '#1a202c',
-            surface: '#2d3748',
-            text: '#f7fafc',
-            textSecondary: '#cbd5e0',
-            border: '#4a5568',
-            shadow: 'rgba(0, 0, 0, 0.3)',
-            hover: '#374151',
-            accent: '#60a5fa',
-            success: '#48bb78',
-            error: '#f56565',
-            warning: '#ed8936'
-          }
-        : {
-            primary: '#fcedda',
-            secondary: '#f8c49c',
-            background: '#ffffff',
-            surface: '#f8f9fa',
-            text: '#212529',
-            textSecondary: '#6c757d',
-            border: '#e0e0e0',
-            shadow: 'rgba(0, 0, 0, 0.1)',
-            hover: '#f1f3f5',
-            accent: '#1890ff',
-            success: '#28a745',
-            error: '#dc3545',
-            warning: '#ffc107'
-          }
+      colors: isDarkMode ? themeColors.theme.darkMode : themeColors.theme.lightMode
     }),
-    [isDarkMode]
+    [isDarkMode, toggleTheme]
   );

10-15: useTheme guard is ineffective with a non-null default context.

createContext({...}) guarantees a truthy value, so the guard never fires. Consider createContext(null) and update the hook to throw on null, or remove the check.

app/global-styles.js (1)

82-91: fadeIn keyframes are unused.

Either apply the animation to a selector (e.g., #app) or remove the keyframes to avoid dead CSS.

🧹 Cleanup option
-  /* Smooth animations for theme switch */
-  `@keyframes` fadeIn {
-    from {
-      opacity: 0;
-      transform: translateY(10px);
-    }
-    to {
-      opacity: 1;
-      transform: translateY(0);
-    }
-  }
app/components/DarkModeToggle/index.js (1)

7-24: Prevent custom style props from leaking to the DOM.

bgcolor, hoverbg, and shadowcolor can be forwarded to the underlying <button> which may cause React warnings and DOM noise. Filter them via shouldForwardProp.

🧩 Suggested prop filtering
-const ToggleButton = styled(IconButton)`
+const ToggleButton = styled(IconButton, {
+  shouldForwardProp: (prop) => !['bgcolor', 'hoverbg', 'shadowcolor'].includes(prop)
+})`
   && {
     position: fixed;
     bottom: 2rem;

Comment on lines +62 to +66
const handleToggle = () => {
setIsAnimating(true);
toggleTheme();
setTimeout(() => setIsAnimating(false), 500);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clear the animation timeout on unmount to avoid setState after unmount.

If the component unmounts within 500ms, the timeout callback will still fire.

🧹 Suggested cleanup
 export const DarkModeToggle = () => {
   const { isDarkMode, toggleTheme, colors } = useTheme();
   const [isAnimating, setIsAnimating] = React.useState(false);
+  const timeoutRef = React.useRef();

+  React.useEffect(
+    () => () => {
+      if (timeoutRef.current) clearTimeout(timeoutRef.current);
+    },
+    []
+  );

   const handleToggle = () => {
     setIsAnimating(true);
     toggleTheme();
-    setTimeout(() => setIsAnimating(false), 500);
+    if (timeoutRef.current) clearTimeout(timeoutRef.current);
+    timeoutRef.current = setTimeout(() => setIsAnimating(false), 500);
   };
🤖 Prompt for AI Agents
In `@app/components/DarkModeToggle/index.js` around lines 62 - 66, The timeout
started in handleToggle (setTimeout(() => setIsAnimating(false), 500)) can call
setState after the component unmounts; store the timer id (e.g., using a ref
like animationTimerRef) when you call setTimeout, and clear it in a useEffect
cleanup (and before setting a new timeout when handleToggle runs again) using
clearTimeout(animationTimerRef.current) to avoid calling setIsAnimating after
unmount; update handleToggle to save the returned id and add a useEffect that
returns a cleanup which clears the timer.

Comment on lines +44 to +55
it('should toggle theme when clicked', () => {
renderWithTheme();
const button = screen.getByRole('button', { name: /toggle dark mode/i });

// Click to switch to dark mode
fireEvent.click(button);
expect(localStorage.getItem('theme')).toBe('dark');

// Click to switch back to light mode
fireEvent.click(button);
expect(localStorage.getItem('theme')).toBe('light');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Make the theme-toggle assertion resilient to effect timing.

localStorage is updated in a useEffect, which may not flush synchronously. Use waitFor to avoid flakiness.

✅ Suggested fix
-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
 ...
-  it('should toggle theme when clicked', () => {
+  it('should toggle theme when clicked', async () => {
     renderWithTheme();
     const button = screen.getByRole('button', { name: /toggle dark mode/i });

     // Click to switch to dark mode
     fireEvent.click(button);
-    expect(localStorage.getItem('theme')).toBe('dark');
+    await waitFor(() => expect(localStorage.getItem('theme')).toBe('dark'));

     // Click to switch back to light mode
     fireEvent.click(button);
-    expect(localStorage.getItem('theme')).toBe('light');
+    await waitFor(() => expect(localStorage.getItem('theme')).toBe('light'));
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should toggle theme when clicked', () => {
renderWithTheme();
const button = screen.getByRole('button', { name: /toggle dark mode/i });
// Click to switch to dark mode
fireEvent.click(button);
expect(localStorage.getItem('theme')).toBe('dark');
// Click to switch back to light mode
fireEvent.click(button);
expect(localStorage.getItem('theme')).toBe('light');
});
it('should toggle theme when clicked', async () => {
renderWithTheme();
const button = screen.getByRole('button', { name: /toggle dark mode/i });
// Click to switch to dark mode
fireEvent.click(button);
await waitFor(() => expect(localStorage.getItem('theme')).toBe('dark'));
// Click to switch back to light mode
fireEvent.click(button);
await waitFor(() => expect(localStorage.getItem('theme')).toBe('light'));
});
🤖 Prompt for AI Agents
In `@app/components/DarkModeToggle/tests/index.test.js` around lines 44 - 55, The
test's assertions read localStorage synchronously but the component writes it
inside a useEffect, causing flakiness; update the test in index.test.js (the
"should toggle theme when clicked" spec that uses renderWithTheme and the button
from getByRole) to wrap the post-click localStorage assertions in async waitFor
calls (await waitFor(() => { expect(localStorage.getItem('theme')).toBe('dark')
}) and similarly for 'light') so the test waits for the effect to run after
fireEvent.click.

Comment on lines +64 to +69
it('should show tooltip on hover', () => {
renderWithTheme();
const button = screen.getByRole('button');
fireEvent.mouseOver(button);
// Material-UI tooltips appear after a delay
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The tooltip test has no assertion.

It currently can pass even if the tooltip is broken. Add an assertion (or remove the test).

🧪 Suggested assertion
-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen, fireEvent, act } from '@testing-library/react';
 ...
   it('should show tooltip on hover', () => {
+    jest.useFakeTimers();
     renderWithTheme();
     const button = screen.getByRole('button');
     fireEvent.mouseOver(button);
-    // Material-UI tooltips appear after a delay
+    act(() => {
+      jest.runAllTimers();
+    });
+    expect(screen.getByRole('tooltip')).toHaveTextContent(/switch to dark mode/i);
+    jest.useRealTimers();
   });
🤖 Prompt for AI Agents
In `@app/components/DarkModeToggle/tests/index.test.js` around lines 64 - 69, The
test "should show tooltip on hover" currently has no assertion; after calling
renderWithTheme() and fireEvent.mouseOver(button) (where button is obtained via
screen.getByRole('button')), make the test async and await an assertion that the
tooltip appears — e.g. await screen.findByRole('tooltip') or await
screen.findByText('<expected tooltip text>') — to assert the Material-UI tooltip
is present in the DOM; update the test function signature to async and add the
awaited assertion after fireEvent.mouseOver(button).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/containers/App/index.js (1)

36-48: ⚠️ Potential issue | 🟠 Major

MUI theme is static and won't respond to dark mode toggle.

The createTheme call on Lines 36–48 uses fixed colors.primary / colors.secondary values and is created once at module scope. When the user toggles dark mode via ThemeProvider (custom context), the MUI theme object passed to <MUIThemeProvider> remains unchanged. This means MUI components (e.g., CssBaseline, Container, any MUI buttons/inputs) will always render with the light palette, causing visual inconsistencies in dark mode.

To fix this, createTheme should be called reactively inside the component tree using the current dark mode state from useTheme(), or the custom ThemeContext colors should be fed into MUI's palette dynamically.

Also applies to: 80-118

🤖 Fix all issues with AI agents
In `@README.md`:
- Around line 72-76: Update README references to point to the actual theme
implementation and state management: replace the mention of app/themes/index.js
as the source of light/dark palettes with app/contexts/themeContext.js and note
that the DarkModeToggle component (app/components/DarkModeToggle/index.js)
reads/updates the theme via the custom ThemeContext (exported from
themeContext.js) rather than Material-UI's ThemeProvider; also clarify that
Material-UI's ThemeProvider is only used to apply the theme object generated
from ThemeContext, not to hold the dark mode state.
- Around line 79-84: The README example imports DarkModeToggle as a default but
the component is exported as a named export (export const DarkModeToggle in
app/components/DarkModeToggle/index.js); update the usage example to use a named
import for DarkModeToggle (i.e., import { DarkModeToggle } from '...') so the
example matches the actual export and will work when rendering <DarkModeToggle
/>.
🧹 Nitpick comments (4)
app/components/ProtectedRoute/index.js (1)

15-19: hasOwnProperty check is redundant here.

Object.keys(routeConstants) on Line 14 already returns only own enumerable properties, so the hasOwnProperty guard on Line 18 can never be false. Not harmful, but it adds noise without providing any actual safety benefit.

app/i18n.js (1)

24-31: hasMessage check on Line 25 is always true.

Since key is sourced from Object.keys(messages) (Line 34), it is guaranteed to be an own property of messages. The hasDefaultMessage check on Line 26, however, is genuinely useful since defaultFormattedMessages may not contain the same keys.

app/containers/App/index.js (2)

89-89: Redundant lodash/map wrapping Object.keys().

map(Object.keys(routeConfig)) just shallow-copies the array — Object.keys(routeConfig) alone is sufficient here.

-                            of={map(Object.keys(routeConfig))}
+                            of={Object.keys(routeConfig)}

90-112: hasOwnProperty guard on Line 92 is redundant — keys come from Object.keys(routeConfig).

Same pattern as in the other files; Object.keys only yields own enumerable properties.

Also, consider using routeKey instead of index for the React key prop (Line 101) — it's more semantically meaningful and stable, even though the route list is static.

-                                  key={index}
+                                  key={routeKey}

- Add ReactDOM.render polyfill using createRoot API
- Add ReactDOM.unmountComponentAtNode polyfill
- Fixes 'react_dom.render is not a function' error
- Fixes 'react_dom.unmountComponentAtNode is not a function' error
- All 3 Storybook tests now passing
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.storybook/preview.js:
- Around line 11-26: The polyfill for ReactDOM.render calls callback immediately
after scheduling root.render, causing the callback to run before the DOM commit;
update the ReactDOM.render implementation (the polyfill wrapping
rootsMap/createRoot/root.render) to call root.render inside flushSync so the
render is committed before invoking the callback, e.g. import flushSync from
React and use flushSync(() => root.render(element)); then call callback() after
flushSync returns to preserve legacy semantics.
🧹 Nitpick comments (1)
.storybook/preview.js (1)

5-9: Consider upgrading Storybook instead of maintaining this polyfill.

Storybook 6.5.16 + React 19 is an unsupported combination. While Storybook 8 provides better React 18/19 compatibility via @storybook/react-dom-shim, full React 19 support is still being finalized (targeted for Storybook 9.0). An upgrade to Storybook 8 would reduce maintenance burden, but expect possible edge cases until Storybook 9 lands. This is worth tracking as follow-up work.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/containers/App/index.js (1)

74-83: ⚠️ Potential issue | 🟠 Major

Open redirect via redirect_uri query parameter.

routeToReplace is taken directly from user-controlled query string and passed to history.replace() without validation. An attacker could craft a URL like ?redirect_uri=//evil.com or ?redirect_uri=javascript:... to redirect users to a malicious site. This is pre-existing code, but worth flagging since it's a security concern.

Consider validating that routeToReplace is a relative path (e.g., starts with / and not //):

Proposed fix
  if (location.search.includes('?redirect_uri=')) {
    const routeToReplace = new URLSearchParams(location.search).get('redirect_uri');
-   history.replace(routeToReplace);
+   if (routeToReplace && routeToReplace.startsWith('/') && !routeToReplace.startsWith('//')) {
+     history.replace(routeToReplace);
+   }
  }
🤖 Fix all issues with AI agents
In `@app/utils/testUtils.js`:
- Around line 39-43: Tests using CustomThemeProvider in app/utils/testUtils.js
cause persistent side effects because ThemeProvider (in themeContext.js) writes
localStorage['theme'] and mutates document.documentElement.classList; update the
test utility to prevent cross-test pollution by either mocking localStorage and
window.matchMedia when mounting CustomThemeProvider or adding cleanup logic that
runs afterEach to remove localStorage.removeItem('theme') and restore
document.documentElement.classList to its original state; specifically, locate
CustomThemeProvider usage in testUtils.js and add setup/teardown helpers that
reset localStorage and clear any theme-related classes and provide a safe mock
for window.matchMedia so tests run deterministically.
🧹 Nitpick comments (2)
app/containers/App/index.js (1)

136-139: AppContent.propTypes declares props that the component never receives.

AppContent is rendered as <AppContent /> with no props (Line 148). The location and history propTypes appear to be leftovers from the previous App function signature. This is dead code and should be removed to avoid confusion.

Proposed fix
-AppContent.propTypes = {
-  location: PropTypes.object,
-  history: PropTypes.object
-};
app/utils/testUtils.js (1)

26-32: renderWithIntl does not include theme providers.

Components that depend on CustomThemeProvider (via useTheme) or MUI's ThemeProvider will fail if rendered with renderWithIntl instead of renderProvider. This may be intentional for lightweight i18n-only tests, but it's worth noting — any component using useTheme() from themeContext will get undefined and likely throw.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/ci.yml:
- Around line 16-18: The workflow currently uses the pull_request_target trigger
together with actions/checkout@v4 checking out the PR head SHA, which allows
untrusted PR code to run with repository secrets; fix by changing the trigger
from pull_request_target to pull_request (update the top-level on: to
pull_request) so the PR runs with the contributor’s context and cannot access
secrets, and remove any checkout of the PR head SHA pattern used with
pull_request_target; alternatively, if secrets (e.g.,
SONAR_TOKEN/SONAR_HOST_URL) are required, split into two workflows: keep an
untrusted build/test workflow triggered by pull_request that checks out and runs
tests (no secrets), and create a separate trusted workflow (e.g., triggered by
workflow_run or on push to main) that consumes artifacts and runs
secret-requiring steps like SonarQube scanning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant