Skip to content

Conversation

@REHAAANNN
Copy link

@REHAAANNN REHAAANNN commented Dec 13, 2025

image image
  • Add ThemeContext with localStorage persistence and system preference detection
  • Create ThemeToggle component with sun/moon icons
  • Add ClientLayout wrapper for client-side components
  • Update Header with dark mode styles and toggle button placement
  • Configure Tailwind with class-based dark mode strategy
  • Add dark mode styles for body, scrollbar, and navigation
  • Ensure smooth transitions between themes

Description

Include a summary of the change and which issue is fixed. List any dependencies that are required for this change.

Fixes # (issue)

Type of change

Please mark the options that are relevant.

  • Updated UI/UX
  • Improved the business logic of code
  • Added new feature
  • Other

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced dark mode with a new theme toggle button accessible in the header and mobile navigation.
    • Theme preference persists across sessions.
  • Styling

    • Updated app styling to support both light and dark themes with smooth transitions.
    • Enhanced scrollbar styling for improved appearance across all themes.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add ThemeContext with localStorage persistence and system preference detection
- Create ThemeToggle component with sun/moon icons
- Add ClientLayout wrapper for client-side components
- Update Header with dark mode styles and toggle button placement
- Configure Tailwind with class-based dark mode strategy
- Add dark mode styles for body, scrollbar, and navigation
- Ensure smooth transitions between themes
@coderabbitai
Copy link

coderabbitai bot commented Dec 13, 2025

Walkthrough

This PR introduces a comprehensive dark mode theming system for the client application. A new ThemeContext provider manages theme state with localStorage persistence and system preference detection. New components (ThemeToggle, ClientLayout) are added, and existing components are updated with dark mode styling and Tailwind class-based dark mode configuration.

Changes

Cohort / File(s) Summary
Dark Mode Configuration
client/tailwind.config.ts
Added darkMode: 'class' property to enable class-based dark mode switching in Tailwind.
Global Styling
client/app/globals.css
Reorganized scrollbar and body styling into Tailwind layers; transitioned from hard-coded colors to Tailwind utilities with dark mode variants (e.g., dark:bg-gray-900); changed body overflow to overflow-y: auto.
Theme Management
client/app/context/ThemeContext.tsx
New file introducing ThemeProvider component that manages theme state with localStorage persistence, system preference detection, and DOM class toggling; exports useTheme hook with hydration safety.
Theme Toggle Component
client/app/components/ThemeToggle.tsx
New UI component providing a button to switch between light and dark themes, with mounted state to prevent hydration mismatches and responsive Tailwind styling.
Layout Components
client/app/components/ClientLayout.tsx
New layout component wrapping Header, ChatBot, and children in a consistent composition.
Component Dark Mode Updates
client/app/components/Header/Header.tsx,
client/app/components/Pages/LoginPage.tsx
Enhanced with dark mode color classes (dark:bg-gray-900, dark:text-*), transition utilities, and integrated ThemeToggle; Header includes dark-mode-aware button hover states.
Layout Restructuring
client/app/layout.tsx
Wrapped entire RootLayout content with ThemeProvider; removed direct Header and ChatBot imports; moved children rendering inside ClientLayout for consistent component composition.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant ThemeProvider
    participant useTheme
    participant DOM as Document.documentElement
    participant Component

    rect rgb(200, 220, 255)
    Note over ThemeProvider,DOM: Theme Initialization
    Browser->>ThemeProvider: Mount ThemeProvider
    ThemeProvider->>ThemeProvider: Check localStorage
    alt localStorage has saved theme
        ThemeProvider->>ThemeProvider: Use saved theme
    else localStorage empty
        ThemeProvider->>Browser: Query system preference
        ThemeProvider->>ThemeProvider: Use system or default
    end
    ThemeProvider->>DOM: Apply "dark" class if needed
    ThemeProvider->>Browser: Store in localStorage
    end

    rect rgb(220, 255, 220)
    Note over User,Component: Theme Toggle Action
    User->>Component: Click ThemeToggle button
    Component->>useTheme: Call toggleTheme()
    useTheme->>ThemeProvider: Update theme state
    ThemeProvider->>ThemeProvider: Flip theme value
    ThemeProvider->>DOM: Toggle "dark" class
    ThemeProvider->>Browser: Persist to localStorage
    ThemeProvider->>Component: Trigger re-render via Context
    Component->>Component: Update icon (Sun ↔ Moon)
    Component->>Browser: Reflect new theme
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Areas requiring extra attention:

  • ThemeContext hydration handling — Verify the mounted flag in useTheme and ThemeProvider prevents hydration mismatches; confirm useEffect dependencies are complete.
  • localStorage persistence — Ensure theme persists correctly across page reloads and that fallback to system preference works when localStorage is unavailable.
  • DOM class timing — Confirm the "dark" class is applied to document.documentElement before components render to avoid flash of wrong theme.
  • ThemeToggle mounted state logic — Verify the placeholder render on server matches hydration expectations and mounted state is set properly on client.

Poem

🐰 A theme to toggle, light and dark,
With context magic and local mark,
The provider whispers to each component near,
"Switch your colors—day or night, my dear!"
No flash of wrong, no hydration fright,
Just smooth transitions, morning to night. 🌙✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing a dark/light mode toggle feature, with a reference to the related issue.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

Copy link

@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)
client/app/components/Header/Header.tsx (1)

56-70: Avoid <button> inside <Link> (nested interactive elements).
Link renders an anchor; placing a button inside is invalid HTML and hurts a11y. Prefer styling the Link itself (or use motion(Link) / motion.a) and drop the inner button.

-  <Link key={item.name} href={item.href} className="relative">
-    <motion.button className={`...`} ...>
-      <item.icon ... />
-      <span>{item.name}</span>
-    </motion.button>
-  </Link>
+  <motion.div key={item.name} className="relative" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
+    <Link href={item.href} className={`inline-flex items-center px-3 py-2 ...`}>
+      <item.icon className="h-5 w-5 mr-2" aria-hidden="true" />
+      <span>{item.name}</span>
+    </Link>
+  </motion.div>
🧹 Nitpick comments (4)
client/tailwind.config.ts (1)

3-22: Consider daisyUI theme compatibility with dark mode.
darkMode: "class" is aligned with the implementation, but daisyui.themes: ["light"] may keep daisyUI components visually “light” in dark mode. If daisyUI components are used, consider adding a dark theme (or custom theme mapping) to avoid mixed styling.

client/app/components/ClientLayout.tsx (1)

1-17: Clean layout wrapper; composition is straightforward.
Optional: if you anticipate routes that shouldn’t show ChatBot, consider a prop/slot (or route-group layout) rather than hard-wiring it here.

client/app/components/ThemeToggle.tsx (1)

6-34: Add pressed-state + focus styles for a11y.
Consider adding aria-pressed={theme === "dark"} and a focus-visible:ring treatment so keyboard users can see focus on the toggle.

client/app/globals.css (1)

21-24: Scrollbar at 1px may be an accessibility/UX regression.
If this isn’t strictly intentional, consider a larger width (or media-query-based sizing) so it remains usable with a mouse/trackpad.

Also applies to: 31-39

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3a4dd and d4fda1c.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • client/app/components/ClientLayout.tsx (1 hunks)
  • client/app/components/Header/Header.tsx (8 hunks)
  • client/app/components/Pages/LoginPage.tsx (1 hunks)
  • client/app/components/ThemeToggle.tsx (1 hunks)
  • client/app/context/ThemeContext.tsx (1 hunks)
  • client/app/globals.css (1 hunks)
  • client/app/layout.tsx (2 hunks)
  • client/tailwind.config.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
client/app/layout.tsx (3)
client/app/context/ThemeContext.tsx (1)
  • ThemeProvider (13-45)
client/app/helpers/client.ts (2)
  • config (7-17)
  • queryClient (19-19)
client/app/components/ClientLayout.tsx (1)
  • ClientLayout (6-18)
client/app/components/ThemeToggle.tsx (1)
client/app/context/ThemeContext.tsx (1)
  • useTheme (47-53)
client/app/components/Header/Header.tsx (1)
client/app/components/ThemeToggle.tsx (1)
  • ThemeToggle (6-35)
🔇 Additional comments (4)
client/app/components/Pages/LoginPage.tsx (1)

5-12: Dark-mode container styling looks correct.
The bg-white dark:bg-gray-900 + transition-colors addition is consistent with class-based theming.

client/app/globals.css (1)

10-14: Global body theming via @layer base is consistent.
This should play nicely with the dark class on html.

client/app/components/Header/Header.tsx (1)

33-140: Dark-mode styling + ThemeToggle placement are coherent.
Transitions and dark variants look consistently applied across header, sidebar, and menu item states.

client/app/layout.tsx (1)

12-40: Confirm that Header + ChatBot should appear with unauthenticated users.

LoginPage is currently rendered conditionally within HomePage (when wallet is not connected), not as a separate route. Since HomePage is wrapped by ClientLayout, the Header and ChatBot appear above LoginPage content (indicated by the pt-20 padding). Verify this is the intended UX for unauthenticated users. If future auth flows require standalone pages without Header/ChatBot, consider using route groups to opt-out selectively.

Comment on lines +13 to +38
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const [mounted, setMounted] = useState(false);

// Load theme from localStorage on mount
useEffect(() => {
setMounted(true);
const savedTheme = localStorage.getItem("theme") as Theme;
if (savedTheme) {
setTheme(savedTheme);
document.documentElement.classList.toggle("dark", savedTheme === "dark");
} else {
// Check system preference
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const initialTheme = prefersDark ? "dark" : "light";
setTheme(initialTheme);
document.documentElement.classList.toggle("dark", prefersDark);
}
}, []);

const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
document.documentElement.classList.toggle("dark", newTheme === "dark");
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Validate localStorage theme + drop unused mounted.
Right now any truthy localStorage.getItem("theme") is trusted via a cast, and mounted is unused. Safer to validate and simplify.

 export function ThemeProvider({ children }: { children: React.ReactNode }) {
   const [theme, setTheme] = useState<Theme>("light");
-  const [mounted, setMounted] = useState(false);

   // Load theme from localStorage on mount
   useEffect(() => {
-    setMounted(true);
-    const savedTheme = localStorage.getItem("theme") as Theme;
-    if (savedTheme) {
-      setTheme(savedTheme);
-      document.documentElement.classList.toggle("dark", savedTheme === "dark");
+    const savedTheme = localStorage.getItem("theme");
+    if (savedTheme === "light" || savedTheme === "dark") {
+      setTheme(savedTheme);
+      document.documentElement.classList.toggle("dark", savedTheme === "dark");
     } else {
       // Check system preference
       const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
       const initialTheme = prefersDark ? "dark" : "light";
       setTheme(initialTheme);
       document.documentElement.classList.toggle("dark", prefersDark);
     }
   }, []);
🤖 Prompt for AI Agents
In client/app/context/ThemeContext.tsx around lines 13 to 38, the code trusts
any truthy localStorage value cast to Theme and keeps an unused mounted state;
validate the saved value against the allowed Theme union ("light" | "dark")
before applying it (fallback to system preference if invalid or missing), remove
the mounted state entirely, and simplify the effect to read localStorage, check
if value === "light" or "dark", apply document.documentElement.classList.toggle
appropriately, set the theme state, and keep the toggleTheme logic the same
(ensure it also writes only "light" or "dark" to localStorage).

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