Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 139 additions & 2 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"react-icons": "^5.5.0",
"react-router-dom": "^6.30.1",
"recharts": "^3.0.2",
"styled-components": "^6.1.19",
"tailwindcss": "^4.1.10"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function App() {
<Footer />
</div>
);

}

export default App;
35 changes: 35 additions & 0 deletions client/src/components/ThemeToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useEffect, useState } from "react";
import { Moon, Sun } from "lucide-react";

const ThemeToggle = () => {

const [isDarkMode, setIsDarkMode] = useState(false);

useEffect(() => {
if( localStorage.getItem('theme') === 'dark' ){
document.documentElement.classList.add('dark');
setIsDarkMode(true);
}

},[])
Comment on lines +8 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Initialize dark mode from storage or system preference; current logic may show wrong icon on first paint.

Defaulting isDarkMode to false while the CSS default is dark causes a mismatch.

-useEffect(() => {
-    if( localStorage.getItem('theme') === 'dark' ){
-        document.documentElement.classList.add('dark');
-        setIsDarkMode(true);
-    }
-    
-},[])
+useEffect(() => {
+  const stored = localStorage.getItem('theme'); // 'dark' | 'light' | null
+  const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches;
+  const nextIsDark = stored ? stored === 'dark' : !!prefersDark;
+  document.documentElement.classList.toggle('dark', nextIsDark);
+  setIsDarkMode(nextIsDark);
+}, []);
📝 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
useEffect(() => {
if( localStorage.getItem('theme') === 'dark' ){
document.documentElement.classList.add('dark');
setIsDarkMode(true);
}
},[])
useEffect(() => {
const stored = localStorage.getItem('theme'); // 'dark' | 'light' | null
const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches;
const nextIsDark = stored ? stored === 'dark' : !!prefersDark;
document.documentElement.classList.toggle('dark', nextIsDark);
setIsDarkMode(nextIsDark);
}, []);
🤖 Prompt for AI Agents
In client/src/components/ThemeToggle.jsx around lines 8 to 14, the component
initializes isDarkMode as false and only updates it in a useEffect, causing an
icon mismatch on first paint; change the implementation so the initial state is
computed from localStorage (if present) or from the prefers-color-scheme media
query and apply the corresponding document.documentElement.classList before
render (or use useLayoutEffect to avoid a paint flicker), and ensure both the
class toggle and setIsDarkMode are synchronized when mounting.

const toggleTheme = () => {
if(isDarkMode){
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
setIsDarkMode(false);
}else{
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
setIsDarkMode(true);
}
}
Comment on lines +15 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Normalize toggle semantics and storage values.

Ensure the class and localStorage reflect the same meaning.

-const toggleTheme = () => {
-    if(isDarkMode){
-        document.documentElement.classList.remove('dark');
-        localStorage.setItem('theme', 'light');
-        setIsDarkMode(false);
-    }else{
-        document.documentElement.classList.add('dark');
-        localStorage.setItem('theme', 'dark');
-        setIsDarkMode(true);
-    }
-}
+const toggleTheme = () => {
+  const next = !isDarkMode;
+  document.documentElement.classList.toggle('dark', next);
+  localStorage.setItem('theme', next ? 'dark' : 'light');
+  setIsDarkMode(next);
+};
📝 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
const toggleTheme = () => {
if(isDarkMode){
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
setIsDarkMode(false);
}else{
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
setIsDarkMode(true);
}
}
const toggleTheme = () => {
const next = !isDarkMode;
document.documentElement.classList.toggle('dark', next);
localStorage.setItem('theme', next ? 'dark' : 'light');
setIsDarkMode(next);
};
🤖 Prompt for AI Agents
In client/src/components/ThemeToggle.jsx around lines 15 to 25, the toggle
should consistently apply the same semantics to the DOM class and localStorage;
replace the branching with a single toggle flow that computes newIsDark =
!isDarkMode, then if newIsDark add the 'dark' class else remove it, set
localStorage.setItem('theme', newIsDark ? 'dark' : 'light'), and finally call
setIsDarkMode(newIsDark) so the class, storage value and state all reflect the
same meaning.

return (
<button onClick={toggleTheme} className="p-1 cursor-pointer hover:bg-slate-500 rounded-lg transition-colors duration-300">

{!isDarkMode ? (<Sun className="h-6 w-6 text-yellow-300"/>) : (<Moon className="h-6 w-6 text-blue-700 hover:transform-fill"/>)
}
</button>
)
};

export default ThemeToggle;
88 changes: 88 additions & 0 deletions client/src/components/TopBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useEffect } from "react";
import { ArrowBigUp } from "lucide-react";
import Styled from "styled-components";

const OnTopBar = () => {
const [isvisible, setIsVisible] = React.useState(false);
const [scrollProgress, setScrollProgress] = React.useState(0);

const onTopbtn = () => {
window.scrollTo({
top: 0,
left: 0,
behavior: "smooth",
});
};

// Updated scroll handler to calculate progress
const handleScroll = () => {
const winScroll = document.documentElement.scrollTop;
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const scrolled = (winScroll / height) * 100;

setScrollProgress(scrolled);

if (window.scrollY > 100) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};
Comment on lines +18 to +30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard division by zero and clamp progress; improve cross-browser scroll source.

Avoid NaN/Infinity when the page isn’t scrollable and support body/html differences.

-const handleScroll = () => {
-  const winScroll =  document.documentElement.scrollTop;
-  const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
-  const scrolled = (winScroll / height) * 100;
-  
-  setScrollProgress(scrolled);
-  
-  if (window.scrollY > 100) {
-    setIsVisible(true);
-  } else {
-    setIsVisible(false);
-  }
-};
+const handleScroll = () => {
+  const doc = document.documentElement;
+  const body = document.body;
+  const winScroll = doc.scrollTop || body.scrollTop || 0;
+  const height = (doc.scrollHeight || body.scrollHeight) - doc.clientHeight;
+  const pct = height > 0 ? Math.min(100, Math.max(0, (winScroll / height) * 100)) : 0;
+  setScrollProgress(pct);
+  setIsVisible(window.scrollY > 100);
+};
📝 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
const handleScroll = () => {
const winScroll = document.documentElement.scrollTop;
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const scrolled = (winScroll / height) * 100;
setScrollProgress(scrolled);
if (window.scrollY > 100) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};
const handleScroll = () => {
const doc = document.documentElement;
const body = document.body;
const winScroll = doc.scrollTop || body.scrollTop || 0;
const height = (doc.scrollHeight || body.scrollHeight) - doc.clientHeight;
const pct = height > 0 ? Math.min(100, Math.max(0, (winScroll / height) * 100)) : 0;
setScrollProgress(pct);
setIsVisible(window.scrollY > 100);
};
🤖 Prompt for AI Agents
In client/src/components/TopBar.jsx around lines 18 to 30, the scroll progress
calculation can produce NaN/Infinity on non-scrollable pages and doesn't account
for cross-browser differences in where scrollTop is stored; update handleScroll
to read scroll position from window.scrollY ||
document.documentElement.scrollTop || document.body.scrollTop, compute total
scrollable height defensively (e.g. const height =
Math.max(document.documentElement.scrollHeight, document.body.scrollHeight) -
Math.max(document.documentElement.clientHeight, document.body.clientHeight)),
guard against division by zero by setting scrolled to 0 when height <= 0, and
clamp the resulting progress between 0 and 100 before calling setScrollProgress;
keep the existing visibility toggle logic.


useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);

return (
<Container>
<div className="progress-bar" style={{ width: `${scrollProgress}%` }} />
{isvisible && (
<div className="on-top-bar" onClick={onTopbtn}>
<ArrowBigUp />
</div>
)}
</Container>
Comment on lines +41 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use a button for accessibility; add aria-label.

Clickable divs are not keyboard-accessible by default.

-  {isvisible && (
-    <div className="on-top-bar" onClick={onTopbtn}>
-      <ArrowBigUp />
-    </div>
-  )}
+  {isVisible && (
+    <button className="on-top-bar" type="button" onClick={onTopbtn} aria-label="Back to top">
+      <ArrowBigUp />
+    </button>
+  )}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In client/src/components/TopBar.jsx around lines 41 to 47, the clickable
"on-top-bar" div is not keyboard-accessible; replace that div with a semantic
<button> element, move the onClick handler to the button, add an appropriate
aria-label (e.g., "Scroll to top"), ensure any styling or className is preserved
on the button, and remove any tabIndex/role hacks so the element is naturally
focusable and accessible to keyboard and assistive technologies.

);
};

const Container = Styled.section`
display: flex;
justify-content: center;
align-items: center;
position: relative;

.progress-bar {
position: fixed;
top: 0;
left: 0;
height: 2px;
background: #00abff;

}

.on-top-bar {
position: fixed;
bottom: 20px;
right: 18px;
background-color: #fff;
color: #1a1a1a;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);

&:hover {
background-color: #0039ff;
}
}
`;

export default OnTopBar;
10 changes: 10 additions & 0 deletions client/src/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ html {
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #056896;
}

:body {
background-color: #1a1a1a;
color: #f3f4f6;
}
Comment on lines +35 to +38
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix invalid selector ':body' (lint error).

:body is not a valid selector; use body.

-:body {
+body {
   background-color: #1a1a1a;
   color: #f3f4f6;
 }
📝 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
:body {
background-color: #1a1a1a;
color: #f3f4f6;
}
body {
background-color: #1a1a1a;
color: #f3f4f6;
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 35-35: Unexpected unknown pseudo-class body

See MDN web docs for more details.

(lint/correctness/noUnknownPseudoClass)

🤖 Prompt for AI Agents
In client/src/styles/index.css around lines 35 to 38, the selector uses an
invalid `:body`; replace it with the valid `body` selector so the rule reads
`body { background-color: #1a1a1a; color: #f3f4f6; }` and then run the CSS
linter to confirm the error is resolved.


.dark {
background-color: #f3f4f6;
color: #1a1a1a;
}
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.