Skip to content
Draft
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
Binary file added frontend/backend/CyberSafeBackend
Binary file not shown.
62 changes: 44 additions & 18 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap");
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap");

:root {
/* Dark theme variables */
--bg-primary: #0d1117;
--bg-secondary: #161b22;
--bg-tertiary: #21262d;
--bg-glass: rgba(255, 255, 255, 0.1);
--bg-hover: rgba(0, 188, 212, 0.1);
--bg-hover-secondary: rgba(0, 91, 234, 0.18);
--text-primary: #fff;
--text-secondary: #aaa;
--border-primary: rgba(255, 255, 255, 0.1);
--border-light: #e0e0e0;
--gradient-primary: linear-gradient(135deg, #0d1117 0%, #161b22 100%);
--gradient-secondary: linear-gradient(135deg, #0d1117 0%, #161b22 50%, #21262d 100%);
--gradient-radial-1: radial-gradient(circle at 20% 80%, rgba(0, 188, 212, 0.08) 0%, transparent 50%);
--gradient-radial-2: radial-gradient(circle at 80% 20%, rgba(38, 198, 218, 0.08) 0%, transparent 50%);
}

/* Light theme variables */
[data-theme="light"] {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #e9ecef;
--bg-glass: rgba(0, 0, 0, 0.05);
--bg-hover: rgba(0, 188, 212, 0.05);
--bg-hover-secondary: rgba(0, 91, 234, 0.1);
--text-primary: #212529;
--text-secondary: #6c757d;
--border-primary: rgba(0, 0, 0, 0.1);
--border-light: #dee2e6;
--gradient-primary: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
--gradient-secondary: linear-gradient(135deg, #ffffff 0%, #f8f9fa 50%, #e9ecef 100%);
--gradient-radial-1: radial-gradient(circle at 20% 80%, rgba(0, 188, 212, 0.04) 0%, transparent 50%);
--gradient-radial-2: radial-gradient(circle at 80% 20%, rgba(38, 198, 218, 0.04) 0%, transparent 50%);
}

body,
html,
#root {
Expand All @@ -9,7 +45,7 @@ html,
font-family:
"Inter", "SF Pro Display", "Segoe UI", "Roboto", "Helvetica Neue",
sans-serif;
background: linear-gradient(135deg, #0d1117 0%, #161b22 100%);
background: var(--gradient-primary);
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
Expand All @@ -22,7 +58,7 @@ html,
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #0d1117 0%, #161b22 50%, #21262d 100%);
background: var(--gradient-secondary);
background-attachment: fixed;
position: relative;
}
Expand All @@ -34,17 +70,7 @@ html,
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(
circle at 20% 80%,
rgba(0, 188, 212, 0.08) 0%,
transparent 50%
),
radial-gradient(
circle at 80% 20%,
rgba(38, 198, 218, 0.08) 0%,
transparent 50%
);
background-image: var(--gradient-radial-1), var(--gradient-radial-2);
pointer-events: none;
}

Expand All @@ -60,19 +86,19 @@ html,
}

.sidebar-glass {
background: rgba(22, 27, 34, 0.95);
background: var(--bg-secondary);
backdrop-filter: blur(12px);
box-shadow:
0 8px 32px 0 rgba(0, 0, 0, 0.3),
0 2px 8px 0 rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(0, 188, 212, 0.1);
border: 1px solid rgba(48, 54, 61, 0.5);
border: 1px solid var(--border-primary);
border-radius: 24px;
padding: 36px 28px;
min-width: 270px;
height: fit-content;
max-height: 80vh;
color: #f5deb3;
color: var(--text-primary);
display: flex;
flex-direction: column;
gap: 36px;
Expand Down Expand Up @@ -129,15 +155,15 @@ html,
padding: 12px 16px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
background: transparent;
color: #8b949e;
color: var(--text-secondary);
font-weight: 500;
position: relative;
border: none;
letter-spacing: 0.01em;
}

.sidebar-btn:hover {
background: rgba(0, 188, 212, 0.08);
background: var(--bg-hover);
color: #00bcd4;
transform: none;
border-radius: 10px;
Expand Down
25 changes: 18 additions & 7 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useState } from "react";
import { CssBaseline, ThemeProvider, Alert } from "@mui/material";
import { CssBaseline, ThemeProvider as MuiThemeProvider, Alert } from "@mui/material";
import {
CozyTypography,
CozyToolContainer,
} from "./components/StyledComponents";
import { cozyTheme } from "./theme/cozyTheme";
import { ThemeProvider, useTheme } from "./hooks/useTheme";
import HashingTool from "./components/HashingTool";
import FileScanTool from "./components/FileScanTool";
import YaraScanner from "./components/YaraScanner";
Expand All @@ -15,8 +15,8 @@ import { useLocalStorage } from "./hooks/useApi";
import { NAVIGATION_TABS, MESSAGES } from "./constants";
import "./App.css";

// Use cozy cottage theme
const theme = cozyTheme;
// Use dynamic theme
// const theme = cozyTheme; // Removed - now using theme from context

// error boundary component
const ErrorFallback = ({ error, resetError }) => (
Expand All @@ -43,8 +43,10 @@ const ErrorFallback = ({ error, resetError }) => (
</CozyToolContainer>
);

// main app component
function App() {
// Main app content component (needs to be inside ThemeProvider)
const AppContent = () => {
const { theme } = useTheme();

// use localStorage to persist the active tab
const [activeTab, setActiveTab] = useLocalStorage(
"cyberSafeActiveTab",
Expand Down Expand Up @@ -85,7 +87,7 @@ function App() {
};

return (
<ThemeProvider theme={theme}>
<MuiThemeProvider theme={theme}>
<CssBaseline />
<div className="main-bg">
<FloatingParticles />
Expand All @@ -96,6 +98,15 @@ function App() {
</main>
</div>
</div>
</MuiThemeProvider>
);
};

// Main app component
function App() {
return (
<ThemeProvider>
<AppContent />
</ThemeProvider>
);
}
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
CozySidebarButton,
} from "./StyledComponents";
import { SIDEBAR_TOOLS, SIDEBAR_RESOURCES } from "../constants";
import ThemeSwitcher from "./ThemeSwitcher";

const Sidebar = ({ activeTab, onTabChange }) => {
const handleResourceClick = (url) => {
Expand All @@ -18,7 +19,10 @@ const Sidebar = ({ activeTab, onTabChange }) => {
role="navigation"
aria-label="Main navigation"
>
<CozyBrandTitle component="h1">CyberSafe</CozyBrandTitle>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "20px" }}>
<CozyBrandTitle component="h1">CyberSafe</CozyBrandTitle>
<ThemeSwitcher />
</div>

<div className="sidebar-section">
<CozySidebarSectionTitle component="h2">TOOLS</CozySidebarSectionTitle>
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/components/ThemeSwitcher.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { IconButton, Tooltip } from "@mui/material";
import { LightMode, DarkMode } from "@mui/icons-material";
import { useTheme } from "../hooks/useTheme";

const ThemeSwitcher = () => {
const { themeMode, toggleTheme } = useTheme();

return (
<Tooltip title={`Switch to ${themeMode === "dark" ? "light" : "dark"} mode`}>
<IconButton
onClick={toggleTheme}
sx={{
color: "primary.main",
backgroundColor: "transparent",
border: "1px solid",
borderColor: "primary.main",
borderRadius: "8px",
padding: "8px",
transition: "all 0.2s ease",
"&:hover": {
backgroundColor: "action.hover",
transform: "scale(1.05)",
},
}}
aria-label={`Switch to ${themeMode === "dark" ? "light" : "dark"} mode`}
>
{themeMode === "dark" ? (
<LightMode sx={{ fontSize: 20 }} />
) : (
<DarkMode sx={{ fontSize: 20 }} />
)}
</IconButton>
</Tooltip>
);
};

export default ThemeSwitcher;
65 changes: 65 additions & 0 deletions frontend/src/hooks/useTheme.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { createContext, useContext, useState, useEffect } from "react";
import { createCozyTheme } from "../theme/cozyTheme";

// Create theme context
const ThemeContext = createContext();

// Theme provider component
export const ThemeProvider = ({ children }) => {
// Initialize theme from localStorage or default to dark
const [themeMode, setThemeMode] = useState(() => {
try {
const savedTheme = localStorage.getItem("cyberSafeTheme");
const initialTheme = savedTheme || "dark";
// Set the initial data-theme attribute
document.documentElement.setAttribute("data-theme", initialTheme);
return initialTheme;
} catch (error) {
console.error("Error reading theme from localStorage:", error);
document.documentElement.setAttribute("data-theme", "dark");
return "dark";
}
});

// Create theme instance based on current mode
const theme = createCozyTheme(themeMode);

// Toggle theme function
const toggleTheme = () => {
const newMode = themeMode === "dark" ? "light" : "dark";
setThemeMode(newMode);
};

// Save theme to localStorage and update document attribute when it changes
useEffect(() => {
try {
localStorage.setItem("cyberSafeTheme", themeMode);
// Update the document's data-theme attribute for CSS custom properties
document.documentElement.setAttribute("data-theme", themeMode);
} catch (error) {
console.error("Error saving theme to localStorage:", error);
}
}, [themeMode]);

const value = {
themeMode,
theme,
toggleTheme,
isDarkMode: themeMode === "dark",
};

return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
};

// Custom hook to use theme
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
};
Loading