feat(15): steps submit form and set next
This commit is contained in:
parent
65bfe3077f
commit
febc77032b
|
@ -40,6 +40,7 @@
|
|||
id="form-step"
|
||||
th:fragment="formFragment(formData)"
|
||||
hx-post="/submit-step/1"
|
||||
hx-swap="outerHTML"
|
||||
action="/submit-step/1"
|
||||
method="post"
|
||||
>
|
||||
|
|
|
@ -39,6 +39,10 @@
|
|||
<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"
|
||||
id="form-step"
|
||||
hx-post="/submit-step/2"
|
||||
hx-swap="outerHTML"
|
||||
action="/submit-step/2"
|
||||
method="post"
|
||||
th:fragment="formFragment(formData)"
|
||||
>
|
||||
<summary
|
||||
|
@ -183,7 +187,13 @@
|
|||
>Go Back</a
|
||||
>
|
||||
<div class="grow"></div>
|
||||
<input
|
||||
type="submit"
|
||||
class="grid place-content-center mr-3 w-24 h-10 text-sm font-semibold text-white rounded md:mr-24 md:w-32 md:h-12 md:text-base md:rounded-lg bg-marine-blue"
|
||||
value="Next Step"
|
||||
>
|
||||
<a
|
||||
th:remove="all"
|
||||
href="step3.html"
|
||||
class="grid place-content-center mr-3 w-24 h-10 text-sm font-semibold text-white rounded md:mr-24 md:w-32 md:h-12 md:text-base md:rounded-lg bg-marine-blue"
|
||||
>Next Step</a
|
||||
|
|
|
@ -39,6 +39,10 @@
|
|||
<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"
|
||||
id="form-step"
|
||||
hx-post="/submit-step/3"
|
||||
hx-swap="outerHTML"
|
||||
action="/submit-step/3"
|
||||
method="post"
|
||||
th:fragment="formFragment(formData)"
|
||||
>
|
||||
<summary
|
||||
|
@ -105,9 +109,7 @@
|
|||
<p class="py-3 text-cool-gray">
|
||||
Add-ons help enhance your gaming experience.
|
||||
</p>
|
||||
<div
|
||||
class="flex flex-col w-full text-sm md:text-base"
|
||||
>
|
||||
<div class="flex flex-col w-full text-sm md:text-base">
|
||||
<label for="multiplayer-games" class="relative pl-6 h-20 md:w-full">
|
||||
<input
|
||||
id="multiplayer-games"
|
||||
|
@ -118,10 +120,14 @@
|
|||
<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"
|
||||
>
|
||||
<div class="grid place-content-center ml-20 h-full grid-cols-[1fr_100px]">
|
||||
<div
|
||||
class="grid place-content-center ml-20 h-full grid-cols-[1fr_100px]"
|
||||
>
|
||||
<h1>Online Service</h1>
|
||||
<p>Access to multiplayer games</p>
|
||||
<p class="col-start-2 row-span-2 row-start-1 self-center">+$1/mo</p>
|
||||
<p class="col-start-2 row-span-2 row-start-1 self-center">
|
||||
+$1/mo
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
@ -136,10 +142,14 @@
|
|||
<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"
|
||||
>
|
||||
<div class="grid place-content-center ml-20 h-full grid-cols-[1fr_100px]">
|
||||
<div
|
||||
class="grid place-content-center ml-20 h-full grid-cols-[1fr_100px]"
|
||||
>
|
||||
<h1>Larger storage</h1>
|
||||
<p>Extra 1TB of cloud save</p>
|
||||
<p class="col-start-2 row-span-2 row-start-1 self-center">+$2/mo</p>
|
||||
<p class="col-start-2 row-span-2 row-start-1 self-center">
|
||||
+$2/mo
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
@ -154,10 +164,14 @@
|
|||
<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"
|
||||
>
|
||||
<div class="grid place-content-center ml-20 h-full grid-cols-[1fr_100px]">
|
||||
<div
|
||||
class="grid place-content-center ml-20 h-full grid-cols-[1fr_100px]"
|
||||
>
|
||||
<h1>Customizable Profile</h1>
|
||||
<p>Custom theme on your profile</p>
|
||||
<p class="col-start-2 row-span-2 row-start-1 self-center">+$2/mo</p>
|
||||
<p class="col-start-2 row-span-2 row-start-1 self-center">
|
||||
+$2/mo
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
@ -176,7 +190,14 @@
|
|||
>Go Back</a
|
||||
>
|
||||
<div class="grow"></div>
|
||||
<input
|
||||
type="submit"
|
||||
href="step4.html"
|
||||
class="grid place-content-center mr-3 w-24 h-10 text-sm font-semibold text-white rounded md:mr-24 md:w-32 md:h-12 md:text-base md:rounded-lg bg-marine-blue"
|
||||
value="Next Step"
|
||||
/>
|
||||
<a
|
||||
th:remove="all"
|
||||
href="step4.html"
|
||||
class="grid place-content-center mr-3 w-24 h-10 text-sm font-semibold text-white rounded md:mr-24 md:w-32 md:h-12 md:text-base md:rounded-lg bg-marine-blue"
|
||||
>Next Step</a
|
||||
|
|
|
@ -39,6 +39,10 @@
|
|||
<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"
|
||||
id="form-step"
|
||||
hx-post="/submit-step/4"
|
||||
action="/submit-step/4"
|
||||
hx-swap="outerHTML"
|
||||
method="post"
|
||||
th:fragment="formFragment(formData)"
|
||||
>
|
||||
<summary
|
||||
|
@ -135,7 +139,13 @@
|
|||
>Go Back</a
|
||||
>
|
||||
<div class="grow"></div>
|
||||
<input
|
||||
type="submit"
|
||||
class="grid place-content-center mr-3 w-24 h-10 text-sm font-semibold text-white rounded md:mr-24 md:w-32 md:h-12 md:text-base md:rounded-lg bg-purplish-blue"
|
||||
value="Confirm"
|
||||
>
|
||||
<a
|
||||
th:remove="all"
|
||||
href="step5.html"
|
||||
class="grid place-content-center mr-3 w-24 h-10 text-sm font-semibold text-white rounded md:mr-24 md:w-32 md:h-12 md:text-base md:rounded-lg bg-purplish-blue"
|
||||
>Confirm</a
|
||||
|
|
|
@ -6,8 +6,77 @@ object Models {
|
|||
final case class Answers(
|
||||
sessionId: String = "id1",
|
||||
currentStep: Int = 1,
|
||||
// fragmentName: String = "step1",
|
||||
step1: StepAnswers.Step1 = StepAnswers.Step1(),
|
||||
step2: StepAnswers.Step2 = StepAnswers.Step2(),
|
||||
step3: StepAnswers.Step3 = StepAnswers.Step3()
|
||||
) {
|
||||
// this is not enforced by compiler, sad, maintain by hand in html template files
|
||||
def fragmentName: String = s"step${currentStep}"
|
||||
def updateStep(stepNum: Int, rawData: String): Answers = {
|
||||
stepNum match {
|
||||
case 1 => this.copy(
|
||||
step1 = this.step1.fromFormData(rawData),
|
||||
currentStep = stepNum + 1
|
||||
)
|
||||
case 2 => this.copy(
|
||||
step2 = this.step2.fromFormData(rawData),
|
||||
currentStep = stepNum + 1
|
||||
)
|
||||
case 3 => this.copy(
|
||||
step3 = this.step3.fromFormData(rawData),
|
||||
currentStep = stepNum + 1
|
||||
)
|
||||
case 4 => this.copy(
|
||||
currentStep = stepNum + 1
|
||||
)
|
||||
case _ => this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PlanType:
|
||||
case Arcade, Advanced, Pro
|
||||
|
||||
enum Addons:
|
||||
case OnlineService, LargerStorage, CustomProfile
|
||||
|
||||
sealed trait StepAnswers {
|
||||
def fromFormData(rawData: String): StepAnswers
|
||||
}
|
||||
object StepAnswers {
|
||||
final case class Step1(
|
||||
name: String = "",
|
||||
email: String = "",
|
||||
phone: String = ""
|
||||
) extends StepAnswers {
|
||||
override def fromFormData(rawData: String): Step1 = {
|
||||
val fieldValues = rawData.split("&").map { field =>
|
||||
val Array(name, value) = field.split("=")
|
||||
name -> value
|
||||
}.toMap
|
||||
|
||||
val name = fieldValues.getOrElse("name", "")
|
||||
val email = fieldValues.getOrElse("email", "")
|
||||
val phone = fieldValues.getOrElse("phone", "")
|
||||
|
||||
Step1(name, email, phone)
|
||||
}
|
||||
}
|
||||
final case class Step2(
|
||||
planType: PlanType = PlanType.Arcade,
|
||||
isYearly: Boolean = false
|
||||
) extends StepAnswers {
|
||||
override def fromFormData(rawData: String): Step2 = {
|
||||
val a = 1
|
||||
Step2()
|
||||
}
|
||||
}
|
||||
final case class Step3(addons: Set[Addons] = Set.empty)
|
||||
extends StepAnswers {
|
||||
override def fromFormData(rawData: String): Step3 = {
|
||||
val a = 1
|
||||
Step3()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,16 +19,23 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger)
|
|||
templateEngine.setTemplateResolver(templateResolver)
|
||||
|
||||
val sessoinCookieName = "sessionId"
|
||||
/**
|
||||
* This route works with and without 'sessionId' cookie present
|
||||
* set's this cookie if not present, and returns initial 'index.html'
|
||||
* where the form is not yet initialized, and will be requested for the session
|
||||
|
||||
/** This route works with and without 'sessionId' cookie present set's this
|
||||
* cookie if not present, and returns initial 'index.html' where the form is
|
||||
* not yet initialized, and will be requested for the session
|
||||
*/
|
||||
@cask.get("/")
|
||||
def getIndex(ctx: cask.Request) = {
|
||||
val sessionCookie = ctx.cookies.get(sessoinCookieName)
|
||||
lazy val newSessionCookies = sessionCookie match {
|
||||
case None => Seq(cask.Cookie(sessoinCookieName, UUID.randomUUID().toString(), path = "/"))
|
||||
case None =>
|
||||
Seq(
|
||||
cask.Cookie(
|
||||
sessoinCookieName,
|
||||
UUID.randomUUID().toString(),
|
||||
path = "/"
|
||||
)
|
||||
)
|
||||
case Some(_) => Seq.empty // don't set new cookies
|
||||
}
|
||||
|
||||
|
@ -45,7 +52,8 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger)
|
|||
|
||||
@cask.get("/force-new-session")
|
||||
def forceNewSession() = {
|
||||
val newSessionCookie = cask.Cookie(sessoinCookieName, UUID.randomUUID().toString(), path = "/")
|
||||
val newSessionCookie =
|
||||
cask.Cookie(sessoinCookieName, UUID.randomUUID().toString(), path = "/")
|
||||
println(s"setting new session ${newSessionCookie.value}")
|
||||
cask.Response(
|
||||
s"New session forced. Force new session",
|
||||
|
@ -54,18 +62,21 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger)
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method only works when cookie 'sessionId' is present
|
||||
* will get or init Form State for the session,
|
||||
* and return last unsubmitted form step fragment
|
||||
val formDataContextVarName = "formData"
|
||||
|
||||
/** This method only works when cookie 'sessionId' is present will get or init
|
||||
* Form State for the session, and return last unsubmitted form step fragment
|
||||
*/
|
||||
@cask.get("/get-form")
|
||||
def getForm(sessionId: cask.Cookie) = {
|
||||
val state = Models.Answers(currentStep = 1)
|
||||
cask.Response("yoyo")
|
||||
val context = new Context()
|
||||
context.setVariable("formData", state)
|
||||
val formFragment = templateEngine.process(state.fragmentName, Set("formFragment").asJava, context)
|
||||
context.setVariable(formDataContextVarName, state)
|
||||
val formFragment = templateEngine.process(
|
||||
state.fragmentName,
|
||||
Set("formFragment").asJava,
|
||||
context
|
||||
)
|
||||
cask.Response(
|
||||
formFragment,
|
||||
headers = Seq("Content-Type" -> "text/html;charset=UTF-8")
|
||||
|
@ -74,20 +85,29 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger)
|
|||
|
||||
// i guess let's make step a hidden input?
|
||||
@cask.post("/submit-step/:stepNum")
|
||||
def submitStep(sessionId: cask.Cookie, stepNum: Int, request: cask.Request) = {
|
||||
def submitStep(
|
||||
sessionId: cask.Cookie,
|
||||
stepNum: Int,
|
||||
request: cask.Request
|
||||
) = {
|
||||
val id = sessionId.value
|
||||
println(s"got $request for $id")
|
||||
println(s"got $request for $id. it's data is ${request.text()}")
|
||||
|
||||
val userAnswers = Sessions.sessionReplies.getOrElse(id, Answers(id))
|
||||
|
||||
// now i want to do what?
|
||||
// select 'answerStep' form userAnswers
|
||||
// delegate parsing and saving of the request.text to it
|
||||
// set current step to next
|
||||
// and return next rendered form step
|
||||
// with the 'form data' caclulated from the updated answers
|
||||
|
||||
cask.Response(s"what i got is $request and ${request.text()}")
|
||||
val updatedAnswers = userAnswers.updateStep(stepNum, request.text())
|
||||
val context = new Context()
|
||||
context.setVariable(formDataContextVarName, updatedAnswers)
|
||||
val nextFormFragment = templateEngine.process(
|
||||
updatedAnswers.fragmentName,
|
||||
Set("formFragment").asJava,
|
||||
context
|
||||
)
|
||||
println(s"the state now is $updatedAnswers")
|
||||
cask.Response(
|
||||
nextFormFragment,
|
||||
headers = Seq("Content-Type" -> "text/html;charset=UTF-8")
|
||||
)
|
||||
}
|
||||
|
||||
@cask.staticResources("/public")
|
||||
|
|
Loading…
Reference in New Issue