From efabaa4cf3103a5821b8815678bb04fc8d5ff7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Sun, 1 Aug 2021 13:44:08 +0200 Subject: [PATCH 1/6] Tickets load level --- IDEAS.md | 36 ++ app/Enums/LoadLevel.php | 35 ++ app/Lib/Utils.php | 68 +++ app/Providers/AppServiceProvider.php | 2 + config/app.php | 1 + config/tickets.php | 8 + resources/sass/_bootswatch.scss | 438 ++++++++++---------- resources/sass/_variables.scss | 40 +- resources/sass/app.scss | 5 + resources/views/layout/nav.blade.php | 6 +- resources/views/modules/loadLevel.blade.php | 16 + resources/views/ticket/index.blade.php | 2 +- 12 files changed, 414 insertions(+), 243 deletions(-) create mode 100644 app/Enums/LoadLevel.php create mode 100644 config/tickets.php create mode 100644 resources/views/modules/loadLevel.blade.php diff --git a/IDEAS.md b/IDEAS.md index a0cddf1..20bfe57 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -4,3 +4,39 @@ - ~~parse de @user + envoie de mail si un seul user est trouvé~~ - permettre de modifier son mot de passe ? (je crois que ça n'as pas été mis exprès) - ~~parser les liens http~~ + + +Algorithme charge de travail ticket + + +delais 7 jours 168h +travaillé 5 jours 37h +temps tickets 1 jours 7.4h + +personnes 4 (TpP) 3.7h / semaine * prs + +délais moyen ticket (Dmt) 0.75h + +délais max attente pour 1 ticket (Dam) 7jours + +niveaux de charge 4: - faible + - stable + - élevée + - saturation + +états tickets(Et) : - nouveaux (non traités) + - en attente (charge pendante) + - traités + +Algo : linéaire + +TpP : 3.7h +P : 4 + +Et(nvx) * Dam + +Nv Charge = (P * TpP) / (Et(nvx) * Dam) - (P * TpP) / (Et(nvx) * DmT) - (Et(att) * DmT) + + +Si nv Charge < 0 => saturation + diff --git a/app/Enums/LoadLevel.php b/app/Enums/LoadLevel.php new file mode 100644 index 0000000..0ceee0d --- /dev/null +++ b/app/Enums/LoadLevel.php @@ -0,0 +1,35 @@ + 0, + 'stable' => 1, + 'high' => 2, + 'overload' => 3 + ]; + } + + protected static function labels(): array + { + return [ + 'weak' => trans('Faible'), + 'stable' => trans('Stable'), + 'high' => trans('Élevée'), + 'overload' => trans('Surcharge') + ]; + } +} diff --git a/app/Lib/Utils.php b/app/Lib/Utils.php index 419eaee..a977e4f 100644 --- a/app/Lib/Utils.php +++ b/app/Lib/Utils.php @@ -2,6 +2,9 @@ namespace App\Lib; +use App\Enums\LoadLevel; +use Illuminate\Support\Facades\View; + class Utils { public static function plainToHtml(string $text) @@ -39,4 +42,69 @@ class Utils $m = []; return \preg_match('/(\.png|\.jpg|\.jpeg|\.gif)$/', $url, $m) === 1; } + + /** + * delais 7 jours 168h + * travaillé 5 jours 37h + * temps tickets 1 jours 7.4h + * + * personnes 4 (TpP) 3.7h / semaine * prs + * + * délais moyen ticket (Dmt) 0.75h + * + * délais max attente pour 1 ticket (Dam) 7jours + * + * niveaux de charge 4: - faible + * - stable + * - élevée + * - saturation + * + * états tickets(Et) : - nouveaux (non traités) + * - en attente (charge pendante) + * - traités + * + * Algo : linéaire + * + * TpP : 3.7h + * P : 4 + * + * Et(nvx) * Dam + * + * Nv Charge = (P * TpP) / (Et(nvx) * Dam) - (P * TpP) / (Et(nvx) * DmT) - (Et(att) * DmT) + * + * + * Si nv Charge < 0 => saturation + * + * @return void + */ + public static function computeWorkload(): LoadLevel + { + $Tnew = View::shared('ticketsNew'); + $Tprogress = View::shared('ticketsInProgress'); + $Twaiting = View::shared('ticketsWaiting'); + + $worked = config('tickets.workedHoursInAWeek'); + $workedDays = config('tickets.workedDaysInAWeek'); + $morningWorkTime = ($worked / $workedDays / 2); // in hours + $workingDevsPerWeek = config('tickets.workingDevsPerWeek'); // Devs handlings tickets per week + $timePerWeekDev = $workingDevsPerWeek * $morningWorkTime; // Worktime per week to handle tickets + + $avgTimeForTicket = config('tickets.avgTimeForTicket'); // 45min for a ticket + + $todoListDelay = $Tnew * $avgTimeForTicket; // The time needed to handle new ticket (average) + $waitingListDelay = $Twaiting * $avgTimeForTicket; // The time needed to handle new ticket (average) + $progressListDelay = $Tprogress * $avgTimeForTicket; // The time needed to handle new ticket (average) + + $totalTicketsDelay = $todoListDelay + $progressListDelay + $waitingListDelay; + $ETAForAnewTicket = $totalTicketsDelay / $timePerWeekDev; // in weeks + + switch ((int)round($ETAForAnewTicket)) { + case LoadLevel::weak()->value: + case LoadLevel::stable()->value: + case LoadLevel::high()->value: + return LoadLevel::make((int)round($ETAForAnewTicket)); + default: + return LoadLevel::overload(); + } + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 90586d2..e795128 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -41,6 +41,7 @@ class AppServiceProvider extends ServiceProvider View::share('license', $appInfos->license); View::share('ticketsNew', Ticket::where('status', TicketStatus::new())->count()); + View::share('ticketsWaiting', Ticket::where('status', TicketStatus::waiting())->count()); View::share('ticketsInProgress', Ticket::whereNotIn( 'status', [ @@ -49,6 +50,7 @@ class AppServiceProvider extends ServiceProvider TicketStatus::estimateValidated(), TicketStatus::toPlan(), TicketStatus::planned(), + TicketStatus::waiting(), TicketStatus::production(), TicketStatus::archived() ] diff --git a/config/app.php b/config/app.php index 1c43c85..8b1a654 100644 --- a/config/app.php +++ b/config/app.php @@ -233,6 +233,7 @@ return [ 'CrudType' => App\Enums\CrudType::class, 'Role' => App\Enums\Role::class, 'FileType' => App\Enums\FileType::class, + 'LoadLevel' => App\Enums\LoadLevel::class, ], ]; diff --git a/config/tickets.php b/config/tickets.php new file mode 100644 index 0000000..3e5600e --- /dev/null +++ b/config/tickets.php @@ -0,0 +1,8 @@ + 37, + 'workedDaysInAWeek' => 5, + 'workingDevsPerWeek' => 2, + 'avgTimeForTicket' => 0.95, // 0.95 heure +]; diff --git a/resources/sass/_bootswatch.scss b/resources/sass/_bootswatch.scss index a8748ac..fd73052 100644 --- a/resources/sass/_bootswatch.scss +++ b/resources/sass/_bootswatch.scss @@ -1,331 +1,331 @@ // Lumen 5.0.2 // Bootswatch - // Variables -@import 'variables'; +@import "variables"; $web-font-path: "https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,700;1,400&display=swap" !default; @if $web-font-path { - @import url($web-font-path); + @import url($web-font-path); } // Mixins -@mixin shadow($width: 4px){ - border-style: solid; - border-width: 0 1px $width 1px; +@mixin shadow($width: 4px) { + border-style: solid; + border-width: 0 1px $width 1px; } // Navbar .navbar { - @include shadow(); + @include shadow(); - @each $color, $value in $theme-colors { - &.bg-#{$color} { - border-color: shade-color($value, 10%); + @each $color, $value in $theme-colors { + &.bg-#{$color} { + border-color: shade-color($value, 10%); + } } - } } // Buttons .btn { - @include shadow(); - text-transform: uppercase; + @include shadow(); + text-transform: uppercase; - &:not(.disabled):hover { - margin-top: 1px; - border-bottom-width: 3px; - } - - &:not(.disabled):active { - margin-top: 2px; - border-bottom-width: 2px; - @include box-shadow(none); - } - - @each $color, $value in $theme-colors { - &-#{$color} { - &:hover, - &:active, - &:focus { - background-color: $value; - } - - &, - &:not(.disabled):hover, - &:not(.disabled):active, - &:focus { - border-color: shade-color($value, 10%); - } + &:not(.disabled):hover { + margin-top: 1px; + border-bottom-width: 3px; + } + + &:not(.disabled):active { + margin-top: 2px; + border-bottom-width: 2px; + @include box-shadow(none); + } + + @each $color, $value in $theme-colors { + &-#{$color} { + &:hover, + &:active, + &:focus { + background-color: $value; + } + + &, + &:not(.disabled):hover, + &:not(.disabled):active, + &:focus { + border-color: shade-color($value, 10%); + } + } } - } } [class*="btn-outline"] { - border-top-width: 1px; + border-top-width: 1px; } .btn-group-vertical { - .btn + .btn { - &:hover { - margin-top: -1px; - border-top-width: 1px; - } + .btn + .btn { + &:hover { + margin-top: -1px; + border-top-width: 1px; + } - &:active { - margin-top: -1px; - border-top-width: 2px; + &:active { + margin-top: -1px; + border-top-width: 2px; + } } - } } // Typography .text-secondary { - color: $gray-700 !important; + color: $gray-700 !important; } .blockquote-footer { - color: $gray-600; + color: $gray-600; } // Forms .form-control { - box-shadow: inset 0 2px 0 rgba(0, 0, 0, .075); + box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.075); } .input-group-sm { - > .input-group-prepend, - > .input-group-append { - .btn { - font-size: $btn-font-size-sm; + > .input-group-prepend, + > .input-group-append { + .btn { + font-size: $btn-font-size-sm; + } } - } } // Navs .nav { - .open > a, - .open > a:hover, - .open > a:focus { - border-color: transparent; - } + .open > a, + .open > a:hover, + .open > a:focus { + border-color: transparent; + } } .nav-tabs { - .nav-link { - color: $body-color; + .nav-link { + color: $body-color; - &, - &.disabled, - &.disabled:hover, - &.disabled:focus { - margin-top: 6px; - border-color: $nav-tabs-border-color; - transition: padding-bottom .2s ease-in-out, margin-top .2s ease-in-out, border-bottom .2s ease-in-out; + &, + &.disabled, + &.disabled:hover, + &.disabled:focus { + margin-top: 6px; + border-color: $nav-tabs-border-color; + transition: padding-bottom 0.2s ease-in-out, + margin-top 0.2s ease-in-out, border-bottom 0.2s ease-in-out; + } + + &:not(.disabled):hover, + &:not(.disabled):focus, + &.active { + padding-bottom: add(0.5rem, 6px); + border-bottom-color: transparent; + margin-top: 0; + } } - &:not(.disabled):hover, - &:not(.disabled):focus, - &.active { - padding-bottom: add(.5rem, 6px); - border-bottom-color: transparent; - margin-top: 0; + &.nav-justified > li { + vertical-align: bottom; } - } - - &.nav-justified > li { - vertical-align: bottom; - } } .dropdown-menu { - margin-top: 0; - @include shadow(); - border-top-width: 1px; - @include box-shadow(none); + margin-top: 0; + @include shadow(); + border-top-width: 1px; + @include box-shadow(none); } .breadcrumb { - border-color: shade-color($breadcrumb-bg, 10%); - @include shadow(); + border-color: shade-color($breadcrumb-bg, 10%); + @include shadow(); } .pagination { - > li > a, - > li > span { - position: relative; - top: 0; - @include shadow(); - color: $pagination-color; - font-weight: 700; - text-transform: uppercase; + > li > a, + > li > span { + position: relative; + top: 0; + @include shadow(); + color: $pagination-color; + font-weight: 700; + text-transform: uppercase; - &:hover, - &:focus { - top: 1px; - border-bottom-width: 3px; - text-decoration: none; + &:hover, + &:focus { + top: 1px; + border-bottom-width: 3px; + text-decoration: none; + } + + &:active { + top: 2px; + border-bottom-width: 2px; + } } - &:active { - top: 2px; - border-bottom-width: 2px; - } - } + > .disabled > a, + > .disabled > span { + &:hover { + top: 0; + @include shadow(); + } - > .disabled > a, - > .disabled > span { - &:hover { - top: 0; - @include shadow(); + &:active { + top: 0; + @include shadow(); + } } - - &:active { - top: 0; - @include shadow(); - } - } } .pager { - > li > a, - > li > span, - > .disabled > a, - > .disabled > span { - &, - &:hover, - &:active { - border-left-width: 2px; - border-right-width: 2px; + > li > a, + > li > span, + > .disabled > a, + > .disabled > span { + &, + &:hover, + &:active { + border-left-width: 2px; + border-right-width: 2px; + } } - } } // Indicators .btn-close { - text-decoration: none; - opacity: .4; + text-decoration: none; + opacity: 0.4; - &:hover, - &:focus { - opacity: 1; - } + &:hover, + &:focus { + opacity: 1; + } } .alert { - color: $white; - @include shadow(); - - &-primary { - background-color: $primary; - border-color: shade-color($primary, 10%); - } - - &-secondary { - background-color: $secondary; - border-color: shade-color($secondary, 10%); - } - - &-success { - background-color: $success; - border-color: shade-color($success, 10%); - } - - &-info { - background-color: $info; - border-color: shade-color($info, 10%); - } - - &-danger { - background-color: $danger; - border-color: shade-color($danger, 10%); - } - - &-warning { - background-color: $warning; - border-color: shade-color($warning, 10%); - } - - &-dark { - background-color: $dark; - border-color: shade-color($dark, 10%); - } - - &-light { - background-color: $light; - border-color: shade-color($light, 10%); - } - - .alert-link { - font-weight: 400; color: $white; - text-decoration: underline; - } + @include shadow(); - &-secondary, - &-light { - &, - a, - .alert-link { - color: $body-color; + &-primary { + background-color: $primary; + border-color: shade-color($primary, 10%); + } + + &-secondary { + background-color: $secondary; + border-color: shade-color($secondary, 10%); + } + + &-success { + background-color: $success; + border-color: shade-color($success, 10%); + } + + &-info { + background-color: $info; + border-color: shade-color($info, 10%); + } + + &-danger { + background-color: $danger; + border-color: shade-color($danger, 10%); + } + + &-warning { + background-color: $warning; + border-color: shade-color($warning, 10%); + } + + &-dark { + background-color: $dark; + border-color: shade-color($dark, 10%); + } + + &-light { + background-color: $light; + border-color: shade-color($light, 10%); + } + + .alert-link { + font-weight: 400; + color: $white; + text-decoration: underline; + } + + &-secondary, + &-light { + &, + a, + .alert-link { + color: $body-color; + } } - } } .badge { - &.bg-secondary, - &.bg-light { - color: $dark; - } + &.bg-secondary, + &.bg-light { + color: $dark; + } } // Containers a.list-group-item { - &-success { - &.active { - background-color: $success; + &-success { + &.active { + background-color: $success; + } + + &.active:hover, + &.active:focus { + background-color: shade-color($success, 10%); + } } - &.active:hover, - &.active:focus { - background-color: shade-color($success, 10%); - } - } + &-warning { + &.active { + background-color: $warning; + } - &-warning { - &.active { - background-color: $warning; + &.active:hover, + &.active:focus { + background-color: shade-color($warning, 10%); + } } - &.active:hover, - &.active:focus { - background-color: shade-color($warning, 10%); - } - } + &-danger { + &.active { + background-color: $danger; + } - &-danger { - &.active { - background-color: $danger; + &.active:hover, + &.active:focus { + background-color: shade-color($danger, 10%); + } } - - &.active:hover, - &.active:focus { - background-color: shade-color($danger, 10%); - } - } } .modal, .toast { - .btn-close { - background-image: url("data:image/svg+xml,"); - } + .btn-close { + background-image: url("data:image/svg+xml,"); + } } diff --git a/resources/sass/_variables.scss b/resources/sass/_variables.scss index 807b2e4..b937c82 100644 --- a/resources/sass/_variables.scss +++ b/resources/sass/_variables.scss @@ -9,48 +9,50 @@ $theme: "lumen" !default; // // Color system // -$lightness: -30%; -$saturation: -9%; +$lightness: -20%; +$saturation: -5%; @function lowerColor($color) { @return color.scale($color, $lightness: $lightness, $saturation: $saturation); } @function lowerBrightness($color) { - @return color.scale($color, $lightness: $lightness - 20%); + @return color.scale($color, $lightness: $lightness + 10%); } -$white: #fff !default; -$gray-100: lowerBrightness(#f6f6f6) !default; -$gray-200: lowerBrightness(#f0f0f0) !default; +$white: lowerBrightness(#f1efec) !default; +$gray-100: lowerBrightness(#f8f9fa) !default; +$gray-200: lowerBrightness(#e9ecef) !default; $gray-300: lowerBrightness(#dee2e6) !default; $gray-400: lowerBrightness(#ced4da) !default; $gray-500: lowerBrightness(#adb5bd) !default; -$gray-600: lowerBrightness(#999) !default; -$gray-700: lowerBrightness(#555) !default; -$gray-800: lowerBrightness(#333) !default; -$gray-900: lowerBrightness(#222) !default; +$gray-600: lowerBrightness(#6c757d) !default; +$gray-700: lowerBrightness(#495057) !default; +$gray-800: lowerBrightness(#343a40) !default; +$gray-900: lowerBrightness(#212529) !default; $black: #000 !default; -$blue: lowerColor(#158cba) !default; +$blue: lowerColor(#337ab7) !default; $indigo: lowerColor(#6610f2) !default; $purple: lowerColor(#6f42c1) !default; -$pink: lowerColor(#e83e8c) !default; -$red: lowerColor(#ff4136) !default; +$pink: lowerColor(#B7337A) !default; +$red: lowerColor(#d9534f) !default; $orange: lowerColor(#fd7e14) !default; -$yellow: lowerColor(#ff851b) !default; -$green: lowerColor(#28b62c) !default; +$yellow: lowerColor(#f0ad4e) !default; +$green: lowerColor(#5cb85c) !default; $teal: lowerColor(#20c997) !default; -$cyan: lowerColor(#75caeb) !default; +$cyan: lowerColor(#5bc0de) !default; $primary: $blue !default; -$secondary: $gray-200 !default; +$secondary: $gray-600 !default; $success: $green !default; -$info: $cyan !default; +$info: $teal !default; $warning: $yellow !default; $danger: $red !default; $light: $gray-100 !default; -$dark: $gray-700 !default; +$dark: $gray-900 !default; + +$body-bg: $white; $min-contrast-ratio: 1.95 !default; diff --git a/resources/sass/app.scss b/resources/sass/app.scss index b6c2081..e8c06d2 100644 --- a/resources/sass/app.scss +++ b/resources/sass/app.scss @@ -16,6 +16,11 @@ a.col_sort { max-width: 1340px !important; } +// background color +body { + background-color: $body-bg; +} + // FOOTER body { position: relative; diff --git a/resources/views/layout/nav.blade.php b/resources/views/layout/nav.blade.php index 36482f6..799b266 100644 --- a/resources/views/layout/nav.blade.php +++ b/resources/views/layout/nav.blade.php @@ -28,7 +28,7 @@ @endif diff --git a/resources/views/modules/loadLevel.blade.php b/resources/views/modules/loadLevel.blade.php new file mode 100644 index 0000000..e5e9987 --- /dev/null +++ b/resources/views/modules/loadLevel.blade.php @@ -0,0 +1,16 @@ +{{ __('Charge') }} :  + @switch(Utils::computeWorkload()) + @case(LoadLevel::weak()) + {{ Utils::computeWorkload()->label }} + @break + @case(LoadLevel::stable()) + {{ Utils::computeWorkload()->label }} + @break + @case(LoadLevel::high()) + {{ Utils::computeWorkload()->label }} + @break + @case(LoadLevel::overload()) + {{ Utils::computeWorkload()->label }} + @break + @endswitch + diff --git a/resources/views/ticket/index.blade.php b/resources/views/ticket/index.blade.php index 1a66129..49b1144 100644 --- a/resources/views/ticket/index.blade.php +++ b/resources/views/ticket/index.blade.php @@ -116,7 +116,7 @@ $userPr = Auth::user()->sites ?? collect([]); $cls = (!$cls && $ticket->status == TicketStatus::inprogress()) ? 'text-dark' : $cls; $cls = (!$cls && $ticket->status == TicketStatus::waiting()) ? 'text-white bg-danger' : $cls; @endphp - {{ $ticket->status->label }} + {{ $ticket->status->label }} {{-- Modal du ticket --}} From 4bd5614caae925e75047b165957e141bb9b5da11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Sun, 1 Aug 2021 18:19:55 +0200 Subject: [PATCH 2/6] =?UTF-8?q?Ticket=20264:=20changement=20de=20mot=20de?= =?UTF-8?q?=20passe=20-=20Passage=20=C3=A0=20laravel=20auth=20au=20lieux?= =?UTF-8?q?=20d'un=20code=20custom=20-=20suppression=20de=20LoginControlle?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Auth/ConfirmPasswordController.php | 39 ++++ .../Auth/ForgotPasswordController.php | 26 +++ app/Http/Controllers/Auth/LoginController.php | 39 ++++ .../Auth/ResetPasswordController.php | 51 ++++++ app/Http/Controllers/LoginController.php | 34 ---- app/Http/Controllers/UserController.php | 10 ++ app/Models/User.php | 6 + app/Notifications/ResetPassword.php | 30 ++++ app/Providers/AuthServiceProvider.php | 3 - app/Providers/RouteServiceProvider.php | 3 + composer.json | 3 +- composer.lock | 55 +++++- ...01_140823_create_password_resets_table.php | 32 ++++ resources/lang/fr.json | 10 ++ resources/lang/fr/validation-inline.php | 131 ++++++++++++++ resources/lang/fr/validation.php | 166 ++++++++---------- resources/views/auth/login.blade.php | 73 ++++++++ .../views/auth/passwords/confirm.blade.php | 49 ++++++ .../views/auth/passwords/email.blade.php | 47 +++++ .../views/auth/passwords/reset.blade.php | 65 +++++++ resources/views/layout/nav.blade.php | 3 + resources/views/login.blade.php | 67 ------- resources/views/users/passwordReset.blade.php | 37 ++++ .../views/vendor/mail/html/header.blade.php | 7 + routes/web.php | 23 ++- 25 files changed, 808 insertions(+), 201 deletions(-) create mode 100644 app/Http/Controllers/Auth/ConfirmPasswordController.php create mode 100644 app/Http/Controllers/Auth/ForgotPasswordController.php create mode 100644 app/Http/Controllers/Auth/LoginController.php create mode 100644 app/Http/Controllers/Auth/ResetPasswordController.php delete mode 100644 app/Http/Controllers/LoginController.php create mode 100644 app/Notifications/ResetPassword.php create mode 100644 database/migrations/2021_08_01_140823_create_password_resets_table.php create mode 100644 resources/lang/fr.json create mode 100644 resources/lang/fr/validation-inline.php create mode 100644 resources/views/auth/login.blade.php create mode 100644 resources/views/auth/passwords/confirm.blade.php create mode 100644 resources/views/auth/passwords/email.blade.php create mode 100644 resources/views/auth/passwords/reset.blade.php delete mode 100644 resources/views/login.blade.php create mode 100644 resources/views/users/passwordReset.blade.php create mode 100644 resources/views/vendor/mail/html/header.blade.php diff --git a/app/Http/Controllers/Auth/ConfirmPasswordController.php b/app/Http/Controllers/Auth/ConfirmPasswordController.php new file mode 100644 index 0000000..d90f81e --- /dev/null +++ b/app/Http/Controllers/Auth/ConfirmPasswordController.php @@ -0,0 +1,39 @@ +middleware('auth'); + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php new file mode 100644 index 0000000..19354dc --- /dev/null +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -0,0 +1,26 @@ +middleware('guest:web'); + } +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php new file mode 100644 index 0000000..018c583 --- /dev/null +++ b/app/Http/Controllers/Auth/LoginController.php @@ -0,0 +1,39 @@ +middleware('guest:web')->except('logout'); + } +} diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php new file mode 100644 index 0000000..6195419 --- /dev/null +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -0,0 +1,51 @@ + 'required', + 'email' => 'required|email', + 'password' => [ + 'required', 'confirmed', Password::min(8) + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->uncompromised() + ], + ]; + } +} diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php deleted file mode 100644 index 38af354..0000000 --- a/app/Http/Controllers/LoginController.php +++ /dev/null @@ -1,34 +0,0 @@ - $request->email, "password" => $request->password - ], $request->remember !== null ? true : false) - ) { - return redirect()->route('home'); - } else { - return back()->withInput($request->except("password")) - ->withErrors("Les identifiants entrés sont incorrects, veuillez réessayer."); - } - } - - public function logout() - { - Auth::logout(); - return redirect()->route('home'); - } -} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 0cafca9..1802678 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -2,8 +2,10 @@ namespace App\Http\Controllers; +use App\Http\Controllers\Auth\ForgotPasswordController; use App\Models\User; use Illuminate\Database\Eloquent\Model; +use Illuminate\Http\Request; use Kwaadpepper\CrudPolicies\Traits\CrudController; class UserController extends Controller @@ -21,4 +23,12 @@ class UserController extends Controller $model = $model; $model->sendMailConfirm(); } + + public function passwordResetSubmit() + { + /** @var \App\Models\User */ + $user = auth()->user(); + $request = new Request(['email' => $user->email]); + return app(ForgotPasswordController::class)->sendResetLinkEmail($request); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 5ac6653..bcaff45 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Enums\Role; use App\Jobs\SendUserCreatedEmail; +use App\Notifications\ResetPassword; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -190,6 +191,11 @@ class User extends Authenticatable dispatch(new SendUserCreatedEmail($this)); } + public function sendPasswordResetNotification($token) + { + $this->notify(new ResetPassword($token)); + } + /** * Is the user an admin or a developper ? * diff --git a/app/Notifications/ResetPassword.php b/app/Notifications/ResetPassword.php new file mode 100644 index 0000000..ecb89e6 --- /dev/null +++ b/app/Notifications/ResetPassword.php @@ -0,0 +1,30 @@ +subject(trans('Reinitialisation de mot de passe')) + ->line(trans('Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte.')) + ->action(trans('Réinitialiser le mot de passe'), $url) + ->line(trans('Ce lien de réinitialisation de mot de passe expirera dans :count minutes. ', ['count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')])) + ->line(trans('Si vous n\'avez pas demandé de réinitialisation de mot de passe, aucune autre action n\'est requise.')); + $message->viewData['greeting'] = 'Bonjour,'; + return $message; + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 565c5af..cb23efd 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,9 +2,6 @@ namespace App\Providers; -use App\Models\Ticket; -use App\Policies\SitePolicy; -use App\Policies\TicketPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f37779f..ec628cf 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -61,6 +61,9 @@ class RouteServiceProvider extends ServiceProvider */ protected function configureRateLimiting() { + RateLimiter::for('credentials', function (Request $request) { + return Limit::perMinute(30)->by(optional($request->user())->id ?: $request->ip()); + }); RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); }); diff --git a/composer.json b/composer.json index a7894c4..4d380ed 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,10 @@ "guzzlehttp/guzzle": "^7.0.1", "kwaadpepper/crud-policies": "^0.1.9", "kwaadpepper/enum": "^0.1.4", + "laravel-lang/lang": "~8.0", "laravel/framework": "^8.12", "laravel/tinker": "^2.5", - "laravel/ui": "^3.1" + "laravel/ui": "^3.3" }, "require-dev": { "facade/ignition": "^2.5", diff --git a/composer.lock b/composer.lock index dd7f334..9207fc2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "03b4961bbf5b8266d0317365ce892a88", + "content-hash": "0da9cba25dcff87fac2343ef3effa795", "packages": [ { "name": "asm89/stack-cors", @@ -1569,6 +1569,59 @@ }, "time": "2021-04-06T14:35:59+00:00" }, + { + "name": "laravel-lang/lang", + "version": "8.1.3", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/lang.git", + "reference": "54048d1319b211daa8ab73655b698feb09588876" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/54048d1319b211daa8ab73655b698feb09588876", + "reference": "54048d1319b211daa8ab73655b698feb09588876", + "shasum": "" + }, + "require": { + "ext-json": "*" + }, + "require-dev": { + "andrey-helldar/pretty-array": "^2.3", + "andrey-helldar/support": "^3.4", + "php": "^8.0", + "phpunit/phpunit": "^9.5", + "symfony/var-dumper": "^5.2" + }, + "suggest": { + "andrey-helldar/laravel-lang-publisher": "Easy installation and update of translation files for your project", + "arcanedev/laravel-lang": "Translations manager and checker for Laravel 5", + "overtrue/laravel-lang": "Command to add languages in your project" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "Languages for Laravel", + "keywords": [ + "lang", + "languages", + "laravel", + "lpm" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/lang/issues", + "source": "https://github.com/Laravel-Lang/lang" + }, + "time": "2021-04-14T17:41:45+00:00" + }, { "name": "laravel/framework", "version": "v8.52.0", diff --git a/database/migrations/2021_08_01_140823_create_password_resets_table.php b/database/migrations/2021_08_01_140823_create_password_resets_table.php new file mode 100644 index 0000000..0ee0a36 --- /dev/null +++ b/database/migrations/2021_08_01_140823_create_password_resets_table.php @@ -0,0 +1,32 @@ +string('email')->index(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('password_resets'); + } +} diff --git a/resources/lang/fr.json b/resources/lang/fr.json new file mode 100644 index 0000000..26f4a4d --- /dev/null +++ b/resources/lang/fr.json @@ -0,0 +1,10 @@ +{ + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si vous ne parvenez pas à cliquer sur le bouton \":actionText\", copiez et collez l'URL ci-dessous\ndans votre navigateur Web :", + "Regards": "Cordialement", + "All rights reserved.": "Tous droits réservés.", + "The :attribute must contain at least one uppercase and one lowercase letter.": ":attribute doit contenir au moins une lettre majuscule et une lettre minuscule.", + "The :attribute must contain at least one letter.": ":attribute doit contenir au moins une lettre.", + "The :attribute must contain at least one symbol.": ":attribute doit contenir au moins un caractère spécial.", + "The :attribute must contain at least one number.": ":attribute doit contenir au moins un nombre.", + "The given :attribute has appeared in a data leak. Please choose a different :attribute.": ":attribute apparait dans une fuite de données sur le web. Merci de choisir un :attribute différent." +} diff --git a/resources/lang/fr/validation-inline.php b/resources/lang/fr/validation-inline.php new file mode 100644 index 0000000..a06110a --- /dev/null +++ b/resources/lang/fr/validation-inline.php @@ -0,0 +1,131 @@ + 'Ce champ doit être accepté.', + 'active_url' => 'Ce n\'est pas une URL valide', + 'after' => 'La date doit être postérieure au :date.', + 'after_or_equal' => 'La date doit être postérieure ou égale au :date.', + 'alpha' => 'Ce champ doit contenir uniquement des lettres', + 'alpha_dash' => 'Ce champ doit contenir uniquement des lettres, des chiffres et des tirets.', + 'alpha_num' => 'Ce champ doit contenir uniquement des chiffres et des lettres.', + 'array' => 'Ce champ doit être un tableau.', + 'attached' => 'Ce champ est déjà attaché.', + 'before' => 'Ce champ doit être une date antérieure au :date.', + 'before_or_equal' => 'Ce champ doit être une date antérieure ou égale au :date.', + 'between' => [ + 'array' => 'Le tableau doit contenir entre :min et :max éléments.', + 'file' => 'La taille du fichier doit être comprise entre :min et :max kilo-octets.', + 'numeric' => 'La valeur doit être comprise entre :min et :max.', + 'string' => 'Le texte doit contenir entre :min et :max caractères.', + ], + 'boolean' => 'Ce champ doit être vrai ou faux.', + 'confirmed' => 'Le champ de confirmation ne correspond pas.', + 'date' => 'Ce n\'est pas une date valide.', + 'date_equals' => 'La date doit être égale à :date.', + 'date_format' => 'Ce champ ne correspond pas au format :format.', + 'different' => 'Cette valeur doit être différente de :other.', + 'digits' => 'Ce champ doit contenir :digits chiffres.', + 'digits_between' => 'Ce champ doit contenir entre :min et :max chiffres.', + 'dimensions' => 'La taille de l\'image n\'est pas conforme.', + 'distinct' => 'Ce champ a une valeur en double.', + 'email' => 'Ce champ doit être une adresse email valide.', + 'ends_with' => 'Ce champ doit se terminer par une des valeurs suivantes : :values', + 'exists' => 'Ce champ sélectionné est invalide.', + 'file' => 'Ce champ doit être un fichier.', + 'filled' => 'Ce champ doit avoir une valeur.', + 'gt' => [ + 'array' => 'Le tableau doit contenir plus de :value éléments.', + 'file' => 'La taille du fichier doit être supérieure à :value kilo-octets.', + 'numeric' => 'La valeur doit être supérieure à :value.', + 'string' => 'Le texte doit contenir plus de :value caractères.', + ], + 'gte' => [ + 'array' => 'Le tableau doit contenir au moins :value éléments.', + 'file' => 'La taille du fichier doit être supérieure ou égale à :value kilo-octets.', + 'numeric' => 'La valeur doit être supérieure ou égale à :value.', + 'string' => 'Le texte doit contenir au moins :value caractères.', + ], + 'image' => 'Ce champ doit être une image.', + 'in' => 'Ce champ est invalide.', + 'in_array' => 'Ce champ n\'existe pas dans :other.', + 'integer' => 'Ce champ doit être un entier.', + 'ip' => 'Ce champ doit être une adresse IP valide.', + 'ipv4' => 'Ce champ doit être une adresse IPv4 valide.', + 'ipv6' => 'Ce champ doit être une adresse IPv6 valide.', + 'json' => 'Ce champ doit être un document JSON valide.', + 'lt' => [ + 'array' => 'Le tableau doit contenir moins de :value éléments.', + 'file' => 'La taille du fichier doit être inférieure à :value kilo-octets.', + 'numeric' => 'La valeur doit être inférieure à :value.', + 'string' => 'Le texte doit contenir moins de :value caractères.', + ], + 'lte' => [ + 'array' => 'Le tableau doit contenir au plus :value éléments.', + 'file' => 'La taille du fichier doit être inférieure ou égale à :value kilo-octets.', + 'numeric' => 'La valeur doit être inférieure ou égale à :value.', + 'string' => 'Le texte doit contenir au plus :value caractères.', + ], + 'max' => [ + 'array' => 'Le tableau ne peut contenir plus de :max éléments.', + 'file' => 'La taille du fichier ne peut pas dépasser :max kilo-octets.', + 'numeric' => 'La valeur ne peut être supérieure à :max.', + 'string' => 'Le texte ne peut contenir plus de :max caractères.', + ], + 'mimes' => 'Le fichier doit être de type : :values.', + 'mimetypes' => 'Le fichier doit être de type : :values.', + 'min' => [ + 'array' => 'Le tableau doit contenir au moins :min éléments.', + 'file' => 'La taille du fichier doit être supérieure à :min kilo-octets.', + 'numeric' => 'La valeur doit être supérieure ou égale à :min.', + 'string' => 'Le texte doit contenir au moins :min caractères.', + ], + 'multiple_of' => 'La valeur doit être un multiple de :value', + 'not_in' => 'Le champ sélectionné n\'est pas valide.', + 'not_regex' => 'Le format du champ n\'est pas valide.', + 'numeric' => 'Ce champ doit contenir un nombre.', + 'password' => 'Le mot de passe est incorrect', + 'present' => 'Ce champ doit être présent.', + 'prohibited' => 'Ce champ est interdit', + 'prohibited_if' => 'Ce champ est interdit quand :other a la valeur :value.', + 'prohibited_unless' => 'Ce champ est interdit à moins que :other ait l\'une des valeurs :values.', + 'regex' => 'Le format du champ est invalide.', + 'relatable' => 'Ce champ n\'est sans doute pas associé avec cette donnée.', + 'required' => 'Ce champ est obligatoire.', + 'required_if' => 'Ce champ est obligatoire quand la valeur de :other est :value.', + 'required_unless' => 'Ce champ est obligatoire sauf si :other est :values.', + 'required_with' => 'Ce champ est obligatoire quand :values est présent.', + 'required_with_all' => 'Ce champ est obligatoire quand :values sont présents.', + 'required_without' => 'Ce champ est obligatoire quand :values n\'est pas présent.', + 'required_without_all' => 'Ce champ est requis quand aucun de :values n\'est présent.', + 'same' => 'Ce champ doit être identique à :other.', + 'size' => [ + 'array' => 'Le tableau doit contenir :size éléments.', + 'file' => 'La taille du fichier doit être de :size kilo-octets.', + 'numeric' => 'La valeur doit être :size.', + 'string' => 'Le texte doit contenir :size caractères.', + ], + 'starts_with' => 'Ce champ doit commencer avec une des valeurs suivantes : :values', + 'string' => 'Ce champ doit être une chaîne de caractères.', + 'timezone' => 'Ce champ doit être un fuseau horaire valide.', + 'unique' => 'La valeur est déjà utilisée.', + 'uploaded' => 'Le fichier n\'a pu être téléversé.', + 'url' => 'Le format de l\'URL n\'est pas valide.', + 'uuid' => 'Ce champ doit être un UUID valide', + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + 'attributes' => [], +]; diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index b0f5da5..5940e43 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -1,42 +1,43 @@ 'Le champ :attribute doit être accepté.', - 'active_url' => "Le champ :attribute n'est pas une URL valide.", + 'active_url' => 'Le champ :attribute n\'est pas une URL valide.', 'after' => 'Le champ :attribute doit être une date postérieure au :date.', 'after_or_equal' => 'Le champ :attribute doit être une date postérieure ou égale au :date.', 'alpha' => 'Le champ :attribute doit contenir uniquement des lettres.', 'alpha_dash' => 'Le champ :attribute doit contenir uniquement des lettres, des chiffres et des tirets.', 'alpha_num' => 'Le champ :attribute doit contenir uniquement des chiffres et des lettres.', 'array' => 'Le champ :attribute doit être un tableau.', + 'attached' => ':attribute est déjà attaché(e).', 'before' => 'Le champ :attribute doit être une date antérieure au :date.', 'before_or_equal' => 'Le champ :attribute doit être une date antérieure ou égale au :date.', 'between' => [ - 'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', - 'file' => 'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.', - 'string' => 'Le texte :attribute doit contenir entre :min et :max caractères.', 'array' => 'Le tableau :attribute doit contenir entre :min et :max éléments.', + 'file' => 'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.', + 'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', + 'string' => 'Le texte :attribute doit contenir entre :min et :max caractères.', ], 'boolean' => 'Le champ :attribute doit être vrai ou faux.', 'confirmed' => 'Le champ de confirmation :attribute ne correspond pas.', - 'date' => "Le champ :attribute n'est pas une date valide.", + 'date' => 'Le champ :attribute n\'est pas une date valide.', 'date_equals' => 'Le champ :attribute doit être une date égale à :date.', 'date_format' => 'Le champ :attribute ne correspond pas au format :format.', 'different' => 'Les champs :attribute et :other doivent être différents.', 'digits' => 'Le champ :attribute doit contenir :digits chiffres.', 'digits_between' => 'Le champ :attribute doit contenir entre :min et :max chiffres.', - 'dimensions' => "La taille de l'image :attribute n'est pas conforme.", + 'dimensions' => 'La taille de l\'image :attribute n\'est pas conforme.', 'distinct' => 'Le champ :attribute a une valeur en double.', 'email' => 'Le champ :attribute doit être une adresse email valide.', 'ends_with' => 'Le champ :attribute doit se terminer par une des valeurs suivantes : :values', @@ -44,137 +45,118 @@ return [ 'file' => 'Le champ :attribute doit être un fichier.', 'filled' => 'Le champ :attribute doit avoir une valeur.', 'gt' => [ - 'numeric' => 'La valeur de :attribute doit être supérieure à :value.', - 'file' => 'La taille du fichier de :attribute doit être supérieure à :value kilo-octets.', - 'string' => 'Le texte :attribute doit contenir plus de :value caractères.', 'array' => 'Le tableau :attribute doit contenir plus de :value éléments.', + 'file' => 'La taille du fichier de :attribute doit être supérieure à :value kilo-octets.', + 'numeric' => 'La valeur de :attribute doit être supérieure à :value.', + 'string' => 'Le texte :attribute doit contenir plus de :value caractères.', ], 'gte' => [ - 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.', - 'file' => 'La taille du fichier de :attribute doit être supérieure ou égale à :value kilo-octets.', - 'string' => 'Le texte :attribute doit contenir au moins :value caractères.', 'array' => 'Le tableau :attribute doit contenir au moins :value éléments.', + 'file' => 'La taille du fichier de :attribute doit être supérieure ou égale à :value kilo-octets.', + 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.', + 'string' => 'Le texte :attribute doit contenir au moins :value caractères.', ], 'image' => 'Le champ :attribute doit être une image.', 'in' => 'Le champ :attribute est invalide.', - 'in_array' => "Le champ :attribute n'existe pas dans :other.", + 'in_array' => 'Le champ :attribute n\'existe pas dans :other.', 'integer' => 'Le champ :attribute doit être un entier.', 'ip' => 'Le champ :attribute doit être une adresse IP valide.', 'ipv4' => 'Le champ :attribute doit être une adresse IPv4 valide.', 'ipv6' => 'Le champ :attribute doit être une adresse IPv6 valide.', 'json' => 'Le champ :attribute doit être un document JSON valide.', 'lt' => [ - 'numeric' => 'La valeur de :attribute doit être inférieure à :value.', - 'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.', - 'string' => 'Le texte :attribute doit contenir moins de :value caractères.', 'array' => 'Le tableau :attribute doit contenir moins de :value éléments.', + 'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.', + 'numeric' => 'La valeur de :attribute doit être inférieure à :value.', + 'string' => 'Le texte :attribute doit contenir moins de :value caractères.', ], 'lte' => [ - 'numeric' => 'La valeur de :attribute doit être inférieure ou égale à :value.', - 'file' => 'La taille du fichier de :attribute doit être inférieure ou égale à :value kilo-octets.', - 'string' => 'Le texte :attribute doit contenir au plus :value caractères.', 'array' => 'Le tableau :attribute doit contenir au plus :value éléments.', + 'file' => 'La taille du fichier de :attribute doit être inférieure ou égale à :value kilo-octets.', + 'numeric' => 'La valeur de :attribute doit être inférieure ou égale à :value.', + 'string' => 'Le texte :attribute doit contenir au plus :value caractères.', ], 'max' => [ - 'numeric' => 'La valeur de :attribute ne peut être supérieure à :max.', - 'file' => 'La taille du fichier de :attribute ne peut pas dépasser :max kilo-octets.', - 'string' => 'Le texte de :attribute ne peut contenir plus de :max caractères.', 'array' => 'Le tableau :attribute ne peut contenir plus de :max éléments.', + 'file' => 'La taille du fichier de :attribute ne peut pas dépasser :max kilo-octets.', + 'numeric' => 'La valeur de :attribute ne peut être supérieure à :max.', + 'string' => 'Le texte de :attribute ne peut contenir plus de :max caractères.', ], 'mimes' => 'Le champ :attribute doit être un fichier de type : :values.', 'mimetypes' => 'Le champ :attribute doit être un fichier de type : :values.', 'min' => [ - 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :min.', - 'file' => 'La taille du fichier de :attribute doit être supérieure à :min kilo-octets.', - 'string' => 'Le texte :attribute doit contenir au moins :min caractères.', 'array' => 'Le tableau :attribute doit contenir au moins :min éléments.', + 'file' => 'La taille du fichier de :attribute doit être supérieure à :min kilo-octets.', + 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :min.', + 'string' => 'Le texte :attribute doit contenir au moins :min caractères.', ], 'multiple_of' => 'La valeur de :attribute doit être un multiple de :value', - 'not_in' => "Le champ :attribute sélectionné n'est pas valide.", - 'not_regex' => "Le format du champ :attribute n'est pas valide.", + 'not_in' => 'Le champ :attribute sélectionné n\'est pas valide.', + 'not_regex' => 'Le format du champ :attribute n\'est pas valide.', 'numeric' => 'Le champ :attribute doit contenir un nombre.', 'password' => 'Le mot de passe est incorrect', 'present' => 'Le champ :attribute doit être présent.', + 'prohibited' => 'Le champ :attribute est interdit.', + 'prohibited_if' => 'Le champ :attribute est interdit quand :other a la valeur :value.', + 'prohibited_unless' => 'Le champ :attribute est interdit à moins que :other est l\'une des valeurs :values.', 'regex' => 'Le format du champ :attribute est invalide.', + 'relatable' => ':attribute n\'est sans doute pas associé(e) avec cette donnée.', 'required' => 'Le champ :attribute est obligatoire.', 'required_if' => 'Le champ :attribute est obligatoire quand la valeur de :other est :value.', 'required_unless' => 'Le champ :attribute est obligatoire sauf si :other est :values.', 'required_with' => 'Le champ :attribute est obligatoire quand :values est présent.', 'required_with_all' => 'Le champ :attribute est obligatoire quand :values sont présents.', - 'required_without' => "Le champ :attribute est obligatoire quand :values n'est pas présent.", - 'required_without_all' => "Le champ :attribute est requis quand aucun de :values n'est présent.", + 'required_without' => 'Le champ :attribute est obligatoire quand :values n\'est pas présent.', + 'required_without_all' => 'Le champ :attribute est requis quand aucun de :values n\'est présent.', 'same' => 'Les champs :attribute et :other doivent être identiques.', 'size' => [ - 'numeric' => 'La valeur de :attribute doit être :size.', - 'file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.', - 'string' => 'Le texte de :attribute doit contenir :size caractères.', 'array' => 'Le tableau :attribute doit contenir :size éléments.', + 'file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.', + 'numeric' => 'La valeur de :attribute doit être :size.', + 'string' => 'Le texte de :attribute doit contenir :size caractères.', ], 'starts_with' => 'Le champ :attribute doit commencer avec une des valeurs suivantes : :values', 'string' => 'Le champ :attribute doit être une chaîne de caractères.', 'timezone' => 'Le champ :attribute doit être un fuseau horaire valide.', 'unique' => 'La valeur du champ :attribute est déjà utilisée.', - 'uploaded' => "Le fichier du champ :attribute n'a pu être téléversé.", - 'url' => "Le format de l'URL de :attribute n'est pas valide.", + 'uploaded' => 'Le fichier du champ :attribute n\'a pu être téléversé.', + 'url' => 'Le format de l\'URL de :attribute n\'est pas valide.', 'uuid' => 'Le champ :attribute doit être un UUID valide', - - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - - 'custom' => [ + 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], - - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - - 'attributes' => [ - 'name' => 'nom', - 'username' => "nom d'utilisateur", + 'attributes' => [ + 'address' => 'adresse', + 'age' => 'âge', + 'available' => 'disponible', + 'city' => 'ville', + 'content' => 'contenu', + 'country' => 'pays', + 'current_password' => 'mot de passe actuel', + 'date' => 'date', + 'day' => 'jour', + 'description' => 'description', 'email' => 'adresse email', + 'excerpt' => 'extrait', 'first_name' => 'prénom', + 'gender' => 'genre', + 'hour' => 'heure', 'last_name' => 'nom', + 'minute' => 'minute', + 'mobile' => 'portable', + 'month' => 'mois', + 'name' => 'nom', 'password' => 'mot de passe', 'password_confirmation' => 'confirmation du mot de passe', - 'city' => 'ville', - 'country' => 'pays', - 'address' => 'adresse', 'phone' => 'téléphone', - 'mobile' => 'portable', - 'age' => 'âge', - 'sex' => 'sexe', - 'gender' => 'genre', - 'day' => 'jour', - 'month' => 'mois', - 'year' => 'année', - 'hour' => 'heure', - 'minute' => 'minute', 'second' => 'seconde', - 'title' => 'titre', - 'content' => 'contenu', - 'description' => 'description', - 'excerpt' => 'extrait', - 'date' => 'date', - 'time' => 'heure', - 'available' => 'disponible', + 'sex' => 'sexe', 'size' => 'taille', + 'time' => 'heure', + 'title' => 'titre', + 'username' => 'nom d\'utilisateur', + 'year' => 'année', ], ]; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php new file mode 100644 index 0000000..14ffd0c --- /dev/null +++ b/resources/views/auth/login.blade.php @@ -0,0 +1,73 @@ +@extends('layout.app') + +@section('content') +
+
+
+
+
{{ __('Connexion') }}
+ +
+
+ @csrf + +
+ + +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + + +
+
+
+ +
+
+ + + @if (Route::has('password.request')) + + {{ __('Mot de passe oublié ?') }} + + @endif +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/passwords/confirm.blade.php b/resources/views/auth/passwords/confirm.blade.php new file mode 100644 index 0000000..74fc023 --- /dev/null +++ b/resources/views/auth/passwords/confirm.blade.php @@ -0,0 +1,49 @@ +@extends('layout.app') + +@section('content') +
+
+
+
+
{{ __('Confirmez votre mot de passe') }}
+ +
+ {{ __('Veuillez confirmer votre mot de passe pour continuer') }} + +
+ @csrf + +
+ + +
+ + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+
+ + + @if (Route::has('password.request')) + + {{ __('Mot de passe oublié ?') }} + + @endif +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/passwords/email.blade.php b/resources/views/auth/passwords/email.blade.php new file mode 100644 index 0000000..29a4951 --- /dev/null +++ b/resources/views/auth/passwords/email.blade.php @@ -0,0 +1,47 @@ +@extends('layout.app') + +@section('content') +
+
+
+
+
{{ __('Réinitialisation de mot de passe') }}
+ +
+ @if (session('status')) + + @endif + +
+ @csrf + +
+ + +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+
+ +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/passwords/reset.blade.php b/resources/views/auth/passwords/reset.blade.php new file mode 100644 index 0000000..a738743 --- /dev/null +++ b/resources/views/auth/passwords/reset.blade.php @@ -0,0 +1,65 @@ +@extends('layout.app') + +@section('content') +
+
+
+
+
{{ __('Réinitialisation de mot de passe') }}
+ +
+
+ @csrf + + + +
+ + +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/layout/nav.blade.php b/resources/views/layout/nav.blade.php index 799b266..76c0790 100644 --- a/resources/views/layout/nav.blade.php +++ b/resources/views/layout/nav.blade.php @@ -62,6 +62,9 @@