feat: tools by category

This commit is contained in:
Ibrahima G. Coulibaly
2024-06-25 09:35:44 +01:00
parent 2b8dcdbd64
commit 429ca09686
8 changed files with 239 additions and 166 deletions

BIN
src/assets/tools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

100
src/components/Hero.tsx Normal file
View File

@@ -0,0 +1,100 @@
import { Autocomplete, Box, Stack, TextField } from '@mui/material';
import Typography from '@mui/material/Typography';
import SearchIcon from '@mui/icons-material/Search';
import Grid from '@mui/material/Grid';
import { useState } from 'react';
import { DefinedTool } from '@tools/defineTool';
import { filterTools, tools } from '@tools/index';
import { useNavigate } from 'react-router-dom';
const exampleTools: { label: string; url: string }[] = [
{
label: 'Create a transparent image',
url: '/png/create-transparent'
},
{ label: 'Convert text to morse code', url: '/string/to-morse' },
{ label: 'Change GIF speed', url: '' },
{ label: 'Pick a random item', url: '' },
{ label: 'Find and replace text', url: '' },
{ label: 'Convert emoji to image', url: '' },
{ label: 'Split a string', url: '/string/split' },
{ label: 'Calculate number sum', url: '/number/sum' },
{ label: 'Pixelate an image', url: '' }
];
export default function Hero() {
const [inputValue, setInputValue] = useState<string>('');
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
const navigate = useNavigate();
const handleInputChange = (
event: React.ChangeEvent<{}>,
newInputValue: string
) => {
setInputValue(newInputValue);
setFilteredTools(filterTools(tools, newInputValue));
};
return (
<Box width={'60%'}>
<Stack mb={1} direction={'row'} spacing={1}>
<Typography fontSize={30}>Transform Your Workflow with </Typography>
<Typography fontSize={30} color={'primary'}>
Omni Tools
</Typography>
</Stack>
<Typography fontSize={20} mb={2}>
Boost your productivity with Omni Tools, the ultimate toolkit for
getting things done quickly! Access thousands of user-friendly utilities
for editing images, text, lists, and data, all directly from your
browser.
</Typography>
<Autocomplete
sx={{ mb: 2 }}
autoHighlight
options={filteredTools}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
fullWidth
placeholder={'Search all tools'}
sx={{ borderRadius: 2 }}
InputProps={{
...params.InputProps,
endAdornment: <SearchIcon />
}}
onChange={(event) => handleInputChange(event, event.target.value)}
/>
)}
renderOption={(props, option) => (
<Box component="li" {...props} onClick={() => navigate(option.path)}>
<Box>
<Typography fontWeight={'bold'}>{option.name}</Typography>
<Typography fontSize={12}>{option.shortDescription}</Typography>
</Box>
</Box>
)}
/>
<Grid container spacing={2} mt={2}>
{exampleTools.map((tool) => (
<Grid onClick={() => navigate(tool.url)} item xs={4} key={tool.label}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
padding: 1,
borderRadius: 3,
borderColor: 'grey',
borderStyle: 'solid',
cursor: 'pointer'
}}
>
<Typography>{tool.label}</Typography>
</Box>
</Grid>
))}
</Grid>
</Box>
);
}

View File

@@ -5,6 +5,7 @@ import ToolHeader from './ToolHeader';
import Separator from '@tools/Separator';
import AllTools from './allTools/AllTools';
import { getToolsByCategory } from '@tools/index';
import { capitalizeFirstLetter } from '../utils/string';
export default function ToolLayout({
children,
@@ -43,7 +44,10 @@ export default function ToolLayout({
<ToolHeader title={title} description={description} image={image} />
{children}
<Separator backgroundColor="#5581b5" margin="50px" />
<AllTools title="All Text Tools" toolCards={otherCategoryTools} />
<AllTools
title={`All ${capitalizeFirstLetter(type)} tools`}
toolCards={otherCategoryTools}
/>
</Box>
</Box>
);

View File

@@ -3,12 +3,17 @@ import { Navigate } from 'react-router-dom';
import { lazy } from 'react';
const Home = lazy(() => import('../pages/home'));
const ToolsByCategory = lazy(() => import('../pages/tools-by-category'));
const routes: RouteObject[] = [
{
path: '/',
element: <Home />
},
{
path: '/categories/:categoryName',
element: <ToolsByCategory />
},
{
path: '*',
element: <Navigate to="404" />

View File

@@ -1,45 +1,14 @@
import {
Autocomplete,
Box,
Card,
CardContent,
Stack,
TextField
} from '@mui/material';
import { Box, Card, CardContent, Stack } from '@mui/material';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import SearchIcon from '@mui/icons-material/Search';
import { Link, useNavigate } from 'react-router-dom';
import { filterTools, getToolsByCategory, tools } from '../../tools';
import { useState } from 'react';
import { DefinedTool } from '@tools/defineTool';
import { getToolsByCategory } from '../../tools';
import Button from '@mui/material/Button';
import Hero from 'components/Hero';
const exampleTools: { label: string; url: string }[] = [
{
label: 'Create a transparent image',
url: '/png/create-transparent'
},
{ label: 'Convert text to morse code', url: '/string/to-morse' },
{ label: 'Change GIF speed', url: '' },
{ label: 'Pick a random item', url: '' },
{ label: 'Find and replace text', url: '' },
{ label: 'Convert emoji to image', url: '' },
{ label: 'Split a string', url: '/string/split' },
{ label: 'Calculate number sum', url: '/number/sum' },
{ label: 'Pixelate an image', url: '' }
];
export default function Home() {
const navigate = useNavigate();
const [inputValue, setInputValue] = useState<string>('');
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
const handleInputChange = (
event: React.ChangeEvent<{}>,
newInputValue: string
) => {
setInputValue(newInputValue);
setFilteredTools(filterTools(tools, newInputValue));
};
return (
<Box
padding={5}
@@ -49,108 +18,38 @@ export default function Home() {
justifyContent={'center'}
width={'100%'}
>
<Box width={'60%'}>
<Stack mb={1} direction={'row'} spacing={1}>
<Typography fontSize={30}>Transform Your Workflow with </Typography>
<Typography fontSize={30} color={'primary'}>
Omni Tools
</Typography>
</Stack>
<Typography fontSize={20} mb={2}>
Boost your productivity with Omni Tools, the ultimate toolkit for
getting things done quickly! Access thousands of user-friendly
utilities for editing images, text, lists, and data, all directly from
your browser.
</Typography>
<Autocomplete
sx={{ mb: 2 }}
autoHighlight
options={filteredTools}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
fullWidth
placeholder={'Search all tools'}
sx={{ borderRadius: 2 }}
InputProps={{
...params.InputProps,
endAdornment: <SearchIcon />
}}
onChange={(event) => handleInputChange(event, event.target.value)}
/>
)}
renderOption={(props, option) => (
<Box
component="li"
{...props}
onClick={() => navigate(option.path)}
>
<Box>
<Typography fontWeight={'bold'}>{option.name}</Typography>
<Typography fontSize={12}>{option.shortDescription}</Typography>
</Box>
</Box>
)}
/>
<Grid container spacing={2} mt={2}>
{exampleTools.map((tool) => (
<Grid
onClick={() => navigate(tool.url)}
item
xs={4}
key={tool.label}
>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
padding: 1,
borderRadius: 3,
borderColor: 'grey',
borderStyle: 'solid',
cursor: 'pointer'
}}
>
<Typography>{tool.label}</Typography>
</Box>
</Grid>
))}
</Grid>
<Grid container mt={2} spacing={2}>
{getToolsByCategory().map((category) => (
<Grid key={category.type} item xs={6}>
<Card>
<CardContent>
<Link
style={{ fontSize: 20 }}
to={'/categories/' + category.type}
>
{category.title}
</Link>
<Typography sx={{ mt: 2 }}>{category.description}</Typography>
<Stack
mt={2}
direction={'row'}
justifyContent={'space-between'}
>
<Button
variant={'contained'}
>{`See all ${category.title}`}</Button>
<Button
onClick={() => navigate(category.example.path)}
variant={'outlined'}
>{`Try ${category.example.title}`}</Button>
</Stack>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Box>
<Hero />
<Grid width={'80%'} container mt={2} spacing={2}>
{getToolsByCategory().map((category) => (
<Grid key={category.type} item xs={6}>
<Card>
<CardContent>
<Link
style={{ fontSize: 20 }}
to={'/categories/' + category.type}
>
{category.title}
</Link>
<Typography sx={{ mt: 2 }}>{category.description}</Typography>
<Stack
mt={2}
direction={'row'}
justifyContent={'space-between'}
>
<Button
onClick={() => navigate('/categories/' + category.type)}
variant={'contained'}
>{`See all ${category.title}`}</Button>
<Button
onClick={() => navigate(category.example.path)}
variant={'outlined'}
>{`Try ${category.example.title}`}</Button>
</Stack>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Box>
);
}

View File

@@ -0,0 +1,74 @@
import {
Box,
Card,
CardContent,
Divider,
Stack,
useTheme
} from '@mui/material';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { getToolsByCategory, tools } from '../../tools';
import Button from '@mui/material/Button';
import Hero from 'components/Hero';
import AllTools from '../../components/allTools/AllTools';
import { capitalizeFirstLetter } from '../../utils/string';
import toolsPng from '@assets/tools.png';
export default function Home() {
const navigate = useNavigate();
const theme = useTheme();
const { categoryName } = useParams();
return (
<Box>
<Box
padding={5}
display={'flex'}
flexDirection={'column'}
alignItems={'center'}
justifyContent={'center'}
width={'100%'}
>
<Hero />
</Box>
<Divider sx={{ borderColor: theme.palette.primary.main }} />
<Box width={'100%'} mt={3} ml={7} padding={3}>
<Typography
fontSize={22}
color={theme.palette.primary.main}
>{`All ${capitalizeFirstLetter(categoryName)} Tools`}</Typography>
<Grid container spacing={2} mt={2}>
{getToolsByCategory()
.find(({ type }) => type === categoryName)
?.tools?.map((tool) => (
<Grid item xs={12} md={4} key={tool.path}>
<Stack
sx={{
cursor: 'pointer',
'&:hover': {
backgroundColor: theme.palette.background.default // Change this to your desired hover color
}
}}
onClick={() => navigate('/' + tool.path)}
direction={'row'}
spacing={2}
padding={2}
border={1}
borderRadius={2}
>
<img width={100} src={tool.image ?? toolsPng} />
<Box>
<Link to={'/' + tool.path}>{tool.name}</Link>
<Typography sx={{ mt: 2 }}>
{tool.shortDescription}
</Typography>
</Box>
</Stack>
</Grid>
))}
</Grid>
</Box>
</Box>
);
}

View File

@@ -1,3 +1,4 @@
export function capitalizeFirstLetter(string: string) {
export function capitalizeFirstLetter(string: string | undefined) {
if (!string) return '';
return string.charAt(0).toUpperCase() + string.slice(1);
}