feat(15): adding dynamic form data source

This commit is contained in:
efim 2023-07-15 12:05:40 +00:00
parent 076dc76ca4
commit 998cc778e6
5 changed files with 64 additions and 15 deletions

View File

@ -38,7 +38,7 @@
<form <form
class="flex flex-col items-center w-screen h-screen md:grid md:items-start md:p-5 md:bg-white md:rounded-2xl md:grid-cols-[auto_1fr] md:w-desktop-form md:h-desktop-form md:drop-shadow-2xl" class="flex flex-col items-center w-screen h-screen md:grid md:items-start md:p-5 md:bg-white md:rounded-2xl md:grid-cols-[auto_1fr] md:w-desktop-form md:h-desktop-form md:drop-shadow-2xl"
id="form-step" id="form-step"
th:fragment="formFragment(formData)" th:fragment="formFragment(formData.userAnswers)"
hx-post="/submit-step/1/2" hx-post="/submit-step/1/2"
hx-swap="outerHTML" hx-swap="outerHTML"
action="/submit-step/1/2" action="/submit-step/1/2"
@ -105,7 +105,7 @@
<label for="name" class="pt-3 text-sm md:pt-5 md:pb-2 text-marine-blue">Name</label> <label for="name" class="pt-3 text-sm md:pt-5 md:pb-2 text-marine-blue">Name</label>
<input <input
id="name" id="name"
th:value="${formData.step1.name}" th:value="${formData.userAnswers.step1.name}"
name="name" name="name"
type="text" type="text"
placeholder="e.g. Stephen King" placeholder="e.g. Stephen King"
@ -116,7 +116,7 @@
> >
<input <input
id="email" id="email"
th:value="${formData.step1.email}" th:value="${formData.userAnswers.step1.email}"
name="email" name="email"
type="email" type="email"
placeholder="e.g. stephenking@lorem.com" placeholder="e.g. stephenking@lorem.com"
@ -127,7 +127,7 @@
> >
<input <input
id="phone" id="phone"
th:value="${formData.step1.phone}" th:value="${formData.userAnswers.step1.phone}"
name="phone" name="phone"
type="tel" type="tel"
placeholder="e.g. +1 234 567 890" placeholder="e.g. +1 234 567 890"

View File

@ -43,7 +43,7 @@
hx-swap="outerHTML" hx-swap="outerHTML"
action="/submit-step/2/3" action="/submit-step/2/3"
method="post" method="post"
th:fragment="formFragment(formData)" th:fragment="formFragment(formData.userAnswers)"
> >
<summary <summary
class="w-full h-44 bg-no-repeat md:row-span-2 bg-sidebar-mobile marker:text-white md:bg-sidebar-desktop md:h-[568px] md:w-[274px]" class="w-full h-44 bg-no-repeat md:row-span-2 bg-sidebar-mobile marker:text-white md:bg-sidebar-desktop md:h-[568px] md:w-[274px]"
@ -119,7 +119,7 @@
name="plan-type" name="plan-type"
value="Arcade" value="Arcade"
class="hidden peer" class="hidden peer"
th:checked="${formData.step2.planType.toString()} == 'Arcade'" th:checked="${formData.userAnswers.step2.planType.toString()} == 'Arcade'"
checked checked
/> />
<div <div
@ -136,7 +136,7 @@
name="plan-type" name="plan-type"
value="Advanced" value="Advanced"
class="hidden peer" class="hidden peer"
th:checked="${formData.step2.planType.toString()} == 'Advanced'" th:checked="${formData.userAnswers.step2.planType.toString()} == 'Advanced'"
/> />
<div <div
class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia" class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia"
@ -152,7 +152,7 @@
name="plan-type" name="plan-type"
value="Pro" value="Pro"
class="hidden peer" class="hidden peer"
th:checked="${formData.step2.planType.toString()} == 'Pro'" th:checked="${formData.userAnswers.step2.planType.toString()} == 'Pro'"
/> />
<div <div
class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia" class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia"
@ -172,7 +172,7 @@
name="isPackageYearly" name="isPackageYearly"
role="switch" role="switch"
id="packageDuration" id="packageDuration"
th:checked="${formData.step2.isYearly}" th:checked="${formData.userAnswers.step2.isYearly}"
/> />
<span class="row-start-1 text-marine-blue peer-checked:text-cool-gray">Monthly</span> <span class="row-start-1 text-marine-blue peer-checked:text-cool-gray">Monthly</span>
<span class="row-start-1 text-cool-gray peer-checked:text-marine-blue">Yearly</span> <span class="row-start-1 text-cool-gray peer-checked:text-marine-blue">Yearly</span>

View File

@ -43,7 +43,7 @@
hx-swap="outerHTML" hx-swap="outerHTML"
action="/submit-step/3/4" action="/submit-step/3/4"
method="post" method="post"
th:fragment="formFragment(formData)" th:fragment="formFragment(formData.userAnswers)"
> >
<summary <summary
class="w-full h-44 bg-no-repeat md:row-span-2 bg-sidebar-mobile marker:text-white md:bg-sidebar-desktop md:h-[568px] md:w-[274px]" class="w-full h-44 bg-no-repeat md:row-span-2 bg-sidebar-mobile marker:text-white md:bg-sidebar-desktop md:h-[568px] md:w-[274px]"
@ -117,7 +117,7 @@
value="OnlineService" value="OnlineService"
name="addon-services" name="addon-services"
class="my-7 w-6 h-6 peer" class="my-7 w-6 h-6 peer"
th:checked="${formData.step3.containsAddon('OnlineService')}" th:checked="${formData.userAnswers.step3.containsAddon('OnlineService')}"
/> />
<div <div
class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia/50" class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia/50"
@ -141,7 +141,7 @@
name="addon-services" name="addon-services"
value="LargerStorage" value="LargerStorage"
class="my-7 w-6 h-6 peer" class="my-7 w-6 h-6 peer"
th:checked="${formData.step3.containsAddon('LargerStorage')}" th:checked="${formData.userAnswers.step3.containsAddon('LargerStorage')}"
/> />
<div <div
class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia/50" class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia/50"
@ -165,7 +165,7 @@
name="addon-services" name="addon-services"
value="CustomProfile" value="CustomProfile"
class="my-7 w-6 h-6 peer" class="my-7 w-6 h-6 peer"
th:checked="${formData.step3.containsAddon('CustomProfile')}" th:checked="${formData.userAnswers.step3.containsAddon('CustomProfile')}"
/> />
<div <div
class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia/50" class="absolute inset-y-0 inset-x-0 rounded-lg border border-cool-gray peer-checked:border-purplish-blue peer-checked:bg-magnolia/50"

View File

@ -12,6 +12,53 @@ object Models {
step4 = StepAnswers.Step4(true) step4 = StepAnswers.Step4(true)
) )
/** Labels and form info which dynamically depend on user answers e.g plan
* name 'Arcade (Yearly)' vs 'Pro (Monthly)'
*/
final case class FormData(
userAnswers: Answers
) {
def planDiscountMessage: Option[String] =
if (userAnswers.step2.isYearly) Some("2 monts free") else None
// yeah, in real world it will not be this simple
def yearlyCost(monthlyCost: Int): Int = 10 * monthlyCost
def periodCostLabel: String = {
if (userAnswers.step2.isYearly) "yr" else "mo"
}
def planCost: Int = {
val monthlyPlanCost = userAnswers.step2.planType match {
case PlanType.Arcade => 9
case PlanType.Advanced => 12
case PlanType.Pro => 15
}
if (userAnswers.step2.isYearly) yearlyCost(monthlyPlanCost)
else monthlyPlanCost
}
def addonMontlyCost: Addons => Int = {
case Addons.OnlineService => 1
case Addons.LargerStorage => 2
case Addons.CustomProfile => 2
}
def addonCost(addon: Addons): Int = {
val monthCost = addonMontlyCost(addon)
if (userAnswers.step2.isYearly) yearlyCost(monthCost) else monthCost
}
def fullPlanName: String = {
val period = if (userAnswers.step2.isYearly) "Yearly" else "Monthly"
s"${userAnswers.step2.planType} (${period})"
}
def fullOrderPrice: Int = {
planCost + userAnswers.step3.addons.map(addonCost).sum
}
}
final case class Answers( final case class Answers(
sessionId: String = "id1", sessionId: String = "id1",
currentStep: Int = 1, currentStep: Int = 1,

View File

@ -75,7 +75,8 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger)
val state = Sessions.sessionReplies.getOrElse(id, Answers(id)) val state = Sessions.sessionReplies.getOrElse(id, Answers(id))
println(s"starting form for $state") println(s"starting form for $state")
val context = new Context() val context = new Context()
context.setVariable(formDataContextVarName, state) val formData = Models.FormData(state)
context.setVariable(formDataContextVarName, formData)
val formFragment = templateEngine.process( val formFragment = templateEngine.process(
state.fragmentName, state.fragmentName,
Set("formFragment").asJava, Set("formFragment").asJava,
@ -106,7 +107,8 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger)
Sessions.sessionReplies.update(id, updatedAnswers) Sessions.sessionReplies.update(id, updatedAnswers)
val context = new Context() val context = new Context()
context.setVariable(formDataContextVarName, updatedAnswers) val formData = Models.FormData(updatedAnswers)
context.setVariable(formDataContextVarName, formData)
val nextFormFragment = templateEngine.process( val nextFormFragment = templateEngine.process(
updatedAnswers.fragmentName, updatedAnswers.fragmentName,
Set("formFragment").asJava, Set("formFragment").asJava,