fix: send URL-encoded form data instead of multipart, show errors inline, default empty date to today
Build and Deploy / build-and-deploy (push) Successful in 15m46s

This commit is contained in:
2026-05-25 22:26:53 -04:00
parent 930fba369c
commit 14f43a4ae9
2 changed files with 33 additions and 20 deletions
+11 -5
View File
@@ -658,11 +658,13 @@ cookLogJs =
, "var form = document.getElementById('roux-record-form');" , "var form = document.getElementById('roux-record-form');"
, "var dateInput = document.getElementById('roux-record-date');" , "var dateInput = document.getElementById('roux-record-date');"
, "var commentInput = document.getElementById('roux-record-comment');" , "var commentInput = document.getElementById('roux-record-comment');"
, "var errorEl = document.getElementById('roux-record-error');"
, "if (!btn || !modal || !form) return;" , "if (!btn || !modal || !form) return;"
, "" , ""
, "function showModal() {" , "function showModal() {"
, " dateInput.value = new Date().toISOString().slice(0,10);" , " dateInput.value = new Date().toISOString().slice(0,10);"
, " commentInput.value = '';" , " commentInput.value = '';"
, " errorEl.style.display = 'none';"
, " modal.style.display = 'block';" , " modal.style.display = 'block';"
, " dateInput.focus();" , " dateInput.focus();"
, "}" , "}"
@@ -681,20 +683,23 @@ cookLogJs =
, "" , ""
, "form.addEventListener('submit', async function(e) {" , "form.addEventListener('submit', async function(e) {"
, " e.preventDefault();" , " e.preventDefault();"
, " var date = dateInput.value;"
, " var comment = commentInput.value;"
, " var filename = btn.dataset.filename;" , " 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), {" , " var res = await fetch('/cook-log/' + encodeURIComponent(filename), {"
, " method: 'POST'," , " method: 'POST',"
, " body: fd" , " headers: { 'Content-Type': 'application/x-www-form-urlencoded' },"
, " body: body"
, " });" , " });"
, " if (!res.ok) {" , " if (!res.ok) {"
, " var err = await res.json();" , " 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;" , " return;"
, " }" , " }"
, " hideModal();" , " hideModal();"
, " var date = fd.get('cooked-date');"
, " var comment = fd.get('comment');"
, " var entryDiv = document.createElement('div');" , " var entryDiv = document.createElement('div');"
, " entryDiv.style.margin = '0.3rem 0';" , " entryDiv.style.margin = '0.3rem 0';"
, " entryDiv.style.fontSize = '0.9rem';" , " 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.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.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.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.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_ "button" ! A.class_ "roux-modal-cancel" ! A.id "roux-modal-cancel" $ "Cancel"
H.button ! A.type_ "submit" ! A.class_ "roux-modal-confirm" $ "Log it" H.button ! A.type_ "submit" ! A.class_ "roux-modal-confirm" $ "Log it"
+13 -6
View File
@@ -26,7 +26,6 @@ import qualified Data.Map.Strict as Map
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (encodeUtf8)
import Data.Time.Clock (diffUTCTime, getCurrentTime)
import qualified Network.HTTP.Types as HTTP import qualified Network.HTTP.Types as HTTP
import qualified Network.Wai as Wai import qualified Network.Wai as Wai
@@ -48,6 +47,7 @@ import qualified Data.ByteString as BS
import Data.Maybe (fromMaybe, mapMaybe) import Data.Maybe (fromMaybe, mapMaybe)
import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding as TE
import Data.Time.Calendar (Day, fromGregorianValid) import Data.Time.Calendar (Day, fromGregorianValid)
import Data.Time.Clock (diffUTCTime, getCurrentTime, utctDay)
import qualified Data.Time.Format as Time import qualified Data.Time.Format as Time
import qualified Database.SQLite.Simple as SQL import qualified Database.SQLite.Simple as SQL
import Roux.Config (AnthropicConfig (..), RouxConfig (..)) import Roux.Config (AnthropicConfig (..), RouxConfig (..))
@@ -672,16 +672,19 @@ handleCookLogPost db filename request respond = do
let params = parseFormBody body let params = parseFormBody body
dateStr = fromMaybe "" (lookup "cooked-date" params) dateStr = fromMaybe "" (lookup "cooked-date" params)
comment = lookup "comment" params comment = lookup "comment" params
case parseDate dateStr of let doInsert d = do
Just day -> do _ <- CookLog.insertEntry db filename d comment
_ <- CookLog.insertEntry db filename day comment respond $
let resp =
Wai.responseLBS Wai.responseLBS
HTTP.status200 HTTP.status200
[("Content-Type", "application/json")] [("Content-Type", "application/json")]
(LB.fromStrict (encodeUtf8 "{\"ok\":true}")) (LB.fromStrict (encodeUtf8 "{\"ok\":true}"))
respond resp case parseDate dateStr of
Just d -> doInsert d
Nothing -> Nothing ->
if T.null dateStr
then getCurrentDay >>= doInsert
else
let resp = let resp =
Wai.responseLBS Wai.responseLBS
HTTP.status400 HTTP.status400
@@ -689,6 +692,10 @@ handleCookLogPost db filename request respond = do
(LB.fromStrict (encodeUtf8 "{\"error\":\"Invalid date format\"}")) (LB.fromStrict (encodeUtf8 "{\"error\":\"Invalid date format\"}"))
in respond resp in respond resp
-- | Get today's date.
getCurrentDay :: IO Day
getCurrentDay = utctDay <$> getCurrentTime
-- | Parse a YYYY-MM-DD date string. -- | Parse a YYYY-MM-DD date string.
parseDate :: Text -> Maybe Day parseDate :: Text -> Maybe Day
parseDate t = case T.splitOn "-" t of parseDate t = case T.splitOn "-" t of