From 4c2c452b91ae7c34e900d3ed4c9371c1e976138b Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Wed, 6 Aug 2025 00:53:35 +0300 Subject: [PATCH] Improve validation and error handling on form field --- client/src/Components/Inputs/Select/index.jsx | 3 +++ client/src/Pages/Uptime/Create/index.jsx | 10 +++++++++- client/src/Validation/validation.js | 15 +++++++++++---- server/src/config/services.js | 2 ++ server/src/controllers/monitorController.js | 3 +-- server/src/routes/monitorRoute.js | 4 +--- server/src/service/business/monitorService.js | 7 ++++++- .../src/service/infrastructure/networkService.js | 2 +- server/src/validation/joi.js | 2 +- 9 files changed, 35 insertions(+), 13 deletions(-) diff --git a/client/src/Components/Inputs/Select/index.jsx b/client/src/Components/Inputs/Select/index.jsx index a438b6bb2..70e6bfca5 100644 --- a/client/src/Components/Inputs/Select/index.jsx +++ b/client/src/Components/Inputs/Select/index.jsx @@ -50,6 +50,7 @@ const Select = ({ onChange, onBlur, sx, + error = false, name = "", labelControlSpacing = 6, maxWidth, @@ -93,6 +94,7 @@ const Select = ({ onChange={onChange} onBlur={onBlur} displayEmpty + error={error} name={name} inputProps={{ id: id }} IconComponent={KeyboardArrowDownIcon} @@ -172,6 +174,7 @@ Select.propTypes = { label: PropTypes.string, placeholder: PropTypes.string, isHidden: PropTypes.bool, + error: PropTypes.bool, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]) .isRequired, items: PropTypes.arrayOf( diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx index 1eea0c783..f821ea9e4 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -270,6 +270,11 @@ const UptimeCreate = ({ isClone = false }) => { setMonitor((prev) => ({ ...prev, [name]: value })); + if (name === "type") { + setErrors({}); + return; + } + const { error } = monitorValidation.validate( { type: monitor.type, [name]: value }, { abortEarly: false } @@ -277,7 +282,9 @@ const UptimeCreate = ({ isClone = false }) => { setErrors((prev) => ({ ...prev, - ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), + ...(error && error.details[0].path[0] === name + ? { [name]: error.details[0].message } + : { [name]: undefined }), })); }; @@ -593,6 +600,7 @@ const UptimeCreate = ({ isClone = false }) => { placeholder={t("chooseGame")} onChange={onChange} items={GAMELIST} + error={errors["gameId"] ? true : false} /> )} { return res.success({ msg: "OK", - data: games, + data: this.monitorService.getAllGames(), }); }, SERVICE_NAME, diff --git a/server/src/routes/monitorRoute.js b/server/src/routes/monitorRoute.js index b31790991..043d361d1 100755 --- a/server/src/routes/monitorRoute.js +++ b/server/src/routes/monitorRoute.js @@ -25,9 +25,6 @@ class MonitorRoutes { // Hardware routes this.router.get("/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById); - // Game routes - this.router.get("/games", this.monitorController.getAllGames); - // General monitor routes this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor); this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); @@ -47,6 +44,7 @@ class MonitorRoutes { this.router.get("/export", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToCSV); this.router.post("/bulk", isAllowed(["admin", "superadmin"]), upload.single("csvFile"), this.monitorController.createBulkMonitors); this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail); + this.router.get("/games", this.monitorController.getAllGames); // Individual monitor CRUD routes this.router.get("/:monitorId", this.monitorController.getMonitorById); diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index 2dd9e9b4f..637870b71 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -4,7 +4,7 @@ const SERVICE_NAME = "MonitorService"; class MonitorService { static SERVICE_NAME = SERVICE_NAME; - constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService }) { + constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService, games }) { this.db = db; this.settingsService = settingsService; this.jobQueue = jobQueue; @@ -13,6 +13,7 @@ class MonitorService { this.papaparse = papaparse; this.logger = logger; this.errorService = errorService; + this.games = games; } get serviceName() { @@ -262,6 +263,10 @@ class MonitorService { const csv = this.papaparse.unparse(csvData); return csv; }; + + getAllGames = () => { + return this.games; + }; } export default MonitorService; diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js index bad4f5899..4a3b79ed3 100755 --- a/server/src/service/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -503,7 +503,7 @@ class NetworkService { host: url, port: port, }).catch((error) => { - return false; + null; }); if (!state) { diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index d3fa25fb5..31f8e97c0 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -171,7 +171,7 @@ const createMonitorBodyValidation = joi.object({ jsonPath: joi.string().allow(""), expectedValue: joi.string().allow(""), matchMethod: joi.string(), - gameId: joi.string(), + gameId: joi.string().allow(""), }); const createMonitorsBodyValidation = joi.array().items(