feat(15): steps submit form and set next
This commit is contained in:
parent
65bfe3077f
commit
febc77032b
|
@ -40,6 +40,7 @@
|
||||||
id="form-step"
|
id="form-step"
|
||||||
th:fragment="formFragment(formData)"
|
th:fragment="formFragment(formData)"
|
||||||
hx-post="/submit-step/1"
|
hx-post="/submit-step/1"
|
||||||
|
hx-swap="outerHTML"
|
||||||
action="/submit-step/1"
|
action="/submit-step/1"
|
||||||
method="post"
|
method="post"
|
||||||
>
|
>
|
||||||
|
|
|
@ -39,6 +39,10 @@
|
||||||
<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"
|
||||||
|
hx-post="/submit-step/2"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
action="/submit-step/2"
|
||||||
|
method="post"
|
||||||
th:fragment="formFragment(formData)"
|
th:fragment="formFragment(formData)"
|
||||||
>
|
>
|
||||||
<summary
|
<summary
|
||||||
|
@ -183,7 +187,13 @@
|
||||||
>Go Back</a
|
>Go Back</a
|
||||||
>
|
>
|
||||||
<div class="grow"></div>
|
<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
|
<a
|
||||||
|
th:remove="all"
|
||||||
href="step3.html"
|
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"
|
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
|
>Next Step</a
|
||||||
|
|
|
@ -39,6 +39,10 @@
|
||||||
<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"
|
||||||
|
hx-post="/submit-step/3"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
action="/submit-step/3"
|
||||||
|
method="post"
|
||||||
th:fragment="formFragment(formData)"
|
th:fragment="formFragment(formData)"
|
||||||
>
|
>
|
||||||
<summary
|
<summary
|
||||||
|
@ -105,9 +109,7 @@
|
||||||
<p class="py-3 text-cool-gray">
|
<p class="py-3 text-cool-gray">
|
||||||
Add-ons help enhance your gaming experience.
|
Add-ons help enhance your gaming experience.
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div class="flex flex-col w-full text-sm md:text-base">
|
||||||
class="flex flex-col w-full text-sm md:text-base"
|
|
||||||
>
|
|
||||||
<label for="multiplayer-games" class="relative pl-6 h-20 md:w-full">
|
<label for="multiplayer-games" class="relative pl-6 h-20 md:w-full">
|
||||||
<input
|
<input
|
||||||
id="multiplayer-games"
|
id="multiplayer-games"
|
||||||
|
@ -118,10 +120,14 @@
|
||||||
<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"
|
||||||
>
|
>
|
||||||
<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>
|
<h1>Online Service</h1>
|
||||||
<p>Access to multiplayer games</p>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -136,10 +142,14 @@
|
||||||
<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"
|
||||||
>
|
>
|
||||||
<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>
|
<h1>Larger storage</h1>
|
||||||
<p>Extra 1TB of cloud save</p>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -154,10 +164,14 @@
|
||||||
<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"
|
||||||
>
|
>
|
||||||
<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>
|
<h1>Customizable Profile</h1>
|
||||||
<p>Custom theme on your profile</p>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -176,7 +190,14 @@
|
||||||
>Go Back</a
|
>Go Back</a
|
||||||
>
|
>
|
||||||
<div class="grow"></div>
|
<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
|
<a
|
||||||
|
th:remove="all"
|
||||||
href="step4.html"
|
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"
|
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
|
>Next Step</a
|
||||||
|
|
|
@ -39,6 +39,10 @@
|
||||||
<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"
|
||||||
|
hx-post="/submit-step/4"
|
||||||
|
action="/submit-step/4"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
method="post"
|
||||||
th:fragment="formFragment(formData)"
|
th:fragment="formFragment(formData)"
|
||||||
>
|
>
|
||||||
<summary
|
<summary
|
||||||
|
@ -135,7 +139,13 @@
|
||||||
>Go Back</a
|
>Go Back</a
|
||||||
>
|
>
|
||||||
<div class="grow"></div>
|
<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
|
<a
|
||||||
|
th:remove="all"
|
||||||
href="step5.html"
|
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"
|
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
|
>Confirm</a
|
||||||
|
|
|
@ -4,10 +4,79 @@ import java.util.UUID
|
||||||
|
|
||||||
object Models {
|
object Models {
|
||||||
final case class Answers(
|
final case class Answers(
|
||||||
sessionId: String = "id1",
|
sessionId: String = "id1",
|
||||||
currentStep: Int = 1,
|
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 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)
|
templateEngine.setTemplateResolver(templateResolver)
|
||||||
|
|
||||||
val sessoinCookieName = "sessionId"
|
val sessoinCookieName = "sessionId"
|
||||||
/**
|
|
||||||
* This route works with and without 'sessionId' cookie present
|
/** This route works with and without 'sessionId' cookie present set's this
|
||||||
* set's this cookie if not present, and returns initial 'index.html'
|
* cookie if not present, and returns initial 'index.html' where the form is
|
||||||
* where the form is not yet initialized, and will be requested for the session
|
* not yet initialized, and will be requested for the session
|
||||||
*/
|
*/
|
||||||
@cask.get("/")
|
@cask.get("/")
|
||||||
def getIndex(ctx: cask.Request) = {
|
def getIndex(ctx: cask.Request) = {
|
||||||
val sessionCookie = ctx.cookies.get(sessoinCookieName)
|
val sessionCookie = ctx.cookies.get(sessoinCookieName)
|
||||||
lazy val newSessionCookies = sessionCookie match {
|
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
|
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")
|
@cask.get("/force-new-session")
|
||||||
def forceNewSession() = {
|
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}")
|
println(s"setting new session ${newSessionCookie.value}")
|
||||||
cask.Response(
|
cask.Response(
|
||||||
s"New session forced. Force new session",
|
s"New session forced. Force new session",
|
||||||
|
@ -54,18 +62,21 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
val formDataContextVarName = "formData"
|
||||||
* This method only works when cookie 'sessionId' is present
|
|
||||||
* will get or init Form State for the session,
|
/** This method only works when cookie 'sessionId' is present will get or init
|
||||||
* and return last unsubmitted form step fragment
|
* Form State for the session, and return last unsubmitted form step fragment
|
||||||
*/
|
*/
|
||||||
@cask.get("/get-form")
|
@cask.get("/get-form")
|
||||||
def getForm(sessionId: cask.Cookie) = {
|
def getForm(sessionId: cask.Cookie) = {
|
||||||
val state = Models.Answers(currentStep = 1)
|
val state = Models.Answers(currentStep = 1)
|
||||||
cask.Response("yoyo")
|
|
||||||
val context = new Context()
|
val context = new Context()
|
||||||
context.setVariable("formData", state)
|
context.setVariable(formDataContextVarName, state)
|
||||||
val formFragment = templateEngine.process(state.fragmentName, Set("formFragment").asJava, context)
|
val formFragment = templateEngine.process(
|
||||||
|
state.fragmentName,
|
||||||
|
Set("formFragment").asJava,
|
||||||
|
context
|
||||||
|
)
|
||||||
cask.Response(
|
cask.Response(
|
||||||
formFragment,
|
formFragment,
|
||||||
headers = Seq("Content-Type" -> "text/html;charset=UTF-8")
|
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?
|
// i guess let's make step a hidden input?
|
||||||
@cask.post("/submit-step/:stepNum")
|
@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
|
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))
|
val userAnswers = Sessions.sessionReplies.getOrElse(id, Answers(id))
|
||||||
|
|
||||||
// now i want to do what?
|
val updatedAnswers = userAnswers.updateStep(stepNum, request.text())
|
||||||
// select 'answerStep' form userAnswers
|
val context = new Context()
|
||||||
// delegate parsing and saving of the request.text to it
|
context.setVariable(formDataContextVarName, updatedAnswers)
|
||||||
// set current step to next
|
val nextFormFragment = templateEngine.process(
|
||||||
// and return next rendered form step
|
updatedAnswers.fragmentName,
|
||||||
// with the 'form data' caclulated from the updated answers
|
Set("formFragment").asJava,
|
||||||
|
context
|
||||||
cask.Response(s"what i got is $request and ${request.text()}")
|
)
|
||||||
|
println(s"the state now is $updatedAnswers")
|
||||||
|
cask.Response(
|
||||||
|
nextFormFragment,
|
||||||
|
headers = Seq("Content-Type" -> "text/html;charset=UTF-8")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@cask.staticResources("/public")
|
@cask.staticResources("/public")
|
||||||
|
|
Loading…
Reference in New Issue