From 14f43a4ae9e06a66eb58d1d7eabe1faf5a40022a Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Mon, 25 May 2026 22:26:53 -0400 Subject: [PATCH] fix: send URL-encoded form data instead of multipart, show errors inline, default empty date to today --- src/Roux/Html.hs | 16 +++++++++++----- src/Roux/Server.hs | 37 ++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/Roux/Html.hs b/src/Roux/Html.hs index c244d78..807ff76 100644 --- a/src/Roux/Html.hs +++ b/src/Roux/Html.hs @@ -658,11 +658,13 @@ cookLogJs = , "var form = document.getElementById('roux-record-form');" , "var dateInput = document.getElementById('roux-record-date');" , "var commentInput = document.getElementById('roux-record-comment');" + , "var errorEl = document.getElementById('roux-record-error');" , "if (!btn || !modal || !form) return;" , "" , "function showModal() {" , " dateInput.value = new Date().toISOString().slice(0,10);" , " commentInput.value = '';" + , " errorEl.style.display = 'none';" , " modal.style.display = 'block';" , " dateInput.focus();" , "}" @@ -681,20 +683,23 @@ cookLogJs = , "" , "form.addEventListener('submit', async function(e) {" , " e.preventDefault();" + , " var date = dateInput.value;" + , " var comment = commentInput.value;" , " var filename = btn.dataset.filename;" - , " var fd = new FormData(form);" + , " var body = 'cooked-date=' + encodeURIComponent(date);" + , " if (comment) body += '&comment=' + encodeURIComponent(comment);" , " var res = await fetch('/cook-log/' + encodeURIComponent(filename), {" , " method: 'POST'," - , " body: fd" + , " headers: { 'Content-Type': 'application/x-www-form-urlencoded' }," + , " body: body" , " });" , " if (!res.ok) {" , " var err = await res.json();" - , " alert(err.error || 'Failed to log cooking');" + , " errorEl.textContent = err.error || 'Failed to log cooking';" + , " errorEl.style.display = 'block';" , " return;" , " }" , " hideModal();" - , " var date = fd.get('cooked-date');" - , " var comment = fd.get('comment');" , " var entryDiv = document.createElement('div');" , " entryDiv.style.margin = '0.3rem 0';" , " entryDiv.style.fontSize = '0.9rem';" @@ -983,6 +988,7 @@ renderRecipe filename recipe entries = do H.input ! A.type_ "date" ! A.id "roux-record-date" ! A.name "cooked-date" ! A.required "" H.label ! A.for "roux-record-comment" $ "Comment (optional)" H.textarea ! A.id "roux-record-comment" ! A.name "comment" ! A.placeholder "How did it go?" ! A.maxlength "500" $ "" + H.p ! A.id "roux-record-error" ! A.style "color: var(--roux-accent); font-size: 0.8rem; margin: 0 0 0.5rem; display: none;" $ "" H.div ! A.class_ "roux-modal-actions" $ do H.button ! A.type_ "button" ! A.class_ "roux-modal-cancel" ! A.id "roux-modal-cancel" $ "Cancel" H.button ! A.type_ "submit" ! A.class_ "roux-modal-confirm" $ "Log it" diff --git a/src/Roux/Server.hs b/src/Roux/Server.hs index 6ca2f1e..bf7e12b 100644 --- a/src/Roux/Server.hs +++ b/src/Roux/Server.hs @@ -26,7 +26,6 @@ import qualified Data.Map.Strict as Map import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) -import Data.Time.Clock (diffUTCTime, getCurrentTime) import qualified Network.HTTP.Types as HTTP import qualified Network.Wai as Wai @@ -48,6 +47,7 @@ import qualified Data.ByteString as BS import Data.Maybe (fromMaybe, mapMaybe) import qualified Data.Text.Encoding as TE import Data.Time.Calendar (Day, fromGregorianValid) +import Data.Time.Clock (diffUTCTime, getCurrentTime, utctDay) import qualified Data.Time.Format as Time import qualified Database.SQLite.Simple as SQL import Roux.Config (AnthropicConfig (..), RouxConfig (..)) @@ -672,22 +672,29 @@ handleCookLogPost db filename request respond = do let params = parseFormBody body dateStr = fromMaybe "" (lookup "cooked-date" params) comment = lookup "comment" params + let doInsert d = do + _ <- CookLog.insertEntry db filename d comment + respond $ + Wai.responseLBS + HTTP.status200 + [("Content-Type", "application/json")] + (LB.fromStrict (encodeUtf8 "{\"ok\":true}")) case parseDate dateStr of - Just day -> do - _ <- CookLog.insertEntry db filename day comment - let resp = - Wai.responseLBS - HTTP.status200 - [("Content-Type", "application/json")] - (LB.fromStrict (encodeUtf8 "{\"ok\":true}")) - respond resp + Just d -> doInsert d Nothing -> - let resp = - Wai.responseLBS - HTTP.status400 - [("Content-Type", "application/json")] - (LB.fromStrict (encodeUtf8 "{\"error\":\"Invalid date format\"}")) - in respond resp + if T.null dateStr + then getCurrentDay >>= doInsert + else + let resp = + Wai.responseLBS + HTTP.status400 + [("Content-Type", "application/json")] + (LB.fromStrict (encodeUtf8 "{\"error\":\"Invalid date format\"}")) + in respond resp + +-- | Get today's date. +getCurrentDay :: IO Day +getCurrentDay = utctDay <$> getCurrentTime -- | Parse a YYYY-MM-DD date string. parseDate :: Text -> Maybe Day