moving to using htmx and the lisp server on camp forms

This commit is contained in:
Chris Cochrun 2024-04-24 08:22:53 -05:00
parent 6034b7764a
commit 22e5161b4c
5 changed files with 298 additions and 7 deletions

View file

@ -3,6 +3,9 @@
:CATEGORY: dev :CATEGORY: dev
:END: :END:
* TODO Move to the lisp server and htmx
SCHEDULED: <2024-04-24 Wed>
I want to move all validation and form processing to the lisp server and htmx. This way I'm not doing javascript which is sometimes complicated since I use it so infrequently, however, this will likely be a bit more involved, so I'll need to learn htmx, but I think it'll be better in the end, since htmx more akin to what the web was supposed to be like.
* DONE Fix bug in accepting multipart/form-data on lisp server * DONE Fix bug in accepting multipart/form-data on lisp server
The multiline text options do not grab the entire entry. I'll need to fix that by ensuring that the entire section is grabbed for every entry. The multiline text options do not grab the entire entry. I'll need to fix that by ensuring that the entire section is grabbed for every entry.
* DONE Make single newline show up * DONE Make single newline show up

View file

@ -4726,6 +4726,66 @@ pre {
color: rgba(var(--color-neutral-500), var(--tw-text-opacity)); color: rgba(var(--color-neutral-500), var(--tw-text-opacity));
} }
.invalid\:border-secondary-100:invalid {
--tw-border-opacity: 1;
border-color: rgba(var(--color-secondary-100), var(--tw-border-opacity));
}
.invalid\:text-secondary-600:invalid {
--tw-text-opacity: 1;
color: rgba(var(--color-secondary-600), var(--tw-text-opacity));
}
.invalid\:text-primary-600:invalid {
--tw-text-opacity: 1;
color: rgba(var(--color-primary-600), var(--tw-text-opacity));
}
.invalid\:text-secondary-100:invalid {
--tw-text-opacity: 1;
color: rgba(var(--color-secondary-100), var(--tw-text-opacity));
}
.invalid\:text-secondary-900:invalid {
--tw-text-opacity: 1;
color: rgba(var(--color-secondary-900), var(--tw-text-opacity));
}
.invalid\:text-\[\#660000\]:invalid {
--tw-text-opacity: 1;
color: rgb(102 0 0 / var(--tw-text-opacity));
}
.invalid\:text-\[\#990000\]:invalid {
--tw-text-opacity: 1;
color: rgb(153 0 0 / var(--tw-text-opacity));
}
.invalid\:text-\[\#FF0000\]:invalid {
--tw-text-opacity: 1;
color: rgb(255 0 0 / var(--tw-text-opacity));
}
.invalid\:text-\[\#F39\]:invalid {
--tw-text-opacity: 1;
color: rgb(255 51 153 / var(--tw-text-opacity));
}
.invalid\:ring-secondary-100:invalid {
--tw-ring-opacity: 1;
--tw-ring-color: rgba(var(--color-secondary-100), var(--tw-ring-opacity));
}
.invalid\:ring-secondary-900:invalid {
--tw-ring-opacity: 1;
--tw-ring-color: rgba(var(--color-secondary-900), var(--tw-ring-opacity));
}
.invalid\:ring-\[\#f39\]:invalid {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(255 51 153 / var(--tw-ring-opacity));
}
.hover\:border-transparent:hover { .hover\:border-transparent:hover {
border-color: transparent; border-color: transparent;
} }
@ -4913,6 +4973,16 @@ pre {
--tw-ring-offset-color: transparent; --tw-ring-offset-color: transparent;
} }
.focus\:invalid\:border-\[\#f39\]:invalid:focus {
--tw-border-opacity: 1;
border-color: rgb(255 51 153 / var(--tw-border-opacity));
}
.focus\:invalid\:ring-\[\#f39\]:invalid:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(255 51 153 / var(--tw-ring-opacity));
}
.group:hover .group-hover\:text-primary-600 { .group:hover .group-hover\:text-primary-600 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgba(var(--color-primary-600), var(--tw-text-opacity)); color: rgba(var(--color-primary-600), var(--tw-text-opacity));

View file

@ -1,6 +1,8 @@
{{ $formClasses := "bg-neutral-500 text-neutral-50 placeholder-neutral-300 focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 focus:ring-offset-transparent m-2 p-3 rounded-lg hover:bg-neutral-500 checked:text-neutral-500" }} {{ $formClasses := "bg-neutral-500 text-neutral-50 placeholder-neutral-300 focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 focus:ring-offset-transparent m-2 p-3 rounded-lg hover:bg-neutral-500 checked:text-neutral-500" }}
{{ $requiredField := "<span class='inline-block text-[#f39] text-sm align-super'>* required</span>" }} {{ $requiredField := "<span class='inline-block text-[#f39] text-sm align-super'>* required</span>" }}
<script src="https://unpkg.com/htmx.org@1.9.12/dist/htmx.js" crossorigin="anonymous"></script>
<script> <script>
function submitForm(e) { function submitForm(e) {
e.preventDefault(); e.preventDefault();
@ -73,8 +75,8 @@
document.getElementById('warning-agreement').style.margin = '0'; document.getElementById('warning-agreement').style.margin = '0';
} }
/* let base = "http://localhost:4242/camp-form"; */ let base = "http://localhost:4242/camp-form";
let base = "https://api.tfcconnection.org/camp-form"; /* let base = "https://api.tfcconnection.org/camp-form"; */
fetch(base, { fetch(base, {
method: "POST", method: "POST",
@ -125,7 +127,7 @@
</script> </script>
<div id="mt-form" class="form text-lg w-full"> <div id="mt-form" class="form text-lg w-full">
<form id='form' onsubmit="submitForm(event)" autocomplete="on" method="post" target="_parent" class="w-full items-center flex flex-wrap"> <form id='form' data-hx-post="http://localhost:4242/camp-api" autocomplete="on" method="post" target="_parent" class="w-full items-center flex flex-wrap">
<h3 class="basis-full">Camp Form</h3> <h3 class="basis-full">Camp Form</h3>
<div class="basis-full flex flex-wrap my-4"> <div class="basis-full flex flex-wrap my-4">
<label for="firstname" class="basis-full">What is your first and last name? <span class='inline-block text-[#f39] text-sm align-sub'>* required</span></label> <label for="firstname" class="basis-full">What is your first and last name? <span class='inline-block text-[#f39] text-sm align-sub'>* required</span></label>
@ -213,11 +215,14 @@
class="basis-full form-input {{ $formClasses }}" class="basis-full form-input {{ $formClasses }}"
required> required>
<label for="parentemail" class="basis-full">Guardian's Email <span class='inline-block text-[#f39] text-sm align-sub'>* required</span></label> <label for="parentemail" class="basis-full">Guardian's Email
<input type="parentemail" id="parentemail" name="parentemail" <span class='inline-block text-[#f39] text-sm align-sub'>* required</span>
pattern="^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$" <input type="parentemail" id="parentemail" name="parentemail"
class="basis-full form-input {{ $formClasses }}"> pattern="^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$"
class="basis-full peer form-input {{ $formClasses }} invalid:text-[#F39] invalid:ring-[#f39] focus:invalid:ring-[#f39] focus:invalid:border-[#f39]">
<span class='invisible text-[#f39] text-sm peer-invalid:visibile'>Please use a valid email</span>
</label>
<label for="allergies" class="basis-full">Do you have any food allergies?</label> <label for="allergies" class="basis-full">Do you have any food allergies?</label>
<input type="text" id="allergies" name="allergies" <input type="text" id="allergies" name="allergies"
class="basis-full form-input {{ $formClasses }}"> class="basis-full form-input {{ $formClasses }}">

View file

@ -381,6 +381,26 @@ with the image attached"
(:th (car row)) (:th (car row))
(:td (cdr row)))))))))))) (:td (cdr row))))))))))))
(hunchentoot:define-easy-handler (respond :uri "/camp-api") ()
(uiop:println "hey")
(setf (hunchentoot:content-type*) "plain/text")
(let* ((request-type (hunchentoot:request-method hunchentoot:*request*))
(data (hunchentoot:post-parameters* hunchentoot:*request*)))
(uiop:println (assoc "registration" data :test 'string=))
(uiop:println (tbnl:headers-out*))
(if (string= (cdr (assoc "registration" data :test 'string=)) "now")
(progn (uiop:println "did it")
(setf (hunchentoot:header-out "HX-Redirect") "https://secure.myvanco.com/L-Z772/campaign/C-13JPJ")
(uiop:println (tbnl:headers-out*))
(uiop:println (hunchentoot:return-code*))
(with-html-string
(:div
(:p "Payment"))))
(progn
(with-html-string
(:div
(:p "Thank You")))))))
(defun define-post-form-handler (uri mail-function) (defun define-post-form-handler (uri mail-function)
"Take a uri and a mailer function and define a handler in hunchentoot "Take a uri and a mailer function and define a handler in hunchentoot
for forms to POST to." for forms to POST to."

193
tailwind.config.js Normal file
View file

@ -0,0 +1,193 @@
module.exports = {
content: [
"./layouts/**/*.html",
"./content/**/*.{html,md}",
"./themes/blowfish/layouts/**/*.html",
"./themes/blowfish/content/**/*.{html,md}",
"./node_modules/tw-elements/dist/js/**/*.js"
],
darkMode: "class",
theme: {
screens: {
'sm': '640px',
'md': '853px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
},
colors: {
transparent: "transparent",
neutral: {
DEFAULT: "rgba(var(--color-neutral), <alpha-value>)",
50: "rgba(var(--color-neutral-50), <alpha-value>)",
100: "rgba(var(--color-neutral-100), <alpha-value>)",
200: "rgba(var(--color-neutral-200), <alpha-value>)",
300: "rgba(var(--color-neutral-300), <alpha-value>)",
400: "rgba(var(--color-neutral-400), <alpha-value>)",
500: "rgba(var(--color-neutral-500), <alpha-value>)",
600: "rgba(var(--color-neutral-600), <alpha-value>)",
700: "rgba(var(--color-neutral-700), <alpha-value>)",
800: "rgba(var(--color-neutral-800), <alpha-value>)",
900: "rgba(var(--color-neutral-900), <alpha-value>)",
},
primary: {
50: "rgba(var(--color-primary-50), <alpha-value>)",
100: "rgba(var(--color-primary-100), <alpha-value>)",
200: "rgba(var(--color-primary-200), <alpha-value>)",
300: "rgba(var(--color-primary-300), <alpha-value>)",
400: "rgba(var(--color-primary-400), <alpha-value>)",
500: "rgba(var(--color-primary-500), <alpha-value>)",
600: "rgba(var(--color-primary-600), <alpha-value>)",
700: "rgba(var(--color-primary-700), <alpha-value>)",
800: "rgba(var(--color-primary-800), <alpha-value>)",
900: "rgba(var(--color-primary-900), <alpha-value>)",
},
red: {
50: "rgba(var(--color-red-50), <alpha-value>)",
100: "rgba(var(--color-red-100), <alpha-value>)",
200: "rgba(var(--color-red-200), <alpha-value>)",
300: "rgba(var(--color-red-300), <alpha-value>)",
400: "rgba(var(--color-red-400), <alpha-value>)",
500: "rgba(var(--color-red-500), <alpha-value>)",
600: "rgba(var(--color-red-600), <alpha-value>)",
700: "rgba(var(--color-red-700), <alpha-value>)",
800: "rgba(var(--color-red-800), <alpha-value>)",
900: "rgba(var(--color-red-900), <alpha-value>)",
},
secondary: {
50: "rgba(var(--color-secondary-50), <alpha-value>)",
100: "rgba(var(--color-secondary-100), <alpha-value>)",
200: "rgba(var(--color-secondary-200), <alpha-value>)",
300: "rgba(var(--color-secondary-300), <alpha-value>)",
400: "rgba(var(--color-secondary-400), <alpha-value>)",
500: "rgba(var(--color-secondary-500), <alpha-value>)",
600: "rgba(var(--color-secondary-600), <alpha-value>)",
700: "rgba(var(--color-secondary-700), <alpha-value>)",
800: "rgba(var(--color-secondary-800), <alpha-value>)",
900: "rgba(var(--color-secondary-900), <alpha-value>)",
},
},
extend: {
typography: ({ theme }) => ({
DEFAULT: {
css: {
"--tw-prose-body": theme("colors.neutral.700 / 1"),
"--tw-prose-headings": theme("colors.neutral.800 / 1"),
"--tw-prose-lead": theme("colors.neutral.500 / 1"),
"--tw-prose-links": theme("colors.primary.600 / 1"),
"--tw-prose-bold": theme("colors.neutral.900 / 1"),
"--tw-prose-counters": theme("colors.neutral.800 / 1"),
"--tw-prose-bullets": theme("colors.neutral.500 / 1"),
"--tw-prose-hr": theme("colors.neutral.200 / 1"),
"--tw-prose-quotes": theme("colors.neutral.700 / 1"),
"--tw-prose-quote-borders": theme("colors.primary.200 / 1"),
"--tw-prose-captions": theme("colors.neutral.500 / 1"),
"--tw-prose-code": theme("colors.secondary.700 / 1"),
"--tw-prose-pre-code": theme("colors.neutral.700 / 1"),
"--tw-prose-pre-bg": theme("colors.neutral.50 / 1"),
"--tw-prose-th-borders": theme("colors.neutral.500 / 1"),
"--tw-prose-td-borders": theme("colors.neutral.300 / 1"),
"--tw-prose-invert-body": theme("colors.neutral.300 / 1"),
"--tw-prose-invert-headings": theme("colors.neutral.50 / 1"),
"--tw-prose-invert-lead": theme("colors.neutral.500 / 1"),
"--tw-prose-invert-links": theme("colors.primary.400 / 1"),
"--tw-prose-invert-bold": theme("colors.neutral.DEFAULT / 1"),
"--tw-prose-invert-counters": theme("colors.neutral.400 / 1"),
"--tw-prose-invert-bullets": theme("colors.neutral.600 / 1"),
"--tw-prose-invert-hr": theme("colors.neutral.500 / 1"),
"--tw-prose-invert-quotes": theme("colors.neutral.200 / 1"),
"--tw-prose-invert-quote-borders": theme("colors.primary.900 / 1"),
"--tw-prose-invert-captions": theme("colors.neutral.400 / 1"),
"--tw-prose-invert-code": theme("colors.secondary.400 / 1"),
"--tw-prose-invert-pre-code": theme("colors.neutral.200 / 1"),
"--tw-prose-invert-pre-bg": theme("colors.neutral.700 / 1"),
"--tw-prose-invert-th-borders": theme("colors.neutral.500 / 1"),
"--tw-prose-invert-td-borders": theme("colors.neutral.700 / 1"),
a: {
textDecoration: "none",
textDecorationColor: theme("colors.primary.300 / 1"),
fontWeight: "500",
"&:hover": {
color: theme("colors.primary.600 / 1"),
textDecoration: "none",
borderRadius: "0.09rem",
},
},
"a code": {
color: "var(--tw-prose-code)",
},
kbd: {
backgroundColor: theme("colors.neutral.200 / 1"),
padding: "0.1rem 0.4rem",
borderRadius: "0.25rem",
fontSize: "0.9rem",
fontWeight: "600",
},
mark: {
color: theme("colors.neutral.800 / 1"),
backgroundColor: theme("colors.primary.600 / 1"),
padding: "0.1rem 0.2rem",
borderRadius: "0.25rem",
},
code:{
backgroundColor: theme("colors.neutral.50 / 1"),
paddingTop: "3px",
paddingBottom: "3px",
paddingLeft: "5px",
paddingRight: "5px",
borderRadius: "0.25rem",
},
'code::before': {
display: 'none'
},
'code::after': {
display: 'none'
},
'p::before': {
display: 'none'
},
'p::after': {
display: 'none'
},
'a.active': {
"text-decoration-color": theme("colors.primary.600 / 1"),
},
'p.active': {
"text-decoration-color": theme("colors.primary.600 / 1"),
}
},
},
invert: {
css: {
a: {
textDecorationColor: theme("colors.neutral.600 / 1"),
"&:hover": {
color: theme("colors.primary.400 / 1"),
},
},
kbd: {
color: theme("colors.neutral.200 / 1"),
backgroundColor: theme("colors.neutral.700 / 1"),
},
mark: {
backgroundColor: theme("colors.primary.400 / 1"),
},
code:{
backgroundColor: theme("colors.neutral.700 / 1"),
},
'a.active': {
"text-decoration-color": theme("colors.primary.400 / 1")
},
'p.active': {
"text-decoration-color": theme("colors.primary.400 / 1")
}
},
},
}),
},
},
plugins: [
require("@tailwindcss/typography"),
require('@tailwindcss/forms'),
],
};