From 263a58e9a6d4909e020c46b5f055f5756210fc81 Mon Sep 17 00:00:00 2001
From: idrissDouelfiqar <idriss.douelfiqar@marketingconfort.com>
Date: Mon, 5 Aug 2024 20:13:37 +0100
Subject: [PATCH] new job edit job details job

---
 package-lock.json                             | 295 ++++++++++-----
 package.json                                  |  10 +-
 src/app/freelancers/job/[id]/edit/page.tsx    |  41 ++
 src/app/freelancers/job/[id]/page.tsx         |  41 ++
 src/app/freelancers/job/new/page.tsx          |  11 +
 src/app/freelancers/job/page.tsx              |  11 +
 src/app/freelancers/layout.tsx                |  22 ++
 src/app/freelancers/loading.tsx               |   7 +
 src/app/freelancers/page.tsx                  |  11 +
 src/app/layout.tsx                            |  13 +-
 src/hooks/use-tabs.ts                         |  21 ++
 src/routes/paths.ts                           |   8 +
 .../country-select/country-select.tsx         | 152 ++++++++
 src/shared/components/country-select/index.ts |   3 +
 src/shared/components/country-select/utils.ts |  19 +
 .../custom-breadcrumbs/breadcrumb-link.tsx    |  57 +++
 .../custom-breadcrumbs/custom-breadcrumbs.tsx |  89 +++++
 .../components/custom-breadcrumbs/index.ts    |   3 +
 .../components/custom-breadcrumbs/types.ts    |  25 ++
 src/shared/components/editor/classes.ts       |  44 +++
 .../components/code-highlight-block.css       |  82 ++++
 .../components/code-highlight-block.tsx       |  41 ++
 .../editor/components/heading-block.tsx       | 134 +++++++
 .../editor/components/image-block.tsx         |  81 ++++
 .../editor/components/link-block.tsx          | 101 +++++
 .../editor/components/toolbar-item.tsx        |  38 ++
 src/shared/components/editor/editor.tsx       | 149 ++++++++
 src/shared/components/editor/index.ts         |   5 +
 src/shared/components/editor/styles.tsx       | 212 +++++++++++
 src/shared/components/editor/toolbar.tsx      | 238 ++++++++++++
 src/shared/components/editor/types.ts         |  42 +++
 .../empty-content/empty-content.tsx           |  82 ++++
 src/shared/components/empty-content/index.ts  |   1 +
 .../filters-result/filters-block.tsx          |  47 +++
 .../filters-result/filters-result.tsx         |  46 +++
 src/shared/components/filters-result/index.ts |   3 +
 src/shared/components/hook-form/fields.tsx    |  32 ++
 src/shared/components/hook-form/index.ts      |  28 ++
 .../components/hook-form/rhf-autocomplete.tsx |  57 +++
 .../components/hook-form/rhf-checkbox.tsx     | 150 ++++++++
 src/shared/components/hook-form/rhf-code.tsx  |  41 ++
 .../hook-form/rhf-country-select.tsx          |  34 ++
 .../components/hook-form/rhf-date-picker.tsx  |  86 +++++
 .../components/hook-form/rhf-editor.tsx       |  34 ++
 .../components/hook-form/rhf-phone-input.tsx  |  33 ++
 .../components/hook-form/rhf-radio-group.tsx  |  85 +++++
 .../components/hook-form/rhf-rating.tsx       |  48 +++
 .../components/hook-form/rhf-select.tsx       | 183 +++++++++
 .../components/hook-form/rhf-slider.tsx       |  33 ++
 .../components/hook-form/rhf-switch.tsx       | 154 ++++++++
 .../components/hook-form/rhf-upload.tsx       |  90 +++++
 .../components/hook-form/schema-helper.ts     | 127 +++++++
 src/shared/components/image/classes.ts        |   7 +
 src/shared/components/image/image.tsx         | 110 ++++++
 src/shared/components/image/index.ts          |   5 +
 src/shared/components/image/styles.css        |   1 +
 src/shared/components/image/types.ts          |  30 ++
 src/shared/components/markdown/classes.ts     |  12 +
 .../markdown/code-highlight-block.css         |  82 ++++
 src/shared/components/markdown/html-tags.ts   | 172 +++++++++
 .../components/markdown/html-to-markdown.ts   |  62 +++
 src/shared/components/markdown/index.ts       |   3 +
 src/shared/components/markdown/markdown.tsx   |  94 +++++
 src/shared/components/markdown/styles.ts      | 165 ++++++++
 src/shared/components/markdown/types.ts       |   9 +
 src/shared/components/phone-input/index.ts    |   3 +
 src/shared/components/phone-input/list.tsx    | 139 +++++++
 .../components/phone-input/phone-input.tsx    |  55 +++
 src/shared/components/phone-input/types.ts    |  16 +
 src/shared/components/phone-input/utils.ts    |  46 +++
 .../upload/components/placeholder.tsx         |  38 ++
 .../upload/components/preview-multi-file.tsx  | 126 +++++++
 .../upload/components/preview-single-file.tsx |  66 ++++
 .../upload/components/rejection-files.tsx     |  56 +++
 src/shared/components/upload/index.ts         |  13 +
 src/shared/components/upload/types.ts         |  39 ++
 .../components/upload/upload-avatar.tsx       | 132 +++++++
 src/shared/components/upload/upload-box.tsx   |  52 +++
 src/shared/components/upload/upload.tsx       | 121 ++++++
 .../sections/job/job-details-candidates.tsx   | 118 ++++++
 .../sections/job/job-details-content.tsx      | 123 ++++++
 .../sections/job/job-details-toolbar.tsx      | 103 +++++
 .../sections/job/job-filters-result.tsx       |  88 +++++
 src/shared/sections/job/job-filters.tsx       | 257 +++++++++++++
 src/shared/sections/job/job-item.tsx          | 158 ++++++++
 src/shared/sections/job/job-list.tsx          |  69 ++++
 src/shared/sections/job/job-new-edit-form.tsx | 356 ++++++++++++++++++
 src/shared/sections/job/job-search.tsx        |  99 +++++
 src/shared/sections/job/job-sort.tsx          |  63 ++++
 src/shared/sections/job/view/index.ts         |   7 +
 .../sections/job/view/job-create-view.tsx     |  29 ++
 .../sections/job/view/job-details-view.tsx    |  75 ++++
 .../sections/job/view/job-edit-view.tsx       |  35 ++
 .../sections/job/view/job-list-view.tsx       | 202 ++++++++++
 src/shared/types/job.ts                       |  49 +++
 src/utils/format-number.ts                    | 106 ++++++
 yarn.lock                                     | 226 +++++++----
 97 files changed, 7033 insertions(+), 184 deletions(-)
 create mode 100644 src/app/freelancers/job/[id]/edit/page.tsx
 create mode 100644 src/app/freelancers/job/[id]/page.tsx
 create mode 100644 src/app/freelancers/job/new/page.tsx
 create mode 100644 src/app/freelancers/job/page.tsx
 create mode 100644 src/app/freelancers/layout.tsx
 create mode 100644 src/app/freelancers/loading.tsx
 create mode 100644 src/app/freelancers/page.tsx
 create mode 100644 src/hooks/use-tabs.ts
 create mode 100644 src/shared/components/country-select/country-select.tsx
 create mode 100644 src/shared/components/country-select/index.ts
 create mode 100644 src/shared/components/country-select/utils.ts
 create mode 100644 src/shared/components/custom-breadcrumbs/breadcrumb-link.tsx
 create mode 100644 src/shared/components/custom-breadcrumbs/custom-breadcrumbs.tsx
 create mode 100644 src/shared/components/custom-breadcrumbs/index.ts
 create mode 100644 src/shared/components/custom-breadcrumbs/types.ts
 create mode 100644 src/shared/components/editor/classes.ts
 create mode 100644 src/shared/components/editor/components/code-highlight-block.css
 create mode 100644 src/shared/components/editor/components/code-highlight-block.tsx
 create mode 100644 src/shared/components/editor/components/heading-block.tsx
 create mode 100644 src/shared/components/editor/components/image-block.tsx
 create mode 100644 src/shared/components/editor/components/link-block.tsx
 create mode 100644 src/shared/components/editor/components/toolbar-item.tsx
 create mode 100644 src/shared/components/editor/editor.tsx
 create mode 100644 src/shared/components/editor/index.ts
 create mode 100644 src/shared/components/editor/styles.tsx
 create mode 100644 src/shared/components/editor/toolbar.tsx
 create mode 100644 src/shared/components/editor/types.ts
 create mode 100644 src/shared/components/empty-content/empty-content.tsx
 create mode 100644 src/shared/components/empty-content/index.ts
 create mode 100644 src/shared/components/filters-result/filters-block.tsx
 create mode 100644 src/shared/components/filters-result/filters-result.tsx
 create mode 100644 src/shared/components/filters-result/index.ts
 create mode 100644 src/shared/components/hook-form/rhf-autocomplete.tsx
 create mode 100644 src/shared/components/hook-form/rhf-checkbox.tsx
 create mode 100644 src/shared/components/hook-form/rhf-code.tsx
 create mode 100644 src/shared/components/hook-form/rhf-country-select.tsx
 create mode 100644 src/shared/components/hook-form/rhf-date-picker.tsx
 create mode 100644 src/shared/components/hook-form/rhf-editor.tsx
 create mode 100644 src/shared/components/hook-form/rhf-phone-input.tsx
 create mode 100644 src/shared/components/hook-form/rhf-radio-group.tsx
 create mode 100644 src/shared/components/hook-form/rhf-rating.tsx
 create mode 100644 src/shared/components/hook-form/rhf-select.tsx
 create mode 100644 src/shared/components/hook-form/rhf-slider.tsx
 create mode 100644 src/shared/components/hook-form/rhf-switch.tsx
 create mode 100644 src/shared/components/hook-form/rhf-upload.tsx
 create mode 100644 src/shared/components/hook-form/schema-helper.ts
 create mode 100644 src/shared/components/image/classes.ts
 create mode 100644 src/shared/components/image/image.tsx
 create mode 100644 src/shared/components/image/index.ts
 create mode 100644 src/shared/components/image/styles.css
 create mode 100644 src/shared/components/image/types.ts
 create mode 100644 src/shared/components/markdown/classes.ts
 create mode 100644 src/shared/components/markdown/code-highlight-block.css
 create mode 100644 src/shared/components/markdown/html-tags.ts
 create mode 100644 src/shared/components/markdown/html-to-markdown.ts
 create mode 100644 src/shared/components/markdown/index.ts
 create mode 100644 src/shared/components/markdown/markdown.tsx
 create mode 100644 src/shared/components/markdown/styles.ts
 create mode 100644 src/shared/components/markdown/types.ts
 create mode 100644 src/shared/components/phone-input/index.ts
 create mode 100644 src/shared/components/phone-input/list.tsx
 create mode 100644 src/shared/components/phone-input/phone-input.tsx
 create mode 100644 src/shared/components/phone-input/types.ts
 create mode 100644 src/shared/components/phone-input/utils.ts
 create mode 100644 src/shared/components/upload/components/placeholder.tsx
 create mode 100644 src/shared/components/upload/components/preview-multi-file.tsx
 create mode 100644 src/shared/components/upload/components/preview-single-file.tsx
 create mode 100644 src/shared/components/upload/components/rejection-files.tsx
 create mode 100644 src/shared/components/upload/index.ts
 create mode 100644 src/shared/components/upload/types.ts
 create mode 100644 src/shared/components/upload/upload-avatar.tsx
 create mode 100644 src/shared/components/upload/upload-box.tsx
 create mode 100644 src/shared/components/upload/upload.tsx
 create mode 100644 src/shared/sections/job/job-details-candidates.tsx
 create mode 100644 src/shared/sections/job/job-details-content.tsx
 create mode 100644 src/shared/sections/job/job-details-toolbar.tsx
 create mode 100644 src/shared/sections/job/job-filters-result.tsx
 create mode 100644 src/shared/sections/job/job-filters.tsx
 create mode 100644 src/shared/sections/job/job-item.tsx
 create mode 100644 src/shared/sections/job/job-list.tsx
 create mode 100644 src/shared/sections/job/job-new-edit-form.tsx
 create mode 100644 src/shared/sections/job/job-search.tsx
 create mode 100644 src/shared/sections/job/job-sort.tsx
 create mode 100644 src/shared/sections/job/view/index.ts
 create mode 100644 src/shared/sections/job/view/job-create-view.tsx
 create mode 100644 src/shared/sections/job/view/job-details-view.tsx
 create mode 100644 src/shared/sections/job/view/job-edit-view.tsx
 create mode 100644 src/shared/sections/job/view/job-list-view.tsx
 create mode 100644 src/shared/types/job.ts
 create mode 100644 src/utils/format-number.ts

diff --git a/package-lock.json b/package-lock.json
index 311119e..e952c92 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
 {
-  "name": "@minimal-kit/starter-next-ts",
+  "name": "hire3-front",
   "version": "6.0.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "@minimal-kit/starter-next-ts",
+      "name": "hire3-front",
       "version": "6.0.1",
       "dependencies": {
         "@auth0/auth0-react": "^2.2.4",
@@ -13,8 +13,8 @@
         "@dnd-kit/sortable": "^8.0.0",
         "@dnd-kit/utilities": "^3.2.2",
         "@emotion/cache": "^11.11.0",
-        "@emotion/react": "^11.11.4",
-        "@emotion/styled": "^11.11.5",
+        "@emotion/react": "^11.13.0",
+        "@emotion/styled": "^11.13.0",
         "@fontsource/barlow": "^5.0.13",
         "@fontsource/dm-sans": "^5.0.21",
         "@fontsource/inter": "^5.0.18",
@@ -30,8 +30,9 @@
         "@hookform/resolvers": "^3.6.0",
         "@iconify/react": "^5.0.1",
         "@mui/lab": "^5.0.0-alpha.170",
-        "@mui/material": "^5.15.20",
+        "@mui/material": "^5.16.6",
         "@mui/material-nextjs": "^5.15.11",
+        "@mui/styled-engine-sc": "^6.0.0-alpha.18",
         "@mui/x-data-grid": "^7.7.0",
         "@mui/x-date-pickers": "^7.7.0",
         "@mui/x-tree-view": "^7.7.0",
@@ -51,7 +52,7 @@
         "autosuggest-highlight": "^3.3.4",
         "aws-amplify": "^6.3.6",
         "axios": "^1.7.2",
-        "dayjs": "^1.11.11",
+        "dayjs": "^1.11.12",
         "embla-carousel": "^8.1.5",
         "embla-carousel-auto-height": "^8.1.5",
         "embla-carousel-auto-scroll": "^8.1.5",
@@ -84,6 +85,7 @@
         "remark-gfm": "^4.0.0",
         "simplebar-react": "^3.2.5",
         "sonner": "^1.5.0",
+        "styled-components": "^6.1.12",
         "stylis": "^4.3.2",
         "stylis-plugin-rtl": "^2.1.1",
         "swr": "^2.2.5",
@@ -3417,12 +3419,6 @@
         "stylis": "4.2.0"
       }
     },
-    "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
-      "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
-      "license": "MIT"
-    },
     "node_modules/@emotion/babel-plugin/node_modules/stylis": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
@@ -3441,18 +3437,6 @@
         "stylis": "4.2.0"
       }
     },
-    "node_modules/@emotion/cache/node_modules/@emotion/memoize": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
-      "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
-      "license": "MIT"
-    },
-    "node_modules/@emotion/cache/node_modules/@emotion/weak-memoize": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
-      "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
-      "license": "MIT"
-    },
     "node_modules/@emotion/cache/node_modules/stylis": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
@@ -3478,30 +3462,30 @@
       "license": "MIT"
     },
     "node_modules/@emotion/is-prop-valid": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
-      "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz",
+      "integrity": "sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==",
       "dependencies": {
-        "@emotion/memoize": "^0.8.1"
+        "@emotion/memoize": "^0.9.0"
       }
     },
     "node_modules/@emotion/memoize": {
-      "version": "0.8.1",
-      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
-      "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+      "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
     },
     "node_modules/@emotion/react": {
-      "version": "11.11.4",
-      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz",
-      "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==",
+      "version": "11.13.0",
+      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz",
+      "integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==",
       "dependencies": {
         "@babel/runtime": "^7.18.3",
-        "@emotion/babel-plugin": "^11.11.0",
-        "@emotion/cache": "^11.11.0",
-        "@emotion/serialize": "^1.1.3",
-        "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
-        "@emotion/utils": "^1.2.1",
-        "@emotion/weak-memoize": "^0.3.1",
+        "@emotion/babel-plugin": "^11.12.0",
+        "@emotion/cache": "^11.13.0",
+        "@emotion/serialize": "^1.3.0",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+        "@emotion/utils": "^1.4.0",
+        "@emotion/weak-memoize": "^0.4.0",
         "hoist-non-react-statics": "^3.3.1"
       },
       "peerDependencies": {
@@ -3526,12 +3510,6 @@
         "csstype": "^3.0.2"
       }
     },
-    "node_modules/@emotion/serialize/node_modules/@emotion/memoize": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
-      "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
-      "license": "MIT"
-    },
     "node_modules/@emotion/sheet": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
@@ -3539,16 +3517,16 @@
       "license": "MIT"
     },
     "node_modules/@emotion/styled": {
-      "version": "11.11.5",
-      "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz",
-      "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==",
+      "version": "11.13.0",
+      "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz",
+      "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==",
       "dependencies": {
         "@babel/runtime": "^7.18.3",
-        "@emotion/babel-plugin": "^11.11.0",
-        "@emotion/is-prop-valid": "^1.2.2",
-        "@emotion/serialize": "^1.1.4",
-        "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
-        "@emotion/utils": "^1.2.1"
+        "@emotion/babel-plugin": "^11.12.0",
+        "@emotion/is-prop-valid": "^1.3.0",
+        "@emotion/serialize": "^1.3.0",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+        "@emotion/utils": "^1.4.0"
       },
       "peerDependencies": {
         "@emotion/react": "^11.0.0-rc.0",
@@ -3567,9 +3545,9 @@
       "license": "MIT"
     },
     "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
-      "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
+      "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==",
       "peerDependencies": {
         "react": ">=16.8.0"
       }
@@ -3581,9 +3559,9 @@
       "license": "MIT"
     },
     "node_modules/@emotion/weak-memoize": {
-      "version": "0.3.1",
-      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
-      "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+      "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
     },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
@@ -4692,10 +4670,9 @@
       }
     },
     "node_modules/@mui/core-downloads-tracker": {
-      "version": "5.16.5",
-      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.5.tgz",
-      "integrity": "sha512-ziFn1oPm6VjvHQcdGcAO+fXvOQEgieIj0BuSqcltFU+JXIxjPdVYNTdn2HU7/Ak5Gabk6k2u7+9PV7oZ6JT5sA==",
-      "license": "MIT",
+      "version": "5.16.6",
+      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.6.tgz",
+      "integrity": "sha512-kytg6LheUG42V8H/o/Ptz3olSO5kUXW9zF0ox18VnblX6bO2yif1FPItgc3ey1t5ansb1+gbe7SatntqusQupg==",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/mui-org"
@@ -4742,16 +4719,15 @@
       }
     },
     "node_modules/@mui/material": {
-      "version": "5.16.5",
-      "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.5.tgz",
-      "integrity": "sha512-eQrjjg4JeczXvh/+8yvJkxWIiKNHVptB/AqpsKfZBWp5mUD5U3VsjODMuUl1K2BSq0omV3CiO/mQmWSSMKSmaA==",
-      "license": "MIT",
+      "version": "5.16.6",
+      "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.6.tgz",
+      "integrity": "sha512-0LUIKBOIjiFfzzFNxXZBRAyr9UQfmTAFzbt6ziOU2FDXhorNN2o3N9/32mNJbCA8zJo2FqFU6d3dtoqUDyIEfA==",
       "dependencies": {
         "@babel/runtime": "^7.23.9",
-        "@mui/core-downloads-tracker": "^5.16.5",
-        "@mui/system": "^5.16.5",
+        "@mui/core-downloads-tracker": "^5.16.6",
+        "@mui/system": "^5.16.6",
         "@mui/types": "^7.2.15",
-        "@mui/utils": "^5.16.5",
+        "@mui/utils": "^5.16.6",
         "@popperjs/core": "^2.11.8",
         "@types/react-transition-group": "^4.4.10",
         "clsx": "^2.1.0",
@@ -4821,13 +4797,12 @@
       }
     },
     "node_modules/@mui/private-theming": {
-      "version": "5.16.5",
-      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.5.tgz",
-      "integrity": "sha512-CSLg0YkpDqg0aXOxtjo3oTMd3XWMxvNb5d0v4AYVqwOltU8q6GvnZjhWyCLjGSCrcgfwm6/VDjaKLPlR14wxIA==",
-      "license": "MIT",
+      "version": "5.16.6",
+      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz",
+      "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==",
       "dependencies": {
         "@babel/runtime": "^7.23.9",
-        "@mui/utils": "^5.16.5",
+        "@mui/utils": "^5.16.6",
         "prop-types": "^15.8.1"
       },
       "engines": {
@@ -4848,10 +4823,9 @@
       }
     },
     "node_modules/@mui/styled-engine": {
-      "version": "5.16.4",
-      "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.4.tgz",
-      "integrity": "sha512-0+mnkf+UiAmTVB8PZFqOhqf729Yh0Cxq29/5cA3VAyDVTRIUUQ8FXQhiAhUIbijFmM72rY80ahFPXIm4WDbzcA==",
-      "license": "MIT",
+      "version": "5.16.6",
+      "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz",
+      "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==",
       "dependencies": {
         "@babel/runtime": "^7.23.9",
         "@emotion/cache": "^11.11.0",
@@ -4879,17 +4853,37 @@
         }
       }
     },
+    "node_modules/@mui/styled-engine-sc": {
+      "version": "6.0.0-alpha.18",
+      "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-6.0.0-alpha.18.tgz",
+      "integrity": "sha512-W3mqR1K01rPL0BVNTgGpIYxdbQ/nTAlwYaohRdmX7FZvbm1yKw9F90OIGxM503dfRMVBi6a/neYPgIUebcGsHw==",
+      "dependencies": {
+        "@babel/runtime": "^7.23.9",
+        "csstype": "^3.1.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "styled-components": "^6.0.0"
+      }
+    },
     "node_modules/@mui/system": {
-      "version": "5.16.5",
-      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.5.tgz",
-      "integrity": "sha512-uzIUGdrWddUx1HPxW4+B2o4vpgKyRxGe/8BxbfXVDPNPHX75c782TseoCnR/VyfnZJfqX87GcxDmnZEE1c031g==",
-      "license": "MIT",
+      "version": "5.16.6",
+      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.6.tgz",
+      "integrity": "sha512-5xgyJjBIMPw8HIaZpfbGAaFYPwImQn7Nyh+wwKWhvkoIeDosQ1ZMVrbTclefi7G8hNmqhip04duYwYpbBFnBgw==",
       "dependencies": {
         "@babel/runtime": "^7.23.9",
-        "@mui/private-theming": "^5.16.5",
-        "@mui/styled-engine": "^5.16.4",
+        "@mui/private-theming": "^5.16.6",
+        "@mui/styled-engine": "^5.16.6",
         "@mui/types": "^7.2.15",
-        "@mui/utils": "^5.16.5",
+        "@mui/utils": "^5.16.6",
         "clsx": "^2.1.0",
         "csstype": "^3.1.3",
         "prop-types": "^15.8.1"
@@ -4934,10 +4928,9 @@
       }
     },
     "node_modules/@mui/utils": {
-      "version": "5.16.5",
-      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.5.tgz",
-      "integrity": "sha512-CwhcA9y44XwK7k2joL3Y29mRUnoBt+gOZZdGyw7YihbEwEErJYBtDwbZwVgH68zAljGe/b+Kd5bzfl63Gi3R2A==",
-      "license": "MIT",
+      "version": "5.16.6",
+      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz",
+      "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==",
       "dependencies": {
         "@babel/runtime": "^7.23.9",
         "@mui/types": "^7.2.15",
@@ -8125,6 +8118,14 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/camelize": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+      "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/can-use-dom": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz",
@@ -8418,6 +8419,14 @@
       "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
       "license": "MIT"
     },
+    "node_modules/css-color-keywords": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+      "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/css-select": {
       "version": "5.1.0",
       "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
@@ -8434,6 +8443,16 @@
         "url": "https://github.com/sponsors/fb55"
       }
     },
+    "node_modules/css-to-react-native": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+      "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+      "dependencies": {
+        "camelize": "^1.0.0",
+        "css-color-keywords": "^1.0.0",
+        "postcss-value-parser": "^4.0.2"
+      }
+    },
     "node_modules/css-tree": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
@@ -8569,9 +8588,9 @@
       }
     },
     "node_modules/dayjs": {
-      "version": "1.11.11",
-      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
-      "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
+      "version": "1.11.12",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
+      "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg=="
     },
     "node_modules/debug": {
       "version": "4.3.4",
@@ -14373,6 +14392,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/shallowequal": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+    },
     "node_modules/shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -14522,9 +14546,9 @@
       }
     },
     "node_modules/source-map-js": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
-      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+      "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
       "engines": {
         "node": ">=0.10.0"
       }
@@ -14743,6 +14767,83 @@
         "inline-style-parser": "0.2.3"
       }
     },
+    "node_modules/styled-components": {
+      "version": "6.1.12",
+      "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz",
+      "integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==",
+      "dependencies": {
+        "@emotion/is-prop-valid": "1.2.2",
+        "@emotion/unitless": "0.8.1",
+        "@types/stylis": "4.2.5",
+        "css-to-react-native": "3.2.0",
+        "csstype": "3.1.3",
+        "postcss": "8.4.38",
+        "shallowequal": "1.1.0",
+        "stylis": "4.3.2",
+        "tslib": "2.6.2"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/styled-components"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8.0",
+        "react-dom": ">= 16.8.0"
+      }
+    },
+    "node_modules/styled-components/node_modules/@emotion/is-prop-valid": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+      "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+      "dependencies": {
+        "@emotion/memoize": "^0.8.1"
+      }
+    },
+    "node_modules/styled-components/node_modules/@emotion/memoize": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+      "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+    },
+    "node_modules/styled-components/node_modules/@emotion/unitless": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+      "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
+    },
+    "node_modules/styled-components/node_modules/@types/stylis": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+      "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="
+    },
+    "node_modules/styled-components/node_modules/postcss": {
+      "version": "8.4.38",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+      "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.7",
+        "picocolors": "^1.0.0",
+        "source-map-js": "^1.2.0"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
     "node_modules/styled-jsx": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
diff --git a/package.json b/package.json
index 2f153ab..3423201 100644
--- a/package.json
+++ b/package.json
@@ -27,8 +27,8 @@
     "@dnd-kit/sortable": "^8.0.0",
     "@dnd-kit/utilities": "^3.2.2",
     "@emotion/cache": "^11.11.0",
-    "@emotion/react": "^11.11.4",
-    "@emotion/styled": "^11.11.5",
+    "@emotion/react": "^11.13.0",
+    "@emotion/styled": "^11.13.0",
     "@fontsource/barlow": "^5.0.13",
     "@fontsource/dm-sans": "^5.0.21",
     "@fontsource/inter": "^5.0.18",
@@ -44,8 +44,9 @@
     "@hookform/resolvers": "^3.6.0",
     "@iconify/react": "^5.0.1",
     "@mui/lab": "^5.0.0-alpha.170",
-    "@mui/material": "^5.15.20",
+    "@mui/material": "^5.16.6",
     "@mui/material-nextjs": "^5.15.11",
+    "@mui/styled-engine-sc": "^6.0.0-alpha.18",
     "@mui/x-data-grid": "^7.7.0",
     "@mui/x-date-pickers": "^7.7.0",
     "@mui/x-tree-view": "^7.7.0",
@@ -65,7 +66,7 @@
     "autosuggest-highlight": "^3.3.4",
     "aws-amplify": "^6.3.6",
     "axios": "^1.7.2",
-    "dayjs": "^1.11.11",
+    "dayjs": "^1.11.12",
     "embla-carousel": "^8.1.5",
     "embla-carousel-auto-height": "^8.1.5",
     "embla-carousel-auto-scroll": "^8.1.5",
@@ -98,6 +99,7 @@
     "remark-gfm": "^4.0.0",
     "simplebar-react": "^3.2.5",
     "sonner": "^1.5.0",
+    "styled-components": "^6.1.12",
     "stylis": "^4.3.2",
     "stylis-plugin-rtl": "^2.1.1",
     "swr": "^2.2.5",
diff --git a/src/app/freelancers/job/[id]/edit/page.tsx b/src/app/freelancers/job/[id]/edit/page.tsx
new file mode 100644
index 0000000..e4f40fd
--- /dev/null
+++ b/src/app/freelancers/job/[id]/edit/page.tsx
@@ -0,0 +1,41 @@
+import { CONFIG } from 'src/config-global';
+import { _jobs } from 'src/shared/_mock/_job';
+
+import { JobEditView } from 'src/shared/sections/job/view';
+
+// ----------------------------------------------------------------------
+
+export const metadata = { title: `Job edit | Dashboard - ${CONFIG.site.name}` };
+
+type Props = {
+  params: { id: string };
+};
+
+export default function Page({ params }: Props) {
+  const { id } = params;
+
+  const currentJob = _jobs.find((job) => job.id === id);
+
+  return <JobEditView job={currentJob} />;
+}
+
+// ----------------------------------------------------------------------
+
+/**
+ * [1] Default
+ * Remove [1] and [2] if not using [2]
+ */
+const dynamic = CONFIG.isStaticExport ? 'auto' : 'force-dynamic';
+
+export { dynamic };
+
+/**
+ * [2] Static exports
+ * https://nextjs.org/docs/app/building-your-application/deploying/static-exports
+ */
+export async function generateStaticParams() {
+  if (CONFIG.isStaticExport) {
+    return _jobs.map((job) => ({ id: job.id }));
+  }
+  return [];
+}
diff --git a/src/app/freelancers/job/[id]/page.tsx b/src/app/freelancers/job/[id]/page.tsx
new file mode 100644
index 0000000..7a4d9d8
--- /dev/null
+++ b/src/app/freelancers/job/[id]/page.tsx
@@ -0,0 +1,41 @@
+import { CONFIG } from 'src/config-global';
+import { _jobs } from 'src/shared/_mock/_job';
+
+import { JobDetailsView } from 'src/shared/sections/job/view';
+
+// ----------------------------------------------------------------------
+
+export const metadata = { title: `Job details | Dashboard - ${CONFIG.site.name}` };
+
+type Props = {
+  params: { id: string };
+};
+
+export default function Page({ params }: Props) {
+  const { id } = params;
+
+  const currentJob = _jobs.find((job) => job.id === id);
+
+  return <JobDetailsView job={currentJob} />;
+}
+
+// ----------------------------------------------------------------------
+
+/**
+ * [1] Default
+ * Remove [1] and [2] if not using [2]
+ */
+const dynamic = CONFIG.isStaticExport ? 'auto' : 'force-dynamic';
+
+export { dynamic };
+
+/**
+ * [2] Static exports
+ * https://nextjs.org/docs/app/building-your-application/deploying/static-exports
+ */
+export async function generateStaticParams() {
+  if (CONFIG.isStaticExport) {
+    return _jobs.map((job) => ({ id: job.id }));
+  }
+  return [];
+}
diff --git a/src/app/freelancers/job/new/page.tsx b/src/app/freelancers/job/new/page.tsx
new file mode 100644
index 0000000..916745d
--- /dev/null
+++ b/src/app/freelancers/job/new/page.tsx
@@ -0,0 +1,11 @@
+import { CONFIG } from 'src/config-global';
+
+import { JobCreateView } from 'src/shared/sections/job/view';
+
+// ----------------------------------------------------------------------
+
+export const metadata = { title: `Create a new job | Dashboard - ${CONFIG.site.name}` };
+
+export default function Page() {
+  return <JobCreateView />;
+}
diff --git a/src/app/freelancers/job/page.tsx b/src/app/freelancers/job/page.tsx
new file mode 100644
index 0000000..dffec79
--- /dev/null
+++ b/src/app/freelancers/job/page.tsx
@@ -0,0 +1,11 @@
+import { CONFIG } from 'src/config-global';
+
+import { JobListView } from 'src/shared/sections/job/view';
+
+// ----------------------------------------------------------------------
+
+export const metadata = { title: `Job list | Dashboard - ${CONFIG.site.name}` };
+
+export default function Page() {
+  return <JobListView />;
+}
diff --git a/src/app/freelancers/layout.tsx b/src/app/freelancers/layout.tsx
new file mode 100644
index 0000000..5fcf8b6
--- /dev/null
+++ b/src/app/freelancers/layout.tsx
@@ -0,0 +1,22 @@
+import { CONFIG } from 'src/config-global';
+import { DashboardLayout } from 'src/shared/layouts/dashboard';
+
+import { AuthGuard } from 'src/auth/guard';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  children: React.ReactNode;
+};
+
+export default function Layout({ children }: Props) {
+  if (CONFIG.auth.skip) {
+    return <DashboardLayout>{children}</DashboardLayout>;
+  }
+
+  return (
+    <AuthGuard>
+      <DashboardLayout>{children}</DashboardLayout>
+    </AuthGuard>
+  );
+}
diff --git a/src/app/freelancers/loading.tsx b/src/app/freelancers/loading.tsx
new file mode 100644
index 0000000..bbbd9f6
--- /dev/null
+++ b/src/app/freelancers/loading.tsx
@@ -0,0 +1,7 @@
+import { LoadingScreen } from 'src/shared/components/loading-screen';
+
+// ----------------------------------------------------------------------
+
+export default function Loading() {
+  return <LoadingScreen />;
+}
diff --git a/src/app/freelancers/page.tsx b/src/app/freelancers/page.tsx
new file mode 100644
index 0000000..7530162
--- /dev/null
+++ b/src/app/freelancers/page.tsx
@@ -0,0 +1,11 @@
+import { CONFIG } from 'src/config-global';
+
+import { BlankView } from 'src/shared/sections/blank/view';
+
+// ----------------------------------------------------------------------
+
+export const metadata = { title: `Dashboard - ${CONFIG.site.name}` };
+
+export default function Page() {
+  return <BlankView title="Freelancers" />;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 738abee..edc05b6 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -6,6 +6,7 @@ import type { Viewport } from 'next';
 
 import { CONFIG } from 'src/config-global';
 import { primary } from 'src/shared/theme/core/palette';
+import { LocalizationProvider } from 'src/shared/locales';
 import { ThemeProvider } from 'src/shared/theme/theme-provider';
 import { getInitColorSchemeScript } from 'src/shared/theme/color-scheme-script';
 
@@ -42,11 +43,13 @@ export default async function RootLayout({ children }: Props) {
             caches={CONFIG.isStaticExport ? 'localStorage' : 'cookie'}
           >
             <ThemeProvider>
-              <MotionLazy>
-                <ProgressBar />
-                <SettingsDrawer />
-                {children}
-              </MotionLazy>
+              <LocalizationProvider>
+                <MotionLazy>
+                  <ProgressBar />
+                  <SettingsDrawer />
+                  {children}
+                </MotionLazy>
+              </LocalizationProvider>
             </ThemeProvider>
           </SettingsProvider>
         </AuthProvider>
diff --git a/src/hooks/use-tabs.ts b/src/hooks/use-tabs.ts
new file mode 100644
index 0000000..eb4977a
--- /dev/null
+++ b/src/hooks/use-tabs.ts
@@ -0,0 +1,21 @@
+import { useMemo, useState, useCallback } from 'react';
+
+// ----------------------------------------------------------------------
+
+export type UseTabsReturn = {
+  value: string;
+  setValue: React.Dispatch<React.SetStateAction<string>>;
+  onChange: (event: React.SyntheticEvent, newValue: string) => void;
+};
+
+export function useTabs(defaultValue: string): UseTabsReturn {
+  const [value, setValue] = useState(defaultValue);
+
+  const onChange = useCallback((event: React.SyntheticEvent, newValue: string) => {
+    setValue(newValue);
+  }, []);
+
+  const memoizedValue = useMemo(() => ({ value, setValue, onChange }), [onChange, value]);
+
+  return memoizedValue;
+}
diff --git a/src/routes/paths.ts b/src/routes/paths.ts
index 779fa8e..f863b64 100644
--- a/src/routes/paths.ts
+++ b/src/routes/paths.ts
@@ -4,6 +4,7 @@ const ROOTS = {
   AUTH: '/auth',
   AUTH_DEMO: '/auth-demo',
   DASHBOARD: '/dashboard',
+  FREELANCERS: '/freelancers',
 };
 
 // ----------------------------------------------------------------------
@@ -49,4 +50,11 @@ export const paths = {
     root: ROOTS.DASHBOARD,
     blank: `${ROOTS.DASHBOARD}/blank`,
   },
+  freelancers: {
+    root: ROOTS.FREELANCERS,
+    jobs: `${ROOTS.FREELANCERS}/job`,
+    newJob: `${ROOTS.FREELANCERS}/job/new`,
+    edit: (id: string) => `${ROOTS.FREELANCERS}/job/${id}/edit`,
+    details: (id: string) => `${ROOTS.FREELANCERS}/job/${id}`,
+  },
 };
diff --git a/src/shared/components/country-select/country-select.tsx b/src/shared/components/country-select/country-select.tsx
new file mode 100644
index 0000000..3552ccd
--- /dev/null
+++ b/src/shared/components/country-select/country-select.tsx
@@ -0,0 +1,152 @@
+import type {
+  AutocompleteProps,
+  AutocompleteRenderInputParams,
+  AutocompleteRenderGetTagProps,
+} from '@mui/material/Autocomplete';
+
+import Chip from '@mui/material/Chip';
+import TextField from '@mui/material/TextField';
+import Autocomplete from '@mui/material/Autocomplete';
+import InputAdornment from '@mui/material/InputAdornment';
+import { filledInputClasses } from '@mui/material/FilledInput';
+
+import { countries } from 'src/shared/assets/data';
+
+import { FlagIcon, iconifyClasses } from 'src/shared/components/iconify';
+
+import { getCountry, displayValueByCountryCode } from './utils';
+
+// ----------------------------------------------------------------------
+
+type Value = string;
+
+export type AutocompleteBaseProps = Omit<
+  AutocompleteProps<any, boolean, boolean, boolean>,
+  'options' | 'renderOption' | 'renderInput' | 'renderTags' | 'getOptionLabel'
+>;
+
+export type CountrySelectProps = AutocompleteBaseProps & {
+  label?: string;
+  error?: boolean;
+  placeholder?: string;
+  hiddenLabel?: boolean;
+  getValue?: 'label' | 'code';
+  helperText?: React.ReactNode;
+};
+
+export function CountrySelect({
+  id,
+  label,
+  error,
+  multiple,
+  helperText,
+  hiddenLabel,
+  placeholder,
+  getValue = 'label',
+  ...other
+}: CountrySelectProps) {
+  const options = countries.map((country) => (getValue === 'label' ? country.label : country.code));
+
+  const renderOption = (props: React.HTMLAttributes<HTMLLIElement>, option: Value) => {
+    const country = getCountry(option);
+
+    if (!country.label) {
+      return null;
+    }
+
+    return (
+      <li {...props} key={country.label}>
+        <FlagIcon
+          key={country.label}
+          code={country.code}
+          sx={{ mr: 1, width: 22, height: 22, borderRadius: '50%' }}
+        />
+        {country.label} ({country.code}) +{country.phone}
+      </li>
+    );
+  };
+
+  const renderInput = (params: AutocompleteRenderInputParams) => {
+    const country = getCountry(params.inputProps.value as Value);
+
+    const baseField = {
+      ...params,
+      label,
+      placeholder,
+      helperText,
+      hiddenLabel,
+      error: !!error,
+      inputProps: {
+        ...params.inputProps,
+        autoComplete: 'new-password',
+      },
+    };
+
+    if (multiple) {
+      return <TextField {...baseField} />;
+    }
+
+    return (
+      <TextField
+        {...baseField}
+        InputProps={{
+          ...params.InputProps,
+          startAdornment: (
+            <InputAdornment position="start" sx={{ ...(!country.code && { display: 'none' }) }}>
+              <FlagIcon
+                key={country.label}
+                code={country.code}
+                sx={{ ml: 0.5, mr: -0.5, width: 22, height: 22, borderRadius: '50%' }}
+              />
+            </InputAdornment>
+          ),
+        }}
+        sx={{
+          ...(!hiddenLabel && {
+            [`& .${filledInputClasses.root}`]: { [`& .${iconifyClasses.root}`]: { mt: -2 } },
+          }),
+        }}
+      />
+    );
+  };
+
+  const renderTags = (selected: Value[], getTagProps: AutocompleteRenderGetTagProps) =>
+    selected.map((option, index) => {
+      const country = getCountry(option);
+
+      return (
+        <Chip
+          {...getTagProps({ index })}
+          key={country.label}
+          label={country.label}
+          size="small"
+          variant="soft"
+          icon={
+            <FlagIcon
+              key={country.label}
+              code={country.code}
+              sx={{ width: 16, height: 16, borderRadius: '50%' }}
+            />
+          }
+        />
+      );
+    });
+
+  const getOptionLabel = (option: Value) =>
+    getValue === 'label' ? option : displayValueByCountryCode(option);
+
+  return (
+    <Autocomplete
+      id={`country-select-${id}`}
+      multiple={multiple}
+      options={options}
+      autoHighlight={!multiple}
+      disableCloseOnSelect={multiple}
+      renderOption={renderOption}
+      renderInput={renderInput}
+      renderTags={multiple ? renderTags : undefined}
+      getOptionLabel={getOptionLabel}
+      {...other}
+    />
+  );
+}
diff --git a/src/shared/components/country-select/index.ts b/src/shared/components/country-select/index.ts
new file mode 100644
index 0000000..f1634a4
--- /dev/null
+++ b/src/shared/components/country-select/index.ts
@@ -0,0 +1,3 @@
+export * from './utils';
+
+export * from './country-select';
diff --git a/src/shared/components/country-select/utils.ts b/src/shared/components/country-select/utils.ts
new file mode 100644
index 0000000..592b9f4
--- /dev/null
+++ b/src/shared/components/country-select/utils.ts
@@ -0,0 +1,19 @@
+import { countries } from 'src/shared/assets/data';
+
+// ----------------------------------------------------------------------
+
+export function getCountry(inputValue: string) {
+  const option = countries.filter(
+    (country) => country.label === inputValue || country.code === inputValue
+  )[0];
+
+  return { code: option?.code, label: option?.label, phone: option?.phone };
+}
+
+// ----------------------------------------------------------------------
+
+export function displayValueByCountryCode(inputValue: string) {
+  const option = countries.filter((country) => country.code === inputValue)[0];
+
+  return option.label;
+}
diff --git a/src/shared/components/custom-breadcrumbs/breadcrumb-link.tsx b/src/shared/components/custom-breadcrumbs/breadcrumb-link.tsx
new file mode 100644
index 0000000..f384aa9
--- /dev/null
+++ b/src/shared/components/custom-breadcrumbs/breadcrumb-link.tsx
@@ -0,0 +1,57 @@
+import Box from '@mui/material/Box';
+import Link from '@mui/material/Link';
+
+import { RouterLink } from 'src/routes/components';
+
+import type { BreadcrumbsLinkProps } from './types';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  disabled: boolean;
+  activeLast?: boolean;
+  link: BreadcrumbsLinkProps;
+};
+
+export function BreadcrumbsLink({ link, activeLast, disabled }: Props) {
+  const styles = {
+    typography: 'body2',
+    alignItems: 'center',
+    color: 'text.primary',
+    display: 'inline-flex',
+    ...(disabled &&
+      !activeLast && { cursor: 'default', pointerEvents: 'none', color: 'text.disabled' }),
+  };
+
+  const renderContent = (
+    <>
+      {link.icon && (
+        <Box
+          component="span"
+          sx={{
+            mr: 1,
+            display: 'inherit',
+            '& svg, & img': {
+              width: 20,
+              height: 20,
+            },
+          }}
+        >
+          {link.icon}
+        </Box>
+      )}
+
+      {link.name}
+    </>
+  );
+
+  if (link.href) {
+    return (
+      <Link component={RouterLink} href={link.href} sx={styles}>
+        {renderContent}
+      </Link>
+    );
+  }
+
+  return <Box sx={styles}> {renderContent} </Box>;
+}
diff --git a/src/shared/components/custom-breadcrumbs/custom-breadcrumbs.tsx b/src/shared/components/custom-breadcrumbs/custom-breadcrumbs.tsx
new file mode 100644
index 0000000..0917d16
--- /dev/null
+++ b/src/shared/components/custom-breadcrumbs/custom-breadcrumbs.tsx
@@ -0,0 +1,89 @@
+import Box from '@mui/material/Box';
+import Link from '@mui/material/Link';
+import Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+import Breadcrumbs from '@mui/material/Breadcrumbs';
+
+import { BreadcrumbsLink } from './breadcrumb-link';
+
+import type { CustomBreadcrumbsProps } from './types';
+
+// ----------------------------------------------------------------------
+
+export function CustomBreadcrumbs({
+  links,
+  action,
+  heading,
+  moreLink,
+  activeLast,
+  slotProps,
+  sx,
+  ...other
+}: CustomBreadcrumbsProps) {
+  const lastLink = links[links.length - 1].name;
+
+  const renderHeading = (
+    <Typography variant="h4" sx={{ mb: 2, ...slotProps?.heading }}>
+      {heading}
+    </Typography>
+  );
+
+  const renderLinks = (
+    <Breadcrumbs separator={<Separator />} sx={slotProps?.breadcrumbs} {...other}>
+      {links.map((link, index) => (
+        <BreadcrumbsLink
+          key={link.name ?? index}
+          link={link}
+          activeLast={activeLast}
+          disabled={link.name === lastLink}
+        />
+      ))}
+    </Breadcrumbs>
+  );
+
+  const renderAction = <Box sx={{ flexShrink: 0, ...slotProps?.action }}> {action} </Box>;
+
+  const renderMoreLink = (
+    <Box component="ul">
+      {moreLink?.map((href) => (
+        <Box key={href} component="li" sx={{ display: 'flex' }}>
+          <Link href={href} variant="body2" target="_blank" rel="noopener" sx={slotProps?.moreLink}>
+            {href}
+          </Link>
+        </Box>
+      ))}
+    </Box>
+  );
+
+  return (
+    <Stack spacing={2} sx={sx}>
+      <Stack direction="row" alignItems="center">
+        <Box sx={{ flexGrow: 1 }}>
+          {heading && renderHeading}
+
+          {!!links.length && renderLinks}
+        </Box>
+
+        {action && renderAction}
+      </Stack>
+
+      {!!moreLink && renderMoreLink}
+    </Stack>
+  );
+}
+
+// ----------------------------------------------------------------------
+
+function Separator() {
+  return (
+    <Box
+      component="span"
+      sx={{
+        width: 4,
+        height: 4,
+        borderRadius: '50%',
+        bgcolor: 'text.disabled',
+      }}
+    />
+  );
+}
diff --git a/src/shared/components/custom-breadcrumbs/index.ts b/src/shared/components/custom-breadcrumbs/index.ts
new file mode 100644
index 0000000..b3cb547
--- /dev/null
+++ b/src/shared/components/custom-breadcrumbs/index.ts
@@ -0,0 +1,3 @@
+export type * from './types';
+
+export * from './custom-breadcrumbs';
diff --git a/src/shared/components/custom-breadcrumbs/types.ts b/src/shared/components/custom-breadcrumbs/types.ts
new file mode 100644
index 0000000..c38d6dd
--- /dev/null
+++ b/src/shared/components/custom-breadcrumbs/types.ts
@@ -0,0 +1,25 @@
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { BreadcrumbsProps } from '@mui/material/Breadcrumbs';
+
+// ----------------------------------------------------------------------
+
+export type BreadcrumbsLinkProps = {
+  name?: string;
+  href?: string;
+  icon?: React.ReactElement;
+};
+
+export type CustomBreadcrumbsProps = BreadcrumbsProps & {
+  heading?: string;
+  moreLink?: string[];
+  activeLast?: boolean;
+  action?: React.ReactNode;
+  links: BreadcrumbsLinkProps[];
+  sx?: SxProps<Theme>;
+  slotProps?: {
+    action: SxProps<Theme>;
+    heading: SxProps<Theme>;
+    moreLink: SxProps<Theme>;
+    breadcrumbs: SxProps<Theme>;
+  };
+};
diff --git a/src/shared/components/editor/classes.ts b/src/shared/components/editor/classes.ts
new file mode 100644
index 0000000..ff6f8bb
--- /dev/null
+++ b/src/shared/components/editor/classes.ts
@@ -0,0 +1,44 @@
+// ----------------------------------------------------------------------
+
+export const editorClasses = {
+  root: 'nml__editor__root',
+  toolbar: {
+    hr: 'nml__editor__toolbar__hr',
+    root: 'nml__editor__toolbar__root',
+    bold: 'nml__editor__toolbar__bold',
+    code: 'nml__editor__toolbar__code',
+    undo: 'nml__editor__toolbar__undo',
+    redo: 'nml__editor__toolbar__redo',
+    link: 'nml__editor__toolbar__link',
+    clear: 'nml__editor__toolbar__clear',
+    image: 'nml__editor__toolbar__image',
+    italic: 'nml__editor__toolbar__italic',
+    strike: 'nml__editor__toolbar__strike',
+    hardbreak: 'nml__editor__toolbar__hardbreak',
+    unsetlink: 'nml__editor__toolbar__unsetlink',
+    codeBlock: 'nml__editor__toolbar__code__block',
+    alignLeft: 'nml__editor__toolbar__align__left',
+    fullscreen: 'nml__editor__toolbar__fullscreen',
+    blockquote: 'nml__editor__toolbar__blockquote',
+    bulletList: 'nml__editor__toolbar__bullet__list',
+    alignRight: 'nml__editor__toolbar__align__right',
+    orderedList: 'nml__editor__toolbar__ordered__list',
+    alignCenter: 'nml__editor__toolbar__align__center',
+    alignJustify: 'nml__editor__toolbar__align__justify',
+  },
+  content: {
+    hr: 'nml__editor__content__hr',
+    root: 'nml__editor__content__root',
+    link: 'nml__editor__content__link',
+    image: 'nml__editor__content__image',
+    codeInline: 'nml__editor__content__code',
+    heading: 'nml__editor__content__heading',
+    listItem: 'nml__editor__content__listItem',
+    codeBlock: 'nml__editor__content__code__block',
+    blockquote: 'nml__editor__content__blockquote',
+    langSelect: 'nml__editor__content__lang__select',
+    placeholder: 'nml__editor__content__placeholder',
+    bulletList: 'nml__editor__content__bullet__list',
+    orderedList: 'nml__editor__content__ordered__list',
+  },
+};
diff --git a/src/shared/components/editor/components/code-highlight-block.css b/src/shared/components/editor/components/code-highlight-block.css
new file mode 100644
index 0000000..82f6af8
--- /dev/null
+++ b/src/shared/components/editor/components/code-highlight-block.css
@@ -0,0 +1,82 @@
+pre {
+  code[as='code'] {
+    .hljs-comment {
+      color: #999;
+    }
+    .hljs-tag {
+      color: #b4b7b4;
+    }
+    .hljs-operator,
+    .hljs-punctuation,
+    .hljs-subst {
+      color: #ccc;
+    }
+    .hljs-operator {
+      opacity: 0.7;
+    }
+    .hljs-bullet,
+    .hljs-deletion,
+    .hljs-name,
+    .hljs-selector-tag,
+    .hljs-template-variable,
+    .hljs-variable {
+      color: #f2777a;
+    }
+    .hljs-attr,
+    .hljs-link,
+    .hljs-literal,
+    .hljs-number,
+    .hljs-symbol,
+    .hljs-variable.constant_ {
+      color: #f99157;
+    }
+    .hljs-class .hljs-title,
+    .hljs-title,
+    .hljs-title.class_ {
+      color: #fc6;
+    }
+    .hljs-strong {
+      font-weight: 700;
+      color: #fc6;
+    }
+    .hljs-addition,
+    .hljs-code,
+    .hljs-string,
+    .hljs-title.class_.inherited__ {
+      color: #9c9;
+    }
+    .hljs-built_in,
+    .hljs-doctag,
+    .hljs-keyword.hljs-atrule,
+    .hljs-quote,
+    .hljs-regexp {
+      color: #6cc;
+    }
+    .hljs-attribute,
+    .hljs-function .hljs-title,
+    .hljs-section,
+    .hljs-title.function_,
+    .ruby .hljs-property {
+      color: #69c;
+    }
+    .diff .hljs-meta,
+    .hljs-keyword,
+    .hljs-template-tag,
+    .hljs-type {
+      color: #c9c;
+    }
+    .hljs-emphasis {
+      color: #c9c;
+      font-style: italic;
+    }
+    .hljs-meta,
+    .hljs-meta .hljs-keyword,
+    .hljs-meta .hljs-string {
+      color: #a3685a;
+    }
+    .hljs-meta .hljs-keyword,
+    .hljs-meta-keyword {
+      font-weight: 700;
+    }
+  }
+}
diff --git a/src/shared/components/editor/components/code-highlight-block.tsx b/src/shared/components/editor/components/code-highlight-block.tsx
new file mode 100644
index 0000000..828d2f4
--- /dev/null
+++ b/src/shared/components/editor/components/code-highlight-block.tsx
@@ -0,0 +1,41 @@
+import './code-highlight-block.css';
+
+import { NodeViewContent, NodeViewWrapper } from '@tiptap/react';
+
+import { editorClasses } from '../classes';
+
+import type { EditorCodeHighlightBlockProps } from '../types';
+
+// ----------------------------------------------------------------------
+
+export function CodeHighlightBlock({
+  node: {
+    attrs: { language: defaultLanguage },
+  },
+  extension,
+  updateAttributes,
+}: EditorCodeHighlightBlockProps) {
+  return (
+    <NodeViewWrapper className={editorClasses.content.codeBlock}>
+      <select
+        name="language"
+        contentEditable={false}
+        defaultValue={defaultLanguage}
+        onChange={(event) => updateAttributes({ language: event.target.value })}
+        className={editorClasses.content.langSelect}
+      >
+        <option value="null">auto</option>
+        <option disabled>—</option>
+        {extension.options.lowlight.listLanguages().map((lang: string) => (
+          <option key={lang} value={lang}>
+            {lang}
+          </option>
+        ))}
+      </select>
+
+      <pre>
+        <NodeViewContent as="code" />
+      </pre>
+    </NodeViewWrapper>
+  );
+}
diff --git a/src/shared/components/editor/components/heading-block.tsx b/src/shared/components/editor/components/heading-block.tsx
new file mode 100644
index 0000000..699631e
--- /dev/null
+++ b/src/shared/components/editor/components/heading-block.tsx
@@ -0,0 +1,134 @@
+import { useState } from 'react';
+
+import Menu from '@mui/material/Menu';
+import { listClasses } from '@mui/material/List';
+import ButtonBase, { buttonBaseClasses } from '@mui/material/ButtonBase';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { Iconify } from '../../iconify';
+import { ToolbarItem } from './toolbar-item';
+
+import type { EditorToolbarProps } from '../types';
+
+// ----------------------------------------------------------------------
+
+export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
+
+const HEADING_OPTIONS = [
+  'Heading 1',
+  'Heading 2',
+  'Heading 3',
+  'Heading 4',
+  'Heading 5',
+  'Heading 6',
+];
+
+export function HeadingBlock({ editor }: Pick<EditorToolbarProps, 'editor'>) {
+  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
+
+  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+    setAnchorEl(event.currentTarget);
+  };
+
+  const handleClose = () => {
+    setAnchorEl(null);
+  };
+
+  if (!editor) {
+    return null;
+  }
+
+  return (
+    <>
+      <ButtonBase
+        id="heading-menu-button"
+        aria-label="Heading menu button"
+        aria-controls={anchorEl ? 'heading-menu-button' : undefined}
+        aria-haspopup="true"
+        aria-expanded={anchorEl ? 'true' : undefined}
+        onClick={handleClick}
+        sx={{
+          px: 1,
+          width: 120,
+          height: 32,
+          borderRadius: 0.75,
+          typography: 'body2',
+          justifyContent: 'space-between',
+          border: (theme) => `solid 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.2)}`,
+        }}
+      >
+        {(editor.isActive('heading', { level: 1 }) && 'Heading 1') ||
+          (editor.isActive('heading', { level: 2 }) && 'Heading 2') ||
+          (editor.isActive('heading', { level: 3 }) && 'Heading 3') ||
+          (editor.isActive('heading', { level: 4 }) && 'Heading 4') ||
+          (editor.isActive('heading', { level: 5 }) && 'Heading 5') ||
+          (editor.isActive('heading', { level: 6 }) && 'Heading 6') ||
+          'Paragraph'}
+
+        <Iconify
+          width={16}
+          icon={anchorEl ? 'eva:arrow-ios-upward-fill' : 'eva:arrow-ios-downward-fill'}
+        />
+      </ButtonBase>
+
+      <Menu
+        id="heading-menu"
+        anchorEl={anchorEl}
+        open={!!anchorEl}
+        onClose={handleClose}
+        MenuListProps={{ 'aria-labelledby': 'heading-button' }}
+        slotProps={{
+          paper: {
+            sx: {
+              width: 120,
+              [`& .${listClasses.root}`]: { gap: 0.5, display: 'flex', flexDirection: 'column' },
+              [`& .${buttonBaseClasses.root}`]: {
+                px: 1,
+                width: 1,
+                height: 34,
+                borderRadius: 0.75,
+                justifyContent: 'flex-start',
+                '&:hover': { backgroundColor: 'action.hover' },
+              },
+            },
+          },
+        }}
+      >
+        <ToolbarItem
+          component="li"
+          label="Paragraph"
+          active={editor.isActive('paragraph')}
+          onClick={() => {
+            handleClose();
+            editor.chain().focus().setParagraph().run();
+          }}
+        />
+
+        {HEADING_OPTIONS.map((heading, index) => {
+          const level = (index + 1) as HeadingLevel;
+
+          return (
+            <ToolbarItem
+              aria-label={heading}
+              component="li"
+              key={heading}
+              label={heading}
+              active={editor.isActive('heading', { level })}
+              onClick={() => {
+                handleClose();
+                editor.chain().focus().toggleHeading({ level }).run();
+              }}
+              sx={{
+                ...(heading !== 'Paragraph' && {
+                  fontSize: 18 - index,
+                  fontWeight: 'fontWeightBold',
+                }),
+              }}
+            />
+          );
+        })}
+      </Menu>
+    </>
+  );
+}
diff --git a/src/shared/components/editor/components/image-block.tsx b/src/shared/components/editor/components/image-block.tsx
new file mode 100644
index 0000000..a6a1604
--- /dev/null
+++ b/src/shared/components/editor/components/image-block.tsx
@@ -0,0 +1,81 @@
+import { useState, useCallback } from 'react';
+
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import Popover from '@mui/material/Popover';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+
+import { editorClasses } from '../classes';
+import { ToolbarItem } from './toolbar-item';
+
+import type { EditorToolbarProps } from '../types';
+
+// ----------------------------------------------------------------------
+
+export function ImageBlock({ editor }: Pick<EditorToolbarProps, 'editor'>) {
+  const [url, setUrl] = useState('');
+
+  const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
+
+  const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => {
+    setAnchorEl(event.currentTarget);
+  };
+
+  const handleClosePopover = () => {
+    setAnchorEl(null);
+  };
+
+  const handleUpdateUrl = useCallback(() => {
+    handleClosePopover();
+
+    if (anchorEl) {
+      editor?.chain().focus().setImage({ src: url }).run();
+    }
+  }, [anchorEl, editor, url]);
+
+  if (!editor) {
+    return null;
+  }
+
+  return (
+    <>
+      <ToolbarItem
+        aria-label="Image"
+        className={editorClasses.toolbar.image}
+        onClick={handleOpenPopover}
+        icon={
+          <path d="M20 5H4V19L13.2923 9.70649C13.6828 9.31595 14.3159 9.31591 14.7065 9.70641L20 15.0104V5ZM2 3.9934C2 3.44476 2.45531 3 2.9918 3H21.0082C21.556 3 22 3.44495 22 3.9934V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934ZM8 11C6.89543 11 6 10.1046 6 9C6 7.89543 6.89543 7 8 7C9.10457 7 10 7.89543 10 9C10 10.1046 9.10457 11 8 11Z" />
+        }
+      />
+
+      <Popover
+        id={anchorEl ? 'simple-popover' : undefined}
+        open={!!anchorEl}
+        anchorEl={anchorEl}
+        onClose={handleClosePopover}
+        anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
+        slotProps={{ paper: { sx: { p: 2.5 } } }}
+      >
+        <Typography variant="subtitle2" sx={{ mb: 1 }}>
+          URL
+        </Typography>
+
+        <Stack direction="row" alignItems="center" spacing={1}>
+          <TextField
+            size="small"
+            placeholder="Enter URL here..."
+            value={url}
+            onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+              setUrl(event.target.value);
+            }}
+            sx={{ width: 240 }}
+          />
+          <Button variant="contained" onClick={handleUpdateUrl}>
+            Apply
+          </Button>
+        </Stack>
+      </Popover>
+    </>
+  );
+}
diff --git a/src/shared/components/editor/components/link-block.tsx b/src/shared/components/editor/components/link-block.tsx
new file mode 100644
index 0000000..254c25c
--- /dev/null
+++ b/src/shared/components/editor/components/link-block.tsx
@@ -0,0 +1,101 @@
+import { useState, useCallback } from 'react';
+
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import Popover from '@mui/material/Popover';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+
+import { editorClasses } from '../classes';
+import { ToolbarItem } from './toolbar-item';
+
+import type { EditorToolbarProps } from '../types';
+
+// ----------------------------------------------------------------------
+
+export function LinkBlock({ editor }: Pick<EditorToolbarProps, 'editor'>) {
+  const [url, setUrl] = useState('');
+
+  const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
+
+  const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => {
+    const previousUrl = editor?.getAttributes('link').href;
+
+    setAnchorEl(event.currentTarget);
+
+    if (previousUrl) {
+      setUrl(previousUrl);
+    } else {
+      setUrl('');
+    }
+  };
+
+  const handleClosePopover = () => {
+    setAnchorEl(null);
+  };
+
+  const handleUpdateUrl = useCallback(() => {
+    handleClosePopover();
+
+    if (!url) {
+      editor?.chain().focus().extendMarkRange('link').unsetLink().run();
+    } else {
+      editor?.chain().focus().extendMarkRange('link').setLink({ href: url }).run();
+    }
+  }, [editor, url]);
+
+  if (!editor) {
+    return null;
+  }
+
+  return (
+    <>
+      <ToolbarItem
+        aria-label="Link"
+        active={editor.isActive('link')}
+        className={editorClasses.toolbar.link}
+        onClick={handleOpenPopover}
+        icon={
+          <path d="M17.6567 14.8284L16.2425 13.4142L17.6567 12C19.2188 10.4379 19.2188 7.90524 17.6567 6.34314C16.0946 4.78105 13.5619 4.78105 11.9998 6.34314L10.5856 7.75736L9.17139 6.34314L10.5856 4.92893C12.9287 2.58578 16.7277 2.58578 19.0709 4.92893C21.414 7.27208 21.414 11.0711 19.0709 13.4142L17.6567 14.8284ZM14.8282 17.6569L13.414 19.0711C11.0709 21.4142 7.27189 21.4142 4.92875 19.0711C2.5856 16.7279 2.5856 12.9289 4.92875 10.5858L6.34296 9.17157L7.75717 10.5858L6.34296 12C4.78086 13.5621 4.78086 16.0948 6.34296 17.6569C7.90506 19.2189 10.4377 19.2189 11.9998 17.6569L13.414 16.2426L14.8282 17.6569ZM14.8282 7.75736L16.2425 9.17157L9.17139 16.2426L7.75717 14.8284L14.8282 7.75736Z" />
+        }
+      />
+      <ToolbarItem
+        aria-label="Unset link"
+        disabled={!editor.isActive('link')}
+        className={editorClasses.toolbar.unsetlink}
+        onClick={() => editor.chain().focus().unsetLink().run()}
+        icon={
+          <path d="M17.657 14.8284L16.2428 13.4142L17.657 12C19.2191 10.4379 19.2191 7.90526 17.657 6.34316C16.0949 4.78106 13.5622 4.78106 12.0001 6.34316L10.5859 7.75737L9.17171 6.34316L10.5859 4.92895C12.9291 2.5858 16.7281 2.5858 19.0712 4.92895C21.4143 7.27209 21.4143 11.0711 19.0712 13.4142L17.657 14.8284ZM14.8286 17.6569L13.4143 19.0711C11.0712 21.4142 7.27221 21.4142 4.92907 19.0711C2.58592 16.7279 2.58592 12.9289 4.92907 10.5858L6.34328 9.17159L7.75749 10.5858L6.34328 12C4.78118 13.5621 4.78118 16.0948 6.34328 17.6569C7.90538 19.219 10.438 19.219 12.0001 17.6569L13.4143 16.2427L14.8286 17.6569ZM14.8286 7.75737L16.2428 9.17159L9.17171 16.2427L7.75749 14.8284L14.8286 7.75737ZM5.77539 2.29291L7.70724 1.77527L8.74252 5.63897L6.81067 6.15661L5.77539 2.29291ZM15.2578 18.3611L17.1896 17.8434L18.2249 21.7071L16.293 22.2248L15.2578 18.3611ZM2.29303 5.77527L6.15673 6.81054L5.63909 8.7424L1.77539 7.70712L2.29303 5.77527ZM18.3612 15.2576L22.2249 16.2929L21.7072 18.2248L17.8435 17.1895L18.3612 15.2576Z" />
+        }
+      />
+
+      <Popover
+        id={anchorEl ? 'simple-popover' : undefined}
+        open={!!anchorEl}
+        anchorEl={anchorEl}
+        onClose={handleClosePopover}
+        anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
+        slotProps={{ paper: { sx: { p: 2.5 } } }}
+      >
+        <Typography variant="subtitle2" sx={{ mb: 1 }}>
+          URL
+        </Typography>
+
+        <Stack direction="row" alignItems="center" spacing={1}>
+          <TextField
+            size="small"
+            placeholder="Enter URL here..."
+            value={url}
+            onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+              setUrl(event.target.value);
+            }}
+            sx={{ width: 240 }}
+          />
+          <Button variant="contained" onClick={handleUpdateUrl}>
+            Apply
+          </Button>
+        </Stack>
+      </Popover>
+    </>
+  );
+}
diff --git a/src/shared/components/editor/components/toolbar-item.tsx b/src/shared/components/editor/components/toolbar-item.tsx
new file mode 100644
index 0000000..c84d086
--- /dev/null
+++ b/src/shared/components/editor/components/toolbar-item.tsx
@@ -0,0 +1,38 @@
+import type { ButtonBaseProps } from '@mui/material/ButtonBase';
+
+import SvgIcon from '@mui/material/SvgIcon';
+import ButtonBase from '@mui/material/ButtonBase';
+
+import type { EditorToolbarItemProps } from '../types';
+
+// ----------------------------------------------------------------------
+
+export function ToolbarItem({
+  sx,
+  icon,
+  label,
+  active,
+  disabled,
+  ...other
+}: ButtonBaseProps & EditorToolbarItemProps) {
+  return (
+    <ButtonBase
+      sx={{
+        px: 0.75,
+        width: 28,
+        height: 28,
+        borderRadius: 0.75,
+        typography: 'body2',
+        '&:hover': { bgcolor: 'action.hover' },
+        ...(active && { bgcolor: 'action.selected' }),
+        ...(disabled && { pointerEvents: 'none', cursor: 'not-allowed', opacity: 0.48 }),
+        ...sx,
+      }}
+      {...other}
+    >
+      {icon && <SvgIcon sx={{ fontSize: 18 }}>{icon}</SvgIcon>}
+
+      {label && label}
+    </ButtonBase>
+  );
+}
diff --git a/src/shared/components/editor/editor.tsx b/src/shared/components/editor/editor.tsx
new file mode 100644
index 0000000..2f1d942
--- /dev/null
+++ b/src/shared/components/editor/editor.tsx
@@ -0,0 +1,149 @@
+import { common, createLowlight } from 'lowlight';
+import LinkExtension from '@tiptap/extension-link';
+import ImageExtension from '@tiptap/extension-image';
+import StarterKitExtension from '@tiptap/starter-kit';
+import TextAlignExtension from '@tiptap/extension-text-align';
+import PlaceholderExtension from '@tiptap/extension-placeholder';
+import { useState, useEffect, forwardRef, useCallback } from 'react';
+import CodeBlockLowlightExtension from '@tiptap/extension-code-block-lowlight';
+import { useEditor, EditorContent, ReactNodeViewRenderer } from '@tiptap/react';
+
+import Stack from '@mui/material/Stack';
+import Portal from '@mui/material/Portal';
+import Backdrop from '@mui/material/Backdrop';
+import FormHelperText from '@mui/material/FormHelperText';
+
+import { Toolbar } from './toolbar';
+import { StyledRoot } from './styles';
+import { editorClasses } from './classes';
+import { CodeHighlightBlock } from './components/code-highlight-block';
+
+import type { EditorProps } from './types';
+
+// ----------------------------------------------------------------------
+
+export const Editor = forwardRef<HTMLDivElement, EditorProps>(
+  (
+    {
+      sx,
+      error,
+      onChange,
+      slotProps,
+      helperText,
+      resetValue,
+      editable = true,
+      fullItem = false,
+      value: content = '',
+      placeholder = 'Write something awesome...',
+      ...other
+    },
+    ref
+  ) => {
+    const [fullScreen, setFullScreen] = useState(false);
+
+    const handleToggleFullScreen = useCallback(() => {
+      setFullScreen((prev) => !prev);
+    }, []);
+
+    const lowlight = createLowlight(common);
+
+    const editor = useEditor({
+      content,
+      editable,
+      extensions: [
+        StarterKitExtension.configure({
+          codeBlock: false,
+          code: { HTMLAttributes: { class: editorClasses.content.codeInline } },
+          heading: { HTMLAttributes: { class: editorClasses.content.heading } },
+          horizontalRule: { HTMLAttributes: { class: editorClasses.content.hr } },
+          listItem: { HTMLAttributes: { class: editorClasses.content.listItem } },
+          blockquote: { HTMLAttributes: { class: editorClasses.content.blockquote } },
+          bulletList: { HTMLAttributes: { class: editorClasses.content.bulletList } },
+          orderedList: { HTMLAttributes: { class: editorClasses.content.orderedList } },
+        }),
+        PlaceholderExtension.configure({
+          placeholder,
+          emptyEditorClass: editorClasses.content.placeholder,
+        }),
+        ImageExtension.configure({ HTMLAttributes: { class: editorClasses.content.image } }),
+        TextAlignExtension.configure({ types: ['heading', 'paragraph'] }),
+        LinkExtension.configure({
+          autolink: true,
+          openOnClick: false,
+          HTMLAttributes: { class: editorClasses.content.link },
+        }),
+        CodeBlockLowlightExtension.extend({
+          addNodeView() {
+            return ReactNodeViewRenderer(CodeHighlightBlock);
+          },
+        }).configure({ lowlight, HTMLAttributes: { class: editorClasses.content.codeBlock } }),
+      ],
+      onUpdate({ editor: _editor }) {
+        const html = _editor.getHTML();
+        onChange?.(html);
+      },
+      ...other,
+    });
+
+    useEffect(() => {
+      const timer = setTimeout(() => {
+        if (editor?.isEmpty && content !== '<p></p>') {
+          editor.commands.setContent(content);
+        }
+      }, 100);
+      return () => clearTimeout(timer);
+    }, [content, editor]);
+
+    useEffect(() => {
+      if (resetValue && !content) {
+        editor?.commands.clearContent();
+      }
+      // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [content]);
+
+    useEffect(() => {
+      if (fullScreen) {
+        document.body.style.overflow = 'hidden';
+      } else {
+        document.body.style.overflow = '';
+      }
+    }, [fullScreen]);
+
+    return (
+      <Portal disablePortal={!fullScreen}>
+        {fullScreen && <Backdrop open sx={{ zIndex: (theme) => theme.zIndex.modal - 1 }} />}
+
+        <Stack sx={{ ...(!editable && { cursor: 'not-allowed' }), ...slotProps?.wrap }}>
+          <StyledRoot
+            error={!!error}
+            disabled={!editable}
+            fullScreen={fullScreen}
+            className={editorClasses.root}
+            sx={sx}
+          >
+            <Toolbar
+              editor={editor}
+              fullItem={fullItem}
+              fullScreen={fullScreen}
+              onToggleFullScreen={handleToggleFullScreen}
+            />
+            <EditorContent
+              ref={ref}
+              spellCheck="false"
+              autoComplete="off"
+              autoCapitalize="off"
+              editor={editor}
+              className={editorClasses.content.root}
+            />
+          </StyledRoot>
+
+          {helperText && (
+            <FormHelperText error={!!error} sx={{ px: 2 }}>
+              {helperText}
+            </FormHelperText>
+          )}
+        </Stack>
+      </Portal>
+    );
+  }
+);
diff --git a/src/shared/components/editor/index.ts b/src/shared/components/editor/index.ts
new file mode 100644
index 0000000..79f9fdf
--- /dev/null
+++ b/src/shared/components/editor/index.ts
@@ -0,0 +1,5 @@
+export * from './editor';
+
+export * from './classes';
+
+export type * from './types';
diff --git a/src/shared/components/editor/styles.tsx b/src/shared/components/editor/styles.tsx
new file mode 100644
index 0000000..c5c4659
--- /dev/null
+++ b/src/shared/components/editor/styles.tsx
@@ -0,0 +1,212 @@
+import type { StackProps } from '@mui/material/Stack';
+
+import Stack from '@mui/material/Stack';
+import { styled } from '@mui/material/styles';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { editorClasses } from './classes';
+
+// ----------------------------------------------------------------------
+
+const MARGIN = '0.75em';
+
+type StyledRootProps = StackProps & {
+  error?: boolean;
+  disabled?: boolean;
+  fullScreen?: boolean;
+};
+export const StyledRoot = styled(Stack, {
+  shouldForwardProp: (prop) => prop !== 'error' && prop !== 'disabled' && prop !== 'fullScreen',
+})<StyledRootProps>(({ error, disabled, fullScreen, theme }) => ({
+  minHeight: 240,
+  borderRadius: theme.shape.borderRadius,
+  border: `solid 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.2)}`,
+  scrollbarWidth: 'thin',
+  scrollbarColor: `${varAlpha(theme.vars.palette.text.disabledChannel, 0.4)} ${varAlpha(theme.vars.palette.text.disabledChannel, 0.08)}`,
+  /**
+   * State: error
+   */
+  ...(error && {
+    border: `solid 1px ${theme.vars.palette.error.main}`,
+  }),
+  /**
+   * State: disabled
+   */
+  ...(disabled && {
+    opacity: 0.48,
+    pointerEvents: 'none',
+  }),
+  /**
+   * State: fullScreen
+   */
+  ...(fullScreen && {
+    top: 16,
+    left: 16,
+    position: 'fixed',
+    zIndex: theme.zIndex.modal,
+    maxHeight: 'unset !important',
+    width: `calc(100% - ${32}px)`,
+    height: `calc(100% - ${32}px)`,
+    backgroundColor: theme.vars.palette.background.default,
+  }),
+  /**
+   * Placeholder
+   */
+  [`& .${editorClasses.content.placeholder}`]: {
+    '&:first-of-type::before': {
+      ...theme.typography.body2,
+      height: 0,
+      float: 'left',
+      pointerEvents: 'none',
+      content: 'attr(data-placeholder)',
+      color: theme.vars.palette.text.disabled,
+    },
+  },
+  /**
+   * Content
+   */
+  [`& .${editorClasses.content.root}`]: {
+    display: 'flex',
+    flex: '1 1 auto',
+    overflowY: 'auto',
+    flexDirection: 'column',
+    borderBottomLeftRadius: 'inherit',
+    borderBottomRightRadius: 'inherit',
+    backgroundColor: varAlpha(theme.vars.palette.grey['500Channel'], 0.08),
+    ...(error && {
+      backgroundColor: varAlpha(theme.vars.palette.error.mainChannel, 0.08),
+    }),
+    '& .tiptap': {
+      '> * + *': {
+        marginTop: 0,
+        marginBottom: MARGIN,
+      },
+      '&.ProseMirror': {
+        flex: '1 1 auto',
+        outline: 'none',
+        padding: theme.spacing(0, 2),
+      },
+      /**
+       * Heading & Paragraph
+       */
+      h1: { ...theme.typography.h1, marginTop: 40, marginBottom: 8 },
+      h2: { ...theme.typography.h2, marginTop: 40, marginBottom: 8 },
+      h3: { ...theme.typography.h3, marginTop: 24, marginBottom: 8 },
+      h4: { ...theme.typography.h4, marginTop: 24, marginBottom: 8 },
+      h5: { ...theme.typography.h5, marginTop: 24, marginBottom: 8 },
+      h6: { ...theme.typography.h6, marginTop: 24, marginBottom: 8 },
+      p: { ...theme.typography.body1, marginBottom: '1.25rem' },
+      [`& .${editorClasses.content.heading}`]: {},
+      /**
+       * Link
+       */
+      [`& .${editorClasses.content.link}`]: {
+        color: theme.vars.palette.primary.main,
+      },
+      /**
+       * Hr Divider
+       */
+      [`& .${editorClasses.content.hr}`]: {
+        flexShrink: 0,
+        borderWidth: 0,
+        margin: '2em 0',
+        msFlexNegative: 0,
+        WebkitFlexShrink: 0,
+        borderStyle: 'solid',
+        borderBottomWidth: 'thin',
+        borderColor: theme.vars.palette.divider,
+      },
+      /**
+       * Image
+       */ [`& .${editorClasses.content.image}`]: {
+        width: '100%',
+        height: 'auto',
+        maxWidth: '100%',
+        margin: 'auto auto 1.25em',
+      },
+      /**
+       * List
+       */ [`& .${editorClasses.content.bulletList}`]: {
+        paddingLeft: 16,
+        listStyleType: 'disc',
+      },
+      [`& .${editorClasses.content.orderedList}`]: {
+        paddingLeft: 16,
+      },
+      [`& .${editorClasses.content.listItem}`]: {
+        lineHeight: 2,
+        '& > p': { margin: 0, display: 'inline-block' },
+      },
+      /**
+       * Blockquote
+       */
+      [`& .${editorClasses.content.blockquote}`]: {
+        lineHeight: 1.5,
+        fontSize: '1.5em',
+        margin: '24px auto',
+        position: 'relative',
+        fontFamily: 'Georgia, serif',
+        padding: theme.spacing(3, 3, 3, 8),
+        color: theme.vars.palette.text.secondary,
+        borderLeft: `solid 8px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.08)}`,
+        [theme.breakpoints.up('md')]: {
+          width: '100%',
+          maxWidth: 640,
+        },
+        '& p': {
+          margin: 0,
+          fontSize: 'inherit',
+          fontFamily: 'inherit',
+        },
+        '&::before': {
+          left: 16,
+          top: -8,
+          display: 'block',
+          fontSize: '3em',
+          content: '"\\201C"',
+          position: 'absolute',
+          color: theme.vars.palette.text.disabled,
+        },
+      },
+      /**
+       * Code inline
+       */
+      [`& .${editorClasses.content.codeInline}`]: {
+        padding: theme.spacing(0.25, 0.5),
+        color: theme.vars.palette.text.secondary,
+        fontSize: theme.typography.body2.fontSize,
+        borderRadius: theme.shape.borderRadius / 2,
+        backgroundColor: varAlpha(theme.vars.palette.grey['500Channel'], 0.2),
+      },
+      /**
+       * Code block
+       */
+      [`& .${editorClasses.content.codeBlock}`]: {
+        position: 'relative',
+        '& pre': {
+          overflowX: 'auto',
+          color: theme.vars.palette.common.white,
+          padding: theme.spacing(5, 3, 3, 3),
+          borderRadius: theme.shape.borderRadius,
+          backgroundColor: theme.vars.palette.grey[900],
+          fontFamily: "'JetBrainsMono', monospace",
+          '& code': { fontSize: theme.typography.body2.fontSize },
+        },
+        [`& .${editorClasses.content.langSelect}`]: {
+          top: 8,
+          right: 8,
+          zIndex: 1,
+          padding: 4,
+          outline: 'none',
+          borderRadius: 4,
+          position: 'absolute',
+          color: theme.vars.palette.common.white,
+          fontWeight: theme.typography.fontWeightMedium,
+          borderColor: varAlpha(theme.vars.palette.grey['500Channel'], 0.08),
+          backgroundColor: varAlpha(theme.vars.palette.grey['500Channel'], 0.08),
+        },
+      },
+    },
+  },
+}));
diff --git a/src/shared/components/editor/toolbar.tsx b/src/shared/components/editor/toolbar.tsx
new file mode 100644
index 0000000..47f11d4
--- /dev/null
+++ b/src/shared/components/editor/toolbar.tsx
@@ -0,0 +1,238 @@
+import Stack from '@mui/material/Stack';
+import Divider from '@mui/material/Divider';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { editorClasses } from './classes';
+import { LinkBlock } from './components/link-block';
+import { ImageBlock } from './components/image-block';
+import { ToolbarItem } from './components/toolbar-item';
+import { HeadingBlock } from './components/heading-block';
+
+import type { EditorToolbarProps } from './types';
+
+// ----------------------------------------------------------------------
+
+/**
+ * https://remixicon.com
+ */
+
+export function Toolbar({ editor, fullItem, fullScreen, onToggleFullScreen }: EditorToolbarProps) {
+  if (!editor) {
+    return null;
+  }
+
+  return (
+    <Stack
+      spacing={1}
+      direction="row"
+      flexWrap="wrap"
+      alignItems="center"
+      divider={<Divider orientation="vertical" flexItem sx={{ height: 16, my: 'auto' }} />}
+      className={editorClasses.toolbar.root}
+      sx={{
+        p: 1.25,
+        bgcolor: 'background.paper',
+        borderTopRightRadius: 'inherit',
+        borderTopLeftRadius: 'inherit',
+        borderBottom: (theme) =>
+          `solid 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.2)}`,
+      }}
+    >
+      <HeadingBlock editor={editor} />
+
+      {/* Text style */}
+      <Stack direction="row" spacing={0.5}>
+        <ToolbarItem
+          aria-label="Bold"
+          active={editor.isActive('bold')}
+          className={editorClasses.toolbar.bold}
+          onClick={() => editor.chain().focus().toggleBold().run()}
+          icon={
+            <path d="M8 11H12.5C13.8807 11 15 9.88071 15 8.5C15 7.11929 13.8807 6 12.5 6H8V11ZM18 15.5C18 17.9853 15.9853 20 13.5 20H6V4H12.5C14.9853 4 17 6.01472 17 8.5C17 9.70431 16.5269 10.7981 15.7564 11.6058C17.0979 12.3847 18 13.837 18 15.5ZM8 13V18H13.5C14.8807 18 16 16.8807 16 15.5C16 14.1193 14.8807 13 13.5 13H8Z" />
+          }
+        />
+        <ToolbarItem
+          aria-label="Italic"
+          active={editor.isActive('italic')}
+          className={editorClasses.toolbar.italic}
+          onClick={() => editor.chain().focus().toggleItalic().run()}
+          icon={<path d="M15 20H7V18H9.92661L12.0425 6H9V4H17V6H14.0734L11.9575 18H15V20Z" />}
+        />
+        <ToolbarItem
+          aria-label="Strike"
+          active={editor.isActive('strike')}
+          className={editorClasses.toolbar.italic}
+          onClick={() => editor.chain().focus().toggleStrike().run()}
+          icon={
+            <path d="M17.1538 14C17.3846 14.5161 17.5 15.0893 17.5 15.7196C17.5 17.0625 16.9762 18.1116 15.9286 18.867C14.8809 19.6223 13.4335 20 11.5862 20C9.94674 20 8.32335 19.6185 6.71592 18.8555V16.6009C8.23538 17.4783 9.7908 17.917 11.3822 17.917C13.9333 17.917 15.2128 17.1846 15.2208 15.7196C15.2208 15.0939 15.0049 14.5598 14.5731 14.1173C14.5339 14.0772 14.4939 14.0381 14.4531 14H3V12H21V14H17.1538ZM13.076 11H7.62908C7.4566 10.8433 7.29616 10.6692 7.14776 10.4778C6.71592 9.92084 6.5 9.24559 6.5 8.45207C6.5 7.21602 6.96583 6.165 7.89749 5.299C8.82916 4.43299 10.2706 4 12.2219 4C13.6934 4 15.1009 4.32808 16.4444 4.98426V7.13591C15.2448 6.44921 13.9293 6.10587 12.4978 6.10587C10.0187 6.10587 8.77917 6.88793 8.77917 8.45207C8.77917 8.87172 8.99709 9.23796 9.43293 9.55079C9.86878 9.86362 10.4066 10.1135 11.0463 10.3004C11.6665 10.4816 12.3431 10.7148 13.076 11H13.076Z" />
+          }
+        />
+      </Stack>
+
+      {/* List */}
+      <Stack direction="row" spacing={0.5}>
+        <ToolbarItem
+          aria-label="Bullet list"
+          active={editor.isActive('bulletList')}
+          className={editorClasses.toolbar.bulletList}
+          onClick={() => editor.chain().focus().toggleBulletList().run()}
+          icon={
+            <path d="M8 4H21V6H8V4ZM4.5 6.5C3.67157 6.5 3 5.82843 3 5C3 4.17157 3.67157 3.5 4.5 3.5C5.32843 3.5 6 4.17157 6 5C6 5.82843 5.32843 6.5 4.5 6.5ZM4.5 13.5C3.67157 13.5 3 12.8284 3 12C3 11.1716 3.67157 10.5 4.5 10.5C5.32843 10.5 6 11.1716 6 12C6 12.8284 5.32843 13.5 4.5 13.5ZM4.5 20.4C3.67157 20.4 3 19.7284 3 18.9C3 18.0716 3.67157 17.4 4.5 17.4C5.32843 17.4 6 18.0716 6 18.9C6 19.7284 5.32843 20.4 4.5 20.4ZM8 11H21V13H8V11ZM8 18H21V20H8V18Z" />
+          }
+        />
+        <ToolbarItem
+          aria-label="Ordered list"
+          active={editor.isActive('orderedList')}
+          className={editorClasses.toolbar.orderedList}
+          onClick={() => editor.chain().focus().toggleOrderedList().run()}
+          icon={
+            <path d="M8 4H21V6H8V4ZM5 3V6H6V7H3V6H4V4H3V3H5ZM3 14V11.5H5V11H3V10H6V12.5H4V13H6V14H3ZM5 19.5H3V18.5H5V18H3V17H6V21H3V20H5V19.5ZM8 11H21V13H8V11ZM8 18H21V20H8V18Z" />
+          }
+        />
+      </Stack>
+
+      {/* Text align */}
+      <Stack direction="row" spacing={0.5}>
+        <ToolbarItem
+          aria-label="Align left"
+          active={editor.isActive({ textAlign: 'left' })}
+          className={editorClasses.toolbar.alignLeft}
+          onClick={() => editor.chain().focus().setTextAlign('left').run()}
+          icon={<path d="M3 4H21V6H3V4ZM3 19H17V21H3V19ZM3 14H21V16H3V14ZM3 9H17V11H3V9Z" />}
+        />
+        <ToolbarItem
+          aria-label="Align center"
+          active={editor.isActive({ textAlign: 'center' })}
+          className={editorClasses.toolbar.alignCenter}
+          onClick={() => editor.chain().focus().setTextAlign('center').run()}
+          icon={<path d="M3 4H21V6H3V4ZM5 19H19V21H5V19ZM3 14H21V16H3V14ZM5 9H19V11H5V9Z" />}
+        />
+        <ToolbarItem
+          aria-label="Align right"
+          active={editor.isActive({ textAlign: 'right' })}
+          className={editorClasses.toolbar.alignRight}
+          onClick={() => editor.chain().focus().setTextAlign('right').run()}
+          icon={<path d="M3 4H21V6H3V4ZM7 19H21V21H7V19ZM3 14H21V16H3V14ZM7 9H21V11H7V9Z" />}
+        />
+        <ToolbarItem
+          aria-label="Align justify"
+          active={editor.isActive({ textAlign: 'justify' })}
+          className={editorClasses.toolbar.alignJustify}
+          onClick={() => editor.chain().focus().setTextAlign('justify').run()}
+          icon={<path d="M3 4H21V6H3V4ZM3 19H21V21H3V19ZM3 14H21V16H3V14ZM3 9H21V11H3V9Z" />}
+        />
+      </Stack>
+
+      {/* Code - Code block */}
+      {fullItem && (
+        <Stack direction="row" spacing={0.5}>
+          <ToolbarItem
+            aria-label="Align justify"
+            active={editor.isActive('code')}
+            className={editorClasses.toolbar.code}
+            onClick={() => editor.chain().focus().toggleCode().run()}
+            icon={
+              <path d="M16.95 8.46448L18.3642 7.05026L23.3139 12L18.3642 16.9498L16.95 15.5355L20.4855 12L16.95 8.46448ZM7.05048 8.46448L3.51495 12L7.05048 15.5355L5.63627 16.9498L0.686523 12L5.63627 7.05026L7.05048 8.46448Z" />
+            }
+          />
+          <ToolbarItem
+            aria-label="Align justify"
+            active={editor.isActive('codeBlock')}
+            className={editorClasses.toolbar.codeBlock}
+            onClick={() => editor.chain().focus().toggleCodeBlock().run()}
+            icon={
+              <path d="M3 3H21C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3ZM4 5V19H20V5H4ZM20 12L16.4645 15.5355L15.0503 14.1213L17.1716 12L15.0503 9.87868L16.4645 8.46447L20 12ZM6.82843 12L8.94975 14.1213L7.53553 15.5355L4 12L7.53553 8.46447L8.94975 9.87868L6.82843 12ZM11.2443 17H9.11597L12.7557 7H14.884L11.2443 17Z" />
+            }
+          />
+        </Stack>
+      )}
+
+      {/* Blockquote - Hr line */}
+      {fullItem && (
+        <Stack direction="row" spacing={0.5}>
+          <ToolbarItem
+            aria-label="Blockquote"
+            active={editor.isActive('blockquote')}
+            className={editorClasses.toolbar.blockquote}
+            onClick={() => editor.chain().focus().toggleBlockquote().run()}
+            icon={
+              <path d="M4.58341 17.3211C3.55316 16.2274 3 15 3 13.0103C3 9.51086 5.45651 6.37366 9.03059 4.82318L9.92328 6.20079C6.58804 8.00539 5.93618 10.346 5.67564 11.822C6.21263 11.5443 6.91558 11.4466 7.60471 11.5105C9.40908 11.6778 10.8312 13.159 10.8312 15C10.8312 16.933 9.26416 18.5 7.33116 18.5C6.2581 18.5 5.23196 18.0095 4.58341 17.3211ZM14.5834 17.3211C13.5532 16.2274 13 15 13 13.0103C13 9.51086 15.4565 6.37366 19.0306 4.82318L19.9233 6.20079C16.588 8.00539 15.9362 10.346 15.6756 11.822C16.2126 11.5443 16.9156 11.4466 17.6047 11.5105C19.4091 11.6778 20.8312 13.159 20.8312 15C20.8312 16.933 19.2642 18.5 17.3312 18.5C16.2581 18.5 15.232 18.0095 14.5834 17.3211Z" />
+            }
+          />
+          <ToolbarItem
+            aria-label="Horizontal"
+            className={editorClasses.toolbar.hr}
+            onClick={() => editor.chain().focus().setHorizontalRule().run()}
+            icon={<path d="M2 11H4V13H2V11ZM6 11H18V13H6V11ZM20 11H22V13H20V11Z" />}
+          />
+        </Stack>
+      )}
+
+      {/* Link - Image */}
+      <Stack direction="row" spacing={0.5}>
+        <LinkBlock editor={editor} />
+        <ImageBlock editor={editor} />
+      </Stack>
+
+      {/* HardBreak - Clear */}
+      <Stack direction="row" spacing={0.5}>
+        <ToolbarItem
+          aria-label="HardBreak"
+          onClick={() => editor.chain().focus().setHardBreak().run()}
+          className={editorClasses.toolbar.hardbreak}
+          icon={
+            <path d="M15 18H16.5C17.8807 18 19 16.8807 19 15.5C19 14.1193 17.8807 13 16.5 13H3V11H16.5C18.9853 11 21 13.0147 21 15.5C21 17.9853 18.9853 20 16.5 20H15V22L11 19L15 16V18ZM3 4H21V6H3V4ZM9 18V20H3V18H9Z" />
+          }
+        />
+        <ToolbarItem
+          aria-label="Clear"
+          className={editorClasses.toolbar.clear}
+          onClick={() => editor.chain().focus().clearNodes().unsetAllMarks().run()}
+          icon={
+            <path d="M12.6512 14.0654L11.6047 20H9.57389L10.9247 12.339L3.51465 4.92892L4.92886 3.51471L20.4852 19.0711L19.071 20.4853L12.6512 14.0654ZM11.7727 7.53009L12.0425 5.99999H10.2426L8.24257 3.99999H19.9999V5.99999H14.0733L13.4991 9.25652L11.7727 7.53009Z" />
+          }
+        />
+      </Stack>
+
+      {/* Undo - Redo */}
+      {fullItem && (
+        <Stack direction="row" spacing={0.5}>
+          <ToolbarItem
+            aria-label="Undo"
+            className={editorClasses.toolbar.undo}
+            disabled={!editor.can().chain().focus().undo().run()}
+            onClick={() => editor.chain().focus().undo().run()}
+            icon={
+              <path d="M8 7V11L2 6L8 1V5H13C17.4183 5 21 8.58172 21 13C21 17.4183 17.4183 21 13 21H4V19H13C16.3137 19 19 16.3137 19 13C19 9.68629 16.3137 7 13 7H8Z" />
+            }
+          />
+          <ToolbarItem
+            aria-label="Redo"
+            className={editorClasses.toolbar.redo}
+            disabled={!editor.can().chain().focus().redo().run()}
+            onClick={() => editor.chain().focus().redo().run()}
+            icon={
+              <path d="M16 7H11C7.68629 7 5 9.68629 5 13C5 16.3137 7.68629 19 11 19H20V21H11C6.58172 21 3 17.4183 3 13C3 8.58172 6.58172 5 11 5H16V1L22 6L16 11V7Z" />
+            }
+          />
+        </Stack>
+      )}
+
+      <Stack direction="row" spacing={0.5}>
+        <ToolbarItem
+          aria-label="Fullscreen"
+          className={editorClasses.toolbar.fullscreen}
+          onClick={onToggleFullScreen}
+          icon={
+            fullScreen ? (
+              <path d="M18 7H22V9H16V3H18V7ZM8 9H2V7H6V3H8V9ZM18 17V21H16V15H22V17H18ZM8 15V21H6V17H2V15H8Z" />
+            ) : (
+              <path d="M16 3H22V9H20V5H16V3ZM2 3H8V5H4V9H2V3ZM20 19V15H22V21H16V19H20ZM4 19H8V21H2V15H4V19Z" />
+            )
+          }
+        />
+      </Stack>
+    </Stack>
+  );
+}
diff --git a/src/shared/components/editor/types.ts b/src/shared/components/editor/types.ts
new file mode 100644
index 0000000..3598e39
--- /dev/null
+++ b/src/shared/components/editor/types.ts
@@ -0,0 +1,42 @@
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { Editor, Extension, EditorOptions } from '@tiptap/react';
+
+// ----------------------------------------------------------------------
+
+export type EditorProps = Partial<EditorOptions> & {
+  value?: string;
+  error?: boolean;
+  fullItem?: boolean;
+  resetValue?: boolean;
+  sx?: SxProps<Theme>;
+  placeholder?: string;
+  helperText?: React.ReactNode;
+  onChange?: (value: string) => void;
+  slotProps?: {
+    wrap: SxProps<Theme>;
+  };
+};
+
+export type EditorToolbarProps = {
+  fullScreen: boolean;
+  editor: Editor | null;
+  onToggleFullScreen: () => void;
+  fullItem?: EditorProps['fullItem'];
+};
+
+export type EditorToolbarItemProps = {
+  icon?: React.ReactNode;
+  label?: string;
+  active?: boolean;
+  disabled?: boolean;
+};
+
+export type EditorCodeHighlightBlockProps = {
+  extension: Extension;
+  updateAttributes: (attributes: Record<string, any>) => void;
+  node: {
+    attrs: {
+      language: string;
+    };
+  };
+};
diff --git a/src/shared/components/empty-content/empty-content.tsx b/src/shared/components/empty-content/empty-content.tsx
new file mode 100644
index 0000000..e011ad5
--- /dev/null
+++ b/src/shared/components/empty-content/empty-content.tsx
@@ -0,0 +1,82 @@
+import type { StackProps } from '@mui/material/Stack';
+import type { Theme, SxProps } from '@mui/material/styles';
+
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+
+import { CONFIG } from 'src/config-global';
+import { varAlpha } from 'src/shared/theme/styles';
+
+// ----------------------------------------------------------------------
+
+export type EmptyContentProps = StackProps & {
+  title?: string;
+  imgUrl?: string;
+  filled?: boolean;
+  description?: string;
+  action?: React.ReactNode;
+  slotProps?: {
+    img?: SxProps<Theme>;
+    title?: SxProps<Theme>;
+    description?: SxProps<Theme>;
+  };
+};
+
+export function EmptyContent({
+  sx,
+  imgUrl,
+  action,
+  filled,
+  slotProps,
+  description,
+  title = 'No data',
+  ...other
+}: EmptyContentProps) {
+  return (
+    <Stack
+      flexGrow={1}
+      alignItems="center"
+      justifyContent="center"
+      sx={{
+        px: 3,
+        height: 1,
+        ...(filled && {
+          borderRadius: 2,
+          bgcolor: (theme) => varAlpha(theme.vars.palette.grey['500Channel'], 0.04),
+          border: (theme) => `dashed 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.08)}`,
+        }),
+        ...sx,
+      }}
+      {...other}
+    >
+      <Box
+        component="img"
+        alt="empty content"
+        src={imgUrl ?? `${CONFIG.site.basePath}/assets/icons/empty/ic-content.svg`}
+        sx={{ width: 1, maxWidth: 160, ...slotProps?.img }}
+      />
+
+      {title && (
+        <Typography
+          variant="h6"
+          component="span"
+          sx={{ mt: 1, textAlign: 'center', ...slotProps?.title, color: 'text.disabled' }}
+        >
+          {title}
+        </Typography>
+      )}
+
+      {description && (
+        <Typography
+          variant="caption"
+          sx={{ mt: 1, textAlign: 'center', color: 'text.disabled', ...slotProps?.description }}
+        >
+          {description}
+        </Typography>
+      )}
+
+      {action && action}
+    </Stack>
+  );
+}
diff --git a/src/shared/components/empty-content/index.ts b/src/shared/components/empty-content/index.ts
new file mode 100644
index 0000000..026ff32
--- /dev/null
+++ b/src/shared/components/empty-content/index.ts
@@ -0,0 +1 @@
+export * from './empty-content';
diff --git a/src/shared/components/filters-result/filters-block.tsx b/src/shared/components/filters-result/filters-block.tsx
new file mode 100644
index 0000000..6cb2ae1
--- /dev/null
+++ b/src/shared/components/filters-result/filters-block.tsx
@@ -0,0 +1,47 @@
+import type { Theme, SxProps } from '@mui/material/styles';
+
+import Box from '@mui/material/Box';
+
+// ----------------------------------------------------------------------
+
+export type FilterBlockProps = {
+  label: string;
+  isShow: boolean;
+  sx?: SxProps<Theme>;
+  children: React.ReactNode;
+};
+
+export function FiltersBlock({ label, children, isShow, sx }: FilterBlockProps) {
+  if (!isShow) {
+    return null;
+  }
+
+  return (
+    <Box
+      gap={1}
+      display="flex"
+      sx={{
+        p: 1,
+        borderRadius: 1,
+        overflow: 'hidden',
+        border: (theme) => `dashed 1px ${theme.vars.palette.divider}`,
+        ...sx,
+      }}
+    >
+      <Box
+        component="span"
+        sx={{
+          height: 24,
+          lineHeight: '24px',
+          fontSize: (theme) => theme.typography.subtitle2.fontSize,
+          fontWeight: (theme) => theme.typography.subtitle2.fontWeight,
+        }}
+      >
+        {label}
+      </Box>
+      <Box gap={1} display="flex" flexWrap="wrap">
+        {children}
+      </Box>
+    </Box>
+  );
+}
diff --git a/src/shared/components/filters-result/filters-result.tsx b/src/shared/components/filters-result/filters-result.tsx
new file mode 100644
index 0000000..3b349f6
--- /dev/null
+++ b/src/shared/components/filters-result/filters-result.tsx
@@ -0,0 +1,46 @@
+import type { ChipProps } from '@mui/material/Chip';
+import type { Theme, SxProps } from '@mui/material/styles';
+
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+
+import { Iconify } from 'src/shared/components/iconify';
+
+// ----------------------------------------------------------------------
+
+export const chipProps: ChipProps = {
+  size: 'small',
+  variant: 'soft',
+};
+
+type FiltersResultProps = {
+  totalResults: number;
+  onReset: () => void;
+  sx?: SxProps<Theme>;
+  children: React.ReactNode;
+};
+
+export function FiltersResult({ totalResults, onReset, sx, children }: FiltersResultProps) {
+  return (
+    <Box sx={sx}>
+      <Box sx={{ mb: 1.5, typography: 'body2' }}>
+        <strong>{totalResults}</strong>
+        <Box component="span" sx={{ color: 'text.secondary', ml: 0.25 }}>
+          results found
+        </Box>
+      </Box>
+
+      <Box flexGrow={1} gap={1} display="flex" flexWrap="wrap" alignItems="center">
+        {children}
+
+        <Button
+          color="error"
+          onClick={onReset}
+          startIcon={<Iconify icon="solar:trash-bin-trash-bold" />}
+        >
+          Clear
+        </Button>
+      </Box>
+    </Box>
+  );
+}
diff --git a/src/shared/components/filters-result/index.ts b/src/shared/components/filters-result/index.ts
new file mode 100644
index 0000000..edbcbbe
--- /dev/null
+++ b/src/shared/components/filters-result/index.ts
@@ -0,0 +1,3 @@
+export * from './filters-block';
+
+export * from './filters-result';
diff --git a/src/shared/components/hook-form/fields.tsx b/src/shared/components/hook-form/fields.tsx
index 360f763..d4dde8a 100644
--- a/src/shared/components/hook-form/fields.tsx
+++ b/src/shared/components/hook-form/fields.tsx
@@ -1,7 +1,39 @@
+import { RHFCode } from './rhf-code';
+import { RHFRating } from './rhf-rating';
+import { RHFEditor } from './rhf-editor';
+import { RHFSlider } from './rhf-slider';
 import { RHFTextField } from './rhf-text-field';
+import { RHFRadioGroup } from './rhf-radio-group';
+import { RHFPhoneInput } from './rhf-phone-input';
+import { RHFAutocomplete } from './rhf-autocomplete';
+import { RHFCountrySelect } from './rhf-country-select';
+import { RHFSwitch, RHFMultiSwitch } from './rhf-switch';
+import { RHFSelect, RHFMultiSelect } from './rhf-select';
+import { RHFCheckbox, RHFMultiCheckbox } from './rhf-checkbox';
+import { RHFUpload, RHFUploadBox, RHFUploadAvatar } from './rhf-upload';
+import { RHFDatePicker, RHFMobileDateTimePicker } from './rhf-date-picker';
 
 // ----------------------------------------------------------------------
 
 export const Field = {
+  Code: RHFCode,
+  Editor: RHFEditor,
+  Select: RHFSelect,
+  Upload: RHFUpload,
+  Switch: RHFSwitch,
+  Slider: RHFSlider,
+  Rating: RHFRating,
   Text: RHFTextField,
+  Phone: RHFPhoneInput,
+  Checkbox: RHFCheckbox,
+  UploadBox: RHFUploadBox,
+  RadioGroup: RHFRadioGroup,
+  DatePicker: RHFDatePicker,
+  MultiSelect: RHFMultiSelect,
+  MultiSwitch: RHFMultiSwitch,
+  UploadAvatar: RHFUploadAvatar,
+  Autocomplete: RHFAutocomplete,
+  MultiCheckbox: RHFMultiCheckbox,
+  CountrySelect: RHFCountrySelect,
+  MobileDateTimePicker: RHFMobileDateTimePicker,
 };
diff --git a/src/shared/components/hook-form/index.ts b/src/shared/components/hook-form/index.ts
index 6f27fc3..dedc8e1 100644
--- a/src/shared/components/hook-form/index.ts
+++ b/src/shared/components/hook-form/index.ts
@@ -1,5 +1,33 @@
 export * from './fields';
 
+export * from './rhf-code';
+
+export * from './rhf-upload';
+
+export * from './rhf-select';
+
+export * from './rhf-rating';
+
+export * from './rhf-switch';
+
+export * from './rhf-editor';
+
+export * from './rhf-slider';
+
+export * from './rhf-checkbox';
+
+export * from './schema-helper';
+
 export * from './form-provider';
 
 export * from './rhf-text-field';
+
+export * from './rhf-date-picker';
+
+export * from './rhf-radio-group';
+
+export * from './rhf-phone-input';
+
+export * from './rhf-autocomplete';
+
+export * from './rhf-country-select';
diff --git a/src/shared/components/hook-form/rhf-autocomplete.tsx b/src/shared/components/hook-form/rhf-autocomplete.tsx
new file mode 100644
index 0000000..adc9da4
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-autocomplete.tsx
@@ -0,0 +1,57 @@
+import type { AutocompleteProps } from '@mui/material/Autocomplete';
+
+import { Controller, useFormContext } from 'react-hook-form';
+
+import TextField from '@mui/material/TextField';
+import Autocomplete from '@mui/material/Autocomplete';
+
+// ----------------------------------------------------------------------
+
+export type AutocompleteBaseProps = Omit<
+  AutocompleteProps<any, boolean, boolean, boolean>,
+  'renderInput'
+>;
+
+export type RHFAutocompleteProps = AutocompleteBaseProps & {
+  name: string;
+  label?: string;
+  placeholder?: string;
+  hiddenLabel?: boolean;
+  helperText?: React.ReactNode;
+};
+
+export function RHFAutocomplete({
+  name,
+  label,
+  helperText,
+  hiddenLabel,
+  placeholder,
+  ...other
+}: RHFAutocompleteProps) {
+  const { control, setValue } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <Autocomplete
+          {...field}
+          id={`rhf-autocomplete-${name}`}
+          onChange={(event, newValue) => setValue(name, newValue, { shouldValidate: true })}
+          renderInput={(params) => (
+            <TextField
+              {...params}
+              label={label}
+              placeholder={placeholder}
+              error={!!error}
+              helperText={error ? error?.message : helperText}
+              inputProps={{ ...params.inputProps, autoComplete: 'new-password' }}
+            />
+          )}
+          {...other}
+        />
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-checkbox.tsx b/src/shared/components/hook-form/rhf-checkbox.tsx
new file mode 100644
index 0000000..1838e6f
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-checkbox.tsx
@@ -0,0 +1,150 @@
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { CheckboxProps } from '@mui/material/Checkbox';
+import type { FormGroupProps } from '@mui/material/FormGroup';
+import type { FormLabelProps } from '@mui/material/FormLabel';
+import type { FormHelperTextProps } from '@mui/material/FormHelperText';
+import type { FormControlLabelProps } from '@mui/material/FormControlLabel';
+
+import { Controller, useFormContext } from 'react-hook-form';
+
+import Box from '@mui/material/Box';
+import Checkbox from '@mui/material/Checkbox';
+import FormGroup from '@mui/material/FormGroup';
+import FormLabel from '@mui/material/FormLabel';
+import FormControl from '@mui/material/FormControl';
+import FormHelperText from '@mui/material/FormHelperText';
+import FormControlLabel from '@mui/material/FormControlLabel';
+
+// ----------------------------------------------------------------------
+
+type RHFCheckboxProps = Omit<FormControlLabelProps, 'control'> & {
+  name: string;
+  helperText?: React.ReactNode;
+  slotProps?: {
+    wrap?: SxProps<Theme>;
+    checkbox?: CheckboxProps;
+    formHelperText?: FormHelperTextProps;
+  };
+};
+
+export function RHFCheckbox({ name, helperText, label, slotProps, ...other }: RHFCheckboxProps) {
+  const { control } = useFormContext();
+
+  const ariaLabel = `Checkbox ${name}`;
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <Box sx={slotProps?.wrap}>
+          <FormControlLabel
+            control={
+              <Checkbox
+                {...field}
+                checked={field.value}
+                {...slotProps?.checkbox}
+                inputProps={{
+                  ...(!label && { 'aria-label': ariaLabel }),
+                  ...slotProps?.checkbox?.inputProps,
+                }}
+              />
+            }
+            label={label}
+            {...other}
+          />
+
+          {(!!error || helperText) && (
+            <FormHelperText error={!!error} {...slotProps?.formHelperText}>
+              {error ? error?.message : helperText}
+            </FormHelperText>
+          )}
+        </Box>
+      )}
+    />
+  );
+}
+
+// ----------------------------------------------------------------------
+
+type RHFMultiCheckboxProps = FormGroupProps & {
+  name: string;
+  label?: string;
+  helperText?: React.ReactNode;
+  slotProps?: {
+    wrap?: SxProps<Theme>;
+    checkbox?: CheckboxProps;
+    formLabel?: FormLabelProps;
+    formHelperText?: FormHelperTextProps;
+  };
+  options: {
+    label: string;
+    value: string;
+  }[];
+};
+
+export function RHFMultiCheckbox({
+  name,
+  label,
+  options,
+  slotProps,
+  helperText,
+  ...other
+}: RHFMultiCheckboxProps) {
+  const { control } = useFormContext();
+
+  const getSelected = (selectedItems: string[], item: string) =>
+    selectedItems.includes(item)
+      ? selectedItems.filter((value) => value !== item)
+      : [...selectedItems, item];
+
+  const accessibility = (val: string) => val;
+  const ariaLabel = (val: string) => `Checkbox ${val}`;
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <FormControl component="fieldset" sx={slotProps?.wrap}>
+          {label && (
+            <FormLabel
+              component="legend"
+              {...slotProps?.formLabel}
+              sx={{ mb: 1, typography: 'body2', ...slotProps?.formLabel?.sx }}
+            >
+              {label}
+            </FormLabel>
+          )}
+
+          <FormGroup {...other}>
+            {options.map((option) => (
+              <FormControlLabel
+                key={option.value}
+                control={
+                  <Checkbox
+                    checked={field.value.includes(option.value)}
+                    onChange={() => field.onChange(getSelected(field.value, option.value))}
+                    name={accessibility(option.label)}
+                    {...slotProps?.checkbox}
+                    inputProps={{
+                      ...(!option.label && { 'aria-label': ariaLabel(option.label) }),
+                      ...slotProps?.checkbox?.inputProps,
+                    }}
+                  />
+                }
+                label={option.label}
+              />
+            ))}
+          </FormGroup>
+
+          {(!!error || helperText) && (
+            <FormHelperText error={!!error} sx={{ mx: 0 }} {...slotProps?.formHelperText}>
+              {error ? error?.message : helperText}
+            </FormHelperText>
+          )}
+        </FormControl>
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-code.tsx b/src/shared/components/hook-form/rhf-code.tsx
new file mode 100644
index 0000000..c9d8413
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-code.tsx
@@ -0,0 +1,41 @@
+import type { MuiOtpInputProps } from 'mui-one-time-password-input';
+
+import { MuiOtpInput } from 'mui-one-time-password-input';
+import { Controller, useFormContext } from 'react-hook-form';
+
+import FormHelperText from '@mui/material/FormHelperText';
+
+// ----------------------------------------------------------------------
+
+type RHFCodesProps = MuiOtpInputProps & {
+  name: string;
+};
+
+export function RHFCode({ name, ...other }: RHFCodesProps) {
+  const { control } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <div>
+          <MuiOtpInput
+            {...field}
+            autoFocus
+            gap={1.5}
+            length={6}
+            TextFieldsProps={{ error: !!error, placeholder: '-' }}
+            {...other}
+          />
+
+          {error && (
+            <FormHelperText sx={{ px: 2 }} error>
+              {error.message}
+            </FormHelperText>
+          )}
+        </div>
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-country-select.tsx b/src/shared/components/hook-form/rhf-country-select.tsx
new file mode 100644
index 0000000..a33368b
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-country-select.tsx
@@ -0,0 +1,34 @@
+import type { CountrySelectProps } from 'src/shared/components/country-select';
+
+import { Controller, useFormContext } from 'react-hook-form';
+
+import { CountrySelect } from 'src/shared/components/country-select';
+
+// ----------------------------------------------------------------------
+
+export function RHFCountrySelect({
+  name,
+  helperText,
+  ...other
+}: CountrySelectProps & {
+  name: string;
+}) {
+  const { control, setValue } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <CountrySelect
+          id={`rhf-country-select-${name}`}
+          value={field.value}
+          onChange={(event, newValue) => setValue(name, newValue, { shouldValidate: true })}
+          error={!!error}
+          helperText={error?.message ?? helperText}
+          {...other}
+        />
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-date-picker.tsx b/src/shared/components/hook-form/rhf-date-picker.tsx
new file mode 100644
index 0000000..1e9c70f
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-date-picker.tsx
@@ -0,0 +1,86 @@
+import type { Dayjs } from 'dayjs';
+import type { TextFieldProps } from '@mui/material/TextField';
+import type { DatePickerProps } from '@mui/x-date-pickers/DatePicker';
+import type { MobileDateTimePickerProps } from '@mui/x-date-pickers/MobileDateTimePicker';
+
+import dayjs from 'dayjs';
+import { Controller, useFormContext } from 'react-hook-form';
+
+import { DatePicker } from '@mui/x-date-pickers/DatePicker';
+import { MobileDateTimePicker } from '@mui/x-date-pickers/MobileDateTimePicker';
+
+import { formatStr } from 'src/utils/format-time';
+
+// ----------------------------------------------------------------------
+
+type RHFDatePickerProps = DatePickerProps<Dayjs> & {
+  name: string;
+};
+
+export function RHFDatePicker({ name, slotProps, ...other }: RHFDatePickerProps) {
+  const { control } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <DatePicker
+          {...field}
+          value={dayjs(field.value)}
+          onChange={(newValue) => field.onChange(dayjs(newValue).format())}
+          format={formatStr.split.date}
+          slotProps={{
+            textField: {
+              fullWidth: true,
+              error: !!error,
+              helperText: error?.message ?? (slotProps?.textField as TextFieldProps)?.helperText,
+              ...slotProps?.textField,
+            },
+            ...slotProps,
+          }}
+          {...other}
+        />
+      )}
+    />
+  );
+}
+
+// ----------------------------------------------------------------------
+
+type RHFMobileDateTimePickerProps = MobileDateTimePickerProps<Dayjs> & {
+  name: string;
+};
+
+export function RHFMobileDateTimePicker({
+  name,
+  slotProps,
+  ...other
+}: RHFMobileDateTimePickerProps) {
+  const { control } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <MobileDateTimePicker
+          {...field}
+          value={dayjs(field.value)}
+          onChange={(newValue) => field.onChange(dayjs(newValue).format())}
+          format={formatStr.split.dateTime}
+          slotProps={{
+            textField: {
+              fullWidth: true,
+              error: !!error,
+              helperText: error?.message ?? (slotProps?.textField as TextFieldProps)?.helperText,
+              ...slotProps?.textField,
+            },
+            ...slotProps,
+          }}
+          {...other}
+        />
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-editor.tsx b/src/shared/components/hook-form/rhf-editor.tsx
new file mode 100644
index 0000000..da697b6
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-editor.tsx
@@ -0,0 +1,34 @@
+import { Controller, useFormContext } from 'react-hook-form';
+
+import { Editor } from '../editor';
+
+import type { EditorProps } from '../editor';
+
+// ----------------------------------------------------------------------
+
+type Props = EditorProps & {
+  name: string;
+};
+
+export function RHFEditor({ name, helperText, ...other }: Props) {
+  const {
+    control,
+    formState: { isSubmitSuccessful },
+  } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <Editor
+          {...field}
+          error={!!error}
+          helperText={error?.message ?? helperText}
+          resetValue={isSubmitSuccessful}
+          {...other}
+        />
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-phone-input.tsx b/src/shared/components/hook-form/rhf-phone-input.tsx
new file mode 100644
index 0000000..3844ca9
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-phone-input.tsx
@@ -0,0 +1,33 @@
+import { Controller, useFormContext } from 'react-hook-form';
+
+import { PhoneInput } from '../phone-input';
+
+import type { PhoneInputProps } from '../phone-input';
+
+// ----------------------------------------------------------------------
+
+type Props = Omit<PhoneInputProps, 'value' | 'onChange'> & {
+  name: string;
+};
+
+export function RHFPhoneInput({ name, helperText, ...other }: Props) {
+  const { control, setValue } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <PhoneInput
+          {...field}
+          fullWidth
+          value={field.value}
+          onChange={(newValue) => setValue(name, newValue, { shouldValidate: true })}
+          error={!!error}
+          helperText={error ? error?.message : helperText}
+          {...other}
+        />
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-radio-group.tsx b/src/shared/components/hook-form/rhf-radio-group.tsx
new file mode 100644
index 0000000..f004446
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-radio-group.tsx
@@ -0,0 +1,85 @@
+import type { RadioProps } from '@mui/material/Radio';
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { FormLabelProps } from '@mui/material/FormLabel';
+import type { RadioGroupProps } from '@mui/material/RadioGroup';
+import type { FormHelperTextProps } from '@mui/material/FormHelperText';
+
+import { Controller, useFormContext } from 'react-hook-form';
+
+import Radio from '@mui/material/Radio';
+import FormLabel from '@mui/material/FormLabel';
+import RadioGroup from '@mui/material/RadioGroup';
+import FormControl from '@mui/material/FormControl';
+import FormHelperText from '@mui/material/FormHelperText';
+import FormControlLabel from '@mui/material/FormControlLabel';
+
+// ----------------------------------------------------------------------
+
+type Props = RadioGroupProps & {
+  name: string;
+  label?: string;
+  helperText?: React.ReactNode;
+  slotProps?: {
+    wrap?: SxProps<Theme>;
+    radio: RadioProps;
+    formLabel: FormLabelProps;
+    formHelperText: FormHelperTextProps;
+  };
+  options: {
+    label: string;
+    value: string;
+  }[];
+};
+
+export function RHFRadioGroup({ name, label, options, helperText, slotProps, ...other }: Props) {
+  const { control } = useFormContext();
+
+  const labelledby = `${name}-radio-buttons-group-label`;
+  const ariaLabel = (val: string) => `Radio ${val}`;
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <FormControl component="fieldset" sx={slotProps?.wrap}>
+          {label && (
+            <FormLabel
+              id={labelledby}
+              component="legend"
+              {...slotProps?.formLabel}
+              sx={{ mb: 1, typography: 'body2', ...slotProps?.formLabel.sx }}
+            >
+              {label}
+            </FormLabel>
+          )}
+
+          <RadioGroup {...field} aria-labelledby={labelledby} {...other}>
+            {options.map((option) => (
+              <FormControlLabel
+                key={option.value}
+                value={option.value}
+                control={
+                  <Radio
+                    {...slotProps?.radio}
+                    inputProps={{
+                      ...(!option.label && { 'aria-label': ariaLabel(option.label) }),
+                      ...slotProps?.radio?.inputProps,
+                    }}
+                  />
+                }
+                label={option.label}
+              />
+            ))}
+          </RadioGroup>
+
+          {(!!error || helperText) && (
+            <FormHelperText error={!!error} sx={{ mx: 0 }} {...slotProps?.formHelperText}>
+              {error ? error?.message : helperText}
+            </FormHelperText>
+          )}
+        </FormControl>
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-rating.tsx b/src/shared/components/hook-form/rhf-rating.tsx
new file mode 100644
index 0000000..e94f13e
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-rating.tsx
@@ -0,0 +1,48 @@
+import type { RatingProps } from '@mui/material/Rating';
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { FormHelperTextProps } from '@mui/material/FormHelperText';
+
+import { Controller, useFormContext } from 'react-hook-form';
+
+import Box from '@mui/material/Box';
+import Rating from '@mui/material/Rating';
+import FormHelperText from '@mui/material/FormHelperText';
+
+// ----------------------------------------------------------------------
+
+type Props = RatingProps & {
+  name: string;
+  helperText?: React.ReactNode;
+  slotProps?: {
+    wrap?: SxProps<Theme>;
+    formHelperText?: FormHelperTextProps;
+  };
+};
+
+export function RHFRating({ name, helperText, slotProps, ...other }: Props) {
+  const { control } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <Box sx={slotProps?.wrap}>
+          <Rating
+            {...field}
+            onChange={(event, newValue) => {
+              field.onChange(Number(newValue));
+            }}
+            {...other}
+          />
+
+          {(error?.message || helperText) && (
+            <FormHelperText error={!!error} {...slotProps?.formHelperText}>
+              {error?.message ?? helperText}
+            </FormHelperText>
+          )}
+        </Box>
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-select.tsx b/src/shared/components/hook-form/rhf-select.tsx
new file mode 100644
index 0000000..120b96e
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-select.tsx
@@ -0,0 +1,183 @@
+import type { ChipProps } from '@mui/material/Chip';
+import type { SelectProps } from '@mui/material/Select';
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { CheckboxProps } from '@mui/material/Checkbox';
+import type { TextFieldProps } from '@mui/material/TextField';
+import type { InputLabelProps } from '@mui/material/InputLabel';
+import type { FormControlProps } from '@mui/material/FormControl';
+import type { FormHelperTextProps } from '@mui/material/FormHelperText';
+
+import { Controller, useFormContext } from 'react-hook-form';
+
+import Box from '@mui/material/Box';
+import Chip from '@mui/material/Chip';
+import Select from '@mui/material/Select';
+import MenuItem from '@mui/material/MenuItem';
+import Checkbox from '@mui/material/Checkbox';
+import TextField from '@mui/material/TextField';
+import InputLabel from '@mui/material/InputLabel';
+import FormControl from '@mui/material/FormControl';
+import FormHelperText from '@mui/material/FormHelperText';
+
+// ----------------------------------------------------------------------
+
+type RHFSelectProps = TextFieldProps & {
+  name: string;
+  native?: boolean;
+  children: React.ReactNode;
+  slotProps?: {
+    paper?: SxProps<Theme>;
+  };
+};
+
+export function RHFSelect({
+  name,
+  native,
+  children,
+  slotProps,
+  helperText,
+  inputProps,
+  InputLabelProps,
+  ...other
+}: RHFSelectProps) {
+  const { control } = useFormContext();
+
+  const labelId = `${name}-select-label`;
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <TextField
+          {...field}
+          select
+          fullWidth
+          SelectProps={{
+            native,
+            MenuProps: { PaperProps: { sx: { maxHeight: 220, ...slotProps?.paper } } },
+            sx: { textTransform: 'capitalize' },
+          }}
+          InputLabelProps={{ htmlFor: labelId, ...InputLabelProps }}
+          inputProps={{ id: labelId, ...inputProps }}
+          error={!!error}
+          helperText={error ? error?.message : helperText}
+          {...other}
+        >
+          {children}
+        </TextField>
+      )}
+    />
+  );
+}
+
+// ----------------------------------------------------------------------
+
+type RHFMultiSelectProps = FormControlProps & {
+  name: string;
+  label?: string;
+  chip?: boolean;
+  checkbox?: boolean;
+  placeholder?: string;
+  helperText?: React.ReactNode;
+  options: {
+    label: string;
+    value: string;
+  }[];
+  slotProps?: {
+    chip?: ChipProps;
+    select: SelectProps;
+    checkbox?: CheckboxProps;
+    inputLabel?: InputLabelProps;
+    formHelperText?: FormHelperTextProps;
+  };
+};
+
+export function RHFMultiSelect({
+  name,
+  chip,
+  label,
+  options,
+  checkbox,
+  placeholder,
+  slotProps,
+  helperText,
+  ...other
+}: RHFMultiSelectProps) {
+  const { control } = useFormContext();
+
+  const labelId = `${name}-select-label`;
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <FormControl error={!!error} {...other}>
+          {label && (
+            <InputLabel htmlFor={labelId} {...slotProps?.inputLabel}>
+              {label}
+            </InputLabel>
+          )}
+
+          <Select
+            {...field}
+            multiple
+            displayEmpty={!!placeholder}
+            label={label}
+            renderValue={(selected) => {
+              const selectedItems = options.filter((item) =>
+                (selected as string[]).includes(item.value)
+              );
+
+              if (!selectedItems.length && placeholder) {
+                return <Box sx={{ color: 'text.disabled' }}>{placeholder}</Box>;
+              }
+
+              if (chip) {
+                return (
+                  <Box sx={{ gap: 0.5, display: 'flex', flexWrap: 'wrap' }}>
+                    {selectedItems.map((item) => (
+                      <Chip
+                        key={item.value}
+                        size="small"
+                        variant="soft"
+                        label={item.label}
+                        {...slotProps?.chip}
+                      />
+                    ))}
+                  </Box>
+                );
+              }
+
+              return selectedItems.map((item) => item.label).join(', ');
+            }}
+            {...slotProps?.select}
+            inputProps={{ id: labelId, ...slotProps?.select?.inputProps }}
+          >
+            {options.map((option) => (
+              <MenuItem key={option.value} value={option.value}>
+                {checkbox && (
+                  <Checkbox
+                    size="small"
+                    disableRipple
+                    checked={field.value.includes(option.value)}
+                    {...slotProps?.checkbox}
+                  />
+                )}
+
+                {option.label}
+              </MenuItem>
+            ))}
+          </Select>
+
+          {(!!error || helperText) && (
+            <FormHelperText error={!!error} {...slotProps?.formHelperText}>
+              {error ? error?.message : helperText}
+            </FormHelperText>
+          )}
+        </FormControl>
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-slider.tsx b/src/shared/components/hook-form/rhf-slider.tsx
new file mode 100644
index 0000000..73c729f
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-slider.tsx
@@ -0,0 +1,33 @@
+import type { SliderProps } from '@mui/material/Slider';
+
+import { Controller, useFormContext } from 'react-hook-form';
+
+import Slider from '@mui/material/Slider';
+import FormHelperText from '@mui/material/FormHelperText';
+
+// ----------------------------------------------------------------------
+
+type Props = SliderProps & {
+  name: string;
+  helperText?: React.ReactNode;
+};
+
+export function RHFSlider({ name, helperText, ...other }: Props) {
+  const { control } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <>
+          <Slider {...field} valueLabelDisplay="auto" {...other} />
+
+          {(!!error || helperText) && (
+            <FormHelperText error={!!error}>{error ? error?.message : helperText}</FormHelperText>
+          )}
+        </>
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-switch.tsx b/src/shared/components/hook-form/rhf-switch.tsx
new file mode 100644
index 0000000..03f0f3e
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-switch.tsx
@@ -0,0 +1,154 @@
+import type { SwitchProps } from '@mui/material/Switch';
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { FormGroupProps } from '@mui/material/FormGroup';
+import type { FormLabelProps } from '@mui/material/FormLabel';
+import type { FormHelperTextProps } from '@mui/material/FormHelperText';
+import type { FormControlLabelProps } from '@mui/material/FormControlLabel';
+
+import { Controller, useFormContext } from 'react-hook-form';
+
+import Box from '@mui/material/Box';
+import Switch from '@mui/material/Switch';
+import FormGroup from '@mui/material/FormGroup';
+import FormLabel from '@mui/material/FormLabel';
+import FormControl from '@mui/material/FormControl';
+import FormHelperText from '@mui/material/FormHelperText';
+import FormControlLabel from '@mui/material/FormControlLabel';
+
+// ----------------------------------------------------------------------
+
+export type RHFSwitchProps = Omit<FormControlLabelProps, 'control'> & {
+  name: string;
+  helperText?: React.ReactNode;
+  slotProps?: {
+    wrap?: SxProps<Theme>;
+    switch: SwitchProps;
+    formHelperText?: FormHelperTextProps;
+  };
+};
+
+export function RHFSwitch({ name, helperText, label, slotProps, ...other }: RHFSwitchProps) {
+  const { control } = useFormContext();
+
+  const ariaLabel = `Switch ${name}`;
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <Box sx={slotProps?.wrap}>
+          <FormControlLabel
+            control={
+              <Switch
+                {...field}
+                checked={field.value}
+                {...slotProps?.switch}
+                inputProps={{
+                  ...(!label && { 'aria-label': ariaLabel }),
+                  ...slotProps?.switch?.inputProps,
+                }}
+              />
+            }
+            label={label}
+            {...other}
+          />
+
+          {(!!error || helperText) && (
+            <FormHelperText
+              error={!!error}
+              {...slotProps?.formHelperText}
+              sx={slotProps?.formHelperText?.sx}
+            >
+              {error ? error?.message : helperText}
+            </FormHelperText>
+          )}
+        </Box>
+      )}
+    />
+  );
+}
+
+// ----------------------------------------------------------------------
+
+type RHFMultiSwitchProps = FormGroupProps & {
+  name: string;
+  label?: string;
+  helperText?: React.ReactNode;
+  options: {
+    label: string;
+    value: string;
+  }[];
+  slotProps?: {
+    wrap?: SxProps<Theme>;
+    switch: SwitchProps;
+    formLabel?: FormLabelProps;
+    formHelperText?: FormHelperTextProps;
+  };
+};
+
+export function RHFMultiSwitch({
+  name,
+  label,
+  options,
+  helperText,
+  slotProps,
+  ...other
+}: RHFMultiSwitchProps) {
+  const { control } = useFormContext();
+
+  const getSelected = (selectedItems: string[], item: string) =>
+    selectedItems.includes(item)
+      ? selectedItems.filter((value) => value !== item)
+      : [...selectedItems, item];
+
+  const accessibility = (val: string) => val;
+  const ariaLabel = (val: string) => `Switch ${val}`;
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <FormControl component="fieldset" sx={slotProps?.wrap}>
+          {label && (
+            <FormLabel
+              component="legend"
+              {...slotProps?.formLabel}
+              sx={{ mb: 1, typography: 'body2', ...slotProps?.formLabel?.sx }}
+            >
+              {label}
+            </FormLabel>
+          )}
+
+          <FormGroup {...other}>
+            {options.map((option) => (
+              <FormControlLabel
+                key={option.value}
+                control={
+                  <Switch
+                    checked={field.value.includes(option.value)}
+                    onChange={() => field.onChange(getSelected(field.value, option.value))}
+                    name={accessibility(option.label)}
+                    {...slotProps?.switch}
+                    inputProps={{
+                      ...(!option.label && { 'aria-label': ariaLabel(option.label) }),
+                      ...slotProps?.switch?.inputProps,
+                    }}
+                  />
+                }
+                label={option.label}
+              />
+            ))}
+          </FormGroup>
+
+          {(!!error || helperText) && (
+            <FormHelperText error={!!error} sx={{ mx: 0 }} {...slotProps?.formHelperText}>
+              {error ? error?.message : helperText}
+            </FormHelperText>
+          )}
+        </FormControl>
+      )}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/rhf-upload.tsx b/src/shared/components/hook-form/rhf-upload.tsx
new file mode 100644
index 0000000..f92ff31
--- /dev/null
+++ b/src/shared/components/hook-form/rhf-upload.tsx
@@ -0,0 +1,90 @@
+import { Controller, useFormContext } from 'react-hook-form';
+
+import FormHelperText from '@mui/material/FormHelperText';
+
+import { Upload, UploadBox, UploadAvatar } from '../upload';
+
+import type { UploadProps } from '../upload';
+
+// ----------------------------------------------------------------------
+
+type Props = UploadProps & {
+  name: string;
+};
+
+// ----------------------------------------------------------------------
+
+export function RHFUploadAvatar({ name, ...other }: Props) {
+  const { control, setValue } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => {
+        const onDrop = (acceptedFiles: File[]) => {
+          const value = acceptedFiles[0];
+
+          setValue(name, value, { shouldValidate: true });
+        };
+
+        return (
+          <div>
+            <UploadAvatar value={field.value} error={!!error} onDrop={onDrop} {...other} />
+
+            {!!error && (
+              <FormHelperText error sx={{ px: 2, textAlign: 'center' }}>
+                {error.message}
+              </FormHelperText>
+            )}
+          </div>
+        );
+      }}
+    />
+  );
+}
+
+// ----------------------------------------------------------------------
+
+export function RHFUploadBox({ name, ...other }: Props) {
+  const { control } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => (
+        <UploadBox value={field.value} error={!!error} {...other} />
+      )}
+    />
+  );
+}
+
+// ----------------------------------------------------------------------
+
+export function RHFUpload({ name, multiple, helperText, ...other }: Props) {
+  const { control, setValue } = useFormContext();
+
+  return (
+    <Controller
+      name={name}
+      control={control}
+      render={({ field, fieldState: { error } }) => {
+        const uploadProps = {
+          multiple,
+          accept: { 'image/*': [] },
+          error: !!error,
+          helperText: error?.message ?? helperText,
+        };
+
+        const onDrop = (acceptedFiles: File[]) => {
+          const value = multiple ? [...field.value, ...acceptedFiles] : acceptedFiles[0];
+
+          setValue(name, value, { shouldValidate: true });
+        };
+
+        return <Upload {...uploadProps} value={field.value} onDrop={onDrop} {...other} />;
+      }}
+    />
+  );
+}
diff --git a/src/shared/components/hook-form/schema-helper.ts b/src/shared/components/hook-form/schema-helper.ts
new file mode 100644
index 0000000..d58cba1
--- /dev/null
+++ b/src/shared/components/hook-form/schema-helper.ts
@@ -0,0 +1,127 @@
+import dayjs from 'dayjs';
+import { z as zod } from 'zod';
+
+// ----------------------------------------------------------------------
+
+// const isSsr = typeof window === 'undefined';
+
+type InputProps = {
+  message?: {
+    required_error?: string;
+    invalid_type_error?: string;
+  };
+  minFiles?: number;
+  isValidPhoneNumber?: (text: string) => boolean;
+};
+
+export const schemaHelper = {
+  /**
+   * Phone number
+   * defaultValue === null
+   */
+  phoneNumber: (props?: InputProps) =>
+    zod
+      .string()
+      .min(1, { message: props?.message?.required_error ?? 'Phone number is required!' })
+      .refine((data) => props?.isValidPhoneNumber?.(data), {
+        message: props?.message?.invalid_type_error ?? 'Invalid phone number!',
+      }),
+  /**
+   * date
+   * defaultValue === null
+   */
+  date: (props?: InputProps) =>
+    zod.coerce
+      .date()
+      .nullable()
+      .transform((dateString, ctx) => {
+        const date = dayjs(dateString).format();
+
+        const stringToDate = zod.string().pipe(zod.coerce.date());
+
+        if (!dateString) {
+          ctx.addIssue({
+            code: zod.ZodIssueCode.custom,
+            message: props?.message?.required_error ?? 'Date is required!',
+          });
+          return null;
+        }
+
+        if (!stringToDate.safeParse(date).success) {
+          ctx.addIssue({
+            code: zod.ZodIssueCode.invalid_date,
+            message: props?.message?.invalid_type_error ?? 'Invalid Date!!',
+          });
+        }
+
+        return date;
+      })
+      .pipe(zod.union([zod.number(), zod.string(), zod.date(), zod.null()])),
+  /**
+   * editor
+   * defaultValue === '' | <p></p>
+   */
+  editor: (props?: InputProps) =>
+    zod.string().min(8, { message: props?.message?.required_error ?? 'Editor is required!' }),
+  /**
+   * object
+   * defaultValue === null
+   */
+  objectOrNull: <T>(props?: InputProps) =>
+    zod
+      .custom<T>()
+      .refine((data) => data !== null, {
+        message: props?.message?.required_error ?? 'Field is required!',
+      })
+      .refine((data) => data !== '', {
+        message: props?.message?.required_error ?? 'Field is required!',
+      }),
+  /**
+   * boolean
+   * defaultValue === false
+   */
+  boolean: (props?: InputProps) =>
+    zod.coerce.boolean().refine((bool) => bool === true, {
+      message: props?.message?.required_error ?? 'Switch is required!',
+    }),
+  /**
+   * file
+   * defaultValue === '' || null
+   */
+  file: (props?: InputProps) =>
+    zod.custom<File | string | null>().transform((data, ctx) => {
+      const hasFile = data instanceof File || (typeof data === 'string' && !!data.length);
+
+      if (!hasFile) {
+        ctx.addIssue({
+          code: zod.ZodIssueCode.custom,
+          message: props?.message?.required_error ?? 'File is required!',
+        });
+        return null;
+      }
+
+      return data;
+    }),
+  /**
+   * files
+   * defaultValue === []
+   */
+  files: (props?: InputProps) =>
+    zod.array(zod.custom<File | string>()).transform((data, ctx) => {
+      const minFiles = props?.minFiles ?? 2;
+
+      if (!data.length) {
+        ctx.addIssue({
+          code: zod.ZodIssueCode.custom,
+          message: props?.message?.required_error ?? 'Files is required!',
+        });
+      } else if (data.length < minFiles) {
+        ctx.addIssue({
+          code: zod.ZodIssueCode.custom,
+          message: `Must have at least ${minFiles} items!`,
+        });
+      }
+
+      return data;
+    }),
+};
diff --git a/src/shared/components/image/classes.ts b/src/shared/components/image/classes.ts
new file mode 100644
index 0000000..76a6059
--- /dev/null
+++ b/src/shared/components/image/classes.ts
@@ -0,0 +1,7 @@
+// ----------------------------------------------------------------------
+
+export const imageClasses = {
+  root: 'mnl__image__root',
+  wrapper: 'mnl__image__wrapper',
+  overlay: 'mnl__image__overlay',
+};
diff --git a/src/shared/components/image/image.tsx b/src/shared/components/image/image.tsx
new file mode 100644
index 0000000..1d8c770
--- /dev/null
+++ b/src/shared/components/image/image.tsx
@@ -0,0 +1,110 @@
+import { forwardRef } from 'react';
+import { LazyLoadImage } from 'react-lazy-load-image-component';
+
+import Box from '@mui/material/Box';
+import { styled } from '@mui/material/styles';
+
+import { CONFIG } from 'src/config-global';
+
+import { imageClasses } from './classes';
+
+import type { ImageProps } from './types';
+
+// ----------------------------------------------------------------------
+
+const ImageWrapper = styled(Box)({
+  overflow: 'hidden',
+  position: 'relative',
+  verticalAlign: 'bottom',
+  display: 'inline-block',
+  [`& .${imageClasses.wrapper}`]: {
+    width: '100%',
+    height: '100%',
+    verticalAlign: 'bottom',
+    backgroundSize: 'cover !important',
+  },
+});
+
+const Overlay = styled('span')({
+  top: 0,
+  left: 0,
+  zIndex: 1,
+  width: '100%',
+  height: '100%',
+  position: 'absolute',
+});
+
+// ----------------------------------------------------------------------
+
+export const Image = forwardRef<HTMLSpanElement, ImageProps>(
+  (
+    {
+      ratio,
+      disabledEffect = false,
+      //
+      alt,
+      src,
+      delayTime,
+      threshold,
+      beforeLoad,
+      delayMethod,
+      placeholder,
+      wrapperProps,
+      scrollPosition,
+      effect = 'blur',
+      visibleByDefault,
+      wrapperClassName,
+      useIntersectionObserver,
+      //
+      slotProps,
+      sx,
+      ...other
+    },
+    ref
+  ) => {
+    const content = (
+      <Box
+        component={LazyLoadImage}
+        alt={alt}
+        src={src}
+        delayTime={delayTime}
+        threshold={threshold}
+        beforeLoad={beforeLoad}
+        delayMethod={delayMethod}
+        placeholder={placeholder}
+        wrapperProps={wrapperProps}
+        scrollPosition={scrollPosition}
+        visibleByDefault={visibleByDefault}
+        effect={visibleByDefault || disabledEffect ? undefined : effect}
+        useIntersectionObserver={useIntersectionObserver}
+        wrapperClassName={wrapperClassName || imageClasses.wrapper}
+        placeholderSrc={
+          visibleByDefault || disabledEffect
+            ? `${CONFIG.site.basePath}/assets/transparent.png`
+            : `${CONFIG.site.basePath}/assets/placeholder.svg`
+        }
+        sx={{
+          width: 1,
+          height: 1,
+          objectFit: 'cover',
+          verticalAlign: 'bottom',
+          aspectRatio: ratio,
+        }}
+      />
+    );
+
+    return (
+      <ImageWrapper
+        ref={ref}
+        component="span"
+        className={imageClasses.root}
+        sx={{ ...(!!ratio && { width: 1 }), ...sx }}
+        {...other}
+      >
+        {slotProps?.overlay && <Overlay className={imageClasses.overlay} sx={slotProps?.overlay} />}
+
+        {content}
+      </ImageWrapper>
+    );
+  }
+);
diff --git a/src/shared/components/image/index.ts b/src/shared/components/image/index.ts
new file mode 100644
index 0000000..7e7e67e
--- /dev/null
+++ b/src/shared/components/image/index.ts
@@ -0,0 +1,5 @@
+export * from './image';
+
+export * from './classes';
+
+export type * from './types';
diff --git a/src/shared/components/image/styles.css b/src/shared/components/image/styles.css
new file mode 100644
index 0000000..0736210
--- /dev/null
+++ b/src/shared/components/image/styles.css
@@ -0,0 +1 @@
+@import 'react-lazy-load-image-component/src/effects/blur.css';
diff --git a/src/shared/components/image/types.ts b/src/shared/components/image/types.ts
new file mode 100644
index 0000000..a4244c8
--- /dev/null
+++ b/src/shared/components/image/types.ts
@@ -0,0 +1,30 @@
+import type { BoxProps } from '@mui/material/Box';
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { LazyLoadImageProps } from 'react-lazy-load-image-component';
+
+// ----------------------------------------------------------------------
+
+type BaseRatioType =
+  | '2/3'
+  | '3/2'
+  | '4/3'
+  | '3/4'
+  | '6/4'
+  | '4/6'
+  | '16/9'
+  | '9/16'
+  | '21/9'
+  | '9/21'
+  | '1/1'
+  | string;
+
+export type ImageRatioType = BaseRatioType | { [key: string]: string };
+
+export type ImageProps = BoxProps &
+  LazyLoadImageProps & {
+    ratio?: ImageRatioType;
+    disabledEffect?: boolean;
+    slotProps?: {
+      overlay: SxProps<Theme>;
+    };
+  };
diff --git a/src/shared/components/markdown/classes.ts b/src/shared/components/markdown/classes.ts
new file mode 100644
index 0000000..0739ea7
--- /dev/null
+++ b/src/shared/components/markdown/classes.ts
@@ -0,0 +1,12 @@
+// ----------------------------------------------------------------------
+
+export const markdownClasses = {
+  root: 'nml__markdown__root',
+  content: {
+    pre: 'nml__editor__content__pre',
+    codeInline: 'nml__editor__content__codeInline',
+    codeBlock: 'nml__editor__content__codeBlock',
+    image: 'nml__editor__content__image',
+    link: 'nml__editor__content__link',
+  },
+};
diff --git a/src/shared/components/markdown/code-highlight-block.css b/src/shared/components/markdown/code-highlight-block.css
new file mode 100644
index 0000000..8d8cf37
--- /dev/null
+++ b/src/shared/components/markdown/code-highlight-block.css
@@ -0,0 +1,82 @@
+pre {
+  code {
+    .hljs-comment {
+      color: #999;
+    }
+    .hljs-tag {
+      color: #b4b7b4;
+    }
+    .hljs-operator,
+    .hljs-punctuation,
+    .hljs-subst {
+      color: #ccc;
+    }
+    .hljs-operator {
+      opacity: 0.7;
+    }
+    .hljs-bullet,
+    .hljs-deletion,
+    .hljs-name,
+    .hljs-selector-tag,
+    .hljs-template-variable,
+    .hljs-variable {
+      color: #f2777a;
+    }
+    .hljs-attr,
+    .hljs-link,
+    .hljs-literal,
+    .hljs-number,
+    .hljs-symbol,
+    .hljs-variable.constant_ {
+      color: #f99157;
+    }
+    .hljs-class .hljs-title,
+    .hljs-title,
+    .hljs-title.class_ {
+      color: #fc6;
+    }
+    .hljs-strong {
+      font-weight: 700;
+      color: #fc6;
+    }
+    .hljs-addition,
+    .hljs-code,
+    .hljs-string,
+    .hljs-title.class_.inherited__ {
+      color: #9c9;
+    }
+    .hljs-built_in,
+    .hljs-doctag,
+    .hljs-keyword.hljs-atrule,
+    .hljs-quote,
+    .hljs-regexp {
+      color: #6cc;
+    }
+    .hljs-attribute,
+    .hljs-function .hljs-title,
+    .hljs-section,
+    .hljs-title.function_,
+    .ruby .hljs-property {
+      color: #69c;
+    }
+    .diff .hljs-meta,
+    .hljs-keyword,
+    .hljs-template-tag,
+    .hljs-type {
+      color: #c9c;
+    }
+    .hljs-emphasis {
+      color: #c9c;
+      font-style: italic;
+    }
+    .hljs-meta,
+    .hljs-meta .hljs-keyword,
+    .hljs-meta .hljs-string {
+      color: #a3685a;
+    }
+    .hljs-meta .hljs-keyword,
+    .hljs-meta-keyword {
+      font-weight: 700;
+    }
+  }
+}
diff --git a/src/shared/components/markdown/html-tags.ts b/src/shared/components/markdown/html-tags.ts
new file mode 100644
index 0000000..44c86bc
--- /dev/null
+++ b/src/shared/components/markdown/html-tags.ts
@@ -0,0 +1,172 @@
+/** All html tags
+ * https://github.com/harrysolovay/all-html-tags
+ */
+
+export const htmlTags = [
+  'a',
+  'abbr',
+  'acronym',
+  'address',
+  'applet',
+  'area',
+  'article',
+  'aside',
+  'audio',
+  'b',
+  'base',
+  'basefont',
+  'bdi',
+  'bdo',
+  'bgsound',
+  'big',
+  'blink',
+  'blockquote',
+  'body',
+  'br',
+  'button',
+  'canvas',
+  'caption',
+  'center',
+  'circle',
+  'cite',
+  'clipPath',
+  'code',
+  'col',
+  'colgroup',
+  'command',
+  'content',
+  'data',
+  'datalist',
+  'dd',
+  'defs',
+  'del',
+  'details',
+  'dfn',
+  'dialog',
+  'dir',
+  'div',
+  'dl',
+  'dt',
+  'element',
+  'ellipse',
+  'em',
+  'embed',
+  'fieldset',
+  'figcaption',
+  'figure',
+  'font',
+  'footer',
+  'foreignObject',
+  'form',
+  'frame',
+  'frameset',
+  'g',
+  'h1',
+  'h2',
+  'h3',
+  'h4',
+  'h5',
+  'h6',
+  'head',
+  'header',
+  'hgroup',
+  'hr',
+  'html',
+  'i',
+  'iframe',
+  'image',
+  'img',
+  'input',
+  'ins',
+  'isindex',
+  'kbd',
+  'keygen',
+  'label',
+  'legend',
+  'li',
+  'line',
+  'linearGradient',
+  'link',
+  'listing',
+  'main',
+  'map',
+  'mark',
+  'marquee',
+  'mask',
+  'math',
+  'menu',
+  'menuitem',
+  'meta',
+  'meter',
+  'multicol',
+  'nav',
+  'nextid',
+  'nobr',
+  'noembed',
+  'noframes',
+  'noscript',
+  'object',
+  'ol',
+  'optgroup',
+  'option',
+  'output',
+  'p',
+  'param',
+  'path',
+  'pattern',
+  'picture',
+  'plaintext',
+  'polygon',
+  'polyline',
+  'pre',
+  'progress',
+  'q',
+  'radialGradient',
+  'rb',
+  'rbc',
+  'rect',
+  'rp',
+  'rt',
+  'rtc',
+  'ruby',
+  's',
+  'samp',
+  'script',
+  'section',
+  'select',
+  'shadow',
+  'slot',
+  'small',
+  'source',
+  'spacer',
+  'span',
+  'stop',
+  'strike',
+  'strong',
+  'style',
+  'sub',
+  'summary',
+  'sup',
+  'svg',
+  'table',
+  'tbody',
+  'td',
+  'template',
+  'text',
+  'textarea',
+  'tfoot',
+  'th',
+  'thead',
+  'time',
+  'title',
+  'tr',
+  'track',
+  'tspan',
+  'tt',
+  'u',
+  'ul',
+  'var',
+  'video',
+  'wbr',
+  'xmp',
+];
diff --git a/src/shared/components/markdown/html-to-markdown.ts b/src/shared/components/markdown/html-to-markdown.ts
new file mode 100644
index 0000000..17fe0a8
--- /dev/null
+++ b/src/shared/components/markdown/html-to-markdown.ts
@@ -0,0 +1,62 @@
+import type { Node, Filter } from 'turndown';
+
+import TurndownService from 'turndown';
+
+import { htmlTags } from './html-tags';
+
+// ----------------------------------------------------------------------
+
+type INode = HTMLElement & {
+  isBlock: boolean;
+};
+
+const excludeTags = ['pre', 'code'];
+
+const turndownService = new TurndownService({ codeBlockStyle: 'fenced', fence: '```' });
+
+const filterTags = htmlTags.filter((item) => !excludeTags.includes(item)) as Filter;
+
+/**
+ * Custom rule
+ * https://github.com/mixmark-io/turndown/issues/241#issuecomment-400591362
+ */
+turndownService.addRule('keep', {
+  filter: filterTags,
+  replacement(content: string, node: Node) {
+    const { isBlock, outerHTML } = node as INode;
+
+    return node && isBlock ? `\n\n${outerHTML}\n\n` : outerHTML;
+  },
+});
+
+// ----------------------------------------------------------------------
+
+export function htmlToMarkdown(html: string) {
+  return turndownService.turndown(html);
+}
+// ----------------------------------------------------------------------
+
+export function isMarkdownContent(content: string) {
+  // Checking if the content contains Markdown-specific patterns
+  const markdownPatterns = [
+    /* Heading */
+    /^#+\s/,
+    /* List item */
+    /^(\*|-|\d+\.)\s/,
+    /* Code block */
+    /^```/,
+    /* Table */
+    /^\|/,
+    /* Unordered list */
+    /^(\s*)[*+-] [^\r\n]+/,
+    /* Ordered list */
+    /^(\s*)\d+\. [^\r\n]+/,
+    /* Image */
+    /!\[.*?\]\(.*?\)/,
+    /* Link */
+    /\[.*?\]\(.*?\)/,
+  ];
+
+  // Checking if any of the patterns match
+  return markdownPatterns.some((pattern) => pattern.test(content));
+}
diff --git a/src/shared/components/markdown/index.ts b/src/shared/components/markdown/index.ts
new file mode 100644
index 0000000..12b07fd
--- /dev/null
+++ b/src/shared/components/markdown/index.ts
@@ -0,0 +1,3 @@
+export * from './markdown';
+
+export type * from './types';
diff --git a/src/shared/components/markdown/markdown.tsx b/src/shared/components/markdown/markdown.tsx
new file mode 100644
index 0000000..f1d35db
--- /dev/null
+++ b/src/shared/components/markdown/markdown.tsx
@@ -0,0 +1,94 @@
+import './code-highlight-block.css';
+
+import type { Options } from 'react-markdown';
+
+import { useMemo } from 'react';
+import remarkGfm from 'remark-gfm';
+import rehypeRaw from 'rehype-raw';
+import rehypeHighlight from 'rehype-highlight';
+
+import Link from '@mui/material/Link';
+
+import { isExternalLink } from 'src/routes/utils';
+import { RouterLink } from 'src/routes/components';
+
+import { Image } from '../image';
+import { StyledRoot } from './styles';
+import { markdownClasses } from './classes';
+import { htmlToMarkdown, isMarkdownContent } from './html-to-markdown';
+
+import type { MarkdownProps } from './types';
+
+// ----------------------------------------------------------------------
+
+export function Markdown({ children, sx, ...other }: MarkdownProps) {
+  const content = useMemo(() => {
+    if (isMarkdownContent(`${children}`)) {
+      return children;
+    }
+    return htmlToMarkdown(`${children}`.trim());
+  }, [children]);
+
+  return (
+    <StyledRoot
+      children={content}
+      components={components as Options['components']}
+      rehypePlugins={rehypePlugins as Options['rehypePlugins']}
+      /* base64-encoded images
+       * https://github.com/remarkjs/react-markdown/issues/774
+       * urlTransform={(value: string) => value}
+       */
+      className={markdownClasses.root}
+      sx={sx}
+      {...other}
+    />
+  );
+}
+
+// ----------------------------------------------------------------------
+
+type ComponentTag = {
+  [key: string]: any;
+};
+
+const rehypePlugins = [rehypeRaw, rehypeHighlight, [remarkGfm, { singleTilde: false }]];
+
+const components = {
+  img: ({ node, ...other }: ComponentTag) => (
+    <Image
+      ratio="16/9"
+      className={markdownClasses.content.image}
+      sx={{ borderRadius: 2 }}
+      {...other}
+    />
+  ),
+  a: ({ href, children, node, ...other }: ComponentTag) => {
+    const linkProps = isExternalLink(href)
+      ? { target: '_blank', rel: 'noopener' }
+      : { component: RouterLink };
+
+    return (
+      <Link {...linkProps} href={href} className={markdownClasses.content.link} {...other}>
+        {children}
+      </Link>
+    );
+  },
+  pre: ({ children }: ComponentTag) => (
+    <div className={markdownClasses.content.codeBlock}>
+      <pre>{children}</pre>
+    </div>
+  ),
+  code({ className, children, node, ...other }: ComponentTag) {
+    const language = /language-(\w+)/.exec(className || '');
+
+    return language ? (
+      <code {...other} className={className}>
+        {children}
+      </code>
+    ) : (
+      <code {...other} className={markdownClasses.content.codeInline}>
+        {children}
+      </code>
+    );
+  },
+};
diff --git a/src/shared/components/markdown/styles.ts b/src/shared/components/markdown/styles.ts
new file mode 100644
index 0000000..994811d
--- /dev/null
+++ b/src/shared/components/markdown/styles.ts
@@ -0,0 +1,165 @@
+import ReactMarkdown from 'react-markdown';
+
+import { styled } from '@mui/material/styles';
+
+import { varAlpha, stylesMode } from 'src/shared/theme/styles';
+
+import { markdownClasses } from './classes';
+
+// ----------------------------------------------------------------------
+
+const MARGIN = '0.75em';
+
+export const StyledRoot = styled(ReactMarkdown)(({ theme }) => ({
+  '> * + *': {
+    marginTop: 0,
+    marginBottom: MARGIN,
+  },
+  /**
+   * Heading & Paragraph
+   */
+  h1: { ...theme.typography.h1, marginTop: 40, marginBottom: 8 },
+  h2: { ...theme.typography.h2, marginTop: 40, marginBottom: 8 },
+  h3: { ...theme.typography.h3, marginTop: 24, marginBottom: 8 },
+  h4: { ...theme.typography.h4, marginTop: 24, marginBottom: 8 },
+  h5: { ...theme.typography.h5, marginTop: 24, marginBottom: 8 },
+  h6: { ...theme.typography.h6, marginTop: 24, marginBottom: 8 },
+  p: { ...theme.typography.body1, marginBottom: '1.25rem' },
+  /**
+   * Hr Divider
+   */
+  hr: {
+    flexShrink: 0,
+    borderWidth: 0,
+    margin: '2em 0',
+    msFlexNegative: 0,
+    WebkitFlexShrink: 0,
+    borderStyle: 'solid',
+    borderBottomWidth: 'thin',
+    borderColor: theme.vars.palette.divider,
+  },
+  /**
+   * Image
+   */
+  [`& .${markdownClasses.content.image}`]: {
+    width: '100%',
+    height: 'auto',
+    maxWidth: '100%',
+    margin: 'auto auto 1.25em',
+  },
+  /**
+   * List
+   */
+  '& ul': {
+    listStyleType: 'disc',
+  },
+  '& ul, & ol': {
+    paddingLeft: 16,
+    '& > li': {
+      lineHeight: 2,
+      '& > p': { margin: 0, display: 'inline-block' },
+    },
+  },
+  /**
+   * Blockquote
+   */
+  '& blockquote': {
+    lineHeight: 1.5,
+    fontSize: '1.5em',
+    margin: '24px auto',
+    position: 'relative',
+    fontFamily: 'Georgia, serif',
+    padding: theme.spacing(3, 3, 3, 8),
+    color: theme.vars.palette.text.secondary,
+    borderLeft: `solid 8px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.08)}`,
+    [theme.breakpoints.up('md')]: {
+      width: '100%',
+      maxWidth: 640,
+    },
+    '& p': {
+      margin: 0,
+      fontSize: 'inherit',
+      fontFamily: 'inherit',
+    },
+    '&::before': {
+      left: 16,
+      top: -8,
+      display: 'block',
+      fontSize: '3em',
+      content: '"\\201C"',
+      position: 'absolute',
+      color: theme.vars.palette.text.disabled,
+    },
+  },
+  /**
+   * Code inline
+   */
+  [`& .${markdownClasses.content.codeInline}`]: {
+    padding: theme.spacing(0.25, 0.5),
+    color: theme.vars.palette.text.secondary,
+    fontSize: theme.typography.body2.fontSize,
+    borderRadius: theme.shape.borderRadius / 2,
+    backgroundColor: varAlpha(theme.vars.palette.grey['500Channel'], 0.2),
+  },
+  /**
+   * Code Block
+   */
+  [`& .${markdownClasses.content.codeBlock}`]: {
+    position: 'relative',
+    '& pre': {
+      overflowX: 'auto',
+      padding: theme.spacing(3),
+      color: theme.vars.palette.common.white,
+      borderRadius: theme.shape.borderRadius,
+      backgroundColor: theme.vars.palette.grey[900],
+      fontFamily: "'JetBrainsMono', monospace",
+      '& code': { fontSize: theme.typography.body2.fontSize },
+    },
+  },
+  /**
+   * Table
+   */
+  table: {
+    width: '100%',
+    borderCollapse: 'collapse',
+    border: `1px solid ${theme.vars.palette.divider}`,
+    'th, td': { padding: theme.spacing(1), border: `1px solid ${theme.vars.palette.divider}` },
+    'tbody tr:nth-of-type(odd)': { backgroundColor: theme.vars.palette.background.neutral },
+  },
+  /**
+   * Checkbox
+   */
+  input: {
+    '&[type=checkbox]': {
+      position: 'relative',
+      cursor: 'pointer',
+      '&:before': {
+        content: '""',
+        top: -2,
+        left: -2,
+        width: 17,
+        height: 17,
+        borderRadius: 3,
+        position: 'absolute',
+        backgroundColor: theme.vars.palette.grey[300],
+        [stylesMode.dark]: { backgroundColor: theme.vars.palette.grey[700] },
+      },
+      '&:checked': {
+        '&:before': { backgroundColor: theme.vars.palette.primary.main },
+        '&:after': {
+          content: '""',
+          top: 1,
+          left: 5,
+          width: 4,
+          height: 9,
+          position: 'absolute',
+          transform: 'rotate(45deg)',
+          msTransform: 'rotate(45deg)',
+          WebkitTransform: 'rotate(45deg)',
+          border: `solid ${theme.vars.palette.common.white}`,
+          borderWidth: '0 2px 2px 0',
+        },
+      },
+    },
+  },
+}));
diff --git a/src/shared/components/markdown/types.ts b/src/shared/components/markdown/types.ts
new file mode 100644
index 0000000..1ab8d83
--- /dev/null
+++ b/src/shared/components/markdown/types.ts
@@ -0,0 +1,9 @@
+import type { Options } from 'react-markdown';
+import type { Theme, SxProps } from '@mui/material/styles';
+
+// ----------------------------------------------------------------------
+
+export interface MarkdownProps extends Options {
+  asHtml?: boolean;
+  sx?: SxProps<Theme>;
+}
diff --git a/src/shared/components/phone-input/index.ts b/src/shared/components/phone-input/index.ts
new file mode 100644
index 0000000..5992c80
--- /dev/null
+++ b/src/shared/components/phone-input/index.ts
@@ -0,0 +1,3 @@
+export type * from './types';
+
+export * from './phone-input';
diff --git a/src/shared/components/phone-input/list.tsx b/src/shared/components/phone-input/list.tsx
new file mode 100644
index 0000000..d9df3e9
--- /dev/null
+++ b/src/shared/components/phone-input/list.tsx
@@ -0,0 +1,139 @@
+import type { Country } from 'react-phone-number-input/input';
+
+import { useState, useCallback } from 'react';
+
+import Box from '@mui/material/Box';
+import Popover from '@mui/material/Popover';
+import Divider from '@mui/material/Divider';
+import MenuList from '@mui/material/MenuList';
+import MenuItem from '@mui/material/MenuItem';
+import TextField from '@mui/material/TextField';
+import ButtonBase from '@mui/material/ButtonBase';
+import ListItemText from '@mui/material/ListItemText';
+import InputAdornment from '@mui/material/InputAdornment';
+
+import { countries } from 'src/shared/assets/data/countries';
+
+import { Iconify, FlagIcon } from 'src/shared/components/iconify';
+import { SearchNotFound } from 'src/shared/components/search-not-found';
+
+import { usePopover } from '../custom-popover';
+import { getCountry, applyFilter } from './utils';
+
+import type { CountryListProps } from './types';
+
+// ----------------------------------------------------------------------
+
+export function CountryListPopover({ countryCode, onClickCountry }: CountryListProps) {
+  const popover = usePopover();
+
+  const selectedCountry = getCountry(countryCode);
+
+  const [searchCountry, setSearchCountry] = useState('');
+
+  const handleSearchCountry = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
+    setSearchCountry(event.target.value);
+  }, []);
+
+  const dataFiltered = applyFilter({ inputData: countries, query: searchCountry });
+
+  const notFound = !dataFiltered.length && !!setSearchCountry;
+
+  const renderButton = (
+    <ButtonBase disableRipple onClick={popover.onOpen}>
+      <FlagIcon code={selectedCountry?.code} sx={{ width: 22, height: 22, borderRadius: '50%' }} />
+
+      <Iconify
+        icon="eva:chevron-down-fill"
+        sx={{ ml: 0.5, flexShrink: 0, color: 'text.disabled' }}
+      />
+
+      <Divider orientation="vertical" flexItem sx={{ mr: 1 }} />
+    </ButtonBase>
+  );
+
+  const renderList = (
+    <MenuList>
+      {dataFiltered.map((country) => {
+        if (!country.code) {
+          return null;
+        }
+
+        return (
+          <MenuItem
+            key={country.code}
+            selected={countryCode === country.code}
+            autoFocus={countryCode === country.code}
+            onClick={() => {
+              popover.onClose();
+              setSearchCountry('');
+              onClickCountry(country.code as Country);
+            }}
+          >
+            <FlagIcon
+              code={country.code}
+              sx={{ mr: 1, width: 22, height: 22, borderRadius: '50%' }}
+            />
+
+            <ListItemText
+              primary={country.label}
+              secondary={`${country.code} (+${country.phone})`}
+              primaryTypographyProps={{ noWrap: true, typography: 'body2' }}
+              secondaryTypographyProps={{ typography: 'caption' }}
+            />
+          </MenuItem>
+        );
+      })}
+    </MenuList>
+  );
+
+  return (
+    <>
+      {renderButton}
+
+      <Popover
+        disableRestoreFocus
+        open={popover.open}
+        anchorEl={popover.anchorEl}
+        onClose={() => {
+          popover.onClose();
+          setSearchCountry('');
+        }}
+        anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
+        transformOrigin={{ vertical: 'top', horizontal: 'left' }}
+        slotProps={{
+          paper: {
+            sx: {
+              width: 1,
+              height: 320,
+              maxWidth: 320,
+              display: 'flex',
+              flexDirection: 'column',
+            },
+          },
+        }}
+      >
+        <Box sx={{ px: 1, py: 1.5 }}>
+          <TextField
+            autoFocus
+            fullWidth
+            value={searchCountry}
+            onChange={handleSearchCountry}
+            placeholder="Search..."
+            InputProps={{
+              startAdornment: (
+                <InputAdornment position="start">
+                  <Iconify icon="eva:search-fill" sx={{ color: 'text.disabled' }} />
+                </InputAdornment>
+              ),
+            }}
+          />
+        </Box>
+
+        <Box sx={{ flex: '1 1 auto', overflowX: 'hidden' }}>
+          {notFound ? <SearchNotFound query={searchCountry} sx={{ px: 2, pt: 5 }} /> : renderList}
+        </Box>
+      </Popover>
+    </>
+  );
+}
diff --git a/src/shared/components/phone-input/phone-input.tsx b/src/shared/components/phone-input/phone-input.tsx
new file mode 100644
index 0000000..a40c6a4
--- /dev/null
+++ b/src/shared/components/phone-input/phone-input.tsx
@@ -0,0 +1,55 @@
+import type { TextFieldProps } from '@mui/material/TextField';
+import type { Country } from 'react-phone-number-input/input';
+
+import { useState, forwardRef } from 'react';
+import PhoneNumberInput from 'react-phone-number-input/input';
+
+import TextField from '@mui/material/TextField';
+import InputAdornment from '@mui/material/InputAdornment';
+
+import { getCountryCode } from './utils';
+import { CountryListPopover } from './list';
+
+import type { PhoneInputProps } from './types';
+
+// ----------------------------------------------------------------------
+
+export const PhoneInput = forwardRef<HTMLDivElement, PhoneInputProps>(
+  ({ value, onChange, placeholder, country: inputCountryCode, disableSelect, ...other }, ref) => {
+    const defaultCountryCode = getCountryCode(value, inputCountryCode);
+
+    const [selectedCountry, setSelectedCountry] = useState(defaultCountryCode);
+
+    return (
+      <PhoneNumberInput
+        ref={ref}
+        country={selectedCountry}
+        inputComponent={CustomInput}
+        value={value}
+        onChange={onChange}
+        placeholder={placeholder ?? 'Enter phone number'}
+        InputProps={
+          disableSelect
+            ? undefined
+            : {
+                startAdornment: (
+                  <InputAdornment position="start" sx={{ ml: 1 }}>
+                    <CountryListPopover
+                      countryCode={selectedCountry}
+                      onClickCountry={(inputValue: Country) => setSelectedCountry(inputValue)}
+                    />
+                  </InputAdornment>
+                ),
+              }
+        }
+        {...other}
+      />
+    );
+  }
+);
+
+// ----------------------------------------------------------------------
+
+const CustomInput = forwardRef<HTMLInputElement, TextFieldProps>(({ ...props }, ref) => (
+  <TextField inputRef={ref} {...props} />
+));
diff --git a/src/shared/components/phone-input/types.ts b/src/shared/components/phone-input/types.ts
new file mode 100644
index 0000000..7b6b4ff
--- /dev/null
+++ b/src/shared/components/phone-input/types.ts
@@ -0,0 +1,16 @@
+import type { TextFieldProps } from '@mui/material/TextField';
+import type { Value, Country } from 'react-phone-number-input/input';
+
+// ----------------------------------------------------------------------
+
+export type PhoneInputProps = Omit<TextFieldProps, 'onChange' | 'ref'> & {
+  value: string;
+  country?: Country;
+  disableSelect?: boolean;
+  onChange: (newValue: Value) => void;
+};
+
+export type CountryListProps = {
+  countryCode?: Country;
+  onClickCountry: (inputValue: Country) => void;
+};
diff --git a/src/shared/components/phone-input/utils.ts b/src/shared/components/phone-input/utils.ts
new file mode 100644
index 0000000..8d761e9
--- /dev/null
+++ b/src/shared/components/phone-input/utils.ts
@@ -0,0 +1,46 @@
+import type { Country } from 'react-phone-number-input';
+
+import { parsePhoneNumber } from 'react-phone-number-input';
+
+import { countries } from 'src/shared/assets/data/countries';
+
+// ----------------------------------------------------------------------
+
+export function getCountryCode(inputValue: string, countryCode?: Country) {
+  if (inputValue) {
+    const phoneNumber = parsePhoneNumber(inputValue);
+
+    if (phoneNumber) {
+      return phoneNumber?.country;
+    }
+  }
+
+  return countryCode ?? 'US';
+}
+
+// ----------------------------------------------------------------------
+
+export function getCountry(countryCode?: Country) {
+  const option = countries.filter((country) => country.code === countryCode)[0];
+  return option;
+}
+
+// ----------------------------------------------------------------------
+
+type ApplyFilterProps = {
+  query: string;
+  inputData: typeof countries;
+};
+
+export function applyFilter({ inputData, query }: ApplyFilterProps) {
+  if (query) {
+    return inputData.filter(
+      (country) =>
+        country.label.toLowerCase().indexOf(query.toLowerCase()) !== -1 ||
+        country.code.toLowerCase().indexOf(query.toLowerCase()) !== -1 ||
+        country.phone.toLowerCase().indexOf(query.toLowerCase()) !== -1
+    );
+  }
+
+  return inputData;
+}
diff --git a/src/shared/components/upload/components/placeholder.tsx b/src/shared/components/upload/components/placeholder.tsx
new file mode 100644
index 0000000..8e184f4
--- /dev/null
+++ b/src/shared/components/upload/components/placeholder.tsx
@@ -0,0 +1,38 @@
+import type { BoxProps } from '@mui/material/Box';
+
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+
+import { UploadIllustration } from 'src/shared/assets/illustrations';
+
+// ----------------------------------------------------------------------
+
+export function UploadPlaceholder({ ...other }: BoxProps) {
+  return (
+    <Box
+      sx={{
+        display: 'flex',
+        alignItems: 'center',
+        flexDirection: 'column',
+        justifyContent: 'center',
+      }}
+      {...other}
+    >
+      <UploadIllustration hideBackground sx={{ width: 200 }} />
+
+      <Stack spacing={1} sx={{ textAlign: 'center' }}>
+        <Box sx={{ typography: 'h6' }}>Drop or select file</Box>
+        <Box sx={{ typography: 'body2', color: 'text.secondary' }}>
+          Drop files here or click to
+          <Box
+            component="span"
+            sx={{ mx: 0.5, color: 'primary.main', textDecoration: 'underline' }}
+          >
+            browse
+          </Box>
+          through your machine.
+        </Box>
+      </Stack>
+    </Box>
+  );
+}
diff --git a/src/shared/components/upload/components/preview-multi-file.tsx b/src/shared/components/upload/components/preview-multi-file.tsx
new file mode 100644
index 0000000..0ded928
--- /dev/null
+++ b/src/shared/components/upload/components/preview-multi-file.tsx
@@ -0,0 +1,126 @@
+import Box from '@mui/material/Box';
+import IconButton from '@mui/material/IconButton';
+import ListItemText from '@mui/material/ListItemText';
+
+import { fData } from 'src/utils/format-number';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { Iconify } from '../../iconify';
+import { fileData, FileThumbnail } from '../../file-thumbnail';
+
+import type { MultiFilePreviewProps } from '../types';
+
+// ----------------------------------------------------------------------
+
+export function MultiFilePreview({
+  sx,
+  onRemove,
+  lastNode,
+  thumbnail,
+  slotProps,
+  firstNode,
+  files = [],
+}: MultiFilePreviewProps) {
+  const renderFirstNode = firstNode && (
+    <Box
+      component="li"
+      sx={{
+        ...(thumbnail && {
+          width: 'auto',
+          display: 'inline-flex',
+        }),
+      }}
+    >
+      {firstNode}
+    </Box>
+  );
+
+  const renderLastNode = lastNode && (
+    <Box
+      component="li"
+      sx={{
+        ...(thumbnail && { width: 'auto', display: 'inline-flex' }),
+      }}
+    >
+      {lastNode}
+    </Box>
+  );
+
+  return (
+    <Box
+      component="ul"
+      sx={{
+        gap: 1,
+        display: 'flex',
+        flexDirection: 'column',
+        ...(thumbnail && {
+          flexWrap: 'wrap',
+          flexDirection: 'row',
+        }),
+        ...sx,
+      }}
+    >
+      {renderFirstNode}
+
+      {files.map((file) => {
+        const { name, size } = fileData(file);
+
+        if (thumbnail) {
+          return (
+            <Box component="li" key={name} sx={{ display: 'inline-flex' }}>
+              <FileThumbnail
+                tooltip
+                imageView
+                file={file}
+                onRemove={() => onRemove?.(file)}
+                sx={{
+                  width: 80,
+                  height: 80,
+                  border: (theme) =>
+                    `solid 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.16)}`,
+                }}
+                slotProps={{ icon: { width: 36, height: 36 } }}
+                {...slotProps?.thumbnail}
+              />
+            </Box>
+          );
+        }
+
+        return (
+          <Box
+            component="li"
+            key={name}
+            sx={{
+              py: 1,
+              pr: 1,
+              pl: 1.5,
+              gap: 1.5,
+              display: 'flex',
+              borderRadius: 1,
+              alignItems: 'center',
+              border: (theme) =>
+                `solid 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.16)}`,
+            }}
+          >
+            <FileThumbnail file={file} {...slotProps?.thumbnail} />
+
+            <ListItemText
+              primary={name}
+              secondary={fData(size)}
+              secondaryTypographyProps={{ component: 'span', typography: 'caption' }}
+            />
+
+            {onRemove && (
+              <IconButton size="small" onClick={() => onRemove(file)}>
+                <Iconify icon="mingcute:close-line" width={16} />
+              </IconButton>
+            )}
+          </Box>
+        );
+      })}
+
+      {renderLastNode}
+    </Box>
+  );
+}
diff --git a/src/shared/components/upload/components/preview-single-file.tsx b/src/shared/components/upload/components/preview-single-file.tsx
new file mode 100644
index 0000000..ba9da44
--- /dev/null
+++ b/src/shared/components/upload/components/preview-single-file.tsx
@@ -0,0 +1,66 @@
+import type { IconButtonProps } from '@mui/material/IconButton';
+
+import Box from '@mui/material/Box';
+import IconButton from '@mui/material/IconButton';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { Iconify } from '../../iconify';
+
+import type { SingleFilePreviewProps } from '../types';
+
+// ----------------------------------------------------------------------
+
+export function SingleFilePreview({ file }: SingleFilePreviewProps) {
+  const fileName = typeof file === 'string' ? file : file.name;
+
+  const previewUrl = typeof file === 'string' ? file : URL.createObjectURL(file);
+
+  return (
+    <Box
+      sx={{
+        p: 1,
+        top: 0,
+        left: 0,
+        width: 1,
+        height: 1,
+        position: 'absolute',
+      }}
+    >
+      <Box
+        component="img"
+        alt={fileName}
+        src={previewUrl}
+        sx={{
+          width: 1,
+          height: 1,
+          borderRadius: 1,
+          objectFit: 'cover',
+        }}
+      />
+    </Box>
+  );
+}
+
+// ----------------------------------------------------------------------
+
+export function DeleteButton({ sx, ...other }: IconButtonProps) {
+  return (
+    <IconButton
+      size="small"
+      sx={{
+        top: 16,
+        right: 16,
+        zIndex: 9,
+        position: 'absolute',
+        color: (theme) => varAlpha(theme.vars.palette.common.whiteChannel, 0.8),
+        bgcolor: (theme) => varAlpha(theme.vars.palette.grey['900Channel'], 0.72),
+        '&:hover': { bgcolor: (theme) => varAlpha(theme.vars.palette.grey['900Channel'], 0.48) },
+        ...sx,
+      }}
+      {...other}
+    >
+      <Iconify icon="mingcute:close-line" width={18} />
+    </IconButton>
+  );
+}
diff --git a/src/shared/components/upload/components/rejection-files.tsx b/src/shared/components/upload/components/rejection-files.tsx
new file mode 100644
index 0000000..dc3d0a5
--- /dev/null
+++ b/src/shared/components/upload/components/rejection-files.tsx
@@ -0,0 +1,56 @@
+import type { FileRejection } from 'react-dropzone';
+
+import Box from '@mui/material/Box';
+import Paper from '@mui/material/Paper';
+import Typography from '@mui/material/Typography';
+
+import { fData } from 'src/utils/format-number';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { fileData } from '../../file-thumbnail';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  files: FileRejection[];
+};
+
+export function RejectionFiles({ files }: Props) {
+  if (!files.length) {
+    return null;
+  }
+
+  return (
+    <Paper
+      variant="outlined"
+      sx={{
+        py: 1,
+        px: 2,
+        mt: 3,
+        textAlign: 'left',
+        borderStyle: 'dashed',
+        borderColor: 'error.main',
+        bgcolor: (theme) => varAlpha(theme.vars.palette.error.mainChannel, 0.08),
+      }}
+    >
+      {files.map(({ file, errors }) => {
+        const { path, size } = fileData(file);
+
+        return (
+          <Box key={path} sx={{ my: 1 }}>
+            <Typography variant="subtitle2" noWrap>
+              {path} - {size ? fData(size) : ''}
+            </Typography>
+
+            {errors.map((error) => (
+              <Box key={error.code} component="span" sx={{ typography: 'caption' }}>
+                - {error.message}
+              </Box>
+            ))}
+          </Box>
+        );
+      })}
+    </Paper>
+  );
+}
diff --git a/src/shared/components/upload/index.ts b/src/shared/components/upload/index.ts
new file mode 100644
index 0000000..e955206
--- /dev/null
+++ b/src/shared/components/upload/index.ts
@@ -0,0 +1,13 @@
+export * from './upload';
+
+export type * from './types';
+
+export * from './upload-box';
+
+export * from './upload-avatar';
+
+export * from './components/rejection-files';
+
+export * from './components/preview-multi-file';
+
+export * from './components/preview-single-file';
diff --git a/src/shared/components/upload/types.ts b/src/shared/components/upload/types.ts
new file mode 100644
index 0000000..f7f6921
--- /dev/null
+++ b/src/shared/components/upload/types.ts
@@ -0,0 +1,39 @@
+import type { DropzoneOptions } from 'react-dropzone';
+import type { Theme, SxProps } from '@mui/material/styles';
+
+import type { FileThumbnailProps } from '../file-thumbnail';
+
+// ----------------------------------------------------------------------
+
+export type FileUploadType = File | string | null;
+
+export type FilesUploadType = (File | string)[];
+
+export type SingleFilePreviewProps = {
+  file: File | string;
+};
+
+export type MultiFilePreviewProps = {
+  files: FilesUploadType;
+  sx?: SxProps<Theme>;
+  lastNode?: React.ReactNode;
+  firstNode?: React.ReactNode;
+  onRemove: UploadProps['onRemove'];
+  thumbnail: UploadProps['thumbnail'];
+  slotProps?: {
+    thumbnail?: Omit<FileThumbnailProps, 'file'>;
+  };
+};
+
+export type UploadProps = DropzoneOptions & {
+  error?: boolean;
+  sx?: SxProps<Theme>;
+  thumbnail?: boolean;
+  onDelete?: () => void;
+  onUpload?: () => void;
+  onRemoveAll?: () => void;
+  helperText?: React.ReactNode;
+  placeholder?: React.ReactNode;
+  value?: FileUploadType | FilesUploadType;
+  onRemove?: (file: File | string) => void;
+};
diff --git a/src/shared/components/upload/upload-avatar.tsx b/src/shared/components/upload/upload-avatar.tsx
new file mode 100644
index 0000000..722f5a4
--- /dev/null
+++ b/src/shared/components/upload/upload-avatar.tsx
@@ -0,0 +1,132 @@
+import { useState, useEffect } from 'react';
+import { useDropzone } from 'react-dropzone';
+
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { Image } from '../image';
+import { Iconify } from '../iconify';
+import { RejectionFiles } from './components/rejection-files';
+
+import type { UploadProps } from './types';
+
+// ----------------------------------------------------------------------
+
+export function UploadAvatar({ sx, error, value, disabled, helperText, ...other }: UploadProps) {
+  const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
+    multiple: false,
+    disabled,
+    accept: { 'image/*': [] },
+    ...other,
+  });
+
+  const hasFile = !!value;
+
+  const hasError = isDragReject || !!error;
+
+  const [preview, setPreview] = useState('');
+
+  useEffect(() => {
+    if (typeof value === 'string') {
+      setPreview(value);
+    } else if (value instanceof File) {
+      setPreview(URL.createObjectURL(value));
+    }
+  }, [value]);
+
+  const renderPreview = hasFile && (
+    <Image alt="avatar" src={preview} sx={{ width: 1, height: 1, borderRadius: '50%' }} />
+  );
+
+  const renderPlaceholder = (
+    <Box
+      className="upload-placeholder"
+      sx={{
+        top: 0,
+        gap: 1,
+        left: 0,
+        width: 1,
+        height: 1,
+        zIndex: 9,
+        display: 'flex',
+        borderRadius: '50%',
+        position: 'absolute',
+        alignItems: 'center',
+        color: 'text.disabled',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        bgcolor: (theme) => varAlpha(theme.vars.palette.grey['500Channel'], 0.08),
+        transition: (theme) =>
+          theme.transitions.create(['opacity'], { duration: theme.transitions.duration.shorter }),
+        '&:hover': { opacity: 0.72 },
+        ...(hasError && {
+          color: 'error.main',
+          bgcolor: (theme) => varAlpha(theme.vars.palette.error.mainChannel, 0.08),
+        }),
+        ...(hasFile && {
+          zIndex: 9,
+          opacity: 0,
+          color: 'common.white',
+          bgcolor: (theme) => varAlpha(theme.vars.palette.grey['900Channel'], 0.64),
+        }),
+      }}
+    >
+      <Iconify icon="solar:camera-add-bold" width={32} />
+
+      <Typography variant="caption">{hasFile ? 'Update photo' : 'Upload photo'}</Typography>
+    </Box>
+  );
+
+  const renderContent = (
+    <Box
+      sx={{
+        width: 1,
+        height: 1,
+        overflow: 'hidden',
+        borderRadius: '50%',
+        position: 'relative',
+      }}
+    >
+      {renderPreview}
+      {renderPlaceholder}
+    </Box>
+  );
+
+  return (
+    <>
+      <Box
+        {...getRootProps()}
+        sx={{
+          p: 1,
+          m: 'auto',
+          width: 144,
+          height: 144,
+          cursor: 'pointer',
+          overflow: 'hidden',
+          borderRadius: '50%',
+          border: (theme) => `1px dashed ${varAlpha(theme.vars.palette.grey['500Channel'], 0.2)}`,
+          ...(isDragActive && { opacity: 0.72 }),
+          ...(disabled && { opacity: 0.48, pointerEvents: 'none' }),
+          ...(hasError && { borderColor: 'error.main' }),
+          ...(hasFile && {
+            ...(hasError && {
+              bgcolor: (theme) => varAlpha(theme.vars.palette.error.mainChannel, 0.08),
+            }),
+            '&:hover .upload-placeholder': { opacity: 1 },
+          }),
+          ...sx,
+        }}
+      >
+        <input {...getInputProps()} />
+
+        {renderContent}
+      </Box>
+
+      {helperText && helperText}
+
+      <RejectionFiles files={fileRejections} />
+    </>
+  );
+}
diff --git a/src/shared/components/upload/upload-box.tsx b/src/shared/components/upload/upload-box.tsx
new file mode 100644
index 0000000..180ee57
--- /dev/null
+++ b/src/shared/components/upload/upload-box.tsx
@@ -0,0 +1,52 @@
+import { useDropzone } from 'react-dropzone';
+
+import Box from '@mui/material/Box';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { Iconify } from '../iconify';
+
+import type { UploadProps } from './types';
+
+// ----------------------------------------------------------------------
+
+export function UploadBox({ placeholder, error, disabled, sx, ...other }: UploadProps) {
+  const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({
+    disabled,
+    ...other,
+  });
+
+  const hasError = isDragReject || error;
+
+  return (
+    <Box
+      {...getRootProps()}
+      sx={{
+        width: 64,
+        height: 64,
+        flexShrink: 0,
+        display: 'flex',
+        borderRadius: 1,
+        cursor: 'pointer',
+        alignItems: 'center',
+        color: 'text.disabled',
+        justifyContent: 'center',
+        bgcolor: (theme) => varAlpha(theme.vars.palette.grey['500Channel'], 0.08),
+        border: (theme) => `dashed 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.16)}`,
+        ...(isDragActive && { opacity: 0.72 }),
+        ...(disabled && { opacity: 0.48, pointerEvents: 'none' }),
+        ...(hasError && {
+          color: 'error.main',
+          borderColor: 'error.main',
+          bgcolor: (theme) => varAlpha(theme.vars.palette.error.mainChannel, 0.08),
+        }),
+        '&:hover': { opacity: 0.72 },
+        ...sx,
+      }}
+    >
+      <input {...getInputProps()} />
+
+      {placeholder || <Iconify icon="eva:cloud-upload-fill" width={28} />}
+    </Box>
+  );
+}
diff --git a/src/shared/components/upload/upload.tsx b/src/shared/components/upload/upload.tsx
new file mode 100644
index 0000000..5309abd
--- /dev/null
+++ b/src/shared/components/upload/upload.tsx
@@ -0,0 +1,121 @@
+import { useDropzone } from 'react-dropzone';
+
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import FormHelperText from '@mui/material/FormHelperText';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { Iconify } from '../iconify';
+import { UploadPlaceholder } from './components/placeholder';
+import { RejectionFiles } from './components/rejection-files';
+import { MultiFilePreview } from './components/preview-multi-file';
+import { DeleteButton, SingleFilePreview } from './components/preview-single-file';
+
+import type { UploadProps } from './types';
+
+// ----------------------------------------------------------------------
+
+export function Upload({
+  sx,
+  value,
+  error,
+  disabled,
+  onDelete,
+  onUpload,
+  onRemove,
+  thumbnail,
+  helperText,
+  onRemoveAll,
+  multiple = false,
+  ...other
+}: UploadProps) {
+  const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
+    multiple,
+    disabled,
+    ...other,
+  });
+
+  const isArray = Array.isArray(value) && multiple;
+
+  const hasFile = !isArray && !!value;
+
+  const hasFiles = isArray && !!value.length;
+
+  const hasError = isDragReject || !!error;
+
+  const renderMultiPreview = hasFiles && (
+    <>
+      <MultiFilePreview files={value} thumbnail={thumbnail} onRemove={onRemove} sx={{ my: 3 }} />
+
+      {(onRemoveAll || onUpload) && (
+        <Stack direction="row" justifyContent="flex-end" spacing={1.5}>
+          {onRemoveAll && (
+            <Button color="inherit" variant="outlined" size="small" onClick={onRemoveAll}>
+              Remove all
+            </Button>
+          )}
+
+          {onUpload && (
+            <Button
+              size="small"
+              variant="contained"
+              onClick={onUpload}
+              startIcon={<Iconify icon="eva:cloud-upload-fill" />}
+            >
+              Upload
+            </Button>
+          )}
+        </Stack>
+      )}
+    </>
+  );
+
+  return (
+    <Box sx={{ width: 1, position: 'relative', ...sx }}>
+      <Box
+        {...getRootProps()}
+        sx={{
+          p: 5,
+          outline: 'none',
+          borderRadius: 1,
+          cursor: 'pointer',
+          overflow: 'hidden',
+          position: 'relative',
+          bgcolor: (theme) => varAlpha(theme.vars.palette.grey['500Channel'], 0.08),
+          border: (theme) => `1px dashed ${varAlpha(theme.vars.palette.grey['500Channel'], 0.2)}`,
+          transition: (theme) => theme.transitions.create(['opacity', 'padding']),
+          '&:hover': { opacity: 0.72 },
+          ...(isDragActive && { opacity: 0.72 }),
+          ...(disabled && { opacity: 0.48, pointerEvents: 'none' }),
+          ...(hasError && {
+            color: 'error.main',
+            borderColor: 'error.main',
+            bgcolor: (theme) => varAlpha(theme.vars.palette.error.mainChannel, 0.08),
+          }),
+          ...(hasFile && { padding: '28% 0' }),
+        }}
+      >
+        <input {...getInputProps()} />
+
+        {/* Single file */}
+        {hasFile ? <SingleFilePreview file={value as File} /> : <UploadPlaceholder />}
+      </Box>
+
+      {/* Single file */}
+      {hasFile && <DeleteButton onClick={onDelete} />}
+
+      {helperText && (
+        <FormHelperText error={!!error} sx={{ px: 2 }}>
+          {helperText}
+        </FormHelperText>
+      )}
+
+      <RejectionFiles files={fileRejections} />
+
+      {/* Multi files */}
+      {renderMultiPreview}
+    </Box>
+  );
+}
diff --git a/src/shared/sections/job/job-details-candidates.tsx b/src/shared/sections/job/job-details-candidates.tsx
new file mode 100644
index 0000000..99f6860
--- /dev/null
+++ b/src/shared/sections/job/job-details-candidates.tsx
@@ -0,0 +1,118 @@
+import type { IJobCandidate } from 'src/shared/types/job';
+
+import Box from '@mui/material/Box';
+import Card from '@mui/material/Card';
+import Stack from '@mui/material/Stack';
+import Avatar from '@mui/material/Avatar';
+import Tooltip from '@mui/material/Tooltip';
+import Pagination from '@mui/material/Pagination';
+import IconButton from '@mui/material/IconButton';
+import ListItemText from '@mui/material/ListItemText';
+
+import { varAlpha } from 'src/shared/theme/styles';
+
+import { Iconify } from 'src/shared/components/iconify';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  candidates: IJobCandidate[];
+};
+
+export function JobDetailsCandidates({ candidates }: Props) {
+  return (
+    <>
+      <Box
+        gap={3}
+        display="grid"
+        gridTemplateColumns={{ xs: 'repeat(1, 1fr)', md: 'repeat(3, 1fr)' }}
+      >
+        {candidates.map((candidate) => (
+          <Card key={candidate.id} sx={{ p: 3, gap: 2, display: 'flex' }}>
+            <IconButton sx={{ position: 'absolute', top: 8, right: 8 }}>
+              <Iconify icon="eva:more-vertical-fill" />
+            </IconButton>
+
+            <Avatar alt={candidate.name} src={candidate.avatarUrl} sx={{ width: 48, height: 48 }} />
+
+            <Stack spacing={2}>
+              <ListItemText
+                primary={candidate.name}
+                secondary={candidate.role}
+                secondaryTypographyProps={{
+                  mt: 0.5,
+                  component: 'span',
+                  typography: 'caption',
+                  color: 'text.disabled',
+                }}
+              />
+
+              <Stack spacing={1} direction="row">
+                <IconButton
+                  size="small"
+                  color="error"
+                  sx={{
+                    borderRadius: 1,
+                    bgcolor: (theme) => varAlpha(theme.vars.palette.error.mainChannel, 0.08),
+                    '&:hover': {
+                      bgcolor: (theme) => varAlpha(theme.vars.palette.error.mainChannel, 0.16),
+                    },
+                  }}
+                >
+                  <Iconify width={18} icon="solar:phone-bold" />
+                </IconButton>
+
+                <IconButton
+                  size="small"
+                  color="info"
+                  sx={{
+                    borderRadius: 1,
+                    bgcolor: (theme) => varAlpha(theme.vars.palette.info.mainChannel, 0.08),
+                    '&:hover': {
+                      bgcolor: (theme) => varAlpha(theme.vars.palette.info.mainChannel, 0.16),
+                    },
+                  }}
+                >
+                  <Iconify width={18} icon="solar:chat-round-dots-bold" />
+                </IconButton>
+
+                <IconButton
+                  size="small"
+                  color="primary"
+                  sx={{
+                    borderRadius: 1,
+                    bgcolor: (theme) => varAlpha(theme.vars.palette.primary.mainChannel, 0.08),
+                    '&:hover': {
+                      bgcolor: (theme) => varAlpha(theme.vars.palette.primary.mainChannel, 0.16),
+                    },
+                  }}
+                >
+                  <Iconify width={18} icon="fluent:mail-24-filled" />
+                </IconButton>
+
+                <Tooltip title="Download CV">
+                  <IconButton
+                    size="small"
+                    color="secondary"
+                    sx={{
+                      borderRadius: 1,
+                      bgcolor: (theme) => varAlpha(theme.vars.palette.secondary.mainChannel, 0.08),
+                      '&:hover': {
+                        bgcolor: (theme) =>
+                          varAlpha(theme.vars.palette.secondary.mainChannel, 0.16),
+                      },
+                    }}
+                  >
+                    <Iconify width={18} icon="eva:cloud-download-fill" />
+                  </IconButton>
+                </Tooltip>
+              </Stack>
+            </Stack>
+          </Card>
+        ))}
+      </Box>
+
+      <Pagination count={10} sx={{ mt: { xs: 5, md: 8 }, mx: 'auto' }} />
+    </>
+  );
+}
diff --git a/src/shared/sections/job/job-details-content.tsx b/src/shared/sections/job/job-details-content.tsx
new file mode 100644
index 0000000..94f9b89
--- /dev/null
+++ b/src/shared/sections/job/job-details-content.tsx
@@ -0,0 +1,123 @@
+import type { IJobItem } from 'src/shared/types/job';
+
+import Chip from '@mui/material/Chip';
+import Card from '@mui/material/Card';
+import Paper from '@mui/material/Paper';
+import Stack from '@mui/material/Stack';
+import Avatar from '@mui/material/Avatar';
+import Grid from '@mui/material/Unstable_Grid2';
+import Typography from '@mui/material/Typography';
+import ListItemText from '@mui/material/ListItemText';
+
+import { fDate } from 'src/utils/format-time';
+import { fCurrency } from 'src/utils/format-number';
+
+import { Iconify } from 'src/shared/components/iconify';
+import { Markdown } from 'src/shared/components/markdown';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  job?: IJobItem;
+};
+
+export function JobDetailsContent({ job }: Props) {
+  const renderContent = (
+    <Card sx={{ p: 3, gap: 3, display: 'flex', flexDirection: 'column' }}>
+      <Typography variant="h4">{job?.title}</Typography>
+
+      <Markdown children={job?.content} />
+
+      <Stack spacing={2}>
+        <Typography variant="h6">Skills</Typography>
+        <Stack direction="row" alignItems="center" spacing={1}>
+          {job?.skills.map((skill) => <Chip key={skill} label={skill} variant="soft" />)}
+        </Stack>
+      </Stack>
+
+      <Stack spacing={2}>
+        <Typography variant="h6">Benefits</Typography>
+        <Stack direction="row" alignItems="center" spacing={1}>
+          {job?.benefits.map((benefit) => <Chip key={benefit} label={benefit} variant="soft" />)}
+        </Stack>
+      </Stack>
+    </Card>
+  );
+
+  const renderOverview = (
+    <Card sx={{ p: 3, gap: 2, display: 'flex', flexDirection: 'column' }}>
+      {[
+        {
+          label: 'Date posted',
+          value: fDate(job?.createdAt),
+          icon: <Iconify icon="solar:calendar-date-bold" />,
+        },
+        {
+          label: 'Expiration date',
+          value: fDate(job?.expiredDate),
+          icon: <Iconify icon="solar:calendar-date-bold" />,
+        },
+        {
+          label: 'Employment type',
+          value: job?.employmentTypes,
+          icon: <Iconify icon="solar:clock-circle-bold" />,
+        },
+        {
+          label: 'Offered salary',
+          value: job?.salary.negotiable ? 'Negotiable' : fCurrency(job?.salary.price),
+          icon: <Iconify icon="solar:wad-of-money-bold" />,
+        },
+        {
+          label: 'Experience',
+          value: job?.experience,
+          icon: <Iconify icon="carbon:skill-level-basic" />,
+        },
+      ].map((item) => (
+        <Stack key={item.label} spacing={1.5} direction="row">
+          {item.icon}
+          <ListItemText
+            primary={item.label}
+            secondary={item.value}
+            primaryTypographyProps={{ typography: 'body2', color: 'text.secondary', mb: 0.5 }}
+            secondaryTypographyProps={{
+              component: 'span',
+              color: 'text.primary',
+              typography: 'subtitle2',
+            }}
+          />
+        </Stack>
+      ))}
+    </Card>
+  );
+
+  const renderCompany = (
+    <Paper variant="outlined" sx={{ p: 3, mt: 3, gap: 2, borderRadius: 2, display: 'flex' }}>
+      <Avatar
+        alt={job?.company.name}
+        src={job?.company.logo}
+        variant="rounded"
+        sx={{ width: 64, height: 64 }}
+      />
+
+      <Stack spacing={1}>
+        <Typography variant="subtitle1">{job?.company.name}</Typography>
+        <Typography variant="body2">{job?.company.fullAddress}</Typography>
+        <Typography variant="body2">{job?.company.phoneNumber}</Typography>
+      </Stack>
+    </Paper>
+  );
+
+  return (
+    <Grid container spacing={3}>
+      <Grid xs={12} md={8}>
+        {renderContent}
+      </Grid>
+
+      <Grid xs={12} md={4}>
+        {renderOverview}
+
+        {renderCompany}
+      </Grid>
+    </Grid>
+  );
+}
diff --git a/src/shared/sections/job/job-details-toolbar.tsx b/src/shared/sections/job/job-details-toolbar.tsx
new file mode 100644
index 0000000..2659836
--- /dev/null
+++ b/src/shared/sections/job/job-details-toolbar.tsx
@@ -0,0 +1,103 @@
+import type { StackProps } from '@mui/material/Stack';
+
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import Tooltip from '@mui/material/Tooltip';
+import MenuList from '@mui/material/MenuList';
+import MenuItem from '@mui/material/MenuItem';
+import IconButton from '@mui/material/IconButton';
+import LoadingButton from '@mui/lab/LoadingButton';
+
+import { RouterLink } from 'src/routes/components';
+
+import { Iconify } from 'src/shared/components/iconify';
+import { usePopover, CustomPopover } from 'src/shared/components/custom-popover';
+
+// ----------------------------------------------------------------------
+
+type Props = StackProps & {
+  backLink: string;
+  editLink: string;
+  liveLink: string;
+  publish: string;
+  onChangePublish: (newValue: string) => void;
+  publishOptions: {
+    value: string;
+    label: string;
+  }[];
+};
+
+export function JobDetailsToolbar({
+  publish,
+  backLink,
+  editLink,
+  liveLink,
+  publishOptions,
+  onChangePublish,
+  sx,
+  ...other
+}: Props) {
+  const popover = usePopover();
+
+  return (
+    <>
+      <Stack spacing={1.5} direction="row" sx={{ mb: { xs: 3, md: 5 }, ...sx }} {...other}>
+        <Button
+          component={RouterLink}
+          href={backLink}
+          startIcon={<Iconify icon="eva:arrow-ios-back-fill" width={16} />}
+        >
+          Back
+        </Button>
+
+        <Box sx={{ flexGrow: 1 }} />
+
+        {publish === 'published' && (
+          <Tooltip title="Go Live">
+            <IconButton component={RouterLink} href={liveLink}>
+              <Iconify icon="eva:external-link-fill" />
+            </IconButton>
+          </Tooltip>
+        )}
+
+        <Tooltip title="Edit">
+          <IconButton component={RouterLink} href={editLink}>
+            <Iconify icon="solar:pen-bold" />
+          </IconButton>
+        </Tooltip>
+
+        <LoadingButton
+          color="inherit"
+          variant="contained"
+          loading={!publish}
+          loadingIndicator="Loading…"
+          endIcon={<Iconify icon="eva:arrow-ios-downward-fill" />}
+          onClick={popover.onOpen}
+          sx={{ textTransform: 'capitalize' }}
+        >
+          {publish}
+        </LoadingButton>
+      </Stack>
+
+      <CustomPopover open={popover.open} anchorEl={popover.anchorEl} onClose={popover.onClose}>
+        <MenuList>
+          {publishOptions.map((option) => (
+            <MenuItem
+              key={option.value}
+              selected={option.value === publish}
+              onClick={() => {
+                popover.onClose();
+                onChangePublish(option.value);
+              }}
+            >
+              {option.value === 'published' && <Iconify icon="eva:cloud-upload-fill" />}
+              {option.value === 'draft' && <Iconify icon="solar:file-text-bold" />}
+              {option.label}
+            </MenuItem>
+          ))}
+        </MenuList>
+      </CustomPopover>
+    </>
+  );
+}
diff --git a/src/shared/sections/job/job-filters-result.tsx b/src/shared/sections/job/job-filters-result.tsx
new file mode 100644
index 0000000..3a6577b
--- /dev/null
+++ b/src/shared/sections/job/job-filters-result.tsx
@@ -0,0 +1,88 @@
+import type { IJobFilters } from 'src/shared/types/job';
+import type { Theme, SxProps } from '@mui/material/styles';
+import type { UseSetStateReturn } from 'src/hooks/use-set-state';
+
+import Chip from '@mui/material/Chip';
+
+import { chipProps, FiltersBlock, FiltersResult } from 'src/shared/components/filters-result';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  totalResults: number;
+  sx?: SxProps<Theme>;
+  filters: UseSetStateReturn<IJobFilters>;
+};
+
+export function JobFiltersResult({ filters, totalResults, sx }: Props) {
+  const handleRemoveEmploymentTypes = (inputValue: string) => {
+    const newValue = filters.state.employmentTypes.filter((item) => item !== inputValue);
+    filters.setState({ employmentTypes: newValue });
+  };
+
+  const handleRemoveExperience = () => {
+    filters.setState({ experience: 'all' });
+  };
+
+  const handleRemoveRoles = (inputValue: string) => {
+    const newValue = filters.state.roles.filter((item) => item !== inputValue);
+    filters.setState({ roles: newValue });
+  };
+
+  const handleRemoveLocations = (inputValue: string) => {
+    const newValue = filters.state.locations.filter((item) => item !== inputValue);
+    filters.setState({ locations: newValue });
+  };
+
+  const handleRemoveBenefits = (inputValue: string) => {
+    const newValue = filters.state.benefits.filter((item) => item !== inputValue);
+    filters.setState({ benefits: newValue });
+  };
+
+  return (
+    <FiltersResult totalResults={totalResults} onReset={filters.onResetState} sx={sx}>
+      <FiltersBlock label="Employment types:" isShow={!!filters.state.employmentTypes.length}>
+        {filters.state.employmentTypes.map((item) => (
+          <Chip
+            {...chipProps}
+            key={item}
+            label={item}
+            onDelete={() => handleRemoveEmploymentTypes(item)}
+          />
+        ))}
+      </FiltersBlock>
+
+      <FiltersBlock label="Experience:" isShow={filters.state.experience !== 'all'}>
+        <Chip {...chipProps} label={filters.state.experience} onDelete={handleRemoveExperience} />
+      </FiltersBlock>
+
+      <FiltersBlock label="Roles:" isShow={!!filters.state.roles.length}>
+        {filters.state.roles.map((item) => (
+          <Chip {...chipProps} key={item} label={item} onDelete={() => handleRemoveRoles(item)} />
+        ))}
+      </FiltersBlock>
+
+      <FiltersBlock label="Locations:" isShow={!!filters.state.locations.length}>
+        {filters.state.locations.map((item) => (
+          <Chip
+            {...chipProps}
+            key={item}
+            label={item}
+            onDelete={() => handleRemoveLocations(item)}
+          />
+        ))}
+      </FiltersBlock>
+
+      <FiltersBlock label="Benefits:" isShow={!!filters.state.benefits.length}>
+        {filters.state.benefits.map((item) => (
+          <Chip
+            {...chipProps}
+            key={item}
+            label={item}
+            onDelete={() => handleRemoveBenefits(item)}
+          />
+        ))}
+      </FiltersBlock>
+    </FiltersResult>
+  );
+}
diff --git a/src/shared/sections/job/job-filters.tsx b/src/shared/sections/job/job-filters.tsx
new file mode 100644
index 0000000..055dab4
--- /dev/null
+++ b/src/shared/sections/job/job-filters.tsx
@@ -0,0 +1,257 @@
+import type { IJobFilters } from 'src/shared/types/job';
+import type { UseSetStateReturn } from 'src/hooks/use-set-state';
+
+import { useCallback } from 'react';
+
+import Box from '@mui/material/Box';
+import Chip from '@mui/material/Chip';
+import Radio from '@mui/material/Radio';
+import Stack from '@mui/material/Stack';
+import Badge from '@mui/material/Badge';
+import Drawer from '@mui/material/Drawer';
+import Button from '@mui/material/Button';
+import Divider from '@mui/material/Divider';
+import Tooltip from '@mui/material/Tooltip';
+import Checkbox from '@mui/material/Checkbox';
+import TextField from '@mui/material/TextField';
+import IconButton from '@mui/material/IconButton';
+import Typography from '@mui/material/Typography';
+import Autocomplete from '@mui/material/Autocomplete';
+import FormControlLabel from '@mui/material/FormControlLabel';
+
+import { Iconify } from 'src/shared/components/iconify';
+import { Scrollbar } from 'src/shared/components/scrollbar';
+import { CountrySelect } from 'src/shared/components/country-select';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  open: boolean;
+  canReset: boolean;
+  onOpen: () => void;
+  onClose: () => void;
+  filters: UseSetStateReturn<IJobFilters>;
+  options: {
+    roles: string[];
+    benefits: string[];
+    experiences: string[];
+    employmentTypes: string[];
+  };
+};
+
+export function JobFilters({ open, canReset, onOpen, onClose, filters, options }: Props) {
+  const handleFilterEmploymentTypes = useCallback(
+    (newValue: string) => {
+      const checked = filters.state.employmentTypes.includes(newValue)
+        ? filters.state.employmentTypes.filter((value) => value !== newValue)
+        : [...filters.state.employmentTypes, newValue];
+
+      filters.setState({ employmentTypes: checked });
+    },
+    [filters]
+  );
+
+  const handleFilterExperience = useCallback(
+    (newValue: string) => {
+      filters.setState({ experience: newValue });
+    },
+    [filters]
+  );
+
+  const handleFilterRoles = useCallback(
+    (newValue: string[]) => {
+      filters.setState({ roles: newValue });
+    },
+    [filters]
+  );
+
+  const handleFilterLocations = useCallback(
+    (newValue: string[]) => {
+      filters.setState({ locations: newValue });
+    },
+    [filters]
+  );
+
+  const handleFilterBenefits = useCallback(
+    (newValue: string) => {
+      const checked = filters.state.benefits.includes(newValue)
+        ? filters.state.benefits.filter((value) => value !== newValue)
+        : [...filters.state.benefits, newValue];
+
+      filters.setState({ benefits: checked });
+    },
+    [filters]
+  );
+
+  const renderHead = (
+    <>
+      <Box display="flex" alignItems="center" sx={{ py: 2, pr: 1, pl: 2.5 }}>
+        <Typography variant="h6" sx={{ flexGrow: 1 }}>
+          Filters
+        </Typography>
+
+        <Tooltip title="Reset">
+          <IconButton onClick={filters.onResetState}>
+            <Badge color="error" variant="dot" invisible={!canReset}>
+              <Iconify icon="solar:restart-bold" />
+            </Badge>
+          </IconButton>
+        </Tooltip>
+
+        <IconButton onClick={onClose}>
+          <Iconify icon="mingcute:close-line" />
+        </IconButton>
+      </Box>
+
+      <Divider sx={{ borderStyle: 'dashed' }} />
+    </>
+  );
+
+  const renderEmploymentTypes = (
+    <Box display="flex" flexDirection="column">
+      <Typography variant="subtitle2" sx={{ mb: 1 }}>
+        Employment types
+      </Typography>
+      {options.employmentTypes.map((option) => (
+        <FormControlLabel
+          key={option}
+          control={
+            <Checkbox
+              checked={filters.state.employmentTypes.includes(option)}
+              onClick={() => handleFilterEmploymentTypes(option)}
+            />
+          }
+          label={option}
+        />
+      ))}
+    </Box>
+  );
+
+  const renderExperience = (
+    <Box display="flex" flexDirection="column">
+      <Typography variant="subtitle2" sx={{ mb: 1 }}>
+        Experience
+      </Typography>
+      {options.experiences.map((option) => (
+        <FormControlLabel
+          key={option}
+          control={
+            <Radio
+              checked={option === filters.state.experience}
+              onClick={() => handleFilterExperience(option)}
+            />
+          }
+          label={option}
+          sx={{ ...(option === 'all' && { textTransform: 'capitalize' }) }}
+        />
+      ))}
+    </Box>
+  );
+
+  const renderRoles = (
+    <Box display="flex" flexDirection="column">
+      <Typography variant="subtitle2" sx={{ mb: 1.5 }}>
+        Roles
+      </Typography>
+      <Autocomplete
+        multiple
+        disableCloseOnSelect
+        options={options.roles.map((option) => option)}
+        getOptionLabel={(option) => option}
+        value={filters.state.roles}
+        onChange={(event, newValue) => handleFilterRoles(newValue)}
+        renderInput={(params) => <TextField placeholder="Select Roles" {...params} />}
+        renderOption={(props, option) => (
+          <li {...props} key={option}>
+            {option}
+          </li>
+        )}
+        renderTags={(selected, getTagProps) =>
+          selected.map((option, index) => (
+            <Chip
+              {...getTagProps({ index })}
+              key={option}
+              label={option}
+              size="small"
+              variant="soft"
+            />
+          ))
+        }
+      />
+    </Box>
+  );
+
+  const renderLocations = (
+    <Box display="flex" flexDirection="column">
+      <Typography variant="subtitle2" sx={{ mb: 1.5 }}>
+        Locations
+      </Typography>
+
+      <CountrySelect
+        id="multiple-locations"
+        multiple
+        fullWidth
+        placeholder={filters.state.locations.length ? '+ Locations' : 'Select Locations'}
+        value={filters.state.locations}
+        onChange={(event, newValue) => handleFilterLocations(newValue)}
+      />
+    </Box>
+  );
+
+  const renderBenefits = (
+    <Box display="flex" flexDirection="column">
+      <Typography variant="subtitle2" sx={{ mb: 1 }}>
+        Benefits
+      </Typography>
+      {options.benefits.map((option) => (
+        <FormControlLabel
+          key={option}
+          control={
+            <Checkbox
+              checked={filters.state.benefits.includes(option)}
+              onClick={() => handleFilterBenefits(option)}
+            />
+          }
+          label={option}
+        />
+      ))}
+    </Box>
+  );
+
+  return (
+    <>
+      <Button
+        disableRipple
+        color="inherit"
+        endIcon={
+          <Badge color="error" variant="dot" invisible={!canReset}>
+            <Iconify icon="ic:round-filter-list" />
+          </Badge>
+        }
+        onClick={onOpen}
+      >
+        Filters
+      </Button>
+
+      <Drawer
+        anchor="right"
+        open={open}
+        onClose={onClose}
+        slotProps={{ backdrop: { invisible: true } }}
+        PaperProps={{ sx: { width: 320 } }}
+      >
+        {renderHead}
+
+        <Scrollbar sx={{ px: 2.5, py: 3 }}>
+          <Stack spacing={3}>
+            {renderEmploymentTypes}
+            {renderExperience}
+            {renderRoles}
+            {renderLocations}
+            {renderBenefits}
+          </Stack>
+        </Scrollbar>
+      </Drawer>
+    </>
+  );
+}
diff --git a/src/shared/sections/job/job-item.tsx b/src/shared/sections/job/job-item.tsx
new file mode 100644
index 0000000..1c8f40e
--- /dev/null
+++ b/src/shared/sections/job/job-item.tsx
@@ -0,0 +1,158 @@
+import type { IJobItem } from 'src/shared/types/job';
+
+import Box from '@mui/material/Box';
+import Link from '@mui/material/Link';
+import Card from '@mui/material/Card';
+import Stack from '@mui/material/Stack';
+import Avatar from '@mui/material/Avatar';
+import Divider from '@mui/material/Divider';
+import MenuList from '@mui/material/MenuList';
+import MenuItem from '@mui/material/MenuItem';
+import IconButton from '@mui/material/IconButton';
+import Typography from '@mui/material/Typography';
+import ListItemText from '@mui/material/ListItemText';
+
+import { paths } from 'src/routes/paths';
+import { RouterLink } from 'src/routes/components';
+
+import { fDate } from 'src/utils/format-time';
+import { fCurrency } from 'src/utils/format-number';
+
+import { Iconify } from 'src/shared/components/iconify';
+import { usePopover, CustomPopover } from 'src/shared/components/custom-popover';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  job: IJobItem;
+  onView: () => void;
+  onEdit: () => void;
+  onDelete: () => void;
+};
+
+export function JobItem({ job, onView, onEdit, onDelete }: Props) {
+  const popover = usePopover();
+
+  return (
+    <>
+      <Card>
+        <IconButton onClick={popover.onOpen} sx={{ position: 'absolute', top: 8, right: 8 }}>
+          <Iconify icon="eva:more-vertical-fill" />
+        </IconButton>
+
+        <Stack sx={{ p: 3, pb: 2 }}>
+          <Avatar
+            alt={job.company.name}
+            src={job.company.logo}
+            variant="rounded"
+            sx={{ width: 48, height: 48, mb: 2 }}
+          />
+
+          <ListItemText
+            sx={{ mb: 1 }}
+            primary={
+              <Link component={RouterLink} href={paths.freelancers.details(job.id)} color="inherit">
+                {job.title}
+              </Link>
+            }
+            secondary={`Posted date: ${fDate(job.createdAt)}`}
+            primaryTypographyProps={{ typography: 'subtitle1' }}
+            secondaryTypographyProps={{
+              mt: 1,
+              component: 'span',
+              typography: 'caption',
+              color: 'text.disabled',
+            }}
+          />
+
+          <Stack
+            spacing={0.5}
+            direction="row"
+            alignItems="center"
+            sx={{ color: 'primary.main', typography: 'caption' }}
+          >
+            <Iconify width={16} icon="solar:users-group-rounded-bold" />
+            {job.candidates.length} candidates
+          </Stack>
+        </Stack>
+
+        <Divider sx={{ borderStyle: 'dashed' }} />
+
+        <Box rowGap={1.5} display="grid" gridTemplateColumns="repeat(2, 1fr)" sx={{ p: 3 }}>
+          {[
+            {
+              label: job.experience,
+              icon: <Iconify width={16} icon="carbon:skill-level-basic" sx={{ flexShrink: 0 }} />,
+            },
+            {
+              label: job.employmentTypes.join(', '),
+              icon: <Iconify width={16} icon="solar:clock-circle-bold" sx={{ flexShrink: 0 }} />,
+            },
+            {
+              label: job.salary.negotiable ? 'Negotiable' : fCurrency(job.salary.price),
+              icon: <Iconify width={16} icon="solar:wad-of-money-bold" sx={{ flexShrink: 0 }} />,
+            },
+            {
+              label: job.role,
+              icon: <Iconify width={16} icon="solar:user-rounded-bold" sx={{ flexShrink: 0 }} />,
+            },
+          ].map((item) => (
+            <Stack
+              key={item.label}
+              spacing={0.5}
+              flexShrink={0}
+              direction="row"
+              alignItems="center"
+              sx={{ color: 'text.disabled', minWidth: 0 }}
+            >
+              {item.icon}
+              <Typography variant="caption" noWrap>
+                {item.label}
+              </Typography>
+            </Stack>
+          ))}
+        </Box>
+      </Card>
+
+      <CustomPopover
+        open={popover.open}
+        anchorEl={popover.anchorEl}
+        onClose={popover.onClose}
+        slotProps={{ arrow: { placement: 'right-top' } }}
+      >
+        <MenuList>
+          <MenuItem
+            onClick={() => {
+              popover.onClose();
+              onView();
+            }}
+          >
+            <Iconify icon="solar:eye-bold" />
+            View
+          </MenuItem>
+
+          <MenuItem
+            onClick={() => {
+              popover.onClose();
+              onEdit();
+            }}
+          >
+            <Iconify icon="solar:pen-bold" />
+            Edit
+          </MenuItem>
+
+          <MenuItem
+            onClick={() => {
+              popover.onClose();
+              onDelete();
+            }}
+            sx={{ color: 'error.main' }}
+          >
+            <Iconify icon="solar:trash-bin-trash-bold" />
+            Delete
+          </MenuItem>
+        </MenuList>
+      </CustomPopover>
+    </>
+  );
+}
diff --git a/src/shared/sections/job/job-list.tsx b/src/shared/sections/job/job-list.tsx
new file mode 100644
index 0000000..7cafc43
--- /dev/null
+++ b/src/shared/sections/job/job-list.tsx
@@ -0,0 +1,69 @@
+import type { IJobItem } from 'src/shared/types/job';
+
+import { useCallback } from 'react';
+
+import Box from '@mui/material/Box';
+import Pagination, { paginationClasses } from '@mui/material/Pagination';
+
+import { paths } from 'src/routes/paths';
+import { useRouter } from 'src/routes/hooks';
+
+import { JobItem } from './job-item';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  jobs: IJobItem[];
+};
+
+export function JobList({ jobs }: Props) {
+  const router = useRouter();
+
+  const handleView = useCallback(
+    (id: string) => {
+      router.push(paths.freelancers.details(id));
+    },
+    [router]
+  );
+
+  const handleEdit = useCallback(
+    (id: string) => {
+      router.push(paths.freelancers.edit(id));
+    },
+    [router]
+  );
+
+  const handleDelete = useCallback((id: string) => {
+    console.info('DELETE', id);
+  }, []);
+
+  return (
+    <>
+      <Box
+        gap={3}
+        display="grid"
+        gridTemplateColumns={{ xs: 'repeat(1, 1fr)', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' }}
+      >
+        {jobs.map((job) => (
+          <JobItem
+            key={job.id}
+            job={job}
+            onView={() => handleView(job.id)}
+            onEdit={() => handleEdit(job.id)}
+            onDelete={() => handleDelete(job.id)}
+          />
+        ))}
+      </Box>
+
+      {jobs.length > 8 && (
+        <Pagination
+          count={8}
+          sx={{
+            mt: { xs: 8, md: 8 },
+            [`& .${paginationClasses.ul}`]: { justifyContent: 'center' },
+          }}
+        />
+      )}
+    </>
+  );
+}
diff --git a/src/shared/sections/job/job-new-edit-form.tsx b/src/shared/sections/job/job-new-edit-form.tsx
new file mode 100644
index 0000000..24c9020
--- /dev/null
+++ b/src/shared/sections/job/job-new-edit-form.tsx
@@ -0,0 +1,356 @@
+import type { IJobItem } from 'src/shared/types/job';
+
+import { z as zod } from 'zod';
+import { useMemo, useEffect } from 'react';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm, Controller } from 'react-hook-form';
+
+import Box from '@mui/material/Box';
+import Chip from '@mui/material/Chip';
+import Card from '@mui/material/Card';
+import Stack from '@mui/material/Stack';
+import Paper from '@mui/material/Paper';
+import Switch from '@mui/material/Switch';
+import Divider from '@mui/material/Divider';
+import ButtonBase from '@mui/material/ButtonBase';
+import CardHeader from '@mui/material/CardHeader';
+import Typography from '@mui/material/Typography';
+import LoadingButton from '@mui/lab/LoadingButton';
+import InputAdornment from '@mui/material/InputAdornment';
+import FormControlLabel from '@mui/material/FormControlLabel';
+
+import { paths } from 'src/routes/paths';
+import { useRouter } from 'src/routes/hooks';
+
+import {
+  _roles,
+  JOB_SKILL_OPTIONS,
+  JOB_BENEFIT_OPTIONS,
+  JOB_EXPERIENCE_OPTIONS,
+  JOB_EMPLOYMENT_TYPE_OPTIONS,
+  JOB_WORKING_SCHEDULE_OPTIONS,
+} from 'src/shared/_mock';
+
+import { toast } from 'src/shared/components/snackbar';
+import { Iconify } from 'src/shared/components/iconify';
+import { Form, Field, schemaHelper } from 'src/shared/components/hook-form';
+
+// ----------------------------------------------------------------------
+
+export type NewJobSchemaType = zod.infer<typeof NewJobSchema>;
+
+export const NewJobSchema = zod.object({
+  title: zod.string().min(1, { message: 'Title is required!' }),
+  content: zod.string().min(1, { message: 'Content is required!' }),
+  employmentTypes: zod.string().array().nonempty({ message: 'Choose at least one option!' }),
+  role: schemaHelper.objectOrNull<string | null>({
+    message: { required_error: 'Role is required!' },
+  }),
+  skills: zod.string().array().nonempty({ message: 'Choose at least one option!' }),
+  workingSchedule: zod.string().array().nonempty({ message: 'Choose at least one option!' }),
+  locations: zod.string().array().nonempty({ message: 'Choose at least one option!' }),
+  expiredDate: schemaHelper.date({ message: { required_error: 'Expired date is required!' } }),
+  salary: zod.object({
+    price: zod.number().min(1, { message: 'Price is required!' }),
+    // Not required
+    type: zod.string(),
+    negotiable: zod.boolean(),
+  }),
+  benefits: zod.string().array().nonempty({ message: 'Choose at least one option!' }),
+  // Not required
+  experience: zod.string(),
+});
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  currentJob?: IJobItem;
+};
+
+export function JobNewEditForm({ currentJob }: Props) {
+  const router = useRouter();
+
+  const defaultValues = useMemo(
+    () => ({
+      title: currentJob?.title || '',
+      content: currentJob?.content || '',
+      employmentTypes: currentJob?.employmentTypes || [],
+      experience: currentJob?.experience || '1 year exp',
+      role: currentJob?.role || _roles[1],
+      skills: currentJob?.skills || [],
+      workingSchedule: currentJob?.workingSchedule || [],
+      locations: currentJob?.locations || [],
+      expiredDate: currentJob?.expiredDate || null,
+      salary: currentJob?.salary || { type: 'Hourly', price: 0, negotiable: false },
+      benefits: currentJob?.benefits || [],
+    }),
+    [currentJob]
+  );
+
+  const methods = useForm<NewJobSchemaType>({
+    mode: 'all',
+    resolver: zodResolver(NewJobSchema),
+    defaultValues,
+  });
+
+  const {
+    reset,
+    control,
+    handleSubmit,
+    formState: { isSubmitting },
+  } = methods;
+
+  useEffect(() => {
+    if (currentJob) {
+      reset(defaultValues);
+    }
+  }, [currentJob, defaultValues, reset]);
+
+  const onSubmit = handleSubmit(async (data) => {
+    try {
+      await new Promise((resolve) => setTimeout(resolve, 500));
+      reset();
+      toast.success(currentJob ? 'Update success!' : 'Create success!');
+      router.push(paths.freelancers.jobs);
+      console.info('DATA', data);
+    } catch (error) {
+      console.error(error);
+    }
+  });
+
+  const renderDetails = (
+    <Card>
+      <CardHeader title="Details" subheader="Title, short description, image..." sx={{ mb: 3 }} />
+
+      <Divider />
+
+      <Stack spacing={3} sx={{ p: 3 }}>
+        <Stack spacing={1.5}>
+          <Typography variant="subtitle2">Title</Typography>
+          <Field.Text name="title" placeholder="Ex: Software Engineer..." />
+        </Stack>
+
+        <Stack spacing={1.5}>
+          <Typography variant="subtitle2">Content</Typography>
+          <Field.Editor name="content" sx={{ maxHeight: 480 }} />
+        </Stack>
+      </Stack>
+    </Card>
+  );
+
+  const renderProperties = (
+    <Card>
+      <CardHeader
+        title="Properties"
+        subheader="Additional functions and attributes..."
+        sx={{ mb: 3 }}
+      />
+
+      <Divider />
+
+      <Stack spacing={3} sx={{ p: 3 }}>
+        <Stack spacing={1}>
+          <Typography variant="subtitle2">Employment type</Typography>
+          <Field.MultiCheckbox
+            row
+            name="employmentTypes"
+            options={JOB_EMPLOYMENT_TYPE_OPTIONS}
+            sx={{ gap: 4 }}
+          />
+        </Stack>
+
+        <Stack spacing={1}>
+          <Typography variant="subtitle2">Experience</Typography>
+          <Field.RadioGroup
+            row
+            name="experience"
+            options={JOB_EXPERIENCE_OPTIONS}
+            sx={{ gap: 4 }}
+          />
+        </Stack>
+
+        <Stack spacing={1.5}>
+          <Typography variant="subtitle2">Role</Typography>
+          <Field.Autocomplete
+            name="role"
+            autoHighlight
+            options={_roles.map((option) => option)}
+            getOptionLabel={(option) => option}
+            renderOption={(props, option) => (
+              <li {...props} key={option}>
+                {option}
+              </li>
+            )}
+          />
+        </Stack>
+
+        <Stack spacing={1.5}>
+          <Typography variant="subtitle2">Skills</Typography>
+          <Field.Autocomplete
+            name="skills"
+            placeholder="+ Skills"
+            multiple
+            disableCloseOnSelect
+            options={JOB_SKILL_OPTIONS.map((option) => option)}
+            getOptionLabel={(option) => option}
+            renderOption={(props, option) => (
+              <li {...props} key={option}>
+                {option}
+              </li>
+            )}
+            renderTags={(selected, getTagProps) =>
+              selected.map((option, index) => (
+                <Chip
+                  {...getTagProps({ index })}
+                  key={option}
+                  label={option}
+                  size="small"
+                  color="info"
+                  variant="soft"
+                />
+              ))
+            }
+          />
+        </Stack>
+
+        <Stack spacing={1.5}>
+          <Typography variant="subtitle2">Working schedule</Typography>
+          <Field.Autocomplete
+            name="workingSchedule"
+            placeholder="+ Schedule"
+            multiple
+            disableCloseOnSelect
+            options={JOB_WORKING_SCHEDULE_OPTIONS.map((option) => option)}
+            getOptionLabel={(option) => option}
+            renderOption={(props, option) => (
+              <li {...props} key={option}>
+                {option}
+              </li>
+            )}
+            renderTags={(selected, getTagProps) =>
+              selected.map((option, index) => (
+                <Chip
+                  {...getTagProps({ index })}
+                  key={option}
+                  label={option}
+                  size="small"
+                  color="info"
+                  variant="soft"
+                />
+              ))
+            }
+          />
+        </Stack>
+
+        <Stack spacing={1.5}>
+          <Typography variant="subtitle2">Locations</Typography>
+          <Field.CountrySelect multiple name="locations" placeholder="+ Locations" />
+        </Stack>
+
+        <Stack spacing={1.5}>
+          <Typography variant="subtitle2">Expired</Typography>
+
+          <Field.DatePicker name="expiredDate" />
+        </Stack>
+
+        <Stack spacing={2}>
+          <Typography variant="subtitle2">Salary</Typography>
+
+          <Controller
+            name="salary.type"
+            control={control}
+            render={({ field }) => (
+              <Box gap={2} display="grid" gridTemplateColumns="repeat(2, 1fr)">
+                {[
+                  {
+                    label: 'Hourly',
+                    icon: <Iconify icon="solar:clock-circle-bold" width={32} sx={{ mb: 2 }} />,
+                  },
+                  {
+                    label: 'Custom',
+                    icon: <Iconify icon="solar:wad-of-money-bold" width={32} sx={{ mb: 2 }} />,
+                  },
+                ].map((item) => (
+                  <Paper
+                    component={ButtonBase}
+                    variant="outlined"
+                    key={item.label}
+                    onClick={() => field.onChange(item.label)}
+                    sx={{
+                      p: 2.5,
+                      borderRadius: 1,
+                      typography: 'subtitle2',
+                      flexDirection: 'column',
+                      ...(item.label === field.value && {
+                        borderWidth: 2,
+                        borderColor: 'text.primary',
+                      }),
+                    }}
+                  >
+                    {item.icon}
+                    {item.label}
+                  </Paper>
+                ))}
+              </Box>
+            )}
+          />
+
+          <Field.Text
+            name="salary.price"
+            placeholder="0.00"
+            type="number"
+            InputProps={{
+              startAdornment: (
+                <InputAdornment position="start">
+                  <Box sx={{ typography: 'subtitle2', color: 'text.disabled' }}>$</Box>
+                </InputAdornment>
+              ),
+            }}
+          />
+          <Field.Switch name="salary.negotiable" label="Salary is negotiable" />
+        </Stack>
+
+        <Stack spacing={1}>
+          <Typography variant="subtitle2">Benefits</Typography>
+          <Field.MultiCheckbox
+            name="benefits"
+            options={JOB_BENEFIT_OPTIONS}
+            sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)' }}
+          />
+        </Stack>
+      </Stack>
+    </Card>
+  );
+
+  const renderActions = (
+    <Box display="flex" alignItems="center" flexWrap="wrap">
+      <FormControlLabel
+        control={<Switch defaultChecked inputProps={{ id: 'publish-switch' }} />}
+        label="Publish"
+        sx={{ flexGrow: 1, pl: 3 }}
+      />
+
+      <LoadingButton
+        type="submit"
+        variant="contained"
+        size="large"
+        loading={isSubmitting}
+        sx={{ ml: 2 }}
+      >
+        {!currentJob ? 'Create job' : 'Save changes'}
+      </LoadingButton>
+    </Box>
+  );
+
+  return (
+    <Form methods={methods} onSubmit={onSubmit}>
+      <Stack spacing={{ xs: 3, md: 5 }} sx={{ mx: 'auto', maxWidth: { xs: 720, xl: 880 } }}>
+        {renderDetails}
+
+        {renderProperties}
+
+        {renderActions}
+      </Stack>
+    </Form>
+  );
+}
diff --git a/src/shared/sections/job/job-search.tsx b/src/shared/sections/job/job-search.tsx
new file mode 100644
index 0000000..a6f1e23
--- /dev/null
+++ b/src/shared/sections/job/job-search.tsx
@@ -0,0 +1,99 @@
+import type { IJobItem } from 'src/shared/types/job';
+import type { UseSetStateReturn } from 'src/hooks/use-set-state';
+
+import parse from 'autosuggest-highlight/parse';
+import match from 'autosuggest-highlight/match';
+
+import Box from '@mui/material/Box';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import Autocomplete from '@mui/material/Autocomplete';
+import InputAdornment from '@mui/material/InputAdornment';
+
+import { paths } from 'src/routes/paths';
+import { useRouter } from 'src/routes/hooks';
+
+import { Iconify } from 'src/shared/components/iconify';
+import { SearchNotFound } from 'src/shared/components/search-not-found';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  onSearch: (inputValue: string) => void;
+  search: UseSetStateReturn<{
+    query: string;
+    results: IJobItem[];
+  }>;
+};
+
+export function JobSearch({ search, onSearch }: Props) {
+  const router = useRouter();
+
+  const handleClick = (id: string) => {
+    router.push(paths.freelancers.details(id));
+  };
+
+  const handleKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => {
+    if (search.state.query) {
+      if (event.key === 'Enter') {
+        const selectProduct = search.state.results.filter(
+          (job) => job.title === search.state.query
+        )[0];
+
+        handleClick(selectProduct.id);
+      }
+    }
+  };
+
+  return (
+    <Autocomplete
+      sx={{ width: { xs: 1, sm: 260 } }}
+      autoHighlight
+      popupIcon={null}
+      options={search.state.results}
+      onInputChange={(event, newValue) => onSearch(newValue)}
+      getOptionLabel={(option) => option.title}
+      noOptionsText={<SearchNotFound query={search.state.query} />}
+      isOptionEqualToValue={(option, value) => option.id === value.id}
+      renderInput={(params) => (
+        <TextField
+          {...params}
+          placeholder="Search..."
+          onKeyUp={handleKeyUp}
+          InputProps={{
+            ...params.InputProps,
+            startAdornment: (
+              <InputAdornment position="start">
+                <Iconify icon="eva:search-fill" sx={{ ml: 1, color: 'text.disabled' }} />
+              </InputAdornment>
+            ),
+          }}
+        />
+      )}
+      renderOption={(props, job, { inputValue }) => {
+        const matches = match(job.title, inputValue);
+        const parts = parse(job.title, matches);
+
+        return (
+          <Box component="li" {...props} onClick={() => handleClick(job.id)} key={job.id}>
+            <div>
+              {parts.map((part, index) => (
+                <Typography
+                  key={index}
+                  component="span"
+                  color={part.highlight ? 'primary' : 'textPrimary'}
+                  sx={{
+                    typography: 'body2',
+                    fontWeight: part.highlight ? 'fontWeightSemiBold' : 'fontWeightMedium',
+                  }}
+                >
+                  {part.text}
+                </Typography>
+              ))}
+            </div>
+          </Box>
+        );
+      }}
+    />
+  );
+}
diff --git a/src/shared/sections/job/job-sort.tsx b/src/shared/sections/job/job-sort.tsx
new file mode 100644
index 0000000..ad0f2f1
--- /dev/null
+++ b/src/shared/sections/job/job-sort.tsx
@@ -0,0 +1,63 @@
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import MenuList from '@mui/material/MenuList';
+import MenuItem from '@mui/material/MenuItem';
+
+import { Iconify } from 'src/shared/components/iconify';
+import { usePopover, CustomPopover } from 'src/shared/components/custom-popover';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  sort: string;
+  onSort: (newValue: string) => void;
+  sortOptions: {
+    value: string;
+    label: string;
+  }[];
+};
+
+export function JobSort({ sort, onSort, sortOptions }: Props) {
+  const popover = usePopover();
+
+  return (
+    <>
+      <Button
+        disableRipple
+        color="inherit"
+        onClick={popover.onOpen}
+        endIcon={
+          <Iconify
+            icon={popover.open ? 'eva:arrow-ios-upward-fill' : 'eva:arrow-ios-downward-fill'}
+          />
+        }
+        sx={{ fontWeight: 'fontWeightSemiBold' }}
+      >
+        Sort by:
+        <Box
+          component="span"
+          sx={{ ml: 0.5, fontWeight: 'fontWeightBold', textTransform: 'capitalize' }}
+        >
+          {sort}
+        </Box>
+      </Button>
+
+      <CustomPopover open={popover.open} anchorEl={popover.anchorEl} onClose={popover.onClose}>
+        <MenuList>
+          {sortOptions.map((option) => (
+            <MenuItem
+              key={option.value}
+              selected={option.value === sort}
+              onClick={() => {
+                popover.onClose();
+                onSort(option.value);
+              }}
+            >
+              {option.label}
+            </MenuItem>
+          ))}
+        </MenuList>
+      </CustomPopover>
+    </>
+  );
+}
diff --git a/src/shared/sections/job/view/index.ts b/src/shared/sections/job/view/index.ts
new file mode 100644
index 0000000..1f96715
--- /dev/null
+++ b/src/shared/sections/job/view/index.ts
@@ -0,0 +1,7 @@
+export * from './job-list-view';
+
+export * from './job-edit-view';
+
+export * from './job-create-view';
+
+export * from './job-details-view';
diff --git a/src/shared/sections/job/view/job-create-view.tsx b/src/shared/sections/job/view/job-create-view.tsx
new file mode 100644
index 0000000..e6c521f
--- /dev/null
+++ b/src/shared/sections/job/view/job-create-view.tsx
@@ -0,0 +1,29 @@
+'use client';
+
+import { paths } from 'src/routes/paths';
+
+import { DashboardContent } from 'src/shared/layouts/dashboard';
+
+import { CustomBreadcrumbs } from 'src/shared/components/custom-breadcrumbs';
+
+import { JobNewEditForm } from '../job-new-edit-form';
+
+// ----------------------------------------------------------------------
+
+export function JobCreateView() {
+  return (
+    <DashboardContent>
+      <CustomBreadcrumbs
+        heading="Create a new job"
+        links={[
+          { name: 'Dashboard', href: paths.dashboard.root },
+          { name: 'Freelancers', href: paths.freelancers.root },
+          { name: 'New job' },
+        ]}
+        sx={{ mb: { xs: 3, md: 5 } }}
+      />
+
+      <JobNewEditForm />
+    </DashboardContent>
+  );
+}
diff --git a/src/shared/sections/job/view/job-details-view.tsx b/src/shared/sections/job/view/job-details-view.tsx
new file mode 100644
index 0000000..93c6a30
--- /dev/null
+++ b/src/shared/sections/job/view/job-details-view.tsx
@@ -0,0 +1,75 @@
+'use client';
+
+import type { IJobItem } from 'src/shared/types/job';
+
+import { useState, useCallback } from 'react';
+
+import Tab from '@mui/material/Tab';
+import Tabs from '@mui/material/Tabs';
+
+import { paths } from 'src/routes/paths';
+
+import { useTabs } from 'src/hooks/use-tabs';
+
+import { DashboardContent } from 'src/shared/layouts/dashboard';
+import { JOB_DETAILS_TABS, JOB_PUBLISH_OPTIONS } from 'src/shared/_mock';
+
+import { Label } from 'src/shared/components/label';
+
+import { JobDetailsToolbar } from '../job-details-toolbar';
+import { JobDetailsContent } from '../job-details-content';
+import { JobDetailsCandidates } from '../job-details-candidates';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  job?: IJobItem;
+};
+
+export function JobDetailsView({ job }: Props) {
+  const tabs = useTabs('content');
+
+  const [publish, setPublish] = useState(job?.publish);
+
+  const handleChangePublish = useCallback((newValue: string) => {
+    setPublish(newValue);
+  }, []);
+
+  const renderTabs = (
+    <Tabs value={tabs.value} onChange={tabs.onChange} sx={{ mb: { xs: 3, md: 5 } }}>
+      {JOB_DETAILS_TABS.map((tab) => (
+        <Tab
+          key={tab.value}
+          iconPosition="end"
+          value={tab.value}
+          label={tab.label}
+          icon={
+            tab.value === 'candidates' ? (
+              <Label variant="filled">{job?.candidates.length}</Label>
+            ) : (
+              ''
+            )
+          }
+        />
+      ))}
+    </Tabs>
+  );
+
+  return (
+    <DashboardContent>
+      <JobDetailsToolbar
+        backLink={paths.freelancers.jobs}
+        editLink={paths.freelancers.edit(`${job?.id}`)}
+        liveLink="#"
+        publish={publish || ''}
+        onChangePublish={handleChangePublish}
+        publishOptions={JOB_PUBLISH_OPTIONS}
+      />
+      {renderTabs}
+
+      {tabs.value === 'content' && <JobDetailsContent job={job} />}
+
+      {tabs.value === 'candidates' && <JobDetailsCandidates candidates={job?.candidates ?? []} />}
+    </DashboardContent>
+  );
+}
diff --git a/src/shared/sections/job/view/job-edit-view.tsx b/src/shared/sections/job/view/job-edit-view.tsx
new file mode 100644
index 0000000..f98e34b
--- /dev/null
+++ b/src/shared/sections/job/view/job-edit-view.tsx
@@ -0,0 +1,35 @@
+'use client';
+
+import type { IJobItem } from 'src/shared/types/job';
+
+import { paths } from 'src/routes/paths';
+
+import { DashboardContent } from 'src/shared/layouts/dashboard';
+
+import { CustomBreadcrumbs } from 'src/shared/components/custom-breadcrumbs';
+
+import { JobNewEditForm } from '../job-new-edit-form';
+
+// ----------------------------------------------------------------------
+
+type Props = {
+  job?: IJobItem;
+};
+
+export function JobEditView({ job }: Props) {
+  return (
+    <DashboardContent>
+      <CustomBreadcrumbs
+        heading="Edit"
+        links={[
+          { name: 'Dashboard', href: paths.dashboard.root },
+          { name: 'Job', href: paths.freelancers.jobs },
+          { name: job?.title },
+        ]}
+        sx={{ mb: { xs: 3, md: 5 } }}
+      />
+
+      <JobNewEditForm currentJob={job} />
+    </DashboardContent>
+  );
+}
diff --git a/src/shared/sections/job/view/job-list-view.tsx b/src/shared/sections/job/view/job-list-view.tsx
new file mode 100644
index 0000000..be5ca40
--- /dev/null
+++ b/src/shared/sections/job/view/job-list-view.tsx
@@ -0,0 +1,202 @@
+'use client';
+
+import type { IJobItem, IJobFilters } from 'src/shared/types/job';
+
+import { useState, useCallback } from 'react';
+
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+
+import { paths } from 'src/routes/paths';
+import { RouterLink } from 'src/routes/components';
+
+import { useBoolean } from 'src/hooks/use-boolean';
+import { useSetState } from 'src/hooks/use-set-state';
+
+import { orderBy } from 'src/utils/helper';
+
+import { DashboardContent } from 'src/shared/layouts/dashboard';
+import {
+  _jobs,
+  _roles,
+  JOB_SORT_OPTIONS,
+  JOB_BENEFIT_OPTIONS,
+  JOB_EXPERIENCE_OPTIONS,
+  JOB_EMPLOYMENT_TYPE_OPTIONS,
+} from 'src/shared/_mock';
+
+import { Iconify } from 'src/shared/components/iconify';
+import { EmptyContent } from 'src/shared/components/empty-content';
+import { CustomBreadcrumbs } from 'src/shared/components/custom-breadcrumbs';
+
+import { JobList } from '../job-list';
+import { JobSort } from '../job-sort';
+import { JobSearch } from '../job-search';
+import { JobFilters } from '../job-filters';
+import { JobFiltersResult } from '../job-filters-result';
+
+// ----------------------------------------------------------------------
+
+export function JobListView() {
+  const openFilters = useBoolean();
+
+  const [sortBy, setSortBy] = useState('latest');
+
+  const search = useSetState<{
+    query: string;
+    results: IJobItem[];
+  }>({ query: '', results: [] });
+
+  const filters = useSetState<IJobFilters>({
+    roles: [],
+    locations: [],
+    benefits: [],
+    experience: 'all',
+    employmentTypes: [],
+  });
+
+  const dataFiltered = applyFilter({ inputData: _jobs, filters: filters.state, sortBy });
+
+  const canReset =
+    filters.state.roles.length > 0 ||
+    filters.state.locations.length > 0 ||
+    filters.state.benefits.length > 0 ||
+    filters.state.employmentTypes.length > 0 ||
+    filters.state.experience !== 'all';
+
+  const notFound = !dataFiltered.length && canReset;
+
+  const handleSortBy = useCallback((newValue: string) => {
+    setSortBy(newValue);
+  }, []);
+
+  const handleSearch = useCallback(
+    (inputValue: string) => {
+      search.setState({ query: inputValue });
+
+      if (inputValue) {
+        const results = _jobs.filter(
+          (job) => job.title.toLowerCase().indexOf(search.state.query.toLowerCase()) !== -1
+        );
+
+        search.setState({ results });
+      }
+    },
+    [search]
+  );
+
+  const renderFilters = (
+    <Stack
+      spacing={3}
+      justifyContent="space-between"
+      alignItems={{ xs: 'flex-end', sm: 'center' }}
+      direction={{ xs: 'column', sm: 'row' }}
+    >
+      <JobSearch search={search} onSearch={handleSearch} />
+
+      <Stack direction="row" spacing={1} flexShrink={0}>
+        <JobFilters
+          filters={filters}
+          canReset={canReset}
+          open={openFilters.value}
+          onOpen={openFilters.onTrue}
+          onClose={openFilters.onFalse}
+          options={{
+            roles: _roles,
+            benefits: JOB_BENEFIT_OPTIONS.map((option) => option.label),
+            employmentTypes: JOB_EMPLOYMENT_TYPE_OPTIONS.map((option) => option.label),
+            experiences: ['all', ...JOB_EXPERIENCE_OPTIONS.map((option) => option.label)],
+          }}
+        />
+
+        <JobSort sort={sortBy} onSort={handleSortBy} sortOptions={JOB_SORT_OPTIONS} />
+      </Stack>
+    </Stack>
+  );
+
+  const renderResults = <JobFiltersResult filters={filters} totalResults={dataFiltered.length} />;
+
+  return (
+    <DashboardContent>
+      <CustomBreadcrumbs
+        heading="List"
+        links={[
+          { name: 'Dashboard', href: paths.dashboard.root },
+          { name: 'Job', href: paths.freelancers.jobs },
+          { name: 'List' },
+        ]}
+        action={
+          <Button
+            component={RouterLink}
+            href={paths.freelancers.newJob}
+            variant="contained"
+            startIcon={<Iconify icon="mingcute:add-line" />}
+          >
+            New job
+          </Button>
+        }
+        sx={{ mb: { xs: 3, md: 5 } }}
+      />
+
+      <Stack spacing={2.5} sx={{ mb: { xs: 3, md: 5 } }}>
+        {renderFilters}
+
+        {canReset && renderResults}
+      </Stack>
+
+      {notFound && <EmptyContent filled sx={{ py: 10 }} />}
+
+      <JobList jobs={dataFiltered} />
+    </DashboardContent>
+  );
+}
+
+// ----------------------------------------------------------------------
+
+type ApplyFilterProps = {
+  inputData: IJobItem[];
+  filters: IJobFilters;
+  sortBy: string;
+};
+
+const applyFilter = ({ inputData, filters, sortBy }: ApplyFilterProps) => {
+  const { employmentTypes, experience, roles, locations, benefits } = filters;
+
+  // Sort by
+  if (sortBy === 'latest') {
+    inputData = orderBy(inputData, ['createdAt'], ['desc']);
+  }
+
+  if (sortBy === 'oldest') {
+    inputData = orderBy(inputData, ['createdAt'], ['asc']);
+  }
+
+  if (sortBy === 'popular') {
+    inputData = orderBy(inputData, ['totalViews'], ['desc']);
+  }
+
+  // Filters
+  if (employmentTypes.length) {
+    inputData = inputData.filter((job) =>
+      job.employmentTypes.some((item) => employmentTypes.includes(item))
+    );
+  }
+
+  if (experience !== 'all') {
+    inputData = inputData.filter((job) => job.experience === experience);
+  }
+
+  if (roles.length) {
+    inputData = inputData.filter((job) => roles.includes(job.role));
+  }
+
+  if (locations.length) {
+    inputData = inputData.filter((job) => job.locations.some((item) => locations.includes(item)));
+  }
+
+  if (benefits.length) {
+    inputData = inputData.filter((job) => job.benefits.some((item) => benefits.includes(item)));
+  }
+
+  return inputData;
+};
diff --git a/src/shared/types/job.ts b/src/shared/types/job.ts
new file mode 100644
index 0000000..0d72b17
--- /dev/null
+++ b/src/shared/types/job.ts
@@ -0,0 +1,49 @@
+// ----------------------------------------------------------------------
+
+export type IJobFilters = {
+  roles: string[];
+  experience: string;
+  locations: string[];
+  benefits: string[];
+  employmentTypes: string[];
+};
+
+export type IJobCandidate = {
+  id: string;
+  name: string;
+  role: string;
+  avatarUrl: string;
+};
+
+export type IJobCompany = {
+  name: string;
+  logo: string;
+  phoneNumber: string;
+  fullAddress: string;
+};
+
+export type IJobSalary = {
+  type: string;
+  price: number;
+  negotiable: boolean;
+};
+
+export type IJobItem = {
+  id: string;
+  role: string;
+  title: string;
+  content: string;
+  publish: string;
+  skills: string[];
+  totalViews: number;
+  experience: string;
+  salary: IJobSalary;
+  benefits: string[];
+  locations: string[];
+  company: IJobCompany;
+  createdAt: string | null;
+  employmentTypes: string[];
+  workingSchedule: string[];
+  expiredDate: string | null;
+  candidates: IJobCandidate[];
+};
diff --git a/src/utils/format-number.ts b/src/utils/format-number.ts
new file mode 100644
index 0000000..135b3de
--- /dev/null
+++ b/src/utils/format-number.ts
@@ -0,0 +1,106 @@
+import { formatNumberLocale } from 'src/shared/locales';
+
+// ----------------------------------------------------------------------
+
+/*
+ * Locales code
+ * https://gist.github.com/raushankrjha/d1c7e35cf87e69aa8b4208a8171a8416
+ */
+
+export type InputNumberValue = string | number | null | undefined;
+
+type Options = Intl.NumberFormatOptions | undefined;
+
+const DEFAULT_LOCALE = { code: 'en-US', currency: 'USD' };
+
+function processInput(inputValue: InputNumberValue): number | null {
+  if (inputValue == null || Number.isNaN(inputValue)) return null;
+  return Number(inputValue);
+}
+
+// ----------------------------------------------------------------------
+
+export function fNumber(inputValue: InputNumberValue, options?: Options) {
+  const locale = formatNumberLocale() || DEFAULT_LOCALE;
+
+  const number = processInput(inputValue);
+  if (number === null) return '';
+
+  const fm = new Intl.NumberFormat(locale.code, {
+    minimumFractionDigits: 0,
+    maximumFractionDigits: 2,
+    ...options,
+  }).format(number);
+
+  return fm;
+}
+
+// ----------------------------------------------------------------------
+
+export function fCurrency(inputValue: InputNumberValue, options?: Options) {
+  const locale = formatNumberLocale() || DEFAULT_LOCALE;
+
+  const number = processInput(inputValue);
+  if (number === null) return '';
+
+  const fm = new Intl.NumberFormat(locale.code, {
+    style: 'currency',
+    currency: locale.currency,
+    minimumFractionDigits: 0,
+    maximumFractionDigits: 2,
+    ...options,
+  }).format(number);
+
+  return fm;
+}
+
+// ----------------------------------------------------------------------
+
+export function fPercent(inputValue: InputNumberValue, options?: Options) {
+  const locale = formatNumberLocale() || DEFAULT_LOCALE;
+
+  const number = processInput(inputValue);
+  if (number === null) return '';
+
+  const fm = new Intl.NumberFormat(locale.code, {
+    style: 'percent',
+    minimumFractionDigits: 0,
+    maximumFractionDigits: 1,
+    ...options,
+  }).format(number / 100);
+
+  return fm;
+}
+
+// ----------------------------------------------------------------------
+
+export function fShortenNumber(inputValue: InputNumberValue, options?: Options) {
+  const locale = formatNumberLocale() || DEFAULT_LOCALE;
+
+  const number = processInput(inputValue);
+  if (number === null) return '';
+
+  const fm = new Intl.NumberFormat(locale.code, {
+    notation: 'compact',
+    maximumFractionDigits: 2,
+    ...options,
+  }).format(number);
+
+  return fm.replace(/[A-Z]/g, (match) => match.toLowerCase());
+}
+
+// ----------------------------------------------------------------------
+
+export function fData(inputValue: InputNumberValue) {
+  const number = processInput(inputValue);
+  if (number === null || number === 0) return '0 bytes';
+
+  const units = ['bytes', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb'];
+  const decimal = 2;
+  const baseValue = 1024;
+
+  const index = Math.floor(Math.log(number) / Math.log(baseValue));
+  const fm = `${parseFloat((number / baseValue ** index).toFixed(decimal))} ${units[index]}`;
+
+  return fm;
+}
diff --git a/yarn.lock b/yarn.lock
index a650521..9bf1480 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1720,7 +1720,7 @@
   dependencies:
     tslib "^2.0.0"
 
-"@emotion/babel-plugin@^11.11.0", "@emotion/babel-plugin@^11.12.0":
+"@emotion/babel-plugin@^11.12.0":
   version "11.12.0"
   resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz"
   integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==
@@ -1764,7 +1764,14 @@
   resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz"
   integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
 
-"@emotion/is-prop-valid@*", "@emotion/is-prop-valid@^1.2.2":
+"@emotion/is-prop-valid@*", "@emotion/is-prop-valid@^1.3.0":
+  version "1.3.0"
+  resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz"
+  integrity sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==
+  dependencies:
+    "@emotion/memoize" "^0.9.0"
+
+"@emotion/is-prop-valid@1.2.2":
   version "1.2.2"
   resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz"
   integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==
@@ -1781,21 +1788,21 @@
   resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz"
   integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
 
-"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.11.4", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0", "@emotion/react@^11.9.0":
-  version "11.11.4"
-  resolved "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz"
-  integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==
+"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.11.4", "@emotion/react@^11.13.0", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0", "@emotion/react@^11.9.0":
+  version "11.13.0"
+  resolved "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz"
+  integrity sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==
   dependencies:
     "@babel/runtime" "^7.18.3"
-    "@emotion/babel-plugin" "^11.11.0"
-    "@emotion/cache" "^11.11.0"
-    "@emotion/serialize" "^1.1.3"
-    "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1"
-    "@emotion/utils" "^1.2.1"
-    "@emotion/weak-memoize" "^0.3.1"
+    "@emotion/babel-plugin" "^11.12.0"
+    "@emotion/cache" "^11.13.0"
+    "@emotion/serialize" "^1.3.0"
+    "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0"
+    "@emotion/utils" "^1.4.0"
+    "@emotion/weak-memoize" "^0.4.0"
     hoist-non-react-statics "^3.3.1"
 
-"@emotion/serialize@^1.1.3", "@emotion/serialize@^1.1.4", "@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0":
+"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0":
   version "1.3.0"
   resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz"
   integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==
@@ -1811,38 +1818,38 @@
   resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz"
   integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
 
-"@emotion/styled@^11.11.5", "@emotion/styled@^11.3.0", "@emotion/styled@^11.8.1":
-  version "11.11.5"
-  resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz"
-  integrity sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==
+"@emotion/styled@^11.11.5", "@emotion/styled@^11.13.0", "@emotion/styled@^11.3.0", "@emotion/styled@^11.8.1":
+  version "11.13.0"
+  resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz"
+  integrity sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==
   dependencies:
     "@babel/runtime" "^7.18.3"
-    "@emotion/babel-plugin" "^11.11.0"
-    "@emotion/is-prop-valid" "^1.2.2"
-    "@emotion/serialize" "^1.1.4"
-    "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1"
-    "@emotion/utils" "^1.2.1"
+    "@emotion/babel-plugin" "^11.12.0"
+    "@emotion/is-prop-valid" "^1.3.0"
+    "@emotion/serialize" "^1.3.0"
+    "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0"
+    "@emotion/utils" "^1.4.0"
 
 "@emotion/unitless@^0.9.0":
   version "0.9.0"
   resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz"
   integrity sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==
 
-"@emotion/use-insertion-effect-with-fallbacks@^1.0.1":
-  version "1.0.1"
-  resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz"
-  integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==
+"@emotion/unitless@0.8.1":
+  version "0.8.1"
+  resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz"
+  integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
 
-"@emotion/utils@^1.2.1", "@emotion/utils@^1.4.0":
+"@emotion/use-insertion-effect-with-fallbacks@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz"
+  integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==
+
+"@emotion/utils@^1.4.0":
   version "1.4.0"
   resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz"
   integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==
 
-"@emotion/weak-memoize@^0.3.1":
-  version "0.3.1"
-  resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz"
-  integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
-
 "@emotion/weak-memoize@^0.4.0":
   version "0.4.0"
   resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz"
@@ -2540,10 +2547,10 @@
     clsx "^2.1.0"
     prop-types "^15.8.1"
 
-"@mui/core-downloads-tracker@^5.16.5":
-  version "5.16.5"
-  resolved "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.5.tgz"
-  integrity sha512-ziFn1oPm6VjvHQcdGcAO+fXvOQEgieIj0BuSqcltFU+JXIxjPdVYNTdn2HU7/Ak5Gabk6k2u7+9PV7oZ6JT5sA==
+"@mui/core-downloads-tracker@^5.16.6":
+  version "5.16.6"
+  resolved "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.6.tgz"
+  integrity sha512-kytg6LheUG42V8H/o/Ptz3olSO5kUXW9zF0ox18VnblX6bO2yif1FPItgc3ey1t5ansb1+gbe7SatntqusQupg==
 
 "@mui/lab@^5.0.0-alpha.170":
   version "5.0.0-alpha.170"
@@ -2565,16 +2572,16 @@
   dependencies:
     "@babel/runtime" "^7.23.9"
 
-"@mui/material@^5.0.0", "@mui/material@^5.15.14", "@mui/material@^5.15.20", "@mui/material@^5.16.0", "@mui/material@>=5.15.0":
-  version "5.16.5"
-  resolved "https://registry.npmjs.org/@mui/material/-/material-5.16.5.tgz"
-  integrity sha512-eQrjjg4JeczXvh/+8yvJkxWIiKNHVptB/AqpsKfZBWp5mUD5U3VsjODMuUl1K2BSq0omV3CiO/mQmWSSMKSmaA==
+"@mui/material@^5.0.0", "@mui/material@^5.15.14", "@mui/material@^5.16.0", "@mui/material@^5.16.6", "@mui/material@>=5.15.0":
+  version "5.16.6"
+  resolved "https://registry.npmjs.org/@mui/material/-/material-5.16.6.tgz"
+  integrity sha512-0LUIKBOIjiFfzzFNxXZBRAyr9UQfmTAFzbt6ziOU2FDXhorNN2o3N9/32mNJbCA8zJo2FqFU6d3dtoqUDyIEfA==
   dependencies:
     "@babel/runtime" "^7.23.9"
-    "@mui/core-downloads-tracker" "^5.16.5"
-    "@mui/system" "^5.16.5"
+    "@mui/core-downloads-tracker" "^5.16.6"
+    "@mui/system" "^5.16.6"
     "@mui/types" "^7.2.15"
-    "@mui/utils" "^5.16.5"
+    "@mui/utils" "^5.16.6"
     "@popperjs/core" "^2.11.8"
     "@types/react-transition-group" "^4.4.10"
     clsx "^2.1.0"
@@ -2583,35 +2590,45 @@
     react-is "^18.3.1"
     react-transition-group "^4.4.5"
 
-"@mui/private-theming@^5.16.5":
-  version "5.16.5"
-  resolved "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.5.tgz"
-  integrity sha512-CSLg0YkpDqg0aXOxtjo3oTMd3XWMxvNb5d0v4AYVqwOltU8q6GvnZjhWyCLjGSCrcgfwm6/VDjaKLPlR14wxIA==
+"@mui/private-theming@^5.16.6":
+  version "5.16.6"
+  resolved "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz"
+  integrity sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==
   dependencies:
     "@babel/runtime" "^7.23.9"
-    "@mui/utils" "^5.16.5"
+    "@mui/utils" "^5.16.6"
     prop-types "^15.8.1"
 
-"@mui/styled-engine@^5.16.4":
-  version "5.16.4"
-  resolved "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.4.tgz"
-  integrity sha512-0+mnkf+UiAmTVB8PZFqOhqf729Yh0Cxq29/5cA3VAyDVTRIUUQ8FXQhiAhUIbijFmM72rY80ahFPXIm4WDbzcA==
+"@mui/styled-engine-sc@^6.0.0-alpha.18":
+  version "6.0.0-alpha.18"
+  resolved "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-6.0.0-alpha.18.tgz"
+  integrity sha512-W3mqR1K01rPL0BVNTgGpIYxdbQ/nTAlwYaohRdmX7FZvbm1yKw9F90OIGxM503dfRMVBi6a/neYPgIUebcGsHw==
+  dependencies:
+    "@babel/runtime" "^7.23.9"
+    csstype "^3.1.3"
+    hoist-non-react-statics "^3.3.2"
+    prop-types "^15.8.1"
+
+"@mui/styled-engine@^5.16.6":
+  version "5.16.6"
+  resolved "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz"
+  integrity sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==
   dependencies:
     "@babel/runtime" "^7.23.9"
     "@emotion/cache" "^11.11.0"
     csstype "^3.1.3"
     prop-types "^15.8.1"
 
-"@mui/system@^5.15.15", "@mui/system@^5.16.5":
-  version "5.16.5"
-  resolved "https://registry.npmjs.org/@mui/system/-/system-5.16.5.tgz"
-  integrity sha512-uzIUGdrWddUx1HPxW4+B2o4vpgKyRxGe/8BxbfXVDPNPHX75c782TseoCnR/VyfnZJfqX87GcxDmnZEE1c031g==
+"@mui/system@^5.15.15", "@mui/system@^5.16.6":
+  version "5.16.6"
+  resolved "https://registry.npmjs.org/@mui/system/-/system-5.16.6.tgz"
+  integrity sha512-5xgyJjBIMPw8HIaZpfbGAaFYPwImQn7Nyh+wwKWhvkoIeDosQ1ZMVrbTclefi7G8hNmqhip04duYwYpbBFnBgw==
   dependencies:
     "@babel/runtime" "^7.23.9"
-    "@mui/private-theming" "^5.16.5"
-    "@mui/styled-engine" "^5.16.4"
+    "@mui/private-theming" "^5.16.6"
+    "@mui/styled-engine" "^5.16.6"
     "@mui/types" "^7.2.15"
-    "@mui/utils" "^5.16.5"
+    "@mui/utils" "^5.16.6"
     clsx "^2.1.0"
     csstype "^3.1.3"
     prop-types "^15.8.1"
@@ -2621,10 +2638,10 @@
   resolved "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz"
   integrity sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==
 
-"@mui/utils@^5.15.14", "@mui/utils@^5.16.5":
-  version "5.16.5"
-  resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.16.5.tgz"
-  integrity sha512-CwhcA9y44XwK7k2joL3Y29mRUnoBt+gOZZdGyw7YihbEwEErJYBtDwbZwVgH68zAljGe/b+Kd5bzfl63Gi3R2A==
+"@mui/utils@^5.15.14", "@mui/utils@^5.16.6":
+  version "5.16.6"
+  resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz"
+  integrity sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==
   dependencies:
     "@babel/runtime" "^7.23.9"
     "@mui/types" "^7.2.15"
@@ -3870,6 +3887,11 @@
   resolved "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.6.tgz"
   integrity sha512-4nebF2ZJGzQk0ka0O6+FZUWceyFv4vWq/0dXBMmrSeAwzOuOd/GxE5Pa64d/ndeNLG73dXoBsRzvtsVsYUv6Uw==
 
+"@types/stylis@4.2.5":
+  version "4.2.5"
+  resolved "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz"
+  integrity sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==
+
 "@types/unist@*", "@types/unist@^3.0.0":
   version "3.0.2"
   resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz"
@@ -4404,6 +4426,11 @@ camelcase@^6.2.0:
   resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz"
   integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
 
+camelize@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz"
+  integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==
+
 can-use-dom@^0.1.0:
   version "0.1.0"
   resolved "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz"
@@ -4618,6 +4645,11 @@ crypto-js@^4.2.0:
   resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz"
   integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
 
+css-color-keywords@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz"
+  integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==
+
 css-select@^5.1.0:
   version "5.1.0"
   resolved "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz"
@@ -4629,6 +4661,15 @@ css-select@^5.1.0:
     domutils "^3.0.1"
     nth-check "^2.0.1"
 
+css-to-react-native@3.2.0:
+  version "3.2.0"
+  resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz"
+  integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==
+  dependencies:
+    camelize "^1.0.0"
+    css-color-keywords "^1.0.0"
+    postcss-value-parser "^4.0.2"
+
 css-tree@^2.2.1:
   version "2.3.1"
   resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz"
@@ -4667,7 +4708,7 @@ csso@5.0.5:
   dependencies:
     css-tree "~2.2.0"
 
-csstype@^3.0.2, csstype@^3.1.3:
+csstype@^3.0.2, csstype@^3.1.3, csstype@3.1.3:
   version "3.1.3"
   resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
   integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
@@ -4704,10 +4745,10 @@ data-view-byte-offset@^1.0.0:
     es-errors "^1.3.0"
     is-data-view "^1.0.1"
 
-dayjs@^1.10.7, dayjs@^1.11.11:
-  version "1.11.11"
-  resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz"
-  integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==
+dayjs@^1.10.7, dayjs@^1.11.12:
+  version "1.11.12"
+  resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz"
+  integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==
 
 debug@^3.2.7:
   version "3.2.7"
@@ -5858,7 +5899,7 @@ highlight.js@~11.9.0:
   resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz"
   integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
 
-hoist-non-react-statics@^3.3.1:
+hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
   version "3.3.2"
   resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
   integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -7104,7 +7145,7 @@ murmurhash-js@^1.0.0:
   resolved "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz"
   integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==
 
-nanoid@^3.3.6:
+nanoid@^3.3.6, nanoid@^3.3.7:
   version "3.3.7"
   resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz"
   integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
@@ -7397,7 +7438,7 @@ possible-typed-array-names@^1.0.0:
   resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz"
   integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==
 
-postcss-value-parser@^4.1.0:
+postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
   version "4.2.0"
   resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
   integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@@ -7411,6 +7452,15 @@ postcss@8.4.31:
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
 
+postcss@8.4.38:
+  version "8.4.38"
+  resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz"
+  integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
+  dependencies:
+    nanoid "^3.3.7"
+    picocolors "^1.0.0"
+    source-map-js "^1.2.0"
+
 potpack@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz"
@@ -7667,7 +7717,7 @@ react-apexcharts@^1.4.1:
   dependencies:
     prop-types "^15.8.1"
 
-"react-dom@^16.11.0 || ^17 || ^18", "react-dom@^16.7.0 || ^17 || ^18 || ^19", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@^18.3.1, "react-dom@>= 16.12.0", react-dom@>=16.3.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, "react-dom@15 - 18":
+"react-dom@^16.11.0 || ^17 || ^18", "react-dom@^16.7.0 || ^17 || ^18 || ^19", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@^18.3.1, "react-dom@>= 16.12.0", "react-dom@>= 16.8.0", react-dom@>=16.3.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, "react-dom@15 - 18":
   version "18.3.1"
   resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
   integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@@ -8136,6 +8186,11 @@ set-value@^2.0.1:
     is-plain-object "^2.0.3"
     split-string "^3.0.1"
 
+shallowequal@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz"
+  integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
+
 shebang-command@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
@@ -8222,10 +8277,10 @@ sort-object@^3.0.3:
     sort-desc "^0.2.0"
     union-value "^1.0.1"
 
-source-map-js@^1.0.1, source-map-js@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
-  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
+  integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
 
 source-map@^0.5.7:
   version "0.5.7"
@@ -8348,6 +8403,21 @@ style-to-object@^1.0.0:
   dependencies:
     inline-style-parser "0.2.3"
 
+styled-components@^6.0.0, styled-components@^6.1.12:
+  version "6.1.12"
+  resolved "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz"
+  integrity sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==
+  dependencies:
+    "@emotion/is-prop-valid" "1.2.2"
+    "@emotion/unitless" "0.8.1"
+    "@types/stylis" "4.2.5"
+    css-to-react-native "3.2.0"
+    csstype "3.1.3"
+    postcss "8.4.38"
+    shallowequal "1.1.0"
+    stylis "4.3.2"
+    tslib "2.6.2"
+
 styled-jsx@5.1.1:
   version "5.1.1"
   resolved "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz"
@@ -8362,7 +8432,7 @@ stylis-plugin-rtl@^2.1.1:
   dependencies:
     cssjanus "^2.0.1"
 
-stylis@^4.3.2, stylis@4.x:
+stylis@^4.3.2, stylis@4.3.2, stylis@4.x:
   version "4.3.2"
   resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz"
   integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==
@@ -8587,7 +8657,7 @@ tslib@^1.11.1:
   resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
-tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.2:
+tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.2, tslib@2.6.2:
   version "2.6.2"
   resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
   integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
-- 
GitLab