diff --git a/src/components/App.tsx b/src/components/App.tsx
index b71abd8..a578cc8 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -12,6 +12,7 @@ import { darkTheme, lightTheme } from '../config/muiConfig';
import ScrollToTopButton from './ScrollToTopButton';
import { I18nextProvider } from 'react-i18next';
import i18n from '../i18n';
+import { UserTypeFilterProvider } from 'providers/UserTypeFilterProvider';
export type Mode = 'dark' | 'light' | 'system';
@@ -57,18 +58,20 @@ function App() {
}}
>
-
- {
- setMode((prev) => nextMode(prev));
- localStorage.setItem('theme', nextMode(mode));
- }}
- />
- }>
-
-
-
+
+
+ {
+ setMode((prev) => nextMode(prev));
+ localStorage.setItem('theme', nextMode(mode));
+ }}
+ />
+ }>
+
+
+
+
diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx
index 0b4d2d3..0404216 100644
--- a/src/components/Hero.tsx
+++ b/src/components/Hero.tsx
@@ -17,7 +17,6 @@ import { filterTools, tools } from '@tools/index';
import { useNavigate } from 'react-router-dom';
import { Icon } from '@iconify/react';
import { getToolCategoryTitle } from '@utils/string';
-import UserTypeFilter, { useUserTypeFilter } from './UserTypeFilter';
import { useTranslation } from 'react-i18next';
import { FullI18nKey, validNamespaces } from '../i18n';
import {
@@ -26,6 +25,7 @@ import {
toggleBookmarked
} from '@utils/bookmark';
import IconButton from '@mui/material/IconButton';
+import { useUserTypeFilter } from '../providers/UserTypeFilterProvider';
const GroupHeader = styled('div')(({ theme }) => ({
position: 'sticky',
@@ -51,7 +51,7 @@ export default function Hero() {
const { t } = useTranslation(validNamespaces);
const [inputValue, setInputValue] = useState('');
const theme = useTheme();
- const { selectedUserTypes, setSelectedUserTypes } = useUserTypeFilter();
+ const { selectedUserTypes } = useUserTypeFilter();
const [filteredTools, setFilteredTools] = useState(tools);
const [bookmarkedToolPaths, setBookmarkedToolPaths] = useState(
getBookmarkedToolPaths()
@@ -105,11 +105,6 @@ export default function Hero() {
setFilteredTools(filterTools(tools, newInputValue, selectedUserTypes, t));
};
- const handleUserTypesChange = (userTypes: string[]) => {
- setSelectedUserTypes(userTypes as any);
- setFilteredTools(filterTools(tools, inputValue, userTypes as any, t));
- };
-
const toolsMap = new Map();
for (const tool of filteredTools) {
toolsMap.set(tool.path, {
@@ -225,11 +220,6 @@ export default function Hero() {
/>
-
)}
onChange={(event, newValue) => {
diff --git a/src/components/UserTypeFilter.tsx b/src/components/UserTypeFilter.tsx
index 0eea9b8..6ae2ac7 100644
--- a/src/components/UserTypeFilter.tsx
+++ b/src/components/UserTypeFilter.tsx
@@ -2,66 +2,43 @@ import React, { useState, useEffect } from 'react';
import { Box, Chip, Typography } from '@mui/material';
import { UserType } from '@tools/defineTool';
-const userTypes: UserType[] = [
- 'General Users',
- 'Developers',
- 'Designers',
- 'CyberSec'
-];
+const userTypes: UserType[] = ['General Users', 'Developers', 'CyberSec'];
interface UserTypeFilterProps {
selectedUserTypes: UserType[];
onUserTypesChange: (userTypes: UserType[]) => void;
- label?: string;
}
export default function UserTypeFilter({
selectedUserTypes,
- onUserTypesChange,
- label = 'Filter by User Type'
+ onUserTypesChange
}: UserTypeFilterProps) {
return (
-
-
- {label}
-
-
- {userTypes.map((userType) => (
- {
- const isSelected = selectedUserTypes.includes(userType);
- const newUserTypes = isSelected
- ? selectedUserTypes.filter((ut) => ut !== userType)
- : [...selectedUserTypes, userType];
- onUserTypesChange(newUserTypes);
- }}
- sx={{ cursor: 'pointer' }}
- />
- ))}
-
+
+ {userTypes.map((userType) => (
+ {
+ const isSelected = selectedUserTypes.includes(userType);
+ const newUserTypes = isSelected
+ ? selectedUserTypes.filter((ut) => ut !== userType)
+ : [...selectedUserTypes, userType];
+ onUserTypesChange(newUserTypes);
+ }}
+ sx={{ cursor: 'pointer' }}
+ />
+ ))}
);
}
-
-// Hook to manage user type filter state with localStorage
-export function useUserTypeFilter() {
- const [selectedUserTypes, setSelectedUserTypes] = useState(() => {
- const saved = localStorage.getItem('selectedUserTypes');
- return saved ? JSON.parse(saved) : [];
- });
-
- useEffect(() => {
- localStorage.setItem(
- 'selectedUserTypes',
- JSON.stringify(selectedUserTypes)
- );
- }, [selectedUserTypes]);
-
- return {
- selectedUserTypes,
- setSelectedUserTypes
- };
-}
diff --git a/src/pages/home/Categories.tsx b/src/pages/home/Categories.tsx
index 99d943d..d5f7e27 100644
--- a/src/pages/home/Categories.tsx
+++ b/src/pages/home/Categories.tsx
@@ -7,10 +7,9 @@ import Button from '@mui/material/Button';
import { useState } from 'react';
import { categoriesColors } from 'config/uiConfig';
import { Icon } from '@iconify/react';
-import { useUserTypeFilter } from '@components/UserTypeFilter';
import { useTranslation } from 'react-i18next';
import { getI18nNamespaceFromToolCategory } from '@utils/string';
-import { validNamespaces } from '../../i18n';
+import { useUserTypeFilter } from '../../providers/UserTypeFilterProvider';
type ArrayElement =
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
@@ -119,7 +118,7 @@ export default function Categories() {
const categories = getToolsByCategory(selectedUserTypes, t);
return (
-
+
{categories.map((category, index) => (
))}
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
index 0d2fd92..c65d24f 100644
--- a/src/pages/home/index.tsx
+++ b/src/pages/home/index.tsx
@@ -2,17 +2,12 @@ import { Box, useTheme } from '@mui/material';
import Hero from 'components/Hero';
import Categories from './Categories';
import { Helmet } from 'react-helmet';
-import UserTypeFilter, { useUserTypeFilter } from 'components/UserTypeFilter';
-import { UserType } from '@tools/defineTool';
+import { useUserTypeFilter } from 'providers/UserTypeFilterProvider';
+import UserTypeFilter from '@components/UserTypeFilter';
export default function Home() {
const theme = useTheme();
const { selectedUserTypes, setSelectedUserTypes } = useUserTypeFilter();
-
- const handleUserTypesChange = (userTypes: UserType[]) => {
- setSelectedUserTypes(userTypes);
- };
-
return (
-
+
+
+
);
diff --git a/src/pages/tools-by-category/index.tsx b/src/pages/tools-by-category/index.tsx
index 24fb833..dae4f69 100644
--- a/src/pages/tools-by-category/index.tsx
+++ b/src/pages/tools-by-category/index.tsx
@@ -22,9 +22,10 @@ import IconButton from '@mui/material/IconButton';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import SearchIcon from '@mui/icons-material/Search';
import { Helmet } from 'react-helmet';
-import UserTypeFilter, { useUserTypeFilter } from '@components/UserTypeFilter';
+import UserTypeFilter from '@components/UserTypeFilter';
import { useTranslation } from 'react-i18next';
import { I18nNamespaces, validNamespaces } from '../../i18n';
+import { useUserTypeFilter } from '../../providers/UserTypeFilterProvider';
const StyledLink = styled(Link)(({ theme }) => ({
'&:hover': {
@@ -60,10 +61,6 @@ export default function ToolsByCategory() {
}
}, []);
- const handleUserTypesChange = (userTypes: string[]) => {
- setSelectedUserTypes(userTypes as any);
- };
-
return (
@@ -90,27 +87,32 @@ export default function ToolsByCategory() {
{t('translation:toolLayout.allToolsTitle', { type: rawTitle })}
-
- ,
- sx: {
- borderRadius: 4,
- backgroundColor: 'background.paper',
- maxWidth: 400
- }
- }}
- onChange={(event) => setSearchTerm(event.target.value)}
- />
-
-
+ ,
+ sx: {
+ borderRadius: 4,
+ backgroundColor: 'background.paper',
+ maxWidth: 400
+ }
+ }}
+ onChange={(event) => setSearchTerm(event.target.value)}
+ />
-
+
+
+
+
{categoryTools.map((tool, index) => (
void;
+}
+
+const UserTypeFilterContext = createContext(
+ null
+);
+
+interface UserTypeFilterProviderProps {
+ children: ReactNode;
+}
+
+export function UserTypeFilterProvider({
+ children
+}: UserTypeFilterProviderProps) {
+ const [selectedUserTypes, setSelectedUserTypes] = useState(() => {
+ try {
+ const saved = localStorage.getItem('selectedUserTypes');
+ return saved ? JSON.parse(saved) : [];
+ } catch (error) {
+ console.error(
+ 'Error loading selectedUserTypes from localStorage:',
+ error
+ );
+ return [];
+ }
+ });
+
+ useEffect(() => {
+ try {
+ localStorage.setItem(
+ 'selectedUserTypes',
+ JSON.stringify(selectedUserTypes)
+ );
+ } catch (error) {
+ console.error('Error saving selectedUserTypes to localStorage:', error);
+ }
+ }, [selectedUserTypes]);
+
+ const contextValue = useMemo(
+ () => ({
+ selectedUserTypes,
+ setSelectedUserTypes
+ }),
+ [selectedUserTypes]
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useUserTypeFilter(): UserTypeFilterContextType {
+ const context = useContext(UserTypeFilterContext);
+
+ if (!context) {
+ throw new Error(
+ 'useUserTypeFilter must be used within a UserTypeFilterProvider. ' +
+ 'Make sure your component is wrapped with .'
+ );
+ }
+
+ return context;
+}
diff --git a/src/tools/defineTool.tsx b/src/tools/defineTool.tsx
index 32f64fb..ae60e84 100644
--- a/src/tools/defineTool.tsx
+++ b/src/tools/defineTool.tsx
@@ -4,11 +4,7 @@ import { IconifyIcon } from '@iconify/react';
import { FullI18nKey, validNamespaces } from '../i18n';
import { useTranslation } from 'react-i18next';
-export type UserType =
- | 'General Users'
- | 'Developers'
- | 'Designers'
- | 'CyberSec';
+export type UserType = 'General Users' | 'Developers' | 'CyberSec';
export interface ToolMeta {
path: string;