diff --git a/README.md b/README.md
index a140263..038a7a5 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ A web clone of [DevToys](https://github.com/veler/DevToys)
## Todo
- [x] Add site layout
-- [ ] Add all tools page mock
+- [x] Add all tools page mock
- [ ] Implement tools
- [ ] Converters
- [ ] Encoders / Decoders
diff --git a/package.json b/package.json
index dd9a0a9..62c8d26 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"@mui/icons-material": "^5.5.1",
"@mui/material": "^5.5.1",
"fast-deep-equal": "^3.1.3",
+ "fuse.js": "^6.5.3",
"next": "12.1.0",
"react": "17.0.2",
"react-dom": "17.0.2",
diff --git a/src/components/common/Main.tsx b/src/components/common/Main.tsx
new file mode 100644
index 0000000..bd56341
--- /dev/null
+++ b/src/components/common/Main.tsx
@@ -0,0 +1,25 @@
+import { Box, css, Stack, Theme, Typography } from "@mui/material";
+import { memo, PropsWithChildren } from "react";
+
+type Props = PropsWithChildren<{
+ title: string;
+}>;
+
+const mainTitle = (theme: Theme) => css`
+ font-size: ${theme.typography.fontSize * 3}px;
+ font-weight: 400;
+ margin-bottom: ${theme.spacing(2)};
+`;
+
+const StyledComponent = ({ children, title }: Props) => (
+
+
+ {title}
+
+ {children}
+
+);
+
+export const Component = memo(StyledComponent);
+
+export default Component;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
new file mode 100644
index 0000000..c5caf67
--- /dev/null
+++ b/src/components/common/index.ts
@@ -0,0 +1,3 @@
+import Main from "./Main";
+
+export { Main };
diff --git a/src/components/pages/home/Card.tsx b/src/components/pages/home/Card.tsx
new file mode 100644
index 0000000..f6a8ed2
--- /dev/null
+++ b/src/components/pages/home/Card.tsx
@@ -0,0 +1,96 @@
+import {
+ Box,
+ Card,
+ CardActionArea,
+ CardContent,
+ CardMedia,
+ css,
+ Theme,
+ Tooltip,
+ Typography,
+} from "@mui/material";
+import NextLink, { LinkProps } from "next/link";
+import { memo, ReactNode } from "react";
+
+type Props = {
+ icon: ReactNode;
+ title: string;
+ description: string;
+ disabled?: boolean;
+} & Pick;
+
+const cardStyle = css`
+ text-align: left;
+ width: 160px;
+ height: 300px;
+`;
+
+const cardActionArea = css`
+ height: 100%;
+`;
+
+const cardMedia = (theme: Theme) => css`
+ text-align: center;
+ padding: ${theme.spacing(4)};
+`;
+
+const cardMediaIconWrapper = (theme: Theme) => css`
+ padding: ${theme.spacing(2)};
+ height: 96px;
+ > * {
+ width: 100%;
+ height: 100%;
+ }
+`;
+
+const cardContent = css`
+ padding-top: 0;
+`;
+
+const cardTitle = css`
+ font-size: 14px;
+ font-weight: 500;
+`;
+
+const cardDescription = (theme: Theme) => css`
+ font-size: 12px;
+ color: ${theme.palette.grey[600]};
+`;
+
+const StyledComponent = ({ icon, title, description, href, disabled }: Props) => {
+ const card = (
+
+
+
+
+ {icon}
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+
+ );
+
+ return disabled ? (
+
+ {card}
+
+ ) : (
+ card
+ );
+};
+
+export const Component = memo(StyledComponent);
+
+export default Component;
diff --git a/src/components/pages/home/Content.tsx b/src/components/pages/home/Content.tsx
new file mode 100644
index 0000000..b41e6f4
--- /dev/null
+++ b/src/components/pages/home/Content.tsx
@@ -0,0 +1,158 @@
+import CodeIcon from "@mui/icons-material/Code";
+import DataObjectIcon from "@mui/icons-material/DataObject";
+import DragHandleIcon from "@mui/icons-material/DragHandle";
+import FilterIcon from "@mui/icons-material/Filter";
+import FingerprintIcon from "@mui/icons-material/Fingerprint";
+import KeyIcon from "@mui/icons-material/Key";
+import LinkIcon from "@mui/icons-material/Link";
+import NumbersIcon from "@mui/icons-material/Numbers";
+import TextIncreaseIcon from "@mui/icons-material/TextIncrease";
+import TransformIcon from "@mui/icons-material/Transform";
+import { Grid } from "@mui/material";
+import Fuse from "fuse.js";
+import { memo } from "react";
+import { selector, useRecoilValue } from "recoil";
+
+import { Main } from "@/components/common";
+import { searchTextState } from "@/components/layout/states";
+import { pagesPath } from "@/libs/$path";
+
+import Card from "./Card";
+
+type Props = {
+ filteredTools: typeof tools;
+};
+
+const tools = [
+ {
+ icon: ,
+ title: "Json <> Yaml Converter",
+ description: "Convert Json data to Yaml and vice versa",
+ keywords: "json yaml converter",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "Number Base Converter",
+ description: "Convert numbers from one base to another",
+ keywords: "number base converter",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "HTML Encoder / Decoder",
+ description:
+ "Encode or decode all the applicable characters to their corresponding HTML entities",
+ keywords: "html encoder escaper decocder unescaper",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "URL Encoder / Decoder",
+ description:
+ "Encode or decode all the applicable characters to their corresponding URL entities",
+ keywords: "url encoder escaper decocder unescaper",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "Base 64 Encoder / Decoder",
+ description: "Encode and decode Base64 data",
+ keywords: "base64 encoder decocder",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "JWT Decoder",
+ description: "Decode a JWT header, payload and signature",
+ keywords: "jwt json web token decocder",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "JSON Formatter",
+ description: "Indent or minify JSON data",
+ keywords: "json formatter",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "Hash Generator",
+ description: "Calculate MD5, SHA1, SHA256 and SHA512 hash from text data",
+ keywords: "hash generator md5 sha1 sha256 sha512",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "UUID Generator",
+ description: "Generate UUIDs version 1 and 4",
+ keywords: "guid uuid1 uuid4 generator",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "Regex Tester",
+ description: "Validate and test regular expressions",
+ keywords: "regular expression regex validator tester",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+ {
+ icon: ,
+ title: "PNG / JPEG Compressor",
+ description: "Lossless PNG and JPEG optimizer",
+ keywords: "png jpeg compressor optimizer image",
+ href: pagesPath.$url(),
+ disabled: true,
+ },
+];
+
+const StyledComponent = ({ filteredTools }: Props) => (
+
+
+ {filteredTools.map(({ icon, title, description, href, disabled }) => (
+
+
+
+ ))}
+
+
+);
+
+export const Component = memo(StyledComponent);
+
+const filteredToolsState = selector({
+ key: "filteredToolsState",
+ get: ({ get }) => {
+ const searchText = get(searchTextState).trim();
+
+ if (searchText === "") {
+ return { filteredTools: tools };
+ }
+
+ const searchWords = searchText.split(" ").map(word => ({ keywords: word }));
+
+ const fuse = new Fuse(tools, { keys: ["keywords"], threshold: 0.5 });
+ const result = fuse.search({ $and: searchWords });
+ const filteredTools = result.map(({ item }) => item);
+
+ return { filteredTools };
+ },
+});
+
+const Container = () => {
+ const { filteredTools } = useRecoilValue(filteredToolsState);
+
+ return ;
+};
+
+export default Container;
diff --git a/src/components/pages/home/index.ts b/src/components/pages/home/index.ts
new file mode 100644
index 0000000..ae1b8af
--- /dev/null
+++ b/src/components/pages/home/index.ts
@@ -0,0 +1,3 @@
+import Content from "./Content";
+
+export { Content };
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 510d058..6c75db8 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,5 +1,7 @@
import type { NextPage } from "next";
-const Page: NextPage = () => hello
;
+import { Content } from "@/components/pages/home";
+
+const Page: NextPage = Content;
export default Page;
diff --git a/yarn.lock b/yarn.lock
index 3ae8730..dc3c809 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1287,6 +1287,11 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
+fuse.js@^6.5.3:
+ version "6.5.3"
+ resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.5.3.tgz#7446c0acbc4ab0ab36fa602e97499bdb69452b93"
+ integrity sha512-sA5etGE7yD/pOqivZRBvUBd/NaL2sjAu6QuSaFoe1H2BrJSkH/T/UXAJ8CdXdw7DvY3Hs8CXKYkDWX7RiP5KOg==
+
get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"