diff --git a/app/controllers/UserController.scala b/app/controllers/UserController.scala
index e4888c884..1d3e32ff3 100644
--- a/app/controllers/UserController.scala
+++ b/app/controllers/UserController.scala
@@ -34,13 +34,17 @@ import models.EventType.{
UserEdited,
UserIsUsed,
UserNotFound,
+ UserProfileShowed,
+ UserProfileShowedError,
+ UserProfileUpdated,
+ UserProfileUpdatedError,
UserShowed,
UsersCreated,
UsersShowed,
ViewUserUnauthorized
}
import models._
-import models.formModels.ValidateSubscriptionForm
+import models.formModels.{EditProfileFormData, ValidateSubscriptionForm}
import org.postgresql.util.PSQLException
import org.webjars.play.WebJarsUtil
import play.api.Configuration
@@ -53,6 +57,7 @@ import play.filters.csrf.CSRF.Token
import serializers.{Keys, UserAndGroupCsvSerializer}
import services._
+import scala.concurrent.Future.successful
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
@@ -71,9 +76,73 @@ case class UserController @Inject() (
with UserOperators
with GroupOperators {
+ def showEditProfile =
+ loginAction.async { implicit request =>
+ // Should be better if User could contains List[UserGroup] instead of List[UUID]
+ val user = request.currentUser
+ val profile = EditProfileFormData(
+ user.firstName.orEmpty,
+ user.lastName.orEmpty,
+ user.qualite,
+ user.phoneNumber.orEmpty
+ )
+ val form = EditProfileFormData.form.fill(profile)
+ groupService
+ .byIdsFuture(user.groupIds)
+ .map { groups =>
+ eventService.log(UserProfileShowed, "Visualise la modification de profil")
+ Ok(views.html.editProfile(request.currentUser, request.rights)(form, user.email, groups))
+ }
+ .recoverWith { case exception =>
+ val message =
+ s"Impossible de visualiser la modification de profil : ${exception.getMessage}"
+ eventService.log(UserProfileShowedError, message)
+ Future.successful(InternalServerError(views.html.welcome(user, request.rights)))
+ }
+ }
+
+ def editProfile =
+ loginAction.async { implicit request =>
+ val user = request.currentUser
+ if (user.sharedAccount) {
+ eventService.log(UserProfileUpdatedError, "Impossible de modifier un profil partagé")
+ successful(BadRequest(views.html.welcome(user, request.rights)))
+ } else
+ EditProfileFormData.form
+ .bindFromRequest()
+ .fold(
+ errors => {
+ eventService.log(UserProfileUpdatedError, "Erreur lors de la modification du profil")
+ groupService
+ .byIdsFuture(user.groupIds)
+ .map(groups =>
+ BadRequest(
+ views.html.editProfile(user, request.rights)(errors, user.email, groups)
+ )
+ )
+ },
+ success => {
+ import success._
+ val edited =
+ userService.editProfile(user.id)(firstName, lastName, qualite, phoneNumber)
+ successful(edited)
+ .map { _ =>
+ val message = "Votre profil a bien été modifié"
+ eventService.log(UserProfileUpdated, message)
+ Redirect(routes.UserController.editProfile()).flashing("success" -> message)
+ }
+ .recover { e =>
+ val errorMessage = s"Erreur lors de la modification du profil: ${e.getMessage}"
+ eventService.log(UserProfileUpdatedError, errorMessage)
+ InternalServerError(views.html.welcome(user, request.rights))
+ }
+ }
+ )
+ }
+
def home =
loginAction {
- TemporaryRedirect(controllers.routes.UserController.all(Area.allArea.id).url)
+ TemporaryRedirect(routes.UserController.all(Area.allArea.id).url)
}
def all(areaId: UUID): Action[AnyContent] =
@@ -282,7 +351,7 @@ case class UserController @Inject() (
val message = s"Utilisateur $userId / ${user.email} a été supprimé"
eventService.log(UserDeleted, message, involvesUser = Some(user))
Future(
- Redirect(controllers.routes.UserController.home()).flashing("success" -> message)
+ Redirect(routes.UserController.home()).flashing("success" -> message)
)
}
}
diff --git a/app/helper/StringHelper.scala b/app/helper/StringHelper.scala
index 2319faf02..dd3ca1085 100644
--- a/app/helper/StringHelper.scala
+++ b/app/helper/StringHelper.scala
@@ -57,6 +57,11 @@ object StringHelper {
def unapply(s: String): Option[String] = s.some.map(_.trim).filter(_.nonEmpty)
}
+ implicit class StringOps(s: String) {
+ def normalized = StringHelper.commonStringInputNormalization(s)
+ def capitalizeWords = StringHelper.capitalizeName(s)
+ }
+
implicit class StringListOps(list: List[String]) {
def mkStringIfNonEmpty(start: String, sep: String, end: String) = {
diff --git a/app/models/EventType.scala b/app/models/EventType.scala
index 0857f2a44..d297a6ff6 100644
--- a/app/models/EventType.scala
+++ b/app/models/EventType.scala
@@ -145,4 +145,9 @@ object EventType {
object StatsIncorrectSetup extends Warn
object TryLoginByKey extends Info
object UnknownEmail extends Warn
+
+ object UserProfileShowed extends Info
+ object UserProfileShowedError extends Error
+ object UserProfileUpdated extends Info
+ object UserProfileUpdatedError extends Error
}
diff --git a/app/models/formModels.scala b/app/models/formModels.scala
index 91564f52d..e056b2ad1 100644
--- a/app/models/formModels.scala
+++ b/app/models/formModels.scala
@@ -4,11 +4,37 @@ import java.util.UUID
import play.api.data.Form
import play.api.data.Forms._
-import play.api.data.validation.Constraints.maxLength
+import play.api.data.validation.Constraints.{maxLength, nonEmpty, pattern}
import play.api.data.validation.{Constraint, Invalid, Valid, ValidationError}
object formModels {
+ final case class EditProfileFormData(
+ firstName: String,
+ lastName: String,
+ qualite: String,
+ phoneNumber: String
+ )
+
+ object EditProfileFormData {
+
+ val form: Form[EditProfileFormData] =
+ Form(
+ mapping(
+ "firstName" -> text.verifying(maxLength(100), nonEmpty),
+ "lastName" -> text.verifying(maxLength(100), nonEmpty),
+ "qualite" -> text.verifying(maxLength(100), nonEmpty),
+ "phone-number" -> text.verifying(
+ pattern(
+ """0\d \d{2} \d{2} \d{2} \d{2}""".r,
+ error = "Le format doit être XX XX XX XX XX"
+ )
+ )
+ )(EditProfileFormData.apply)(EditProfileFormData.unapply)
+ )
+
+ }
+
case class ApplicationFormData(
subject: String,
description: String,
diff --git a/app/services/UserService.scala b/app/services/UserService.scala
index ffe933da3..ee420738d 100644
--- a/app/services/UserService.scala
+++ b/app/services/UserService.scala
@@ -4,17 +4,12 @@ import java.util.UUID
import anorm._
import cats.syntax.all._
-import cats.implicits.{catsKernelStdMonoidForString, catsSyntaxOption}
+import helper.StringHelper.{capitalizeName, normalizeNFKC, StringOps}
import helper.{Hash, Time}
import javax.inject.Inject
import models.User
-import javax.inject.Inject
-import models.User
-import models.formModels.ValidateSubscriptionForm
import org.postgresql.util.PSQLException
import play.api.db.Database
-import play.api.db.Database
-import views.html.helper.form
import scala.concurrent.Future
@@ -251,4 +246,26 @@ class UserService @Inject() (
""".executeUpdate()
}
+ def editProfile(userId: UUID)(
+ firstName: String,
+ lastName: String,
+ qualite: String,
+ phoneNumber: String
+ ) =
+ db.withConnection { implicit cnx =>
+ val normalizedFirstName = firstName.normalized
+ val normalizedLastName = lastName.normalized
+ val normalizedQualite = qualite.normalized
+ val name = s"${normalizedLastName.toUpperCase} ${normalizedFirstName.capitalizeWords}"
+ SQL"""
+ UPDATE "user" SET
+ name = $name,
+ first_name = ${normalizedFirstName.capitalizeWords},
+ last_name = ${normalizedLastName.capitalizeWords},
+ qualite = $normalizedQualite,
+ phone_number = $phoneNumber
+ WHERE id = $userId::uuid
+ """.executeUpdate()
+ }
+
}
diff --git a/app/views/editProfile.scala.html b/app/views/editProfile.scala.html
new file mode 100644
index 000000000..cb8f1fceb
--- /dev/null
+++ b/app/views/editProfile.scala.html
@@ -0,0 +1,80 @@
+@import _root_.helper.MDLForms._
+@import models._
+@import models.formModels.EditProfileFormData
+@(currentUser: User, currentUserRights: Authorization.UserRights)(form: Form[EditProfileFormData], email: String, groups: List[UserGroup])(implicit webJarsUtil: org.webjars.play.WebJarsUtil, flash: Flash, messagesProvider: MessagesProvider, request: RequestHeader)
+
+ @main(currentUser, currentUserRights)(s"Mon profil") {
+
+ } {
+ @helper.form(action = routes.UserController.editProfile(), "method" -> "post", "class" -> "mdl-grid mdl-cell mdl-cell--12-col") {
+
Mon profil
+
+ Vous pouvez modifier les informations vous concernant
+
+ @helper.CSRF.formField
+
+ @if(form.hasGlobalErrors) {
+
@form.globalErrors.mkString(", ")
+ }
+ @helper.input(form("id"), "label" -> "Id", "class" -> "hidden") { (id, name, value, args) =>
+
+ }
+ @helper.input(form("email"), "label" -> "Email") { (id, name, _, args) =>
+
+ }
+ @helper.input(form("lastName"), "label" -> "Nom") { (id, name, value, args) =>
+
+ }
+ @helper.input(form("firstName"), "label" -> "Prénom") { (id, name, value, args) =>
+
+ }
+ @helper.input(form("qualite"), "label" -> "Fonction / Rôle") { (id, name, value, args) =>
+
+ }
+ @helper.input(form("phone-number"), "label" -> "Numéro de téléphone") { (id, name, value, args) =>
+
+ }
+ @if(groups.isEmpty) {
+
Vous n’appartenez à aucun groupe
+ }
+ @if(groups.nonEmpty) {
+
Vous appartenez aux groupes suivants :
+ @for(group <- groups) {
+
@group.name
+ }
+ }
+
+
+
+
+ }
+ } {
+ }
diff --git a/app/views/helpers/menuNav.scala.html b/app/views/helpers/menuNav.scala.html
index 4f7a48d9f..159c450c9 100644
--- a/app/views/helpers/menuNav.scala.html
+++ b/app/views/helpers/menuNav.scala.html
@@ -15,7 +15,6 @@
folder_openMes demandes
-
@if(Authorization.isAdmin(currentUserRights)) {
placeTerritoires
diff --git a/app/views/main.scala.html b/app/views/main.scala.html
index 3220ddb95..95116eeef 100644
--- a/app/views/main.scala.html
+++ b/app/views/main.scala.html
@@ -62,11 +62,20 @@
-
-
+
@currentUser.name
@currentUser.email
-
+ @if(!currentUser.sharedAccount) {
+
+
+
+
+
+ Mon profil
+
+ }
+
diff --git a/conf/routes b/conf/routes
index 5cb059cd8..d0000753e 100644
--- a/conf/routes
+++ b/conf/routes
@@ -46,6 +46,8 @@ GET /validation-connexion
# Users
+GET /me controllers.UserController.showEditProfile
+POST /me controllers.UserController.editProfile
GET /utilisateurs controllers.UserController.home
GET /utilisateurs/:userId controllers.UserController.editUser(userId: java.util.UUID)
POST /utilisateurs/:userId controllers.UserController.editUserPost(userId: java.util.UUID)
diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css
index 78989d53a..516bbe3d7 100644
--- a/public/stylesheets/main.css
+++ b/public/stylesheets/main.css
@@ -1,10 +1,11 @@
html, body {
- font-family: 'Roboto', 'Helvetica', sans-serif;
+ font-family: 'Roboto', 'Helvetica', sans-serif;
color: #00234d;
}
+
.logo {
- width: 126px;
- height: 100px;
+ width: 126px;
+ height: 100px;
}
.logo-header {
@@ -18,42 +19,48 @@ html, body {
}
.demo-layout .mdl-layout__header .mdl-layout__drawer-button {
- color: rgba(0, 0, 0, 0.54);
+ color: rgba(0, 0, 0, 0.54);
}
+
.mdl-layout__drawer .avatar {
- margin-bottom: 16px;
+ margin-bottom: 16px;
}
+
.demo-drawer {
- border: none;
+ border: none;
}
+
/* iOS Safari specific workaround */
.demo-drawer .mdl-menu__container {
- z-index: -1;
+ z-index: -1;
}
+
.demo-drawer .demo-navigation {
- z-index: -2;
+ z-index: -2;
}
+
/* END iOS Safari specific workaround */
.demo-drawer .mdl-menu .mdl-menu__item {
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-align-items: center;
- -ms-flex-align: center;
- align-items: center;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
}
+
.drawer-header, .drawer-footer {
- box-sizing: border-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-flex-direction: column;
- -ms-flex-direction: column;
- flex-direction: column;
- -webkit-justify-content: flex-end;
- -ms-flex-pack: end;
- justify-content: flex-end;
- padding: 16px;
+ box-sizing: border-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-justify-content: flex-end;
+ -ms-flex-pack: end;
+ justify-content: flex-end;
+ padding: 16px;
}
.drawer-footer {
@@ -61,9 +68,9 @@ html, body {
}
.navigation {
- -webkit-flex-grow: 1;
- -ms-flex-positive: 1;
- flex-grow: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
}
.mdl-navigation__link--active {
@@ -73,25 +80,26 @@ html, body {
}
.demo-layout .navigation .mdl-navigation__link {
- display: -webkit-flex !important;
- display: -ms-flexbox !important;
- display: flex !important;
- -webkit-flex-direction: row;
- -ms-flex-direction: row;
- flex-direction: row;
- -webkit-align-items: center;
- -ms-flex-align: center;
- align-items: center;
- font-weight: 500;
+ display: -webkit-flex !important;
+ display: -ms-flexbox !important;
+ display: flex !important;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+ font-weight: 500;
color: white;
}
+
.demo-layout .navigation .mdl-navigation__link:hover {
- background-color: #00BCD4;
- color: #37474F;
+ background-color: #00BCD4;
+ color: #37474F;
}
.demo-content {
- max-width: 1080px;
+ max-width: 1080px;
}
.mdl-button--raised.mdl-button--colored {
@@ -134,29 +142,37 @@ html, body {
.mdl-layout--fixed-drawer > .mdl-layout__content {
margin-left: 0px !important;
}
+
.mdl-data-table tbody tr {
height: 15px;
padding: 2px;
}
+
.mdl-data-table td {
height: 15px;
padding: 2px;
}
+
.mdl-cell--6-col-print {
width: calc(50% - 16px) !important;
}
+
.mdl-cell--8-col-print {
width: calc(66.6666666% - 16px) !important;
}
+
.mdl-cell--4-col-print {
width: calc(33.3333333% - 16px) !important;
}
+
.mdl-layout {
height: initial;
}
+
.mdl-data-table td:first-of-type, .mdl-data-table th:first-of-type {
padding-left: 2px;
}
+
.mdl-layout__content {
background-color: white !important;
height: initial !important;
@@ -194,7 +210,7 @@ html, body {
}
.do-not-print {
- display:none !important;
+ display: none !important;
}
}
@@ -274,11 +290,13 @@ html, body {
.hidden {
display: none;
}
+
@media (max-width: 767px) {
.hidden--small-screen {
display: none;
}
}
+
@media (min-width: 767px) {
.show--small-screen {
display: none;
@@ -291,16 +309,19 @@ html, body {
font-weight: 600;
line-height: 14px;
}
+
.application__name {
color: #555555;;
font-size: 14px;
font-weight: bold;
line-height: 16px;
}
+
.application__age {
font-style: italic;
font-size: 13px;
}
+
.application__anwsers {
font-size: 12px;
font-weight: bold;
@@ -320,9 +341,11 @@ html, body {
background-color: #e2181a;
color: white;
}
+
.tag--instructor {
background-color: #bee1ff;
}
+
.tag--aidant {
background-color: greenyellow;
}
@@ -341,7 +364,7 @@ dialog.dialog--50percent-width {
}
a {
- color: initial;
+ color: initial;
}
.mdl-textfield--large {
@@ -369,7 +392,7 @@ a {
border-top: 2px solid;
margin: 0 20px 0 20px;
flex: 1 0 20px;
-}
+}
.ariane {
font-weight: bold;
@@ -400,7 +423,7 @@ a {
}
.mdl-textarea--more-height {
- height: 20em;
+ height: 20em;
}
.--padding-20px {
@@ -427,7 +450,7 @@ a {
}
.notification--error {
- background: rgba(239,172,166,0.45882);
+ background: rgba(239, 172, 166, 0.45882);
border-color: #d63626;
}
@@ -436,7 +459,6 @@ a {
}
-
.avoid-clicks {
pointer-events: none;
}
@@ -445,8 +467,8 @@ a {
position: absolute;
top: 50%;
left: 50%;
- -webkit-transform: translate(-12px,-12px);
- transform: translate(-12px,-12px);
+ -webkit-transform: translate(-12px, -12px);
+ transform: translate(-12px, -12px);
line-height: 24px;
width: 20px;
}
@@ -476,11 +498,13 @@ a {
width: 200px;
max-width: 100%;
}
+
.aplus-textfield__label {
font-size: 16px;
margin-bottom: 5px;
display: inline-block;
}
+
.aplus-textfield__label__example {
font-style: italic;
color: #71777A;
@@ -642,6 +666,10 @@ a {
padding-left: 16px;
}
+.single--padding-left-40px {
+ padding-left: 40px;
+}
+
.single--padding-left-48px {
padding-left: 48px;
}
@@ -679,8 +707,8 @@ a {
.single--flex-grow-1 {
-webkit-box-flex: 1;
- -ms-flex-positive: 1;
- flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
}
.single--font-size-14px {
@@ -707,18 +735,18 @@ a {
.single--align-items-center {
-webkit-box-align: center;
- -ms-flex-align: center;
- align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
}
.single--align-content-flex-start {
-ms-flex-line-pack: start;
- align-content: flex-start;
+ align-content: flex-start;
}
.single--flex-wrap-wrap {
-ms-flex-wrap: wrap;
- flex-wrap: wrap;
+ flex-wrap: wrap;
}
.single--white-space-nowrap {
@@ -745,6 +773,10 @@ a {
font-weight: 600;
}
+.single--line-height-24px {
+ line-height: 24px !important;
+}
+
@media (max-width: 479px) {
.single--hidden-phone {
@@ -796,6 +828,7 @@ Chart
height: 300px;
width: 95%;
}
+
.chart--500px-height {
height: 500px;
width: 95%;
@@ -842,4 +875,9 @@ Tabs
border-radius: 50%;
background: red;
color: white;
+}
+
+.global-errors {
+ color: red;
+ font-weight: bold;
}
\ No newline at end of file