diff --git a/frontend/src/Components/DashBoard/LeetCode.jsx b/frontend/src/Components/DashBoard/LeetCode.jsx index 88911e2..a12b12e 100644 --- a/frontend/src/Components/DashBoard/LeetCode.jsx +++ b/frontend/src/Components/DashBoard/LeetCode.jsx @@ -18,7 +18,7 @@ import BackButton from "../ui/backbutton"; ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); -export default function LeetCode({ platforms = {} }) { +export default function LeetCode() { const [stats, setStats] = useState(null); const { leetUser } = useParams(); diff --git a/frontend/src/Components/Dashboard.jsx b/frontend/src/Components/Dashboard.jsx index 310bf6a..4f32b1c 100644 --- a/frontend/src/Components/Dashboard.jsx +++ b/frontend/src/Components/Dashboard.jsx @@ -101,7 +101,6 @@ export default function Dashboard() { const { socialLinks = [], streak = 0, - githubUsername = null, timeSpent = "0 minutes", activity = [], notes = [] diff --git a/frontend/src/Components/Hero.jsx b/frontend/src/Components/Hero.jsx index d39cda8..a8fd435 100644 --- a/frontend/src/Components/Hero.jsx +++ b/frontend/src/Components/Hero.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useRef } from "react"; import { motion } from "framer-motion"; import { ArrowRightIcon } from "lucide-react"; diff --git a/frontend/src/Components/auth/ForgotPassword.jsx b/frontend/src/Components/auth/ForgotPassword.jsx index a025f38..d94a387 100644 --- a/frontend/src/Components/auth/ForgotPassword.jsx +++ b/frontend/src/Components/auth/ForgotPassword.jsx @@ -26,7 +26,7 @@ const ForgotPassword = () => { } else { setStatus({ type: "error", message: data.message || "Something went wrong" }); } - } catch (error) { + } catch { setStatus({ type: "error", message: "Server error. Try again later." }); } finally { setLoading(false); diff --git a/frontend/src/Components/auth/Login.jsx b/frontend/src/Components/auth/Login.jsx index 6d1182b..ede8dca 100644 --- a/frontend/src/Components/auth/Login.jsx +++ b/frontend/src/Components/auth/Login.jsx @@ -45,7 +45,7 @@ const Login = () => { let data; try { data = await response.json(); - } catch (jsonError) { + } catch { throw new Error("Invalid server response"); } @@ -69,7 +69,7 @@ const Login = () => { } }; - const handleVerificationSuccess = (user) => { + const handleVerificationSuccess = () => { navigate("/dashboard"); }; diff --git a/frontend/src/Components/auth/Register.jsx b/frontend/src/Components/auth/Register.jsx index 936d937..5134caf 100644 --- a/frontend/src/Components/auth/Register.jsx +++ b/frontend/src/Components/auth/Register.jsx @@ -115,16 +115,11 @@ const Register = () => { } }; - const handleVerificationSuccess = (user) => { + const handleVerificationSuccess = () => { // Redirect to dashboard window.location.href = "/dashboard"; }; - const handleGoogleRegister = () => { - // Use backend OAuth route at /auth (not protected /api) - window.location.href = `${import.meta.env.VITE_API_URL}/auth/google`; - }; - if (showVerification) { return ( { message: "Something went wrong. Please try again.", }); } - } catch (error) { + } catch { setStatus("error"); setError("root", { message: "Something went wrong. Please try again." }); } diff --git a/frontend/src/Components/ui/Card.jsx b/frontend/src/Components/ui/Card.jsx index 3689ea8..efc1fd8 100644 --- a/frontend/src/Components/ui/Card.jsx +++ b/frontend/src/Components/ui/Card.jsx @@ -1,4 +1,6 @@ // src/Components/ui/Card.jsx +import React from "react"; + export function Card({ children, className = "" }) { return (
@@ -9,12 +11,16 @@ export function Card({ children, className = "" }) { export function CardHeader({ children, className = "" }) { return ( -
{children}
+
+ {children} +
); } export function CardTitle({ children, className = "" }) { - return

{children}

; + return ( +

{children}

+ ); } export function CardContent({ children, className = "" }) { @@ -24,3 +30,4 @@ export function CardContent({ children, className = "" }) { export function CardFooter({ children, className = "" }) { return
{children}
; } + diff --git a/frontend/src/Components/ui/DarkModeToggle.jsx b/frontend/src/Components/ui/DarkModeToggle.jsx index 87cd174..fa96c08 100644 --- a/frontend/src/Components/ui/DarkModeToggle.jsx +++ b/frontend/src/Components/ui/DarkModeToggle.jsx @@ -12,7 +12,7 @@ export default function DarkModeToggle() { aria-label="Toggle dark mode" className={`flex items-center justify-center w-12 h-12 rounded-full transition-all duration-300 cursor-pointer hover:scale-110 ${ isDark - ? "bg-[ --background]" + ? "bg-[--background]" : "bg-[--foreground]" }`} > diff --git a/frontend/src/Components/ui/Loader.tsx b/frontend/src/Components/ui/Loader.tsx index d26160b..6a8f0b1 100644 --- a/frontend/src/Components/ui/Loader.tsx +++ b/frontend/src/Components/ui/Loader.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { cn } from "@/lib/utils"; // if you have shadcn's `cn`, else remove +// import { cn } from "@/lib/utils"; // if you have shadcn's `cn`, else remove interface LoaderProps { className?: string; diff --git a/frontend/src/Components/ui/ScrollRevealWrapper.jsx b/frontend/src/Components/ui/ScrollRevealWrapper.jsx index 6983a0d..53e6826 100644 --- a/frontend/src/Components/ui/ScrollRevealWrapper.jsx +++ b/frontend/src/Components/ui/ScrollRevealWrapper.jsx @@ -1,15 +1,39 @@ // src/components/ScrollRevealWrapper.jsx +import React, { useRef } from "react"; import { motion } from "framer-motion"; import { useInView } from "react-intersection-observer"; -export default function ScrollRevealWrapper({ children, delay = 0, className = "" }) { - const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.1 }); +interface ScrollRevealWrapperProps { + children: React.ReactNode; + delay?: number; + className?: string; +} + +export default function ScrollRevealWrapper({ + children, + delay = 0, + className = "", +}: ScrollRevealWrapperProps) { + const ref = useRef(null); + + // Track if element is in view + const { inView } = useInView({ + triggerOnce: true, + threshold: 0.1, + }); + + // Animation variants + const variants = { + hidden: { opacity: 0, y: 40 }, + visible: { opacity: 1, y: 0 }, + }; return ( @@ -17,3 +41,4 @@ export default function ScrollRevealWrapper({ children, delay = 0, className = " ); } + diff --git a/frontend/src/Components/ui/button.jsx b/frontend/src/Components/ui/button.jsx index 69ad71f..1e7e278 100644 --- a/frontend/src/Components/ui/button.jsx +++ b/frontend/src/Components/ui/button.jsx @@ -1,39 +1,8 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" -import { cva } from "class-variance-authority"; import { cn } from "@/lib/utils" - -const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", - { - variants: { - variant: { - default: - "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", - destructive: - "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", - outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", - secondary: - "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", - ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) +import { buttonVariants } from "@/lib/buttonVariants" function Button({ className, diff --git a/frontend/src/Components/ui/input.jsx b/frontend/src/Components/ui/input.jsx index e267074..6b21d1f 100644 --- a/frontend/src/Components/ui/input.jsx +++ b/frontend/src/Components/ui/input.jsx @@ -1,22 +1,6 @@ import * as React from "react" -import { cva } from "class-variance-authority" import { cn } from "@/lib/utils" - -const inputVariants = cva( - "flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all", - { - variants: { - size: { - default: "h-10", - sm: "h-9", - lg: "h-11", - }, - }, - defaultVariants: { - size: "default", - }, - } -) +import { inputVariants } from "@/lib/inputVariants" const Input = React.forwardRef(({ className, type, size, ...props }, ref) => { return ( diff --git a/frontend/src/context/TimerContext.jsx b/frontend/src/context/TimerContext.jsx index 79e2d26..8c4dc74 100644 --- a/frontend/src/context/TimerContext.jsx +++ b/frontend/src/context/TimerContext.jsx @@ -1,4 +1,5 @@ -import { createContext, useContext, useState, useEffect } from "react"; +import { createContext, useContext, useState, useEffect, useCallback } from "react"; +import { SESSIONS_BEFORE_LONG_BREAK } from "../lib/timerConstants"; const TimerContext = createContext(); @@ -6,8 +7,6 @@ export function useTimer() { return useContext(TimerContext); } -const SESSIONS_BEFORE_LONG_BREAK = 4; - export function TimerProvider({ children }) { const [workTime, setWorkTime] = useState(25 * 60); const [shortBreak, setShortBreak] = useState(5 * 60); @@ -27,6 +26,27 @@ export function TimerProvider({ children }) { return saved ? Number(saved) : workTime; }); + const handleSessionEnd = useCallback(() => { + const nextSession = isWork ? sessions + 1 : sessions; + setSessions(nextSession); + const nextTime = isWork + ? nextSession % SESSIONS_BEFORE_LONG_BREAK === 0 + ? longBreak + : shortBreak + : workTime; + + setIsWork(prev => !prev); + setTimeLeft(nextTime); + setEndTimestamp(Date.now() + nextTime * 1000); + setIsRunning(true); + + if ("Notification" in window && Notification.permission === "granted") { + new Notification( + isWork ? "Work session complete! Time for a break 🎉" : "Break over! Back to work 💻" + ); + } + }, [sessions, isWork, longBreak, shortBreak, workTime]); + // Timer tick using requestAnimationFrame useEffect(() => { let raf; @@ -50,7 +70,7 @@ export function TimerProvider({ children }) { } return () => cancelAnimationFrame(raf); - }, [isRunning, endTimestamp]); + }, [isRunning, endTimestamp, handleSessionEnd]); // Persist timer info useEffect(() => localStorage.setItem("pomodoroTimeLeft", timeLeft), [timeLeft]); @@ -59,27 +79,6 @@ export function TimerProvider({ children }) { if (endTimestamp) localStorage.setItem("pomodoroEndTimestamp", endTimestamp); }, [endTimestamp]); - const handleSessionEnd = () => { - const nextSession = isWork ? sessions + 1 : sessions; - setSessions(nextSession); - const nextTime = isWork - ? nextSession % SESSIONS_BEFORE_LONG_BREAK === 0 - ? longBreak - : shortBreak - : workTime; - - setIsWork(prev => !prev); - setTimeLeft(nextTime); - setEndTimestamp(Date.now() + nextTime * 1000); - setIsRunning(true); - - if ("Notification" in window && Notification.permission === "granted") { - new Notification( - isWork ? "Work session complete! Time for a break 🎉" : "Break over! Back to work 💻" - ); - } - }; - const startTimer = () => { if (!endTimestamp) setEndTimestamp(Date.now() + timeLeft * 1000); setIsRunning(true); diff --git a/frontend/src/lib/buttonVariants.js b/frontend/src/lib/buttonVariants.js new file mode 100644 index 0000000..327aff1 --- /dev/null +++ b/frontend/src/lib/buttonVariants.js @@ -0,0 +1,32 @@ +import { cva } from "class-variance-authority"; + +export const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", + destructive: + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); diff --git a/frontend/src/lib/inputVariants.js b/frontend/src/lib/inputVariants.js new file mode 100644 index 0000000..342ab89 --- /dev/null +++ b/frontend/src/lib/inputVariants.js @@ -0,0 +1,17 @@ +import { cva } from "class-variance-authority"; + +export const inputVariants = cva( + "flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all", + { + variants: { + size: { + default: "h-10", + sm: "h-9", + lg: "h-11", + }, + }, + defaultVariants: { + size: "default", + }, + } +); diff --git a/frontend/src/lib/timerConstants.js b/frontend/src/lib/timerConstants.js new file mode 100644 index 0000000..25404c7 --- /dev/null +++ b/frontend/src/lib/timerConstants.js @@ -0,0 +1 @@ +export const SESSIONS_BEFORE_LONG_BREAK = 4; diff --git a/frontend/vite.config.js b/frontend/vite.config.js index ea412a0..59ebf1f 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,8 +1,11 @@ import path from "path"; +import { fileURLToPath } from "url"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tailwindcss()],