diff --git a/src/page/kategori/DetailKategori.jsx b/src/page/kategori/DetailKategori.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..cbbabeac2c97fb7f1cdb86e3607a5c9fd3629e4a
--- /dev/null
+++ b/src/page/kategori/DetailKategori.jsx
@@ -0,0 +1,131 @@
+import React, { useCallback, useState } from "react";
+import useFetchSingleData from "../../utils/useFetchSingleData";
+import { css } from "@emotion/core";
+import { ButtonDelete, ErrorDiv } from "../../component/html/html";
+import Table from "../../component/Table";
+import LinkYellow from "../../component/LinkYellow";
+import { navigate } from "@reach/router";
+import { ArrowBack } from "@material-ui/icons";
+import { useAuthContext } from "../../utils/contex";
+
+const DetailKategori = ({ idKategori }) => {
+  const url = `${process.env.REACT_APP_BASE_URL}/categories/${idKategori}/`;
+  const { profile } = useAuthContext();
+  const [errorDelete, setErrorDelete] = useState(false);
+  const [category, error] = useFetchSingleData(url);
+  const deleteProduct = useCallback(() => {
+    fetch(url, {
+      method: "DELETE",
+      headers: {
+        Authorization: `Token ${profile.token}`,
+      },
+    })
+      .then((response) => {
+        if (response.ok) {
+          setErrorDelete(false);
+          navigate("./");
+        } else {
+          throw new Error("Error");
+        }
+      })
+      .catch(() => setErrorDelete(true));
+  }, [url, profile.token, setErrorDelete]);
+  const data = {
+    url: `${process.env.REACT_APP_BASE_URL}/products/`,
+    pageDefault: 1,
+    pageSize: 5,
+    searchDefault: "",
+    pageNeighbours: 1,
+    title: "",
+    keyValuePairs: [
+      ["id", "id"],
+      ["name", "Nama"],
+      ["price", "Harga"],
+      ["stock", "Stok"],
+      ["subcategory_name", "Subcategory"],
+    ],
+    argument: `&subcategory__category=${idKategori}`,
+  };
+  return (
+    <div
+      data-testid="page-detail-kategori"
+      css={css`
+        display: flex;
+        margin: 2rem 3rem 3rem 3rem;
+        flex-direction: column;
+      `}
+    >
+      {error && <ErrorDiv>Error !, Please relogin..</ErrorDiv>}
+      {errorDelete && (
+        <ErrorDiv>
+          Tidak dapat menghapus kategori, mohon periksa apakah ada produk
+          didalam kategori ini.
+        </ErrorDiv>
+      )}
+      <div
+        css={css`
+          display: flex;
+          flex-direction: column;
+        `}
+      >
+        <button
+          css={css`
+            align-self: start;
+            background-color: Transparent;
+            background-repeat: no-repeat;
+            border: none;
+            cursor: pointer;
+            overflow: hidden;
+            outline: none;
+          `}
+          onClick={() => navigate(-1)}
+        >
+          <ArrowBack fontSize="large" />
+        </button>
+        <div
+          css={css`
+            font-size: 2rem;
+            display: flex;
+            flex-direction: row;
+            align-items: baseline;
+          `}
+        >
+          <div
+            css={css`
+              flex-grow: 2;
+            `}
+          >
+            Kategori: {category.name}
+          </div>
+          <div
+            css={css`
+              flex-grow: 1;
+            `}
+          >
+            <LinkYellow to="ubah">EDIT</LinkYellow>
+          </div>
+          <div
+            css={css`
+              flex-grow: 1;
+            `}
+          >
+            <ButtonDelete
+              data-testid="button-delete-category"
+              onClick={deleteProduct}
+            >
+              HAPUS
+            </ButtonDelete>
+          </div>
+        </div>
+        <div
+          css={css`
+            font-size: 1.5rem;
+          `}
+        ></div>
+      </div>
+      <Table {...data} />
+    </div>
+  );
+};
+
+export default DetailKategori;
diff --git a/src/page/kategori/EditKategori.jsx b/src/page/kategori/EditKategori.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fc9945328a16526d446aa524645e4c3e83e4b3af
--- /dev/null
+++ b/src/page/kategori/EditKategori.jsx
@@ -0,0 +1,109 @@
+import React, { useState } from "react";
+import useFetchSingleData from "../../utils/useFetchSingleData";
+import { css } from "@emotion/core";
+import { ErrorDiv } from "../../component/html/html";
+import FormKategori from "./FormKategori";
+import { useAuthContext } from "../../utils/contex";
+import { ArrowBack, ErrorOutline } from "@material-ui/icons";
+import { navigate } from "@reach/router";
+
+const EditKategori = ({ idKategori }) => {
+  const url = `${process.env.REACT_APP_BASE_URL}/categories/${idKategori}/`;
+  const { profile } = useAuthContext();
+  const [error, setError] = useState(false);
+  const [initialData, errorState] = useFetchSingleData(url);
+  const onSubmit = (data) => {
+    const formData = new FormData();
+    formData.append("name", data["name"]);
+    if (data["image"].length !== 0) formData.append("image", data["image"][0]);
+    fetch(url, {
+      method: "PATCH",
+      headers: {
+        Authorization: `Token ${profile.token}`,
+      },
+      body: formData,
+    })
+      .then((response) => {
+        if (response.ok) {
+          setError(false);
+          navigate("./");
+        } else {
+          throw new Error("Error");
+        }
+      })
+      .catch(() => setError(true));
+  };
+  if (errorState || Object.keys(initialData).length === 0)
+    return (
+      <div
+        data-testid="waiting-edit-kategori"
+        css={css`
+          display: flex;
+          margin: 2rem 3rem 3rem 3rem;
+          flex-direction: column;
+          font-size: 25px;
+        `}
+      >
+        Fetching data..
+      </div>
+    );
+  return (
+    <div
+      data-testid="edit-kategori"
+      css={css`
+        display: flex;
+        margin: 2rem 3rem 3rem 3rem;
+        flex-direction: column;
+      `}
+    >
+      {error && <ErrorDiv>Error !, Data tidak dapat disimpan</ErrorDiv>}
+      <div
+        css={css`
+          display: flex;
+          flex-direction: row;
+        `}
+      >
+        <button
+          css={css`
+            background-color: Transparent;
+            background-repeat: no-repeat;
+            border: none;
+            cursor: pointer;
+            overflow: hidden;
+            outline: none;
+          `}
+          onClick={() => navigate(-1)}
+        >
+          <ArrowBack fontSize="large" />
+        </button>
+        <div
+          css={css`
+            font-size: 36px;
+          `}
+        >
+          Edit {initialData.name}
+        </div>
+      </div>
+      <div
+        css={css`
+          margin-top: 2.5rem;
+          display: flex;
+        `}
+      >
+        <ErrorOutline style={{ fontSize: 28, color: "FFC80A" }} />
+        <div
+          css={css`
+            font-weight: 600;
+            font-size: 24px;
+            line-height: 29px;
+          `}
+        >
+          Informasi Kategori
+        </div>
+      </div>
+      <FormKategori {...{ onSubmit, initialData }} />
+    </div>
+  );
+};
+
+export default EditKategori;
diff --git a/src/page/kategori/FormKategori.jsx b/src/page/kategori/FormKategori.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fc0eb42f9677a5032ebcf7f6d2880780af8ec7b9
--- /dev/null
+++ b/src/page/kategori/FormKategori.jsx
@@ -0,0 +1,55 @@
+import React from "react";
+import { useForm } from "react-hook-form";
+import {
+  ErrorDiv,
+  RowInput,
+  InputForm,
+  LabelInput,
+  InputSubmitForm,
+} from "../../component/html/html";
+import { css } from "@emotion/core";
+
+const FormKategori = ({ onSubmit, initialData = null }) => {
+  const { register, handleSubmit, errors } = useForm({
+    defaultValues: initialData !== null ? { name: initialData["name"] } : {},
+  });
+  return (
+    <form
+      data-testid="form-category"
+      onSubmit={handleSubmit(onSubmit)}
+      css={css`
+        display: flex;
+        flex-direction: column;
+      `}
+    >
+      <RowInput>
+        <LabelInput htmlFor="name">Nama kategori: </LabelInput>
+        <InputForm
+          data-testid="name-kategori-input"
+          name="name"
+          ref={register({ required: true })}
+        />
+        {errors.name && <ErrorDiv>Nama kategori tidak boleh kosong</ErrorDiv>}
+      </RowInput>
+      {initialData !== null && initialData["image"] != null ? (
+        <img
+          css={css`
+            height: 10rem;
+            object-fit: contain;
+          `}
+          alt={initialData["name"]}
+          src={initialData["image"]}
+        />
+      ) : null}
+      <RowInput>
+        <LabelInput htmlFor="gambar">Foto kategori: </LabelInput>
+        <InputForm type="file" name="image" ref={register} />
+      </RowInput>
+      <RowInput>
+        <InputSubmitForm type="submit" data-testid="submit-category" />
+      </RowInput>
+    </form>
+  );
+};
+
+export default FormKategori;
diff --git a/src/page/kategori/ListKategori.jsx b/src/page/kategori/ListKategori.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..43ff5aa33a10f50d1a9a2c1ff8db90d819ad34bd
--- /dev/null
+++ b/src/page/kategori/ListKategori.jsx
@@ -0,0 +1,52 @@
+import React from "react";
+import Table from "../../component/Table";
+import { css } from "@emotion/core";
+import LinkYellow from "../../component/LinkYellow";
+
+const ListKategori = () => {
+  const data = {
+    url: `${process.env.REACT_APP_BASE_URL}/categories/`,
+    pageDefault: 1,
+    pageSize: 7,
+    searchDefault: "",
+    pageNeighbours: 1,
+    title: "",
+    keyValuePairs: [
+      ["id", "id"],
+      ["name", "Nama"],
+    ],
+    link: "",
+  };
+  return (
+    <div
+      css={css`
+        display: flex;
+        flex-direction: column;
+        margin: 2rem 3rem 3rem 3rem;
+      `}
+    >
+      <div
+        css={css`
+          font-size: 35px;
+        `}
+      >
+        Kelola Kategori
+      </div>
+      <div
+        css={css`
+          width: 35%;
+          display: flex;
+          flex-direction: row;
+          margin-bottom: 2rem;
+          margin-top: 1rem;
+        `}
+      >
+        <LinkYellow to="/kategori/tambah">Tambah</LinkYellow>
+        <LinkYellow to="/kategori">Lihat</LinkYellow>
+      </div>
+      <Table {...data} />
+    </div>
+  );
+};
+
+export default ListKategori;
diff --git a/src/page/kategori/TambahKategori.jsx b/src/page/kategori/TambahKategori.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c5db9033d74103fa9207b37be0172aa7cb669f4b
--- /dev/null
+++ b/src/page/kategori/TambahKategori.jsx
@@ -0,0 +1,72 @@
+import React from "react";
+import { useAuthContext } from "../../utils/contex";
+import { useState } from "react";
+import { css } from "@emotion/core";
+import { ErrorDiv } from "../../component/html/html";
+import FormKategori from "./FormKategori";
+import LinkYellow from "../../component/LinkYellow";
+import { navigate } from "@reach/router";
+
+const TambahKategori = () => {
+  const url = `${process.env.REACT_APP_BASE_URL}/categories/`;
+  const [error, setError] = useState(false);
+  const { profile } = useAuthContext();
+  const onSubmit = (data) => {
+    const formData = new FormData();
+    formData.append("name", data["name"]);
+    if (data["image"].length !== 0) formData.append("image", data["image"][0]);
+    fetch(url, {
+      method: "POST",
+      headers: {
+        Authorization: `Token ${profile.token}`,
+      },
+      body: formData,
+    })
+      .then((response) => {
+        if (response.ok) {
+          setError(false);
+          navigate("/kategori");
+        } else {
+          throw new Error("Error");
+        }
+      })
+      .catch(() => setError(true));
+  };
+  return (
+    <div
+      data-testid="tambah-kategori"
+      css={css`
+        display: flex;
+        margin: 2rem 3rem 3rem 3rem;
+        flex-direction: column;
+      `}
+    >
+      <div>
+        {error && <ErrorDiv>Error !, Data tidak dapat disimpan</ErrorDiv>}
+      </div>
+
+      <div
+        css={css`
+          font-size: 35px;
+        `}
+      >
+        Tambah Kategori
+      </div>
+      <div
+        css={css`
+          width: 35%;
+          display: flex;
+          flex-direction: row;
+          margin-bottom: 2rem;
+          margin-top: 1rem;
+        `}
+      >
+        <LinkYellow to="/kategori/tambah">Tambah</LinkYellow>
+        <LinkYellow to="/kategori">Lihat</LinkYellow>
+      </div>
+      <FormKategori {...{ onSubmit }} />
+    </div>
+  );
+};
+
+export default TambahKategori;
diff --git a/src/routes.jsx b/src/routes.jsx
index 1fe56810fccc1c4373379c0078d8f4abdc0107d6..814aa365ad0b04f75d71750cbf8420b2a7e58c7e 100644
--- a/src/routes.jsx
+++ b/src/routes.jsx
@@ -10,6 +10,10 @@ import ListSubkategori from "./page/subkategori/ListSubkategori";
 import DetailSubkategori from "./page/subkategori/DetailSubkategori";
 import TambahSubkategori from "./page/subkategori/TambahSubkategori";
 import EditSubkategori from "./page/subkategori/EditSubkategori";
+import ListKategori from "./page/kategori/ListKategori";
+import DetailKategori from "./page/kategori/DetailKategori";
+import TambahKategori from "./page/kategori/TambahKategori";
+import EditKategori from "./page/kategori/EditKategori";
 
 const Placeholder = ({ children }) => children;
 
@@ -34,6 +38,13 @@ const Routes = () => {
         />
       </Placeholder>
 
+      <Placeholder path="kategori">
+        <ProtectedRoute path="/" component={ListKategori} />
+        <ProtectedRoute path="tambah" component={TambahKategori} />
+        <ProtectedRoute path=":idKategori" component={DetailKategori} />
+        <ProtectedRoute path=":idKategori/ubah" component={EditKategori} />
+      </Placeholder>
+
       <UnauthenticatedRoute path="/" component={Login} />
     </Router>
   );