Task #4: Redesign Heartbeat Monitor - UptimeRobot inspired UI

- Clean white/light theme with blue accents (matching UptimeRobot)
- Status overview cards: Services Online, Avg Uptime, Avg Response, Incidents
- Modern card-based layout with hover effects
- Sparkline uptime visualization for each monitor
- Improved typography and spacing
- Better action buttons with hover reveal pattern
- Empty state for no monitors
- Sticky navigation header
- Responsive design maintained
This commit is contained in:
OpenClaw Bot 2026-02-18 16:03:15 -06:00
parent 796a760dc3
commit ac51761c73
68 changed files with 603 additions and 5220 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,3 @@
{
"/api/monitor/route": "app/api/monitor/route.js",
"/page": "app/page.js"
}

File diff suppressed because one or more lines are too long

View File

@ -1,65 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
exports.id = "vendor-chunks/@babel";
exports.ids = ["vendor-chunks/@babel"];
exports.modules = {
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js":
/*!**************************************************************************!*\
!*** ./node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js ***!
\**************************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ _assertThisInitialized)\n/* harmony export */ });\nfunction _assertThisInitialized(e) {\n if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");\n return e;\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vYXNzZXJ0VGhpc0luaXRpYWxpemVkLmpzIiwibWFwcGluZ3MiOiI7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2VzbS9hc3NlcnRUaGlzSW5pdGlhbGl6ZWQuanMiXSwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gX2Fzc2VydFRoaXNJbml0aWFsaXplZChlKSB7XG4gIGlmICh2b2lkIDAgPT09IGUpIHRocm93IG5ldyBSZWZlcmVuY2VFcnJvcihcInRoaXMgaGFzbid0IGJlZW4gaW5pdGlhbGlzZWQgLSBzdXBlcigpIGhhc24ndCBiZWVuIGNhbGxlZFwiKTtcbiAgcmV0dXJuIGU7XG59XG5leHBvcnQgeyBfYXNzZXJ0VGhpc0luaXRpYWxpemVkIGFzIGRlZmF1bHQgfTsiXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/extends.js":
/*!************************************************************!*\
!*** ./node_modules/@babel/runtime/helpers/esm/extends.js ***!
\************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ _extends)\n/* harmony export */ });\nfunction _extends() {\n return _extends = Object.assign ? Object.assign.bind() : function (n) {\n for (var e = 1; e < arguments.length; e++) {\n var t = arguments[e];\n for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);\n }\n return n;\n }, _extends.apply(null, arguments);\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vZXh0ZW5kcy5qcyIsIm1hcHBpbmdzIjoiOzs7O0FBQUE7QUFDQTtBQUNBLG9CQUFvQixzQkFBc0I7QUFDMUM7QUFDQSwwQkFBMEI7QUFDMUI7QUFDQTtBQUNBLEdBQUc7QUFDSCIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2VzbS9leHRlbmRzLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9leHRlbmRzKCkge1xuICByZXR1cm4gX2V4dGVuZHMgPSBPYmplY3QuYXNzaWduID8gT2JqZWN0LmFzc2lnbi5iaW5kKCkgOiBmdW5jdGlvbiAobikge1xuICAgIGZvciAodmFyIGUgPSAxOyBlIDwgYXJndW1lbnRzLmxlbmd0aDsgZSsrKSB7XG4gICAgICB2YXIgdCA9IGFyZ3VtZW50c1tlXTtcbiAgICAgIGZvciAodmFyIHIgaW4gdCkgKHt9KS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHQsIHIpICYmIChuW3JdID0gdFtyXSk7XG4gICAgfVxuICAgIHJldHVybiBuO1xuICB9LCBfZXh0ZW5kcy5hcHBseShudWxsLCBhcmd1bWVudHMpO1xufVxuZXhwb3J0IHsgX2V4dGVuZHMgYXMgZGVmYXVsdCB9OyJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/extends.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js":
/*!******************************************************************!*\
!*** ./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js ***!
\******************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ _inheritsLoose)\n/* harmony export */ });\n/* harmony import */ var _setPrototypeOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./setPrototypeOf.js */ \"(ssr)/./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js\");\n\nfunction _inheritsLoose(t, o) {\n t.prototype = Object.create(o.prototype), t.prototype.constructor = t, (0,_setPrototypeOf_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(t, o);\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vaW5oZXJpdHNMb29zZS5qcyIsIm1hcHBpbmdzIjoiOzs7OztBQUFpRDtBQUNqRDtBQUNBLHlFQUF5RSw4REFBYztBQUN2RiIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2VzbS9pbmhlcml0c0xvb3NlLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBzZXRQcm90b3R5cGVPZiBmcm9tIFwiLi9zZXRQcm90b3R5cGVPZi5qc1wiO1xuZnVuY3Rpb24gX2luaGVyaXRzTG9vc2UodCwgbykge1xuICB0LnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoby5wcm90b3R5cGUpLCB0LnByb3RvdHlwZS5jb25zdHJ1Y3RvciA9IHQsIHNldFByb3RvdHlwZU9mKHQsIG8pO1xufVxuZXhwb3J0IHsgX2luaGVyaXRzTG9vc2UgYXMgZGVmYXVsdCB9OyJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ _objectWithoutPropertiesLoose)\n/* harmony export */ });\nfunction _objectWithoutPropertiesLoose(r, e) {\n if (null == r) return {};\n var t = {};\n for (var n in r) if ({}.hasOwnProperty.call(r, n)) {\n if (-1 !== e.indexOf(n)) continue;\n t[n] = r[n];\n }\n return t;\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vb2JqZWN0V2l0aG91dFByb3BlcnRpZXNMb29zZS5qcyIsIm1hcHBpbmdzIjoiOzs7O0FBQUE7QUFDQTtBQUNBO0FBQ0EseUJBQXlCO0FBQ3pCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIi9Vc2Vycy9tYXR0YnJ1Y2UvRG9jdW1lbnRzL1Byb2plY3RzL09wZW5DbGF3L1dlYi9oZWFydGJlYXQtbW9uaXRvci9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vb2JqZWN0V2l0aG91dFByb3BlcnRpZXNMb29zZS5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfb2JqZWN0V2l0aG91dFByb3BlcnRpZXNMb29zZShyLCBlKSB7XG4gIGlmIChudWxsID09IHIpIHJldHVybiB7fTtcbiAgdmFyIHQgPSB7fTtcbiAgZm9yICh2YXIgbiBpbiByKSBpZiAoe30uaGFzT3duUHJvcGVydHkuY2FsbChyLCBuKSkge1xuICAgIGlmICgtMSAhPT0gZS5pbmRleE9mKG4pKSBjb250aW51ZTtcbiAgICB0W25dID0gcltuXTtcbiAgfVxuICByZXR1cm4gdDtcbn1cbmV4cG9ydCB7IF9vYmplY3RXaXRob3V0UHJvcGVydGllc0xvb3NlIGFzIGRlZmF1bHQgfTsiXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js":
/*!*******************************************************************!*\
!*** ./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js ***!
\*******************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ _setPrototypeOf)\n/* harmony export */ });\nfunction _setPrototypeOf(t, e) {\n return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {\n return t.__proto__ = e, t;\n }, _setPrototypeOf(t, e);\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vc2V0UHJvdG90eXBlT2YuanMiLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSCIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2VzbS9zZXRQcm90b3R5cGVPZi5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfc2V0UHJvdG90eXBlT2YodCwgZSkge1xuICByZXR1cm4gX3NldFByb3RvdHlwZU9mID0gT2JqZWN0LnNldFByb3RvdHlwZU9mID8gT2JqZWN0LnNldFByb3RvdHlwZU9mLmJpbmQoKSA6IGZ1bmN0aW9uICh0LCBlKSB7XG4gICAgcmV0dXJuIHQuX19wcm90b19fID0gZSwgdDtcbiAgfSwgX3NldFByb3RvdHlwZU9mKHQsIGUpO1xufVxuZXhwb3J0IHsgX3NldFByb3RvdHlwZU9mIGFzIGRlZmF1bHQgfTsiXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js\n");
/***/ })
};
;

View File

@ -1,25 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
exports.id = "vendor-chunks/clsx";
exports.ids = ["vendor-chunks/clsx"];
exports.modules = {
/***/ "(ssr)/./node_modules/clsx/dist/clsx.mjs":
/*!*****************************************!*\
!*** ./node_modules/clsx/dist/clsx.mjs ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ clsx: () => (/* binding */ clsx),\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nfunction r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (clsx);//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvY2xzeC9kaXN0L2Nsc3gubWpzIiwibWFwcGluZ3MiOiI7Ozs7O0FBQUEsY0FBYyxhQUFhLCtDQUErQyxnREFBZ0QsZUFBZSxRQUFRLElBQUksMENBQTBDLHlDQUF5QyxTQUFnQixnQkFBZ0Isd0NBQXdDLElBQUksbURBQW1ELFNBQVMsaUVBQWUsSUFBSSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9jbHN4L2Rpc3QvY2xzeC5tanMiXSwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gcihlKXt2YXIgdCxmLG49XCJcIjtpZihcInN0cmluZ1wiPT10eXBlb2YgZXx8XCJudW1iZXJcIj09dHlwZW9mIGUpbis9ZTtlbHNlIGlmKFwib2JqZWN0XCI9PXR5cGVvZiBlKWlmKEFycmF5LmlzQXJyYXkoZSkpe3ZhciBvPWUubGVuZ3RoO2Zvcih0PTA7dDxvO3QrKyllW3RdJiYoZj1yKGVbdF0pKSYmKG4mJihuKz1cIiBcIiksbis9Zil9ZWxzZSBmb3IoZiBpbiBlKWVbZl0mJihuJiYobis9XCIgXCIpLG4rPWYpO3JldHVybiBufWV4cG9ydCBmdW5jdGlvbiBjbHN4KCl7Zm9yKHZhciBlLHQsZj0wLG49XCJcIixvPWFyZ3VtZW50cy5sZW5ndGg7ZjxvO2YrKykoZT1hcmd1bWVudHNbZl0pJiYodD1yKGUpKSYmKG4mJihuKz1cIiBcIiksbis9dCk7cmV0dXJuIG59ZXhwb3J0IGRlZmF1bHQgY2xzeDsiXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/clsx/dist/clsx.mjs\n");
/***/ })
};
;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -51,6 +51,56 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/circle-alert.js":
/*!******************************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/circle-alert.js ***!
\******************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ CircleAlert)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"circle\",\n {\n cx: \"12\",\n cy: \"12\",\n r: \"10\",\n key: \"1mglay\"\n }\n ],\n [\n \"line\",\n {\n x1: \"12\",\n x2: \"12\",\n y1: \"8\",\n y2: \"12\",\n key: \"1pkeuh\"\n }\n ],\n [\n \"line\",\n {\n x1: \"12\",\n x2: \"12.01\",\n y1: \"16\",\n y2: \"16\",\n key: \"4dfq90\"\n }\n ]\n];\nconst CircleAlert = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"CircleAlert\", __iconNode);\n //# sourceMappingURL=circle-alert.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2NpcmNsZS1hbGVydC5qcyIsIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFHTyxNQUFNLFVBQXVCO0lBQ2xDO1FBQUMsUUFBVTtRQUFBO1lBQUUsRUFBSTtZQUFNLENBQUksUUFBTTtZQUFBLENBQUc7WUFBTSxHQUFLO1FBQUEsQ0FBVTtLQUFBO0lBQ3pEO1FBQUM7UUFBUSxDQUFFO1lBQUEsSUFBSSxDQUFNO1lBQUEsSUFBSSxDQUFNO1lBQUEsR0FBSSxJQUFLO1lBQUEsR0FBSSxLQUFNO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtJQUNqRTtRQUFDO1FBQVEsQ0FBRTtZQUFBLElBQUksQ0FBTTtZQUFBLElBQUksQ0FBUztZQUFBLEdBQUksS0FBTTtZQUFBLEdBQUksS0FBTTtZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7Q0FDdkU7QUFhTSxrQkFBYyxrRUFBaUIsZ0JBQWUsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL2NpcmNsZS1hbGVydC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgY3JlYXRlTHVjaWRlSWNvbiBmcm9tICcuLi9jcmVhdGVMdWNpZGVJY29uJztcbmltcG9ydCB7IEljb25Ob2RlIH0gZnJvbSAnLi4vdHlwZXMnO1xuXG5leHBvcnQgY29uc3QgX19pY29uTm9kZTogSWNvbk5vZGUgPSBbXG4gIFsnY2lyY2xlJywgeyBjeDogJzEyJywgY3k6ICcxMicsIHI6ICcxMCcsIGtleTogJzFtZ2xheScgfV0sXG4gIFsnbGluZScsIHsgeDE6ICcxMicsIHgyOiAnMTInLCB5MTogJzgnLCB5MjogJzEyJywga2V5OiAnMXBrZXVoJyB9XSxcbiAgWydsaW5lJywgeyB4MTogJzEyJywgeDI6ICcxMi4wMScsIHkxOiAnMTYnLCB5MjogJzE2Jywga2V5OiAnNGRmcTkwJyB9XSxcbl07XG5cbi8qKlxuICogQGNvbXBvbmVudCBAbmFtZSBDaXJjbGVBbGVydFxuICogQGRlc2NyaXB0aW9uIEx1Y2lkZSBTVkcgaWNvbiBjb21wb25lbnQsIHJlbmRlcnMgU1ZHIEVsZW1lbnQgd2l0aCBjaGlsZHJlbi5cbiAqXG4gKiBAcHJldmlldyAhW2ltZ10oZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlBZ2VHMXNibk05SW1oMGRIQTZMeTkzZDNjdWR6TXViM0puTHpJd01EQXZjM1puSWdvZ0lIZHBaSFJvUFNJeU5DSUtJQ0JvWldsbmFIUTlJakkwSWdvZ0lIWnBaWGRDYjNnOUlqQWdNQ0F5TkNBeU5DSUtJQ0JtYVd4c1BTSnViMjVsSWdvZ0lITjBjbTlyWlQwaUl6QXdNQ0lnYzNSNWJHVTlJbUpoWTJ0bmNtOTFibVF0WTI5c2IzSTZJQ05tWm1ZN0lHSnZjbVJsY2kxeVlXUnBkWE02SURKd2VDSUtJQ0J6ZEhKdmEyVXRkMmxrZEdnOUlqSWlDaUFnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lnb2dJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaUNqNEtJQ0E4WTJseVkyeGxJR040UFNJeE1pSWdZM2s5SWpFeUlpQnlQU0l4TUNJZ0x6NEtJQ0E4YkdsdVpTQjRNVDBpTVRJaUlIZ3lQU0l4TWlJZ2VURTlJamdpSUhreVBTSXhNaUlnTHo0S0lDQThiR2x1WlNCNE1UMGlNVElpSUhneVBTSXhNaTR3TVNJZ2VURTlJakUySWlCNU1qMGlNVFlpSUM4K0Nqd3ZjM1puUGdvPSkgLSBodHRwczovL2x1Y2lkZS5kZXYvaWNvbnMvY2lyY2xlLWFsZXJ0XG4gKiBAc2VlIGh0dHBzOi8vbHVjaWRlLmRldi9ndWlkZS9wYWNrYWdlcy9sdWNpZGUtcmVhY3QgLSBEb2N1bWVudGF0aW9uXG4gKlxuICogQHBhcmFtIHtPYmplY3R9IHByb3BzIC0gTHVjaWRlIGljb25zIHByb3BzIGFuZCBhbnkgdmFsaWQgU1ZHIGF0dHJpYnV0ZVxuICogQHJldHVybnMge0pTWC5FbGVtZW50fSBKU1ggRWxlbWVudFxuICpcbiAqL1xuY29uc3QgQ2lyY2xlQWxlcnQgPSBjcmVhdGVMdWNpZGVJY29uKCdDaXJjbGVBbGVydCcsIF9faWNvbk5vZGUpO1xuXG5leHBvcnQgZGVmYXVsdCBDaXJjbGVBbGVydDtcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/circle-alert.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/circle-check.js":
/*!******************************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/circle-check.js ***!
\******************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ CircleCheck)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"circle\",\n {\n cx: \"12\",\n cy: \"12\",\n r: \"10\",\n key: \"1mglay\"\n }\n ],\n [\n \"path\",\n {\n d: \"m9 12 2 2 4-4\",\n key: \"dzmm74\"\n }\n ]\n];\nconst CircleCheck = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"CircleCheck\", __iconNode);\n //# sourceMappingURL=circle-check.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2NpcmNsZS1jaGVjay5qcyIsIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFHTyxNQUFNLFVBQXVCO0lBQ2xDO1FBQUMsUUFBVTtRQUFBO1lBQUUsRUFBSTtZQUFNLENBQUksUUFBTTtZQUFBLENBQUc7WUFBTSxHQUFLO1FBQUEsQ0FBVTtLQUFBO0lBQ3pEO1FBQUMsTUFBUTtRQUFBO1lBQUUsR0FBRyxDQUFpQjtZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7Q0FDaEQ7QUFhTSxrQkFBYyxrRUFBaUIsZ0JBQWUsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL2NpcmNsZS1jaGVjay50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgY3JlYXRlTHVjaWRlSWNvbiBmcm9tICcuLi9jcmVhdGVMdWNpZGVJY29uJztcbmltcG9ydCB7IEljb25Ob2RlIH0gZnJvbSAnLi4vdHlwZXMnO1xuXG5leHBvcnQgY29uc3QgX19pY29uTm9kZTogSWNvbk5vZGUgPSBbXG4gIFsnY2lyY2xlJywgeyBjeDogJzEyJywgY3k6ICcxMicsIHI6ICcxMCcsIGtleTogJzFtZ2xheScgfV0sXG4gIFsncGF0aCcsIHsgZDogJ205IDEyIDIgMiA0LTQnLCBrZXk6ICdkem1tNzQnIH1dLFxuXTtcblxuLyoqXG4gKiBAY29tcG9uZW50IEBuYW1lIENpcmNsZUNoZWNrXG4gKiBAZGVzY3JpcHRpb24gTHVjaWRlIFNWRyBpY29uIGNvbXBvbmVudCwgcmVuZGVycyBTVkcgRWxlbWVudCB3aXRoIGNoaWxkcmVuLlxuICpcbiAqIEBwcmV2aWV3ICFbaW1nXShkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUFnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JZ29nSUhkcFpIUm9QU0l5TkNJS0lDQm9aV2xuYUhROUlqSTBJZ29nSUhacFpYZENiM2c5SWpBZ01DQXlOQ0F5TkNJS0lDQm1hV3hzUFNKdWIyNWxJZ29nSUhOMGNtOXJaVDBpSXpBd01DSWdjM1I1YkdVOUltSmhZMnRuY205MWJtUXRZMjlzYjNJNklDTm1abVk3SUdKdmNtUmxjaTF5WVdScGRYTTZJREp3ZUNJS0lDQnpkSEp2YTJVdGQybGtkR2c5SWpJaUNpQWdjM1J5YjJ0bExXeHBibVZqWVhBOUluSnZkVzVrSWdvZ0lITjBjbTlyWlMxc2FXNWxhbTlwYmowaWNtOTFibVFpQ2o0S0lDQThZMmx5WTJ4bElHTjRQU0l4TWlJZ1kzazlJakV5SWlCeVBTSXhNQ0lnTHo0S0lDQThjR0YwYUNCa1BTSnRPU0F4TWlBeUlESWdOQzAwSWlBdlBnbzhMM04yWno0SykgLSBodHRwczovL2x1Y2lkZS5kZXYvaWNvbnMvY2lyY2xlLWNoZWNrXG4gKiBAc2VlIGh0dHBzOi8vbHVjaWRlLmRldi9ndWlkZS9wYWNrYWdlcy9sdWNpZGUtcmVhY3QgLSBEb2N1bWVudGF0aW9uXG4gKlxuICogQHBhcmFtIHtPYmplY3R9IHByb3BzIC0gTHVjaWRlIGljb25zIHByb3BzIGFuZCBhbnkgdmFsaWQgU1ZHIGF0dHJpYnV0ZVxuICogQHJldHVybnMge0pTWC5FbGVtZW50fSBKU1ggRWxlbWVudFxuICpcbiAqL1xuY29uc3QgQ2lyY2xlQ2hlY2sgPSBjcmVhdGVMdWNpZGVJY29uKCdDaXJjbGVDaGVjaycsIF9faWNvbk5vZGUpO1xuXG5leHBvcnQgZGVmYXVsdCBDaXJjbGVDaGVjaztcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/circle-check.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/circle-x.js":
/*!**************************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/circle-x.js ***!
\**************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ CircleX)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"circle\",\n {\n cx: \"12\",\n cy: \"12\",\n r: \"10\",\n key: \"1mglay\"\n }\n ],\n [\n \"path\",\n {\n d: \"m15 9-6 6\",\n key: \"1uzhvr\"\n }\n ],\n [\n \"path\",\n {\n d: \"m9 9 6 6\",\n key: \"z0biqf\"\n }\n ]\n];\nconst CircleX = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"CircleX\", __iconNode);\n //# sourceMappingURL=circle-x.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2NpcmNsZS14LmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUdPLE1BQU0sVUFBdUI7SUFDbEM7UUFBQyxRQUFVO1FBQUE7WUFBRSxFQUFJO1lBQU0sQ0FBSSxRQUFNO1lBQUEsQ0FBRztZQUFNLEdBQUs7UUFBQSxDQUFVO0tBQUE7SUFDekQ7UUFBQyxNQUFRO1FBQUE7WUFBRSxHQUFHLENBQWE7WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0lBQzFDO1FBQUMsTUFBUTtRQUFBO1lBQUUsR0FBRyxDQUFZO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtDQUMzQztBQWFNLGNBQVUsa0VBQWlCLFlBQVcsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL2NpcmNsZS14LnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcmVhdGVMdWNpZGVJY29uIGZyb20gJy4uL2NyZWF0ZUx1Y2lkZUljb24nO1xuaW1wb3J0IHsgSWNvbk5vZGUgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBfX2ljb25Ob2RlOiBJY29uTm9kZSA9IFtcbiAgWydjaXJjbGUnLCB7IGN4OiAnMTInLCBjeTogJzEyJywgcjogJzEwJywga2V5OiAnMW1nbGF5JyB9XSxcbiAgWydwYXRoJywgeyBkOiAnbTE1IDktNiA2Jywga2V5OiAnMXV6aHZyJyB9XSxcbiAgWydwYXRoJywgeyBkOiAnbTkgOSA2IDYnLCBrZXk6ICd6MGJpcWYnIH1dLFxuXTtcblxuLyoqXG4gKiBAY29tcG9uZW50IEBuYW1lIENpcmNsZVhcbiAqIEBkZXNjcmlwdGlvbiBMdWNpZGUgU1ZHIGljb24gY29tcG9uZW50LCByZW5kZXJzIFNWRyBFbGVtZW50IHdpdGggY2hpbGRyZW4uXG4gKlxuICogQHByZXZpZXcgIVtpbWddKGRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QWdlRzFzYm5NOUltaDBkSEE2THk5M2QzY3Vkek11YjNKbkx6SXdNREF2YzNabklnb2dJSGRwWkhSb1BTSXlOQ0lLSUNCb1pXbG5hSFE5SWpJMElnb2dJSFpwWlhkQ2IzZzlJakFnTUNBeU5DQXlOQ0lLSUNCbWFXeHNQU0p1YjI1bElnb2dJSE4wY205clpUMGlJekF3TUNJZ2MzUjViR1U5SW1KaFkydG5jbTkxYm1RdFkyOXNiM0k2SUNObVptWTdJR0p2Y21SbGNpMXlZV1JwZFhNNklESndlQ0lLSUNCemRISnZhMlV0ZDJsa2RHZzlJaklpQ2lBZ2MzUnliMnRsTFd4cGJtVmpZWEE5SW5KdmRXNWtJZ29nSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpY205MWJtUWlDajRLSUNBOFkybHlZMnhsSUdONFBTSXhNaUlnWTNrOUlqRXlJaUJ5UFNJeE1DSWdMejRLSUNBOGNHRjBhQ0JrUFNKdE1UVWdPUzAySURZaUlDOCtDaUFnUEhCaGRHZ2daRDBpYlRrZ09TQTJJRFlpSUM4K0Nqd3ZjM1puUGdvPSkgLSBodHRwczovL2x1Y2lkZS5kZXYvaWNvbnMvY2lyY2xlLXhcbiAqIEBzZWUgaHR0cHM6Ly9sdWNpZGUuZGV2L2d1aWRlL3BhY2thZ2VzL2x1Y2lkZS1yZWFjdCAtIERvY3VtZW50YXRpb25cbiAqXG4gKiBAcGFyYW0ge09iamVjdH0gcHJvcHMgLSBMdWNpZGUgaWNvbnMgcHJvcHMgYW5kIGFueSB2YWxpZCBTVkcgYXR0cmlidXRlXG4gKiBAcmV0dXJucyB7SlNYLkVsZW1lbnR9IEpTWCBFbGVtZW50XG4gKlxuICovXG5jb25zdCBDaXJjbGVYID0gY3JlYXRlTHVjaWRlSWNvbignQ2lyY2xlWCcsIF9faWNvbk5vZGUpO1xuXG5leHBvcnQgZGVmYXVsdCBDaXJjbGVYO1xuIl0sIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/circle-x.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/clock.js":
/*!***********************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/clock.js ***!
\***********************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ Clock)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"circle\",\n {\n cx: \"12\",\n cy: \"12\",\n r: \"10\",\n key: \"1mglay\"\n }\n ],\n [\n \"polyline\",\n {\n points: \"12 6 12 12 16 14\",\n key: \"68esgv\"\n }\n ]\n];\nconst Clock = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"Clock\", __iconNode);\n //# sourceMappingURL=clock.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2Nsb2NrLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUdPLE1BQU0sVUFBdUI7SUFDbEM7UUFBQyxRQUFVO1FBQUE7WUFBRSxFQUFJO1lBQU0sQ0FBSSxRQUFNO1lBQUEsQ0FBRztZQUFNLEdBQUs7UUFBQSxDQUFVO0tBQUE7SUFDekQ7UUFBQyxVQUFZO1FBQUE7WUFBRSxRQUFRLENBQW9CO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtDQUM1RDtBQWFNLFlBQVEsa0VBQWlCLFVBQVMsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL2Nsb2NrLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcmVhdGVMdWNpZGVJY29uIGZyb20gJy4uL2NyZWF0ZUx1Y2lkZUljb24nO1xuaW1wb3J0IHsgSWNvbk5vZGUgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBfX2ljb25Ob2RlOiBJY29uTm9kZSA9IFtcbiAgWydjaXJjbGUnLCB7IGN4OiAnMTInLCBjeTogJzEyJywgcjogJzEwJywga2V5OiAnMW1nbGF5JyB9XSxcbiAgWydwb2x5bGluZScsIHsgcG9pbnRzOiAnMTIgNiAxMiAxMiAxNiAxNCcsIGtleTogJzY4ZXNndicgfV0sXG5dO1xuXG4vKipcbiAqIEBjb21wb25lbnQgQG5hbWUgQ2xvY2tcbiAqIEBkZXNjcmlwdGlvbiBMdWNpZGUgU1ZHIGljb24gY29tcG9uZW50LCByZW5kZXJzIFNWRyBFbGVtZW50IHdpdGggY2hpbGRyZW4uXG4gKlxuICogQHByZXZpZXcgIVtpbWddKGRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QWdlRzFzYm5NOUltaDBkSEE2THk5M2QzY3Vkek11YjNKbkx6SXdNREF2YzNabklnb2dJSGRwWkhSb1BTSXlOQ0lLSUNCb1pXbG5hSFE5SWpJMElnb2dJSFpwWlhkQ2IzZzlJakFnTUNBeU5DQXlOQ0lLSUNCbWFXeHNQU0p1YjI1bElnb2dJSE4wY205clpUMGlJekF3TUNJZ2MzUjViR1U5SW1KaFkydG5jbTkxYm1RdFkyOXNiM0k2SUNObVptWTdJR0p2Y21SbGNpMXlZV1JwZFhNNklESndlQ0lLSUNCemRISnZhMlV0ZDJsa2RHZzlJaklpQ2lBZ2MzUnliMnRsTFd4cGJtVmpZWEE5SW5KdmRXNWtJZ29nSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpY205MWJtUWlDajRLSUNBOFkybHlZMnhsSUdONFBTSXhNaUlnWTNrOUlqRXlJaUJ5UFNJeE1DSWdMejRLSUNBOGNHOXNlV3hwYm1VZ2NHOXBiblJ6UFNJeE1pQTJJREV5SURFeUlERTJJREUwSWlBdlBnbzhMM04yWno0SykgLSBodHRwczovL2x1Y2lkZS5kZXYvaWNvbnMvY2xvY2tcbiAqIEBzZWUgaHR0cHM6Ly9sdWNpZGUuZGV2L2d1aWRlL3BhY2thZ2VzL2x1Y2lkZS1yZWFjdCAtIERvY3VtZW50YXRpb25cbiAqXG4gKiBAcGFyYW0ge09iamVjdH0gcHJvcHMgLSBMdWNpZGUgaWNvbnMgcHJvcHMgYW5kIGFueSB2YWxpZCBTVkcgYXR0cmlidXRlXG4gKiBAcmV0dXJucyB7SlNYLkVsZW1lbnR9IEpTWCBFbGVtZW50XG4gKlxuICovXG5jb25zdCBDbG9jayA9IGNyZWF0ZUx1Y2lkZUljb24oJ0Nsb2NrJywgX19pY29uTm9kZSk7XG5cbmV4cG9ydCBkZWZhdWx0IENsb2NrO1xuIl0sIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/clock.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/ellipsis.js":
/*!**************************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/ellipsis.js ***!
\**************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ Ellipsis)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"circle\",\n {\n cx: \"12\",\n cy: \"12\",\n r: \"1\",\n key: \"41hilf\"\n }\n ],\n [\n \"circle\",\n {\n cx: \"19\",\n cy: \"12\",\n r: \"1\",\n key: \"1wjl8i\"\n }\n ],\n [\n \"circle\",\n {\n cx: \"5\",\n cy: \"12\",\n r: \"1\",\n key: \"1pcz8c\"\n }\n ]\n];\nconst Ellipsis = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"Ellipsis\", __iconNode);\n //# sourceMappingURL=ellipsis.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2VsbGlwc2lzLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUdPLE1BQU0sVUFBdUI7SUFDbEM7UUFBQyxRQUFVO1FBQUE7WUFBRSxFQUFJO1lBQU0sQ0FBSSxRQUFNO1lBQUEsQ0FBRztZQUFLLEdBQUs7UUFBQSxDQUFVO0tBQUE7SUFDeEQ7UUFBQyxRQUFVO1FBQUE7WUFBRSxFQUFJO1lBQU0sQ0FBSSxRQUFNO1lBQUEsQ0FBRztZQUFLLEdBQUs7UUFBQSxDQUFVO0tBQUE7SUFDeEQ7UUFBQyxRQUFVO1FBQUE7WUFBRSxFQUFJO1lBQUssQ0FBSSxRQUFNO1lBQUEsQ0FBRztZQUFLLEdBQUs7UUFBQSxDQUFVO0tBQUE7Q0FDekQ7QUFhTSxlQUFXLGtFQUFpQixhQUFZLENBQVUiLCJzb3VyY2VzIjpbIi9Vc2Vycy9tYXR0YnJ1Y2UvRG9jdW1lbnRzL1Byb2plY3RzL3NyYy9pY29ucy9lbGxpcHNpcy50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgY3JlYXRlTHVjaWRlSWNvbiBmcm9tICcuLi9jcmVhdGVMdWNpZGVJY29uJztcbmltcG9ydCB7IEljb25Ob2RlIH0gZnJvbSAnLi4vdHlwZXMnO1xuXG5leHBvcnQgY29uc3QgX19pY29uTm9kZTogSWNvbk5vZGUgPSBbXG4gIFsnY2lyY2xlJywgeyBjeDogJzEyJywgY3k6ICcxMicsIHI6ICcxJywga2V5OiAnNDFoaWxmJyB9XSxcbiAgWydjaXJjbGUnLCB7IGN4OiAnMTknLCBjeTogJzEyJywgcjogJzEnLCBrZXk6ICcxd2psOGknIH1dLFxuICBbJ2NpcmNsZScsIHsgY3g6ICc1JywgY3k6ICcxMicsIHI6ICcxJywga2V5OiAnMXBjejhjJyB9XSxcbl07XG5cbi8qKlxuICogQGNvbXBvbmVudCBAbmFtZSBFbGxpcHNpc1xuICogQGRlc2NyaXB0aW9uIEx1Y2lkZSBTVkcgaWNvbiBjb21wb25lbnQsIHJlbmRlcnMgU1ZHIEVsZW1lbnQgd2l0aCBjaGlsZHJlbi5cbiAqXG4gKiBAcHJldmlldyAhW2ltZ10oZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlBZ2VHMXNibk05SW1oMGRIQTZMeTkzZDNjdWR6TXViM0puTHpJd01EQXZjM1puSWdvZ0lIZHBaSFJvUFNJeU5DSUtJQ0JvWldsbmFIUTlJakkwSWdvZ0lIWnBaWGRDYjNnOUlqQWdNQ0F5TkNBeU5DSUtJQ0JtYVd4c1BTSnViMjVsSWdvZ0lITjBjbTlyWlQwaUl6QXdNQ0lnYzNSNWJHVTlJbUpoWTJ0bmNtOTFibVF0WTI5c2IzSTZJQ05tWm1ZN0lHSnZjbVJsY2kxeVlXUnBkWE02SURKd2VDSUtJQ0J6ZEhKdmEyVXRkMmxrZEdnOUlqSWlDaUFnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lnb2dJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaUNqNEtJQ0E4WTJseVkyeGxJR040UFNJeE1pSWdZM2s5SWpFeUlpQnlQU0l4SWlBdlBnb2dJRHhqYVhKamJHVWdZM2c5SWpFNUlpQmplVDBpTVRJaUlISTlJakVpSUM4K0NpQWdQR05wY21Oc1pTQmplRDBpTlNJZ1kzazlJakV5SWlCeVBTSXhJaUF2UGdvOEwzTjJaejRLKSAtIGh0dHBzOi8vbHVjaWRlLmRldi9pY29ucy9lbGxpcHNpc1xuICogQHNlZSBodHRwczovL2x1Y2lkZS5kZXYvZ3VpZGUvcGFja2FnZXMvbHVjaWRlLXJlYWN0IC0gRG9jdW1lbnRhdGlvblxuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBwcm9wcyAtIEx1Y2lkZSBpY29ucyBwcm9wcyBhbmQgYW55IHZhbGlkIFNWRyBhdHRyaWJ1dGVcbiAqIEByZXR1cm5zIHtKU1guRWxlbWVudH0gSlNYIEVsZW1lbnRcbiAqXG4gKi9cbmNvbnN0IEVsbGlwc2lzID0gY3JlYXRlTHVjaWRlSWNvbignRWxsaXBzaXMnLCBfX2ljb25Ob2RlKTtcblxuZXhwb3J0IGRlZmF1bHQgRWxsaXBzaXM7XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/ellipsis.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/external-link.js":
/*!*******************************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/external-link.js ***!
@ -61,6 +111,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/globe.js":
/*!***********************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/globe.js ***!
\***********************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ Globe)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"circle\",\n {\n cx: \"12\",\n cy: \"12\",\n r: \"10\",\n key: \"1mglay\"\n }\n ],\n [\n \"path\",\n {\n d: \"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\",\n key: \"13o1zl\"\n }\n ],\n [\n \"path\",\n {\n d: \"M2 12h20\",\n key: \"9i4pu4\"\n }\n ]\n];\nconst Globe = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"Globe\", __iconNode);\n //# sourceMappingURL=globe.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2dsb2JlLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUdPLE1BQU0sVUFBdUI7SUFDbEM7UUFBQyxRQUFVO1FBQUE7WUFBRSxFQUFJO1lBQU0sQ0FBSSxRQUFNO1lBQUEsQ0FBRztZQUFNLEdBQUs7UUFBQSxDQUFVO0tBQUE7SUFDekQ7UUFBQyxNQUFRO1FBQUE7WUFBRSxHQUFHLENBQW1EO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtJQUNoRjtRQUFDLE1BQVE7UUFBQTtZQUFFLEdBQUcsQ0FBWTtZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7Q0FDM0M7QUFhTSxZQUFRLGtFQUFpQixVQUFTLENBQVUiLCJzb3VyY2VzIjpbIi9Vc2Vycy9tYXR0YnJ1Y2UvRG9jdW1lbnRzL1Byb2plY3RzL3NyYy9pY29ucy9nbG9iZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgY3JlYXRlTHVjaWRlSWNvbiBmcm9tICcuLi9jcmVhdGVMdWNpZGVJY29uJztcbmltcG9ydCB7IEljb25Ob2RlIH0gZnJvbSAnLi4vdHlwZXMnO1xuXG5leHBvcnQgY29uc3QgX19pY29uTm9kZTogSWNvbk5vZGUgPSBbXG4gIFsnY2lyY2xlJywgeyBjeDogJzEyJywgY3k6ICcxMicsIHI6ICcxMCcsIGtleTogJzFtZ2xheScgfV0sXG4gIFsncGF0aCcsIHsgZDogJ00xMiAyYTE0LjUgMTQuNSAwIDAgMCAwIDIwIDE0LjUgMTQuNSAwIDAgMCAwLTIwJywga2V5OiAnMTNvMXpsJyB9XSxcbiAgWydwYXRoJywgeyBkOiAnTTIgMTJoMjAnLCBrZXk6ICc5aTRwdTQnIH1dLFxuXTtcblxuLyoqXG4gKiBAY29tcG9uZW50IEBuYW1lIEdsb2JlXG4gKiBAZGVzY3JpcHRpb24gTHVjaWRlIFNWRyBpY29uIGNvbXBvbmVudCwgcmVuZGVycyBTVkcgRWxlbWVudCB3aXRoIGNoaWxkcmVuLlxuICpcbiAqIEBwcmV2aWV3ICFbaW1nXShkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUFnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JZ29nSUhkcFpIUm9QU0l5TkNJS0lDQm9aV2xuYUhROUlqSTBJZ29nSUhacFpYZENiM2c5SWpBZ01DQXlOQ0F5TkNJS0lDQm1hV3hzUFNKdWIyNWxJZ29nSUhOMGNtOXJaVDBpSXpBd01DSWdjM1I1YkdVOUltSmhZMnRuY205MWJtUXRZMjlzYjNJNklDTm1abVk3SUdKdmNtUmxjaTF5WVdScGRYTTZJREp3ZUNJS0lDQnpkSEp2YTJVdGQybGtkR2c5SWpJaUNpQWdjM1J5YjJ0bExXeHBibVZqWVhBOUluSnZkVzVrSWdvZ0lITjBjbTlyWlMxc2FXNWxhbTlwYmowaWNtOTFibVFpQ2o0S0lDQThZMmx5WTJ4bElHTjRQU0l4TWlJZ1kzazlJakV5SWlCeVBTSXhNQ0lnTHo0S0lDQThjR0YwYUNCa1BTSk5NVElnTW1FeE5DNDFJREUwTGpVZ01DQXdJREFnTUNBeU1DQXhOQzQxSURFMExqVWdNQ0F3SURBZ01DMHlNQ0lnTHo0S0lDQThjR0YwYUNCa1BTSk5NaUF4TW1neU1DSWdMejRLUEM5emRtYytDZz09KSAtIGh0dHBzOi8vbHVjaWRlLmRldi9pY29ucy9nbG9iZVxuICogQHNlZSBodHRwczovL2x1Y2lkZS5kZXYvZ3VpZGUvcGFja2FnZXMvbHVjaWRlLXJlYWN0IC0gRG9jdW1lbnRhdGlvblxuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBwcm9wcyAtIEx1Y2lkZSBpY29ucyBwcm9wcyBhbmQgYW55IHZhbGlkIFNWRyBhdHRyaWJ1dGVcbiAqIEByZXR1cm5zIHtKU1guRWxlbWVudH0gSlNYIEVsZW1lbnRcbiAqXG4gKi9cbmNvbnN0IEdsb2JlID0gY3JlYXRlTHVjaWRlSWNvbignR2xvYmUnLCBfX2ljb25Ob2RlKTtcblxuZXhwb3J0IGRlZmF1bHQgR2xvYmU7XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/globe.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/plus.js":
/*!**********************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/plus.js ***!
@ -91,6 +151,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/trending-up.js":
/*!*****************************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/icons/trending-up.js ***!
\*****************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ TrendingUp)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"polyline\",\n {\n points: \"22 7 13.5 15.5 8.5 10.5 2 17\",\n key: \"126l90\"\n }\n ],\n [\n \"polyline\",\n {\n points: \"16 7 22 7 22 13\",\n key: \"kwv8wd\"\n }\n ]\n];\nconst TrendingUp = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"TrendingUp\", __iconNode);\n //# sourceMappingURL=trending-up.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL3RyZW5kaW5nLXVwLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUdPLE1BQU0sVUFBdUI7SUFDbEM7UUFBQyxVQUFZO1FBQUE7WUFBRSxRQUFRLENBQWdDO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtJQUN0RTtRQUFDLFVBQVk7UUFBQTtZQUFFLFFBQVEsQ0FBbUI7WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0NBQzNEO0FBYU0saUJBQWEsa0VBQWlCLGVBQWMsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL3RyZW5kaW5nLXVwLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcmVhdGVMdWNpZGVJY29uIGZyb20gJy4uL2NyZWF0ZUx1Y2lkZUljb24nO1xuaW1wb3J0IHsgSWNvbk5vZGUgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBfX2ljb25Ob2RlOiBJY29uTm9kZSA9IFtcbiAgWydwb2x5bGluZScsIHsgcG9pbnRzOiAnMjIgNyAxMy41IDE1LjUgOC41IDEwLjUgMiAxNycsIGtleTogJzEyNmw5MCcgfV0sXG4gIFsncG9seWxpbmUnLCB7IHBvaW50czogJzE2IDcgMjIgNyAyMiAxMycsIGtleTogJ2t3djh3ZCcgfV0sXG5dO1xuXG4vKipcbiAqIEBjb21wb25lbnQgQG5hbWUgVHJlbmRpbmdVcFxuICogQGRlc2NyaXB0aW9uIEx1Y2lkZSBTVkcgaWNvbiBjb21wb25lbnQsIHJlbmRlcnMgU1ZHIEVsZW1lbnQgd2l0aCBjaGlsZHJlbi5cbiAqXG4gKiBAcHJldmlldyAhW2ltZ10oZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlBZ2VHMXNibk05SW1oMGRIQTZMeTkzZDNjdWR6TXViM0puTHpJd01EQXZjM1puSWdvZ0lIZHBaSFJvUFNJeU5DSUtJQ0JvWldsbmFIUTlJakkwSWdvZ0lIWnBaWGRDYjNnOUlqQWdNQ0F5TkNBeU5DSUtJQ0JtYVd4c1BTSnViMjVsSWdvZ0lITjBjbTlyWlQwaUl6QXdNQ0lnYzNSNWJHVTlJbUpoWTJ0bmNtOTFibVF0WTI5c2IzSTZJQ05tWm1ZN0lHSnZjbVJsY2kxeVlXUnBkWE02SURKd2VDSUtJQ0J6ZEhKdmEyVXRkMmxrZEdnOUlqSWlDaUFnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lnb2dJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaUNqNEtJQ0E4Y0c5c2VXeHBibVVnY0c5cGJuUnpQU0l5TWlBM0lERXpMalVnTVRVdU5TQTRMalVnTVRBdU5TQXlJREUzSWlBdlBnb2dJRHh3YjJ4NWJHbHVaU0J3YjJsdWRITTlJakUySURjZ01qSWdOeUF5TWlBeE15SWdMejRLUEM5emRtYytDZz09KSAtIGh0dHBzOi8vbHVjaWRlLmRldi9pY29ucy90cmVuZGluZy11cFxuICogQHNlZSBodHRwczovL2x1Y2lkZS5kZXYvZ3VpZGUvcGFja2FnZXMvbHVjaWRlLXJlYWN0IC0gRG9jdW1lbnRhdGlvblxuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBwcm9wcyAtIEx1Y2lkZSBpY29ucyBwcm9wcyBhbmQgYW55IHZhbGlkIFNWRyBhdHRyaWJ1dGVcbiAqIEByZXR1cm5zIHtKU1guRWxlbWVudH0gSlNYIEVsZW1lbnRcbiAqXG4gKi9cbmNvbnN0IFRyZW5kaW5nVXAgPSBjcmVhdGVMdWNpZGVJY29uKCdUcmVuZGluZ1VwJywgX19pY29uTm9kZSk7XG5cbmV4cG9ydCBkZWZhdWx0IFRyZW5kaW5nVXA7XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/trending-up.js\n");
/***/ }),
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/shared/src/utils.js":
/*!****************************************************************!*\
!*** ./node_modules/lucide-react/dist/esm/shared/src/utils.js ***!

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,25 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
exports.id = "vendor-chunks/tiny-invariant";
exports.ids = ["vendor-chunks/tiny-invariant"];
exports.modules = {
/***/ "(ssr)/./node_modules/tiny-invariant/dist/esm/tiny-invariant.js":
/*!****************************************************************!*\
!*** ./node_modules/tiny-invariant/dist/esm/tiny-invariant.js ***!
\****************************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ invariant)\n/* harmony export */ });\nvar isProduction = \"development\" === 'production';\nvar prefix = 'Invariant failed';\nfunction invariant(condition, message) {\n if (condition) {\n return;\n }\n if (isProduction) {\n throw new Error(prefix);\n }\n var provided = typeof message === 'function' ? message() : message;\n var value = provided ? \"\".concat(prefix, \": \").concat(provided) : prefix;\n throw new Error(value);\n}\n\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvdGlueS1pbnZhcmlhbnQvZGlzdC9lc20vdGlueS1pbnZhcmlhbnQuanMiLCJtYXBwaW5ncyI6Ijs7OztBQUFBLG1CQUFtQixhQUFvQjtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRWdDIiwic291cmNlcyI6WyIvVXNlcnMvbWF0dGJydWNlL0RvY3VtZW50cy9Qcm9qZWN0cy9PcGVuQ2xhdy9XZWIvaGVhcnRiZWF0LW1vbml0b3Ivbm9kZV9tb2R1bGVzL3RpbnktaW52YXJpYW50L2Rpc3QvZXNtL3RpbnktaW52YXJpYW50LmpzIl0sInNvdXJjZXNDb250ZW50IjpbInZhciBpc1Byb2R1Y3Rpb24gPSBwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gJ3Byb2R1Y3Rpb24nO1xudmFyIHByZWZpeCA9ICdJbnZhcmlhbnQgZmFpbGVkJztcbmZ1bmN0aW9uIGludmFyaWFudChjb25kaXRpb24sIG1lc3NhZ2UpIHtcbiAgICBpZiAoY29uZGl0aW9uKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGlzUHJvZHVjdGlvbikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IocHJlZml4KTtcbiAgICB9XG4gICAgdmFyIHByb3ZpZGVkID0gdHlwZW9mIG1lc3NhZ2UgPT09ICdmdW5jdGlvbicgPyBtZXNzYWdlKCkgOiBtZXNzYWdlO1xuICAgIHZhciB2YWx1ZSA9IHByb3ZpZGVkID8gXCJcIi5jb25jYXQocHJlZml4LCBcIjogXCIpLmNvbmNhdChwcm92aWRlZCkgOiBwcmVmaXg7XG4gICAgdGhyb3cgbmV3IEVycm9yKHZhbHVlKTtcbn1cblxuZXhwb3J0IHsgaW52YXJpYW50IGFzIGRlZmF1bHQgfTtcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/tiny-invariant/dist/esm/tiny-invariant.js\n");
/***/ })
};
;

File diff suppressed because one or more lines are too long

View File

@ -47,11 +47,6 @@
/******/ __webpack_require__.m = __webpack_modules__;
/******/
/************************************************************************/
/******/ /* webpack/runtime/amd options */
/******/ (() => {
/******/ __webpack_require__.amdO = {};
/******/ })();
/******/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
@ -130,7 +125,7 @@
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("1930de2390f27e98")
/******/ __webpack_require__.h = () => ("785dd2b49a5aa02a")
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */

File diff suppressed because one or more lines are too long

View File

@ -190,7 +190,7 @@
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("946779c75e05dcfd")
/******/ __webpack_require__.h = () => ("2729dbbc18021cd9")
/******/ })();
/******/
/******/ /* webpack/runtime/global */

View File

@ -1 +0,0 @@
{"c":["app/page","webpack"],"r":[],"m":["(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/circle-check.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/circle-x.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/layout-dashboard.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/server.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/settings.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/trending-up.js","(app-pages-browser)/./node_modules/recharts/es6/cartesian/Area.js","(app-pages-browser)/./node_modules/recharts/es6/cartesian/CartesianGrid.js","(app-pages-browser)/./node_modules/recharts/es6/chart/AreaChart.js"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@ self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("946779c75e05dcfd")
/******/ __webpack_require__.h = () => ("9eff81fd3e9516c2")
/******/ })();
/******/
/******/ }

View File

@ -11,7 +11,7 @@ self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("c48572fb170ff111")
/******/ __webpack_require__.h = () => ("2729dbbc18021cd9")
/******/ })();
/******/
/******/ }

View File

@ -1,18 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("591979329fc83864")
/******/ })();
/******/
/******/ }
);

View File

@ -1,18 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("1122623f302a8ef0")
/******/ })();
/******/
/******/ }
);

File diff suppressed because one or more lines are too long

View File

@ -105,6 +105,103 @@
"appId": "blog-backup",
"timestamp": "2026-02-18T19:39:48.171Z",
"status": "down"
},
{
"appId": "blog-backup",
"timestamp": "2026-02-18T19:59:22.519Z",
"status": "up",
"responseTime": 46
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T19:59:23.672Z",
"status": "up",
"responseTime": 76
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T19:59:24.380Z",
"status": "up",
"responseTime": 79
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T19:59:24.557Z",
"status": "up",
"responseTime": 46
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T19:59:24.794Z",
"status": "up",
"responseTime": 58
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T20:12:40.172Z",
"status": "down"
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T20:12:41.385Z",
"status": "down"
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T20:12:42.051Z",
"status": "down"
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T20:12:42.811Z",
"status": "down"
},
{
"appId": "blog-backup",
"timestamp": "2026-02-18T20:12:43.592Z",
"status": "down"
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T20:15:42.085Z",
"status": "up",
"responseTime": 627
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T20:15:42.415Z",
"status": "up",
"responseTime": 84
},
{
"appId": "blog-backup",
"timestamp": "2026-02-18T20:15:43.640Z",
"status": "up",
"responseTime": 209
},
{
"appId": "project-hub",
"timestamp": "2026-02-18T21:03:54.619Z",
"status": "up",
"responseTime": 355
},
{
"appId": "blog-backup",
"timestamp": "2026-02-18T21:03:55.810Z",
"status": "up",
"responseTime": 242
},
{
"appId": "heartbeat-monitor",
"timestamp": "2026-02-18T21:03:56.828Z",
"status": "up",
"responseTime": 29
},
{
"appId": "heartbeat-monitor",
"timestamp": "2026-02-18T21:03:58.010Z",
"status": "up",
"responseTime": 27
}
]
}

View File

@ -1,7 +1,20 @@
"use client";
import { useState, useEffect } from "react";
import { Activity, Plus, RefreshCw, Trash2, ExternalLink } from "lucide-react";
import {
Activity,
Plus,
RefreshCw,
Trash2,
ExternalLink,
CheckCircle2,
XCircle,
Clock,
Globe,
TrendingUp,
AlertCircle,
MoreHorizontal
} from "lucide-react";
interface App {
id: string;
@ -29,6 +42,7 @@ export default function HeartbeatMonitor() {
const [loading, setLoading] = useState(true);
const [checking, setChecking] = useState<string | null>(null);
const [showAddApp, setShowAddApp] = useState(false);
const [selectedApp, setSelectedApp] = useState<App | null>(null);
const [newApp, setNewApp] = useState<Partial<App>>({
name: "",
description: "",
@ -142,196 +156,303 @@ export default function HeartbeatMonitor() {
const uptime = appStatus.length > 0
? Math.round((appStatus.filter(s => s.status === "up").length / appStatus.length) * 100)
: 100;
const avgResponseTime = appStatus.length > 0
? Math.round(appStatus.filter(s => s.responseTime).reduce((acc, s) => acc + (s.responseTime || 0), 0) / appStatus.filter(s => s.responseTime).length)
: 0;
return { latest, isUp, uptime };
return { latest, isUp, uptime, avgResponseTime, history: appStatus.slice(-24) };
}
const totalApps = apps.length;
const onlineApps = apps.filter((app) => getAppStatus(app.id).isUp).length;
const offlineApps = totalApps - onlineApps;
const allUp = onlineApps === totalApps && totalApps > 0;
// Generate sparkline data
function Sparkline({ data, isUp }: { data: StatusEntry[], isUp: boolean }) {
if (data.length === 0) return <div className="h-8 w-24 bg-gray-100 rounded" />;
const bars = data.slice(-12).map((entry, i) => (
<div
key={i}
className={`w-1.5 rounded-sm ${entry.status === "up" ? "bg-emerald-500" : "bg-red-500"}`}
style={{ height: `${Math.max(20, Math.min(100, (entry.responseTime || 100) / 5))}%` }}
/>
));
return (
<div className="h-8 w-24 flex items-end gap-0.5">
{bars}
</div>
);
}
if (loading) {
return (
<div className="min-h-screen bg-slate-950 flex items-center justify-center" role="status" aria-live="polite">
<div className="text-slate-400">Loading monitor...</div>
<div className="min-h-screen bg-gray-50 flex items-center justify-center" role="status" aria-live="polite">
<div className="flex items-center gap-3 text-gray-500">
<RefreshCw className="w-5 h-5 animate-spin" />
<span>Loading dashboard...</span>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-slate-950 text-slate-100">
{/* Skip Link for Accessibility */}
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-emerald-500 focus:text-white focus:px-4 focus:py-2 focus:rounded-lg"
>
Skip to main content
</a>
{/* Header */}
<header className="border-b border-slate-800 bg-slate-900">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="min-h-screen bg-gray-50">
{/* Top Navigation */}
<nav className="bg-white border-b border-gray-200 sticky top-0 z-40">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center gap-3">
<Activity className="w-7 h-7 text-emerald-500" aria-hidden="true" />
<div className="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center shadow-lg shadow-blue-600/20">
<Activity className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-xl font-semibold text-white">Heartbeat Monitor</h1>
<p className="text-sm text-slate-400" aria-live="polite">
{onlineApps} of {totalApps} services online
</p>
<h1 className="text-xl font-bold text-gray-900">Heartbeat Monitor</h1>
<p className="text-sm text-gray-500">Local Infrastructure Monitoring</p>
</div>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-3">
<button
onClick={fetchData}
className="inline-flex items-center justify-center p-2.5 bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-900 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
aria-label="Refresh data"
>
<RefreshCw className="w-5 h-5" aria-hidden="true" />
<RefreshCw className="w-4 h-4" />
Refresh
</button>
<button
onClick={() => setShowAddApp(true)}
className="inline-flex items-center gap-2 bg-emerald-600 hover:bg-emerald-500 text-white px-4 py-2.5 rounded-lg font-medium transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
<Plus className="w-4 h-4" aria-hidden="true" />
Add App
<Plus className="w-4 h-4" />
Add Monitor
</button>
</div>
</div>
</div>
</header>
</nav>
{/* Main Content */}
<main id="main-content" className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Table */}
<div className="bg-slate-900 rounded-lg border border-slate-800 overflow-hidden">
<table className="w-full text-left">
<thead>
<tr className="bg-slate-800 border-b border-slate-700">
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Status</th>
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Name</th>
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">URL</th>
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Port</th>
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Uptime</th>
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">Response</th>
<th scope="col" className="px-4 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider">
<span className="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
{apps.map((app) => {
const { isUp, uptime, latest } = getAppStatus(app.id);
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Status Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-2xl p-6 shadow-sm border border-gray-200">
<div className="flex items-center justify-between mb-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${allUp ? 'bg-emerald-100' : 'bg-red-100'}`}>
{allUp ? (
<CheckCircle2 className="w-6 h-6 text-emerald-600" />
) : (
<XCircle className="w-6 h-6 text-red-600" />
)}
</div>
<span className="text-2xl font-bold text-gray-900">{onlineApps}/{totalApps}</span>
</div>
<p className="text-sm font-medium text-gray-600">Services Online</p>
<p className="text-xs text-gray-400 mt-1">
{allUp ? 'All systems operational' : `${offlineApps} service${offlineApps > 1 ? 's' : ''} down`}
</p>
</div>
return (
<tr key={app.id} className="hover:bg-slate-800/50 transition-colors">
<td className="px-4 py-4">
<span
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${
isUp
? "bg-emerald-500/10 text-emerald-400 border border-emerald-500/20"
: "bg-red-500/10 text-red-400 border border-red-500/20"
}`}
>
<span className={`w-1.5 h-1.5 rounded-full ${isUp ? "bg-emerald-400" : "bg-red-400"}`} aria-hidden="true" />
{isUp ? "Online" : "Offline"}
</span>
</td>
<td className="px-4 py-4">
<div className="bg-white rounded-2xl p-6 shadow-sm border border-gray-200">
<div className="flex items-center justify-between mb-4">
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center">
<TrendingUp className="w-6 h-6 text-blue-600" />
</div>
<span className="text-2xl font-bold text-gray-900">
{totalApps > 0 ? Math.round(apps.reduce((acc, app) => acc + getAppStatus(app.id).uptime, 0) / totalApps) : 0}%
</span>
</div>
<p className="text-sm font-medium text-gray-600">Average Uptime</p>
<p className="text-xs text-gray-400 mt-1">Last 24 hours</p>
</div>
<div className="bg-white rounded-2xl p-6 shadow-sm border border-gray-200">
<div className="flex items-center justify-between mb-4">
<div className="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center">
<Clock className="w-6 h-6 text-purple-600" />
</div>
<span className="text-2xl font-bold text-gray-900">
{totalApps > 0 ? Math.round(apps.reduce((acc, app) => acc + (getAppStatus(app.id).avgResponseTime || 0), 0) / totalApps) : 0}ms
</span>
</div>
<p className="text-sm font-medium text-gray-600">Avg Response</p>
<p className="text-xs text-gray-400 mt-1">Across all monitors</p>
</div>
<div className="bg-white rounded-2xl p-6 shadow-sm border border-gray-200">
<div className="flex items-center justify-between mb-4">
<div className="w-12 h-12 bg-amber-100 rounded-xl flex items-center justify-center">
<AlertCircle className="w-6 h-6 text-amber-600" />
</div>
<span className="text-2xl font-bold text-gray-900">
{status.filter(s => s.status === "down").length}
</span>
</div>
<p className="text-sm font-medium text-gray-600">Incidents</p>
<p className="text-xs text-gray-400 mt-1">Total recorded</p>
</div>
</div>
{/* Monitors Section */}
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<div className="flex items-center gap-3">
<Globe className="w-5 h-5 text-gray-400" />
<h2 className="text-lg font-semibold text-gray-900">Monitored Services</h2>
</div>
<span className="text-sm text-gray-500">{totalApps} active monitor{totalApps !== 1 ? 's' : ''}</span>
</div>
<div className="divide-y divide-gray-100">
{apps.map((app) => {
const { isUp, uptime, avgResponseTime, history, latest } = getAppStatus(app.id);
return (
<div
key={app.id}
className="px-6 py-5 hover:bg-gray-50 transition-colors group"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
{/* Status Indicator */}
<div className={`w-3 h-3 rounded-full ${isUp ? 'bg-emerald-500 shadow-lg shadow-emerald-500/30' : 'bg-red-500 shadow-lg shadow-red-500/30'}`} />
{/* App Info */}
<div>
<p className="font-medium text-white">{app.name}</p>
{app.description && (
<p className="text-sm text-slate-500">{app.description}</p>
)}
<h3 className="text-base font-semibold text-gray-900">{app.name}</h3>
<div className="flex items-center gap-3 mt-1">
<a
href={app.url}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-blue-600 hover:text-blue-700 hover:underline"
>
{app.url}
</a>
<span className="text-gray-300">|</span>
<span className="text-sm text-gray-500">Port {app.port}</span>
</div>
</div>
</td>
<td className="px-4 py-4">
<a
href={app.url}
target="_blank"
rel="noopener noreferrer"
className="text-emerald-400 hover:text-emerald-300 hover:underline text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 rounded"
>
{app.url}
</a>
</td>
<td className="px-4 py-4">
<code className="text-sm text-slate-300 bg-slate-800 px-2 py-1 rounded">{app.port}</code>
</td>
<td className="px-4 py-4">
<span className={`text-sm font-medium ${uptime >= 90 ? "text-emerald-400" : uptime >= 50 ? "text-yellow-400" : "text-red-400"}`}>
{uptime}%
</span>
</td>
<td className="px-4 py-4">
<span className="text-sm text-slate-300">
{latest?.responseTime ? `${latest.responseTime}ms` : "—"}
</span>
</td>
<td className="px-4 py-4">
<div className="flex items-center gap-1">
</div>
{/* Stats */}
<div className="flex items-center gap-8">
{/* Uptime */}
<div className="text-right">
<p className="text-2xl font-bold text-gray-900">{uptime}%</p>
<p className="text-xs text-gray-500">Uptime</p>
</div>
{/* Response Time */}
<div className="text-right">
<p className="text-2xl font-bold text-gray-900">
{avgResponseTime > 0 ? `${avgResponseTime}ms` : '—'}
</p>
<p className="text-xs text-gray-500">Response</p>
</div>
{/* Sparkline */}
<div className="hidden sm:block">
<Sparkline data={history} isUp={isUp} />
</div>
{/* Actions */}
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => checkApp(app)}
disabled={checking === app.id}
className="inline-flex items-center justify-center p-2 text-slate-400 hover:text-white hover:bg-slate-700 rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 disabled:opacity-50 disabled:cursor-not-allowed"
aria-label={`Check ${app.name} status`}
className="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
aria-label={`Check ${app.name}`}
title="Check now"
>
<RefreshCw className={`w-4 h-4 ${checking === app.id ? "animate-spin" : ""}`} aria-hidden="true" />
<RefreshCw className={`w-4 h-4 ${checking === app.id ? 'animate-spin' : ''}`} />
</button>
<a
href={app.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center p-2 text-slate-400 hover:text-white hover:bg-slate-700 rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
className="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
aria-label={`Open ${app.name}`}
title="Open app"
>
<ExternalLink className="w-4 h-4" aria-hidden="true" />
<ExternalLink className="w-4 h-4" />
</a>
<button
onClick={() => deleteApp(app.id)}
className="inline-flex items-center justify-center p-2 text-slate-400 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
aria-label={`Delete ${app.name}`}
onClick={() => setSelectedApp(app)}
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
aria-label={`Details for ${app.name}`}
title="View details"
>
<Trash2 className="w-4 h-4" aria-hidden="true" />
<MoreHorizontal className="w-4 h-4" />
</button>
<button
onClick={() => deleteApp(app.id)}
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
aria-label={`Delete ${app.name}`}
title="Delete monitor"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
{/* Last Checked */}
<div className="mt-3 flex items-center gap-4 text-xs text-gray-400">
<span>Last checked: {latest ? new Date(latest.timestamp).toLocaleTimeString() : 'Never'}</span>
{latest?.responseTime && (
<span>Response: {latest.responseTime}ms</span>
)}
</div>
</div>
);
})}
</div>
{apps.length === 0 && (
<div className="px-6 py-12 text-center">
<div className="w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Activity className="w-8 h-8 text-gray-400" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">No monitors yet</h3>
<p className="text-gray-500 mb-4">Add your first service to start monitoring</p>
<button
onClick={() => setShowAddApp(true)}
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors"
>
<Plus className="w-4 h-4" />
Add Monitor
</button>
</div>
)}
</div>
</main>
{/* Add App Modal */}
{showAddApp && (
<div
className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4"
role="dialog"
aria-modal="true"
aria-labelledby="add-app-title"
>
<div className="bg-slate-900 rounded-lg border border-slate-800 p-6 w-full max-w-md shadow-2xl">
<h2 id="add-app-title" className="text-xl font-semibold text-white mb-6">Add New App</h2>
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl p-6 w-full max-w-md shadow-2xl">
<h2 className="text-xl font-bold text-gray-900 mb-6">Add New Monitor</h2>
<form onSubmit={addApp} className="space-y-5">
<div>
<label htmlFor="app-name" className="block text-sm font-medium text-slate-400 mb-2">
Name <span className="text-red-400">*</span>
<label htmlFor="app-name" className="block text-sm font-medium text-gray-700 mb-2">
Monitor Name <span className="text-red-500">*</span>
</label>
<input
id="app-name"
type="text"
value={newApp.name}
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all"
placeholder="My Application"
required
autoComplete="off"
/>
</div>
<div>
<label htmlFor="app-desc" className="block text-sm font-medium text-slate-400 mb-2">
<label htmlFor="app-desc" className="block text-sm font-medium text-gray-700 mb-2">
Description
</label>
<input
@ -339,37 +460,35 @@ export default function HeartbeatMonitor() {
type="text"
value={newApp.description}
onChange={(e) => setNewApp({ ...newApp, description: e.target.value })}
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
placeholder="Brief description…"
autoComplete="off"
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all"
placeholder="Brief description..."
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label htmlFor="app-url" className="block text-sm font-medium text-slate-400 mb-2">
URL <span className="text-red-400">*</span>
<label htmlFor="app-url" className="block text-sm font-medium text-gray-700 mb-2">
URL <span className="text-red-500">*</span>
</label>
<input
id="app-url"
type="url"
value={newApp.url}
onChange={(e) => setNewApp({ ...newApp, url: e.target.value })}
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all"
placeholder="http://localhost:3000"
required
autoComplete="off"
/>
</div>
<div>
<label htmlFor="app-port" className="block text-sm font-medium text-slate-400 mb-2">
Port <span className="text-red-400">*</span>
<label htmlFor="app-port" className="block text-sm font-medium text-gray-700 mb-2">
Port <span className="text-red-500">*</span>
</label>
<input
id="app-port"
type="number"
value={newApp.port}
onChange={(e) => setNewApp({ ...newApp, port: parseInt(e.target.value) })}
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all"
required
min="1"
max="65535"
@ -380,15 +499,15 @@ export default function HeartbeatMonitor() {
<button
type="button"
onClick={() => setShowAddApp(false)}
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 py-2.5 rounded-lg font-medium transition-colors focus-visible:ring-2 focus-visible:ring-slate-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
className="flex-1 px-4 py-2.5 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors"
>
Cancel
</button>
<button
type="submit"
className="flex-1 bg-emerald-600 hover:bg-emerald-500 text-white py-2.5 rounded-lg font-medium transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
className="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-sm"
>
Add App
Add Monitor
</button>
</div>
</form>