create users vs invite to a class
parent
4b5bcb8a09
commit
9beea401c3
|
@ -66,7 +66,13 @@ final class Clas(
|
|||
|
||||
def studentForm(id: String) = Secure(_.Teacher) { implicit ctx => me =>
|
||||
WithClass(me, lila.clas.Clas.Id(id)) { _ => clas =>
|
||||
Ok(html.clas.student.form(clas, env.clas.forms.student.create)).fuccess
|
||||
Ok(
|
||||
html.clas.student.form(
|
||||
clas,
|
||||
env.clas.forms.student.invite,
|
||||
env.clas.forms.student.create
|
||||
)
|
||||
).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,15 +83,19 @@ final class Clas(
|
|||
env.clas.forms.student.create
|
||||
.bindFromRequest()(ctx.body)
|
||||
.fold(
|
||||
err => BadRequest(html.clas.student.form(clas, err)).fuccess,
|
||||
err =>
|
||||
BadRequest(
|
||||
html.clas.student.form(
|
||||
clas,
|
||||
env.clas.forms.student.invite,
|
||||
err
|
||||
)
|
||||
).fuccess,
|
||||
username =>
|
||||
env.clas.api.student.create(clas, username)(env.user.authenticator.passEnc) flatMap {
|
||||
env.clas.api.student.create(clas, username)(env.user.authenticator.passEnc) map {
|
||||
case (user, password) =>
|
||||
env.clas.api.student.get(clas, user) map {
|
||||
_ ?? { student =>
|
||||
Ok(html.clas.student.show(clas, student, password.some))
|
||||
}
|
||||
}
|
||||
Redirect(routes.Clas.studentShow(clas.id.value, user.username))
|
||||
.flashing("password" -> password.value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -93,6 +103,32 @@ final class Clas(
|
|||
}
|
||||
}
|
||||
|
||||
def studentInvite(id: String) = SecureBody(_.Teacher) { implicit ctx => me =>
|
||||
WithClass(me, lila.clas.Clas.Id(id)) { _ => clas =>
|
||||
env.clas.forms.student.invite
|
||||
.bindFromRequest()(ctx.body)
|
||||
.pp
|
||||
.fold(
|
||||
err =>
|
||||
BadRequest(
|
||||
html.clas.student.form(
|
||||
clas,
|
||||
err,
|
||||
env.clas.forms.student.create
|
||||
)
|
||||
).fuccess,
|
||||
username =>
|
||||
env.user.repo named username flatMap {
|
||||
_ ?? { user =>
|
||||
env.clas.api.student.invite(clas, user) inject
|
||||
Redirect(routes.Clas.studentForm(clas.id.value))
|
||||
.flashing("success" -> s"${user.username} has been invited")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def studentShow(id: String, username: String) = Secure(_.Teacher) { implicit ctx => me =>
|
||||
WithClass(me, lila.clas.Clas.Id(id)) { t => clas =>
|
||||
env.user.repo named username flatMap {
|
||||
|
|
|
@ -12,8 +12,7 @@ object student {
|
|||
|
||||
def show(
|
||||
clas: Clas,
|
||||
student: Student.WithUser,
|
||||
password: Option[lila.user.User.ClearPassword] = none
|
||||
student: Student.WithUser
|
||||
)(implicit ctx: Context) =
|
||||
bits.layout(student.user.username, Left(clas))(
|
||||
cls := "student-show",
|
||||
|
@ -31,16 +30,16 @@ object student {
|
|||
)("User profile")
|
||||
)
|
||||
),
|
||||
password map { pass =>
|
||||
ctx.flash("password") map { pass =>
|
||||
div(cls := "box__pad password")(
|
||||
iconTag("E")(cls := "is-green"),
|
||||
div(
|
||||
p(
|
||||
"Make sure to copy or write down the password now. You won’t be able to see it again!"
|
||||
),
|
||||
code(s"Password: ${pass.value}"),
|
||||
code(s"Password: $pass"),
|
||||
a(
|
||||
href := routes.Clas.studentCreate(clas.id.value),
|
||||
href := routes.Clas.studentForm(clas.id.value),
|
||||
cls := "button button-green text",
|
||||
dataIcon := "O"
|
||||
)("Add another student")
|
||||
|
@ -78,19 +77,51 @@ object student {
|
|||
)
|
||||
)
|
||||
|
||||
def form(c: lila.clas.Clas, form: Form[String])(implicit ctx: Context) =
|
||||
def form(c: lila.clas.Clas, invite: Form[String], create: Form[String])(implicit ctx: Context) =
|
||||
bits.layout("Add student", Left(c))(
|
||||
cls := "box-pad",
|
||||
cls := "box-pad student-add",
|
||||
h1("Add student"),
|
||||
p(
|
||||
"To ",
|
||||
a(href := routes.Clas.show(c.id.value))(c.name)
|
||||
),
|
||||
postForm(cls := "form3", action := routes.Clas.studentCreate(c.id.value))(
|
||||
form3.group(form("username"), frag("Username"))(form3.input(_)(autofocus)),
|
||||
form3.actions(
|
||||
a(href := routes.Clas.show(c.id.value))(trans.cancel()),
|
||||
form3.submit(trans.signUp())
|
||||
ctx.flash("success") map { msg =>
|
||||
div(cls := "flash-success")(msg)
|
||||
},
|
||||
div(cls := "student-add__choice")(
|
||||
div(cls := "student-add__choice__invite")(
|
||||
h2("Invite a Lichess account"),
|
||||
p(
|
||||
"If the student already has a Lichess account, ",
|
||||
"you can invite them to the class. ",
|
||||
"They will receive a message on Lichess with a link to join the class.",
|
||||
strong("Important: only invite students you know, and who actively want to join the class."),
|
||||
"Never send unsolicited invites to arbitrary players."
|
||||
),
|
||||
postForm(cls := "form3", action := routes.Clas.studentInvite(c.id.value))(
|
||||
form3.group(invite("invite"), frag("Invite username"))(
|
||||
form3.input(_, klass = "user-autocomplete")(autofocus)(dataTag := "span")
|
||||
),
|
||||
form3.submit("Invite")
|
||||
)
|
||||
),
|
||||
div(cls := "student-add__choice__create")(
|
||||
h2("Create a new Lichess account"),
|
||||
p(
|
||||
"If the student doesn't have a Lichess account yet, ",
|
||||
"you can create one for them here. ",
|
||||
br,
|
||||
"No email address is required. A password will be generated, ",
|
||||
"and you will have to transmit it to the student, so they can log in.",
|
||||
br,
|
||||
strong("Important: a student must not have multiple accounts."),
|
||||
" ",
|
||||
"If they already have one, use the invite form instead."
|
||||
),
|
||||
postForm(cls := "form3", action := routes.Clas.studentCreate(c.id.value))(
|
||||
form3.group(create("username"), frag("Create username"))(form3.input(_)(autofocus)),
|
||||
form3.submit(trans.signUp())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -444,8 +444,9 @@ POST /class/new controllers.Clas.create
|
|||
GET /class/$id<\w{8}> controllers.Clas.show(id: String)
|
||||
GET /class/$id<\w{8}>/edit controllers.Clas.edit(id: String)
|
||||
POST /class/$id<\w{8}>/edit controllers.Clas.update(id: String)
|
||||
GET /class/$id<\w{8}>/student/new controllers.Clas.studentForm(id: String)
|
||||
GET /class/$id<\w{8}>/student/add controllers.Clas.studentForm(id: String)
|
||||
POST /class/$id<\w{8}>/student/new controllers.Clas.studentCreate(id: String)
|
||||
POST /class/$id<\w{8}>/student/invite controllers.Clas.studentInvite(id: String)
|
||||
GET /class/$id<\w{8}>/student/:username controllers.Clas.studentShow(id: String, username: String)
|
||||
|
||||
# DB image
|
||||
|
|
|
@ -80,6 +80,8 @@ sealed trait Context extends lila.user.UserContextWrapper {
|
|||
def zoom: Int = {
|
||||
req.session get "zoom2" flatMap (_.toIntOption) map (_ - 100) filter (0 <=) filter (100 >=)
|
||||
} | 85
|
||||
|
||||
def flash(name: String): Option[String] = req.flash get name
|
||||
}
|
||||
|
||||
sealed abstract class BaseContext(
|
||||
|
|
|
@ -102,5 +102,7 @@ final class ClasApi(
|
|||
(user -> password)
|
||||
}
|
||||
}
|
||||
|
||||
def invite(clas: Clas, user: User): Funit = funit
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ package lila.clas
|
|||
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
final class ClasForm(
|
||||
lightUserAsync: lila.common.LightUser.Getter,
|
||||
securityForms: lila.security.DataForm
|
||||
) {
|
||||
|
||||
|
@ -26,6 +28,17 @@ final class ClasForm(
|
|||
object student {
|
||||
|
||||
def create = securityForms.signup.managed
|
||||
|
||||
def invite = Form(
|
||||
single(
|
||||
"invite" -> lila.user.DataForm.historicalUsernameField.verifying("Unknown username", {
|
||||
blockingFetchUser(_).isDefined
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
private def blockingFetchUser(username: String) =
|
||||
lightUserAsync(lila.user.User normalize username).await(1 second, "clasInviteUser")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import lila.common.config._
|
|||
final class Env(
|
||||
db: lila.db.Db,
|
||||
userRepo: lila.user.UserRepo,
|
||||
lightUserAsync: lila.common.LightUser.Getter,
|
||||
securityForms: lila.security.DataForm
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ object Student {
|
|||
private[clas] object password {
|
||||
|
||||
private val random = new java.security.SecureRandom()
|
||||
private val chars = ('2' to '9') ++ ('a' to 'z' - 'l') mkString
|
||||
private val chars = ('2' to '9') ++ (('a' to 'z').toSet - 'l') mkString
|
||||
private val nbChars = chars.size
|
||||
private def secureChar = chars(random nextInt nbChars)
|
||||
|
||||
|
|
|
@ -99,3 +99,30 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.student-add {
|
||||
|
||||
.flash-success {
|
||||
@extend %box-radius;
|
||||
padding: 1em 2em;
|
||||
margin: 2em 0;
|
||||
background: $c-good;
|
||||
color: $c-good-over;
|
||||
&::before {
|
||||
@extend %data-icon;
|
||||
content: 'E';
|
||||
margin-right: 1em;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&__choice {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(20em, 1fr));
|
||||
grid-gap: var(--box-padding);
|
||||
|
||||
h2 {
|
||||
margin: 1em 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue