diff --git a/package-lock.json b/package-lock.json
index e5b8dad..e6e6078 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
       "version": "0.1.0",
       "license": "MIT",
       "dependencies": {
-        "html-to-text": "^8.0.0"
+        "react-grid-system": "^7.3.1"
       },
       "devDependencies": {
         "@ant-design/icons": "^4.7.0",
@@ -3379,18 +3379,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/@selderee/plugin-htmlparser2": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz",
-      "integrity": "sha512-J3jpy002TyBjd4N/p6s+s90eX42H2eRhK3SbsZuvTDv977/E8p2U3zikdiehyJja66do7FlxLomZLPlvl2/xaA==",
-      "dependencies": {
-        "domhandler": "^4.2.0",
-        "selderee": "^0.6.0"
-      },
-      "funding": {
-        "url": "https://ko-fi.com/killymxi"
-      }
-    },
     "node_modules/@sinonjs/commons": {
       "version": "1.8.3",
       "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
@@ -10829,6 +10817,7 @@
       "version": "2.20.3",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/commondir": {
@@ -11973,6 +11962,7 @@
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
       "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -12290,11 +12280,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/discontinuous-range": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
-      "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo="
-    },
     "node_modules/doctrine": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -12322,6 +12307,7 @@
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
       "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "domelementtype": "^2.0.1",
@@ -12353,6 +12339,7 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
       "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
+      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -12375,6 +12362,7 @@
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz",
       "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==",
+      "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
         "domelementtype": "^2.2.0"
@@ -12390,6 +12378,7 @@
       "version": "2.8.0",
       "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
       "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
+      "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
         "dom-serializer": "^1.0.1",
@@ -12696,6 +12685,7 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
       "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+      "dev": true,
       "license": "BSD-2-Clause",
       "funding": {
         "url": "https://github.com/fb55/entities?sponsor=1"
@@ -15454,6 +15444,7 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true,
       "license": "MIT",
       "bin": {
         "he": "bin/he"
@@ -15578,25 +15569,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/html-to-text": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-8.0.0.tgz",
-      "integrity": "sha512-fEtul1OerF2aMEV+Wpy+Ue20tug134jOY1GIudtdqZi7D0uTudB2tVJBKfVhTL03dtqeJoF8gk8EPX9SyMEvLg==",
-      "dependencies": {
-        "@selderee/plugin-htmlparser2": "^0.6.0",
-        "deepmerge": "^4.2.2",
-        "he": "^1.2.0",
-        "htmlparser2": "^6.1.0",
-        "minimist": "^1.2.5",
-        "selderee": "^0.6.0"
-      },
-      "bin": {
-        "html-to-text": "bin/cli.js"
-      },
-      "engines": {
-        "node": ">=10.23.2"
-      }
-    },
     "node_modules/html-void-elements": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz",
@@ -15664,6 +15636,7 @@
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
       "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
+      "dev": true,
       "funding": [
         "https://github.com/fb55/htmlparser2?sponsor=1",
         {
@@ -17487,7 +17460,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/js-yaml": {
@@ -18160,7 +18132,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
       "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "js-tokens": "^3.0.0 || ^4.0.0"
@@ -18703,6 +18674,7 @@
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
       "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/minipass": {
@@ -18833,11 +18805,6 @@
         "mkdirp": "bin/cmd.js"
       }
     },
-    "node_modules/moo": {
-      "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz",
-      "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w=="
-    },
     "node_modules/move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -19009,27 +18976,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/nearley": {
-      "version": "2.20.1",
-      "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
-      "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
-      "dependencies": {
-        "commander": "^2.19.0",
-        "moo": "^0.5.0",
-        "railroad-diagrams": "^1.0.0",
-        "randexp": "0.4.6"
-      },
-      "bin": {
-        "nearley-railroad": "bin/nearley-railroad.js",
-        "nearley-test": "bin/nearley-test.js",
-        "nearley-unparse": "bin/nearley-unparse.js",
-        "nearleyc": "bin/nearleyc.js"
-      },
-      "funding": {
-        "type": "individual",
-        "url": "https://nearley.js.org/#give-to-nearley"
-      }
-    },
     "node_modules/negotiator": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -19351,7 +19297,6 @@
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
       "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -19946,18 +19891,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/parseley": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.7.0.tgz",
-      "integrity": "sha512-xyOytsdDu077M3/46Am+2cGXEKM9U9QclBDv7fimY7e+BBlxh2JcBp2mgNsmkyA9uvgyTjVzDi7cP1v4hcFxbw==",
-      "dependencies": {
-        "moo": "^0.5.1",
-        "nearley": "^2.20.1"
-      },
-      "funding": {
-        "url": "https://ko-fi.com/killymxi"
-      }
-    },
     "node_modules/parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -21200,7 +21133,6 @@
       "version": "15.7.2",
       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
       "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "loose-envify": "^1.4.0",
@@ -21212,7 +21144,6 @@
       "version": "16.13.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/property-information": {
@@ -21372,11 +21303,6 @@
       ],
       "license": "MIT"
     },
-    "node_modules/railroad-diagrams": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
-      "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234="
-    },
     "node_modules/ramda": {
       "version": "0.21.0",
       "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.21.0.tgz",
@@ -21384,18 +21310,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/randexp": {
-      "version": "0.4.6",
-      "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
-      "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
-      "dependencies": {
-        "discontinuous-range": "1.0.0",
-        "ret": "~0.1.10"
-      },
-      "engines": {
-        "node": ">=0.12"
-      }
-    },
     "node_modules/randombytes": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -21489,7 +21403,6 @@
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
       "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "loose-envify": "^1.1.0",
@@ -21878,6 +21791,17 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/react-grid-system": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/react-grid-system/-/react-grid-system-7.3.1.tgz",
+      "integrity": "sha512-q5dTeKURLZ2lAFLTjMKdjgubhibIu+EvdT838bcki/rQ8ftJGm+afzZHT2Da6BASWYpXo9+OSIS9M7VFp4/gbQ==",
+      "dependencies": {
+        "prop-types": "^15.7.2"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.x"
+      }
+    },
     "node_modules/react-helmet-async": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.1.2.tgz",
@@ -22802,6 +22726,7 @@
       "version": "0.1.15",
       "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
       "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.12"
@@ -23342,17 +23267,6 @@
         "url": "https://opencollective.com/webpack"
       }
     },
-    "node_modules/selderee": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.6.0.tgz",
-      "integrity": "sha512-ibqWGV5aChDvfVdqNYuaJP/HnVBhlRGSRrlbttmlMpHcLuTqqbMH36QkSs9GEgj5M88JDYLI8eyP94JaQ8xRlg==",
-      "dependencies": {
-        "parseley": "^0.7.0"
-      },
-      "funding": {
-        "url": "https://ko-fi.com/killymxi"
-      }
-    },
     "node_modules/select": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
@@ -29866,15 +29780,6 @@
         }
       }
     },
-    "@selderee/plugin-htmlparser2": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz",
-      "integrity": "sha512-J3jpy002TyBjd4N/p6s+s90eX42H2eRhK3SbsZuvTDv977/E8p2U3zikdiehyJja66do7FlxLomZLPlvl2/xaA==",
-      "requires": {
-        "domhandler": "^4.2.0",
-        "selderee": "^0.6.0"
-      }
-    },
     "@sinonjs/commons": {
       "version": "1.8.3",
       "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
@@ -35281,7 +35186,8 @@
     "commander": {
       "version": "2.20.3",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true
     },
     "commondir": {
       "version": "1.0.1",
@@ -36135,7 +36041,8 @@
     "deepmerge": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
-      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
+      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+      "dev": true
     },
     "defaults": {
       "version": "1.0.3",
@@ -36371,11 +36278,6 @@
         "path-type": "^4.0.0"
       }
     },
-    "discontinuous-range": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
-      "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo="
-    },
     "doctrine": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -36398,6 +36300,7 @@
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
       "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+      "dev": true,
       "requires": {
         "domelementtype": "^2.0.1",
         "domhandler": "^4.2.0",
@@ -36419,7 +36322,8 @@
     "domelementtype": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
-      "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
+      "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
+      "dev": true
     },
     "domexception": {
       "version": "1.0.1",
@@ -36434,6 +36338,7 @@
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz",
       "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==",
+      "dev": true,
       "requires": {
         "domelementtype": "^2.2.0"
       }
@@ -36442,6 +36347,7 @@
       "version": "2.8.0",
       "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
       "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
+      "dev": true,
       "requires": {
         "dom-serializer": "^1.0.1",
         "domelementtype": "^2.2.0",
@@ -36689,7 +36595,8 @@
     "entities": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
-      "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
+      "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+      "dev": true
     },
     "errno": {
       "version": "0.1.8",
@@ -38732,7 +38639,8 @@
     "he": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
-      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true
     },
     "highcharts": {
       "version": "9.3.0",
@@ -38830,19 +38738,6 @@
       "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==",
       "dev": true
     },
-    "html-to-text": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-8.0.0.tgz",
-      "integrity": "sha512-fEtul1OerF2aMEV+Wpy+Ue20tug134jOY1GIudtdqZi7D0uTudB2tVJBKfVhTL03dtqeJoF8gk8EPX9SyMEvLg==",
-      "requires": {
-        "@selderee/plugin-htmlparser2": "^0.6.0",
-        "deepmerge": "^4.2.2",
-        "he": "^1.2.0",
-        "htmlparser2": "^6.1.0",
-        "minimist": "^1.2.5",
-        "selderee": "^0.6.0"
-      }
-    },
     "html-void-elements": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz",
@@ -38892,6 +38787,7 @@
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
       "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
+      "dev": true,
       "requires": {
         "domelementtype": "^2.0.1",
         "domhandler": "^4.0.0",
@@ -40173,8 +40069,7 @@
     "js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "dev": true
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
     },
     "js-yaml": {
       "version": "3.14.1",
@@ -40666,7 +40561,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
       "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "dev": true,
       "requires": {
         "js-tokens": "^3.0.0 || ^4.0.0"
       }
@@ -41057,7 +40951,8 @@
     "minimist": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true
     },
     "minipass": {
       "version": "3.1.5",
@@ -41153,11 +41048,6 @@
         "minimist": "^1.2.5"
       }
     },
-    "moo": {
-      "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz",
-      "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w=="
-    },
     "move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -41294,17 +41184,6 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
       "dev": true
     },
-    "nearley": {
-      "version": "2.20.1",
-      "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
-      "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
-      "requires": {
-        "commander": "^2.19.0",
-        "moo": "^0.5.0",
-        "railroad-diagrams": "^1.0.0",
-        "randexp": "0.4.6"
-      }
-    },
     "negotiator": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -41561,8 +41440,7 @@
     "object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
-      "dev": true
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
     },
     "object-copy": {
       "version": "0.1.0",
@@ -41967,15 +41845,6 @@
       "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
       "dev": true
     },
-    "parseley": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.7.0.tgz",
-      "integrity": "sha512-xyOytsdDu077M3/46Am+2cGXEKM9U9QclBDv7fimY7e+BBlxh2JcBp2mgNsmkyA9uvgyTjVzDi7cP1v4hcFxbw==",
-      "requires": {
-        "moo": "^0.5.1",
-        "nearley": "^2.20.1"
-      }
-    },
     "parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -42813,7 +42682,6 @@
       "version": "15.7.2",
       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
       "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
-      "dev": true,
       "requires": {
         "loose-envify": "^1.4.0",
         "object-assign": "^4.1.1",
@@ -42823,8 +42691,7 @@
         "react-is": {
           "version": "16.13.1",
           "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
-          "dev": true
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
         }
       }
     },
@@ -42939,26 +42806,12 @@
       "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
       "dev": true
     },
-    "railroad-diagrams": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
-      "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234="
-    },
     "ramda": {
       "version": "0.21.0",
       "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.21.0.tgz",
       "integrity": "sha1-oAGr7bP/YQd9T/HVd9RN536NCjU=",
       "dev": true
     },
-    "randexp": {
-      "version": "0.4.6",
-      "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
-      "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
-      "requires": {
-        "discontinuous-range": "1.0.0",
-        "ret": "~0.1.10"
-      }
-    },
     "randombytes": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -43029,7 +42882,6 @@
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
       "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
-      "dev": true,
       "requires": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1"
@@ -43315,6 +43167,14 @@
       "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==",
       "dev": true
     },
+    "react-grid-system": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/react-grid-system/-/react-grid-system-7.3.1.tgz",
+      "integrity": "sha512-q5dTeKURLZ2lAFLTjMKdjgubhibIu+EvdT838bcki/rQ8ftJGm+afzZHT2Da6BASWYpXo9+OSIS9M7VFp4/gbQ==",
+      "requires": {
+        "prop-types": "^15.7.2"
+      }
+    },
     "react-helmet-async": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.1.2.tgz",
@@ -44000,7 +43860,8 @@
     "ret": {
       "version": "0.1.15",
       "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
-      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+      "dev": true
     },
     "reusify": {
       "version": "1.0.4",
@@ -44379,14 +44240,6 @@
         "ajv-keywords": "^3.5.2"
       }
     },
-    "selderee": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.6.0.tgz",
-      "integrity": "sha512-ibqWGV5aChDvfVdqNYuaJP/HnVBhlRGSRrlbttmlMpHcLuTqqbMH36QkSs9GEgj5M88JDYLI8eyP94JaQ8xRlg==",
-      "requires": {
-        "parseley": "^0.7.0"
-      }
-    },
     "select": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
diff --git a/package.json b/package.json
index ccd2328..b022c30 100644
--- a/package.json
+++ b/package.json
@@ -71,6 +71,6 @@
     "typescript": "^4.4.4"
   },
   "dependencies": {
-    "html-to-text": "^8.0.0"
+    "react-grid-system": "^7.3.1"
   }
 }
diff --git a/scripts/new.sh b/scripts/new.sh
index 246fb9a..59d6017 100755
--- a/scripts/new.sh
+++ b/scripts/new.sh
@@ -2,8 +2,8 @@
 
 mkdir ./src/$1
 
-echo "" > ./src/$1/index.tsx
-echo "" > ./src/$1/props.tsx
+echo "import React from 'react';import { Props } from './props';export const index: React.FC<Props> = ({}) => {return <div />;};" > ./src/$1/index.tsx
+echo "export interface Props {}" > ./src/$1/props.tsx
 echo "" > ./src/$1/stories.tsx
-echo "" > ./src/$1/styles.tsx
+echo "import { Properties } from 'csstype';export const styles = {example: {} as Properties,};" > ./src/$1/styles.tsx
 echo "" > ./src/$1/.test.tsx
\ No newline at end of file
diff --git a/src/ChatFeed/FileThumb/.test.tsx b/src/ChatFeed/FileThumb/.test.tsx
new file mode 100644
index 0000000..3d9a423
--- /dev/null
+++ b/src/ChatFeed/FileThumb/.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import * as ReactDOM from 'react-dom';
+import { Default as Thing } from './stories';
+
+describe('Thing', () => {
+  it('renders without crashing', () => {
+    const div = document.createElement('div');
+    ReactDOM.render(<Thing />, div);
+    ReactDOM.unmountComponentAtNode(div);
+  });
+});
diff --git a/src/ChatFeed/FileThumb/index.tsx b/src/ChatFeed/FileThumb/index.tsx
new file mode 100644
index 0000000..3f2c070
--- /dev/null
+++ b/src/ChatFeed/FileThumb/index.tsx
@@ -0,0 +1,46 @@
+import React, { useState } from 'react';
+
+import { getFileName } from '../../util/file';
+
+import { Props } from './props';
+import { styles } from './styles';
+
+export const FileThumb: React.FC<Props> = ({
+  attachment,
+  isLoading = false,
+  children = 'Loading...',
+  style = {},
+  loadingStyle = {},
+}) => {
+  const [hovered, setHovered] = useState<boolean>(false);
+
+  const fileStyle = {
+    ...styles.fileView,
+    ...{
+      color: hovered ? '#1890ff' : '#434343',
+      border: hovered ? '1px solid #1890ff' : '1px solid #434343',
+    },
+  };
+
+  if (isLoading || !attachment) {
+    return (
+      <div style={{ ...styles.loadingContainer, ...loadingStyle }}>
+        {children}
+      </div>
+    );
+  }
+
+  return (
+    <div
+      style={{
+        ...fileStyle,
+        ...style,
+      }}
+      onMouseEnter={() => setHovered(true)}
+      onMouseLeave={() => setHovered(false)}
+      onClick={() => window.open(attachment.file)}
+    >
+      {getFileName(attachment.file)}
+    </div>
+  );
+};
diff --git a/src/ChatFeed/FileThumb/props.tsx b/src/ChatFeed/FileThumb/props.tsx
new file mode 100644
index 0000000..cf3e473
--- /dev/null
+++ b/src/ChatFeed/FileThumb/props.tsx
@@ -0,0 +1,13 @@
+import { ReactNode } from 'react';
+
+import { Properties } from 'csstype';
+
+import { AttachmentProps } from '../../util/interfaces';
+
+export interface Props {
+  attachment?: AttachmentProps;
+  isLoading?: boolean;
+  children?: ReactNode;
+  style?: Properties;
+  loadingStyle?: Properties;
+}
diff --git a/src/ChatFeed/FileThumb/stories.tsx b/src/ChatFeed/FileThumb/stories.tsx
new file mode 100644
index 0000000..2dbe38b
--- /dev/null
+++ b/src/ChatFeed/FileThumb/stories.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+
+import { Meta, Story } from '@storybook/react';
+
+import { FileThumb } from '.';
+
+import { Props } from './props';
+
+import { fileAttachment } from '../../util/mocks';
+
+const meta: Meta = {
+  title: 'ChatFeed/FileThumb',
+  component: FileThumb,
+  argTypes: {},
+};
+
+export default meta;
+
+const Template: Story<Props> = (props) => <FileThumb {...props}></FileThumb>;
+
+export const Default = Template.bind({});
+Default.args = {
+  attachment: fileAttachment,
+};
+
+export const Loading = Template.bind({});
+Loading.args = {
+  isLoading: true,
+};
diff --git a/src/ChatFeed/FileThumb/styles.tsx b/src/ChatFeed/FileThumb/styles.tsx
new file mode 100644
index 0000000..6c083a6
--- /dev/null
+++ b/src/ChatFeed/FileThumb/styles.tsx
@@ -0,0 +1,28 @@
+import { Properties } from 'csstype';
+
+export const styles = {
+  fileView: {
+    fontFamily: 'Avenir',
+    padding: '12px',
+    borderRadius: '14px',
+    display: 'inline-block',
+    marginBottom: '4px',
+    marginRight: '2px',
+    cursor: 'pointer',
+    transition: 'all .33s ease',
+    WebkitTransition: 'all .33s ease',
+    MozTransition: 'all .33s ease',
+  } as Properties,
+  loadingContainer: {
+    fontFamily: 'Avenir',
+    color: 'white',
+    padding: '14px',
+    display: 'inline-block',
+    borderRadius: '14px',
+    marginRight: '2px',
+    width: '136px',
+    marginBottom: '4px',
+    marginLeft: '4px',
+    backgroundColor: '#d9d9d9',
+  } as Properties,
+};
diff --git a/src/ChatFeed/ImageThumb/.test.tsx b/src/ChatFeed/ImageThumb/.test.tsx
new file mode 100644
index 0000000..3d9a423
--- /dev/null
+++ b/src/ChatFeed/ImageThumb/.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import * as ReactDOM from 'react-dom';
+import { Default as Thing } from './stories';
+
+describe('Thing', () => {
+  it('renders without crashing', () => {
+    const div = document.createElement('div');
+    ReactDOM.render(<Thing />, div);
+    ReactDOM.unmountComponentAtNode(div);
+  });
+});
diff --git a/src/ChatFeed/ImageThumb/index.tsx b/src/ChatFeed/ImageThumb/index.tsx
new file mode 100644
index 0000000..587c1cc
--- /dev/null
+++ b/src/ChatFeed/ImageThumb/index.tsx
@@ -0,0 +1,41 @@
+import React, { useState } from 'react';
+
+import { Props } from './props';
+import { styles } from './styles';
+
+export const ImageThumb: React.FC<Props> = ({
+  attachment,
+  isLoading = false,
+  children = 'Loading...',
+  style = {},
+  loadingStyle = {},
+}) => {
+  const [hovered, setHovered] = useState<boolean>(false);
+
+  const thumbnailStyle = {
+    ...styles.thumbnail,
+    ...{ border: hovered ? '1px solid #1890ff' : '1px solid #fff' },
+  };
+
+  if (isLoading || !attachment) {
+    return (
+      <div style={{ ...styles.loadingContainer, ...loadingStyle }}>
+        {children}
+      </div>
+    );
+  }
+
+  return (
+    <img
+      onMouseEnter={() => setHovered(true)}
+      onMouseLeave={() => setHovered(false)}
+      src={attachment.file}
+      alt={'thumb-nail'}
+      style={{
+        ...thumbnailStyle,
+        ...style,
+      }}
+      onClick={() => window.open(attachment.file)}
+    />
+  );
+};
diff --git a/src/ChatFeed/ImageThumb/props.tsx b/src/ChatFeed/ImageThumb/props.tsx
new file mode 100644
index 0000000..cf3e473
--- /dev/null
+++ b/src/ChatFeed/ImageThumb/props.tsx
@@ -0,0 +1,13 @@
+import { ReactNode } from 'react';
+
+import { Properties } from 'csstype';
+
+import { AttachmentProps } from '../../util/interfaces';
+
+export interface Props {
+  attachment?: AttachmentProps;
+  isLoading?: boolean;
+  children?: ReactNode;
+  style?: Properties;
+  loadingStyle?: Properties;
+}
diff --git a/src/ChatFeed/ImageThumb/stories.tsx b/src/ChatFeed/ImageThumb/stories.tsx
new file mode 100644
index 0000000..67bd4f7
--- /dev/null
+++ b/src/ChatFeed/ImageThumb/stories.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+
+import { Meta, Story } from '@storybook/react';
+
+import { ImageThumb } from '.';
+
+import { Props } from './props';
+
+import { imageAttachment } from '../../util/mocks';
+
+const meta: Meta = {
+  title: 'ChatFeed/ImageThumb',
+  component: ImageThumb,
+  argTypes: {},
+};
+
+export default meta;
+
+const Template: Story<Props> = (props) => <ImageThumb {...props}></ImageThumb>;
+
+export const Default = Template.bind({});
+Default.args = {
+  attachment: imageAttachment,
+};
+
+export const Loading = Template.bind({});
+Loading.args = {
+  isLoading: true,
+};
diff --git a/src/ChatFeed/ImageThumb/styles.tsx b/src/ChatFeed/ImageThumb/styles.tsx
new file mode 100644
index 0000000..eefb906
--- /dev/null
+++ b/src/ChatFeed/ImageThumb/styles.tsx
@@ -0,0 +1,41 @@
+import { Properties } from 'csstype';
+
+export const styles = {
+  thumbnail: {
+    cursor: 'pointer',
+    textAlign: 'right',
+    display: 'inline',
+    objectFit: 'cover',
+    borderRadius: '0.3em',
+    padding: '2px',
+    // Size
+    height: '30vw',
+    width: '30vw',
+    maxHeight: '200px',
+    maxWidth: '200px',
+    minHeight: '100px',
+    minWidth: '100px',
+    transition: 'all .33s ease',
+    WebkitTransition: 'all .33s ease',
+    MozTransition: 'all .33s ease',
+  } as Properties,
+  loadingContainer: {
+    fontFamily: 'Avenir',
+    color: 'white',
+    cursor: 'pointer',
+    textAlign: 'right',
+    display: 'inline-block',
+    objectFit: 'cover',
+    borderRadius: '0.3em',
+    padding: '2px',
+    marginRight: '2px',
+    marginBottom: '4px',
+    height: '30vw',
+    width: '30vw',
+    maxHeight: '200px',
+    maxWidth: '200px',
+    minHeight: '100px',
+    minWidth: '100px',
+    backgroundColor: '#d9d9d9',
+  } as Properties,
+};
diff --git a/src/ChatFeed/MyMessage/.test.tsx b/src/ChatFeed/MyMessage/.test.tsx
new file mode 100644
index 0000000..996a446
--- /dev/null
+++ b/src/ChatFeed/MyMessage/.test.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import * as ReactDOM from 'react-dom';
+import { Default as Thing } from './stories';
+
+import { message } from '../../util/mocks';
+
+describe('Thing', () => {
+  it('renders without crashing', () => {
+    const div = document.createElement('div');
+    ReactDOM.render(<Thing message={message} />, div);
+    ReactDOM.unmountComponentAtNode(div);
+  });
+});
diff --git a/src/ChatFeed/MyMessage/index.tsx b/src/ChatFeed/MyMessage/index.tsx
new file mode 100644
index 0000000..77367a2
--- /dev/null
+++ b/src/ChatFeed/MyMessage/index.tsx
@@ -0,0 +1,175 @@
+import React, { useState } from 'react';
+
+import { Props } from './props';
+import { styles } from './styles';
+
+import { Dot } from '../../Dot';
+
+import { FileThumb } from '../FileThumb';
+import { ImageThumb } from '../ImageThumb';
+
+import { isImage, getFileName } from '../../util/file';
+import { formatTime, getDateTime } from '../../util/dateTime';
+
+import { Row, Col, setConfiguration } from 'react-grid-system';
+
+setConfiguration({ maxScreenClass: 'xl' });
+
+export const MyMessage: React.FC<Props> = ({
+  lastMessage = null,
+  message,
+  nextMessage = null,
+  chat = null,
+  isSending = false,
+}) => {
+  const [hovered, setHovered] = useState<boolean>(false);
+
+  const topRightRadius =
+    !lastMessage || lastMessage.sender_username !== message.sender_username
+      ? '1.3em'
+      : '0.3em';
+  const bottomRightRadius =
+    !nextMessage || nextMessage.sender_username !== message.sender_username
+      ? '1.3em'
+      : '0.3em';
+
+  const borderRadius = `1.3em ${topRightRadius} ${bottomRightRadius} 1.3em`;
+  const paddingBottom =
+    !nextMessage || nextMessage.sender_username !== message.sender_username
+      ? '12px'
+      : '2px';
+
+  const text: string =
+    message.text !== null
+      ? message.text.replace(/<a /g, `<a style="color: 'white';" `)
+      : '';
+
+  const renderImages = () => {
+    const attachments =
+      message && message.attachments ? message.attachments : [];
+
+    return attachments.map((attachment, index) => {
+      const fileName = getFileName(attachment.file);
+
+      if (isImage(fileName)) {
+        return (
+          <ImageThumb
+            attachment={attachment}
+            isLoading={isSending || attachment.file === null}
+          />
+        );
+      }
+
+      return <div key={`attachment${index}`} />;
+    });
+  };
+
+  const renderFiles = () => {
+    const attachments =
+      message && message.attachments ? message.attachments : [];
+
+    return attachments.map((attachment, index) => {
+      const fileName = getFileName(attachment.file);
+
+      if (!isImage(fileName)) {
+        return (
+          <FileThumb
+            attachment={attachment}
+            isLoading={isSending || attachment.file === null}
+          />
+        );
+      }
+
+      return <div key={`attachment${index}`} />;
+    });
+  };
+
+  const renderReads = () => {
+    const members = chat !== null ? chat.people : [];
+    return members.map((chatPerson, index) => {
+      return (
+        <Dot
+          key={`read_${index}`}
+          avatarUrl={chatPerson.person.avatar}
+          username={chatPerson.person.username}
+          visible={message.id === chatPerson.last_read}
+          style={{ float: 'right', marginLeft: '4px' }}
+        />
+      );
+    });
+  };
+
+  return (
+    <div
+      className="ce-message-row ce-my-message"
+      style={{
+        width: '100%',
+        textAlign: 'right',
+        paddingBottom,
+        // display: 'inline-block',
+      }}
+    >
+      <div
+        style={{ display: 'auto' }}
+        className="ce-my-message-attachments-container ce-my-message-images-container"
+      >
+        {renderImages()}
+      </div>
+
+      <div
+        style={{ display: 'auto' }}
+        className="ce-my-message-attachments-container ce-my-message-files-container"
+      >
+        {renderFiles()}
+      </div>
+
+      <Row
+        style={{ paddingRight: '2px' }}
+        className="ce-message-bubble-row ce-my-message-bubble-row"
+      >
+        <Col xs={12} sm={12} md={12} style={{ display: 'inline-block' }}>
+          <span
+            className="ce-message-timestamp ce-my-message-timestamp"
+            style={{
+              ...styles.timeTag,
+              ...{ opacity: hovered ? '1' : '0' },
+            }}
+          >
+            {formatTime(getDateTime(message.created, 0) as Date)}
+          </span>
+
+          {message.text !== null && (
+            <div
+              className={`
+                  ce-message-bubble 
+                  ce-my-message-bubble 
+                  ${isSending && 'ce-my-message-sending-bubble'}
+                `}
+              style={{
+                ...styles.myMessage,
+                ...{ borderRadius },
+                ...{
+                  backgroundColor: isSending ? '#40a9ff' : 'rgb(24, 144, 255)',
+                },
+              }}
+              onMouseEnter={() => setHovered(true)}
+              onMouseLeave={() => setHovered(false)}
+            >
+              <div
+                className="ce_message"
+                dangerouslySetInnerHTML={{ __html: text }}
+              />
+              <style>{`p {margin-block-start: 0px; margin-block-end: 0px;}`}</style>
+            </div>
+          )}
+        </Col>
+
+        <Col xs={1} sm={2} md={3} />
+
+        <Col xs={12} className="ce-reads-row ce-my-reads-row">
+          {renderReads()}
+        </Col>
+      </Row>
+    </div>
+  );
+};
diff --git a/src/ChatFeed/MyMessage/props.tsx b/src/ChatFeed/MyMessage/props.tsx
new file mode 100644
index 0000000..967f981
--- /dev/null
+++ b/src/ChatFeed/MyMessage/props.tsx
@@ -0,0 +1,9 @@
+import { MessageProps, ChatProps } from '../../util/interfaces';
+
+export interface Props {
+  lastMessage?: MessageProps | null;
+  message: MessageProps;
+  nextMessage?: MessageProps | null;
+  chat?: ChatProps | null;
+  isSending?: boolean;
+}
diff --git a/src/ChatFeed/MyMessage/stories.tsx b/src/ChatFeed/MyMessage/stories.tsx
new file mode 100644
index 0000000..022b1c3
--- /dev/null
+++ b/src/ChatFeed/MyMessage/stories.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+
+import { Meta, Story } from '@storybook/react';
+
+import { MyMessage } from '.';
+import { Props } from './props';
+
+import {
+  message,
+  messagePlusAttachments,
+  chatReadMessage,
+} from '../../util/mocks';
+
+const meta: Meta = {
+  title: 'ChatFeed/MyMessage',
+  component: MyMessage,
+  argTypes: {},
+};
+
+export default meta;
+
+const Template: Story<Props> = (props) => <MyMessage {...props} />;
+
+export const Default = Template.bind({});
+Default.args = {
+  message: messagePlusAttachments,
+};
+
+export const BetweenMessages = Template.bind({});
+BetweenMessages.args = {
+  lastMessage: message,
+  message: message,
+  nextMessage: message,
+};
+
+export const Sending = Template.bind({});
+Sending.args = {
+  isSending: true,
+  message: messagePlusAttachments,
+};
+
+export const ReadMessage = Template.bind({});
+ReadMessage.args = {
+  message: message,
+  chat: chatReadMessage,
+};
diff --git a/src/ChatFeed/MyMessage/styles.tsx b/src/ChatFeed/MyMessage/styles.tsx
new file mode 100644
index 0000000..9a3c6ec
--- /dev/null
+++ b/src/ChatFeed/MyMessage/styles.tsx
@@ -0,0 +1,58 @@
+import { Properties } from 'csstype';
+export const styles = {
+  myMessage: {
+    color: 'white',
+    cursor: 'pointer',
+    float: 'right',
+    textAlign: 'left',
+    // Stay right but render text
+    padding: '12px',
+    fontSize: '15px',
+    fontFamily: 'Avenir',
+    whiteSpace: 'pre-line',
+    overflowWrap: 'anywhere',
+    maxWidth: 'calc(100% - 100px)',
+    // CSS Transitions
+    transition: 'all .33s ease',
+    WebkitTransition: 'all .33s ease',
+    MozTransition: 'all .33s ease',
+  } as Properties,
+  timeTag: {
+    fontFamily: 'Avenir',
+    position: 'relative',
+    top: '12px',
+    right: '8px',
+    fontSize: '14px',
+    color: 'rgb(24, 144, 255)',
+    // CSS Transitions
+    transition: 'all .15s ease',
+    WebkitTransition: 'all .15s ease',
+    MozTransition: 'all .15s ease',
+  } as Properties,
+  thumbnail: {
+    cursor: 'pointer',
+    textAlign: 'right',
+    display: 'inline',
+    objectFit: 'cover',
+    borderRadius: '0.3em',
+    paddingRight: '2px',
+    // Size
+    height: '30vw',
+    width: '30vw',
+    maxHeight: '200px',
+    maxWidth: '200px',
+    minHeight: '100px',
+    minWidth: '100px',
+  } as Properties,
+  fileView: {
+    fontFamily: 'Avenir',
+    padding: '12px',
+    borderRadius: '14px',
+    display: 'inline-block',
+    marginBottom: '4px',
+    marginRight: '2px',
+    cursor: 'pointer',
+    color: '#434343',
+    border: '1px solid #434343',
+  } as Properties,
+};
diff --git a/src/ChatList/ChatLoader/stories.tsx b/src/ChatList/ChatLoader/stories.tsx
index 7fdb46c..41d4235 100644
--- a/src/ChatList/ChatLoader/stories.tsx
+++ b/src/ChatList/ChatLoader/stories.tsx
@@ -26,10 +26,6 @@ Default.args = {
 export const CustomStyles = Template.bind({});
 CustomStyles.args = {
   onVisible: () => console.log('Chat Loader is visible.'),
-  style: { maxWidth: '400px', padding: '0px', backgroundColor: '#4a5162' },
-  children: (
-    <LoadingOutlined
-      style={{ fontSize: '21px', padding: '24px', color: '#f0f0f0' }}
-    />
-  ),
+  style: { maxWidth: '400px', backgroundColor: '#4a5162' },
+  children: <LoadingOutlined style={{ fontSize: '21px', color: '#f0f0f0' }} />,
 };
diff --git a/src/ChatList/index.tsx b/src/ChatList/index.tsx
index b674cd9..f803c1c 100644
--- a/src/ChatList/index.tsx
+++ b/src/ChatList/index.tsx
@@ -1,12 +1,13 @@
 import React from 'react';
 
-import { Props, ChatProps } from './props';
+import { Props } from './props';
 import { styles } from './styles';
 
 import { ChatForm } from './ChatForm';
 import { ChatCard } from './ChatCard';
 import { ChatLoader } from './ChatLoader';
 
+import { ChatProps } from '../util/interfaces';
 import { getDateTime } from '../util/dateTime';
 
 export const ChatList: React.FC<Props> = ({
@@ -33,7 +34,7 @@ export const ChatList: React.FC<Props> = ({
   const renderChats = (chats: Array<ChatProps>) => {
     return chats.map((chat, index) => {
       const description =
-        chat.last_message?.text === '' ? 'Say hello!' : chat.last_message?.text;
+        chat.last_message.text !== null ? chat.last_message.text : 'Say hello!';
       const timeStamp = getDateTime(chat.created).toString().substr(4, 6);
       const hasNotification = userName
         ? !readLastMessage(userName, chat)
diff --git a/src/ChatList/props.tsx b/src/ChatList/props.tsx
index 8f191ba..93c4b9e 100644
--- a/src/ChatList/props.tsx
+++ b/src/ChatList/props.tsx
@@ -2,39 +2,7 @@ import { HTMLAttributes } from 'react';
 
 import { Properties } from 'csstype';
 
-export interface MessageProps {
-  id?: number;
-  text: string;
-  sender_username: string;
-  created: string;
-  attachments: Array<object>;
-  custom_json: object | string;
-}
-
-export interface PersonProps {
-  username: string;
-  first_name: string | null;
-  last_name: string | null;
-  avatar: string | null;
-  custom_json: string | object | null;
-  is_online: boolean;
-}
-
-export interface ChatPersonProps {
-  person: PersonProps;
-  chat_updated: string | null;
-  last_read: number | null;
-}
-export interface ChatProps {
-  id: number;
-  title: string;
-  created: string;
-  is_direct_chat: boolean;
-  custom_json: object | string;
-  last_message: MessageProps;
-  attachments: Array<object>;
-  people: Array<ChatPersonProps>;
-}
+import { ChatProps } from '../util/interfaces';
 
 export interface Props extends HTMLAttributes<HTMLDivElement> {
   style?: Properties;
diff --git a/src/ChatList/stories.tsx b/src/ChatList/stories.tsx
index 85c1f0b..c00899a 100644
--- a/src/ChatList/stories.tsx
+++ b/src/ChatList/stories.tsx
@@ -5,77 +5,14 @@ import { Meta, Story } from '@storybook/react';
 import { ChatList } from '.';
 import { Props } from './props';
 
+import { chats } from '../util/mocks';
+
 const meta: Meta = {
   title: 'ChatList',
   component: ChatList,
   argTypes: {},
 };
 
-const chats = [
-  {
-    id: 201,
-    title: 'First Chat ☝️',
-    is_direct_chat: false,
-    created: '2021-01-28T02:41:48.826706Z',
-    custom_json: {},
-    attachments: [],
-    people: [
-      {
-        last_read: 1000,
-        person: {
-          username: 'Adam La Morre',
-          first_name: '',
-          last_name: '',
-          avatar: '',
-          custom_json: '',
-          is_online: true,
-        },
-        chat_updated: '',
-      },
-    ],
-    last_message: {
-      id: 1000,
-      created: '2021-07-14 01:18:24.567443+00:00',
-      attachments: [],
-      sender_username: 'Adam_La_Morre',
-      text: '<p>Hello there world!</p>',
-      custom_json: '{"sender_id":"1626225504264"}',
-    },
-  },
-  {
-    id: 202,
-    title: 'Chat Two ✌️ ',
-    is_direct_chat: false,
-    created: '2021-01-27T02:41:48.826706Z',
-    custom_json: {},
-    attachments: [],
-    people: [],
-    last_message: {
-      created: '',
-      attachments: [],
-      sender_username: '',
-      text: '',
-      custom_json: '',
-    },
-  },
-  {
-    id: 203,
-    title: 'Direct Message w/ Adam',
-    is_direct_chat: true,
-    created: '2021-01-26T02:41:48.826706Z',
-    custom_json: {},
-    attachments: [],
-    people: [],
-    last_message: {
-      created: '',
-      attachments: [],
-      sender_username: '',
-      text: '',
-      custom_json: '',
-    },
-  },
-];
-
 export default meta;
 
 const Template: Story<Props> = (props) => <ChatList {...props}></ChatList>;
@@ -110,7 +47,7 @@ UnreadMessages.args = {
     boxShadow: '0px 0px 3px 6px rgba(0, 0, 0, 0.1)',
     maxHeight: '300px',
   },
-  userName: 'Adam La Morre',
+  userName: 'adam_lamorre',
   chats: chats,
 };
 
diff --git a/src/Dot/index.tsx b/src/Dot/index.tsx
index c99a5e9..cb70c94 100644
--- a/src/Dot/index.tsx
+++ b/src/Dot/index.tsx
@@ -7,24 +7,23 @@ export const Dot = ({
   avatarUrl = undefined,
   username = '',
   style = {},
-  visible = true
+  visible = true,
 }: Props) => {
-
-  const color = stringToColor(username)
+  const color = stringToColor(username);
 
   return (
     <div
-      className='ce-avatar-dot'
+      className="ce-avatar-dot"
       style={{
         ...styles.default,
         ...style,
         ...{
           backgroundColor: avatarUrl ? 'white' : color,
-          backgroundImage: avatarUrl && `url(${avatarUrl})`,
+          backgroundImage: avatarUrl ? `url(${avatarUrl})` : '',
           width: visible ? '13px' : '0px',
-          height: visible ? '13px' : '0px'
-        }
+          height: visible ? '13px' : '0px',
+        },
       }}
     />
-  )
-}
\ No newline at end of file
+  );
+};
diff --git a/src/Dot/props.tsx b/src/Dot/props.tsx
index 8f6343b..8b920b9 100644
--- a/src/Dot/props.tsx
+++ b/src/Dot/props.tsx
@@ -1,8 +1,8 @@
 import CSS from 'csstype';
 
 export interface Props {
-  avatarUrl?: string;
+  avatarUrl?: string | null;
   username?: string;
   style?: CSS.Properties;
   visible?: boolean;
-}
\ No newline at end of file
+}
diff --git a/src/util/dateTime.ts b/src/util/dateTime.ts
index 7956e01..197f3c6 100644
--- a/src/util/dateTime.ts
+++ b/src/util/dateTime.ts
@@ -1,17 +1,22 @@
 export const getDateTime = (date: string, offset = 0) => {
-    if (!date) return '';
+  if (!date) return '';
 
-    date = date.replace(' ', 'T');
-    offset = offset ? offset : 0;
+  date = date.replace(' ', 'T');
+  offset = offset ? offset : 0;
 
-    const year = date.substr(0, 4);
-    const month = date.substr(5, 2);
-    const day = date.substr(8, 2);
-    const hour = date.substr(11, 2);
-    const minute = date.substr(14, 2);
-    const second = date.substr(17, 2);
+  const year = date.substr(0, 4);
+  const month = date.substr(5, 2);
+  const day = date.substr(8, 2);
+  const hour = date.substr(11, 2);
+  const minute = date.substr(14, 2);
+  const second = date.substr(17, 2);
 
-    var d = new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}`);
-    d.setHours(d.getHours() + offset);
-    return d;
-  };
\ No newline at end of file
+  var d = new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}`);
+  d.setHours(d.getHours() + offset);
+  return d;
+};
+
+export function formatTime(dateTime: Date) {
+  var time = dateTime.toLocaleString('en-US');
+  return time.split(' ')[1].slice(0, -3) + ' ' + time.slice(-2);
+}
diff --git a/src/util/file.tsx b/src/util/file.tsx
new file mode 100644
index 0000000..205207f
--- /dev/null
+++ b/src/util/file.tsx
@@ -0,0 +1,15 @@
+const images = ['jpg', 'jpeg', 'png', 'gif', 'tiff'];
+
+export const isImage = (fileName: string) => {
+  const dotSplit = fileName.split('.');
+  return (
+    dotSplit.length > 0 &&
+    images.indexOf(dotSplit[dotSplit.length - 1].toLowerCase()) !== -1
+  );
+};
+
+export const getFileName = (fileUrl: string) => {
+  const slashSplit = fileUrl.split('/');
+  const nameAndHash = slashSplit[slashSplit.length - 1];
+  return nameAndHash.split('?')[0];
+};
diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts
new file mode 100644
index 0000000..e8877b4
--- /dev/null
+++ b/src/util/interfaces.ts
@@ -0,0 +1,38 @@
+export interface AttachmentProps {
+  id: number;
+  file: string;
+  created: string;
+}
+export interface MessageProps {
+  id?: number;
+  text: string | null;
+  sender_username: string;
+  created: string;
+  attachments: Array<AttachmentProps>;
+  custom_json: object | string;
+}
+
+export interface PersonProps {
+  username: string;
+  first_name: string | null;
+  last_name: string | null;
+  avatar: string | null;
+  custom_json: string | object | null;
+  is_online: boolean;
+}
+
+export interface ChatPersonProps {
+  person: PersonProps;
+  chat_updated: string | null;
+  last_read: number | null;
+}
+export interface ChatProps {
+  id: number;
+  title: string;
+  created: string;
+  is_direct_chat: boolean;
+  custom_json: object | string;
+  last_message: MessageProps;
+  attachments: Array<AttachmentProps>;
+  people: Array<ChatPersonProps>;
+}
diff --git a/src/util/mocks.tsx b/src/util/mocks.tsx
new file mode 100644
index 0000000..2c838e8
--- /dev/null
+++ b/src/util/mocks.tsx
@@ -0,0 +1,121 @@
+export const imageAttachment = {
+  id: 10,
+  file: 'https://chat-engine-assets.s3.amazonaws.com/tutorials/my-face-min.png',
+  created: '2021-08-03T00:16:52.633778Z',
+};
+
+export const fileAttachment = {
+  id: 11,
+  file: 'https://chat-engine-assets.s3.amazonaws.com/click.mp3',
+  created: '2021-08-03T00:16:59.633778Z',
+};
+
+export const message = {
+  id: 1000,
+  created: '2021-07-14 01:18:24.567443+00:00',
+  attachments: [],
+  sender_username: 'Adam_La_Morre',
+  text: '<p>Hello there world!</p>',
+  custom_json: '',
+};
+
+export const messagePlusAttachments = {
+  id: 1000,
+  created: '2021-07-14 01:18:24.567443+00:00',
+  attachments: [imageAttachment, fileAttachment],
+  sender_username: 'Adam_La_Morre',
+  text: '<p>Hey check out this image and MP3 file!</p>',
+  custom_json: '',
+};
+
+export const adam = {
+  username: 'adam_lamorre',
+  first_name: 'Adam',
+  last_name: 'La Morre',
+  avatar:
+    'https://chat-engine-assets.s3.amazonaws.com/tutorials/my-face-min.png',
+  custom_json: '',
+  is_online: true,
+};
+
+export const bob = {
+  username: 'bob_baker',
+  first_name: 'Bob',
+  last_name: 'Baker',
+  avatar: null,
+  custom_json: '',
+  is_online: true,
+};
+
+export const chatReadMessage = {
+  id: 201,
+  title: 'First Chat ☝️',
+  is_direct_chat: false,
+  created: '2021-01-28T02:41:48.826706Z',
+  custom_json: {},
+  attachments: [],
+  people: [
+    {
+      last_read: 1000,
+      person: adam,
+      chat_updated: '',
+    },
+    {
+      last_read: 1000,
+      person: bob,
+      chat_updated: '',
+    },
+  ],
+  last_message: message,
+};
+
+export const chats = [
+  {
+    id: 201,
+    title: 'First Chat ☝️',
+    is_direct_chat: false,
+    created: '2021-01-28T02:41:48.826706Z',
+    custom_json: {},
+    attachments: [],
+    people: [
+      {
+        last_read: 1000,
+        person: adam,
+        chat_updated: '',
+      },
+    ],
+    last_message: message,
+  },
+  {
+    id: 202,
+    title: 'Chat Two ✌️ ',
+    is_direct_chat: false,
+    created: '2021-01-27T02:41:48.826706Z',
+    custom_json: {},
+    attachments: [],
+    people: [],
+    last_message: {
+      created: '',
+      attachments: [],
+      sender_username: '',
+      text: '',
+      custom_json: '',
+    },
+  },
+  {
+    id: 203,
+    title: 'Direct Message w/ Adam',
+    is_direct_chat: true,
+    created: '2021-01-26T02:41:48.826706Z',
+    custom_json: {},
+    attachments: [],
+    people: [],
+    last_message: {
+      created: '',
+      attachments: [],
+      sender_username: '',
+      text: '',
+      custom_json: '',
+    },
+  },
+];