From 43bc62bf81298d55ce24642bfb8c2646b26894a2 Mon Sep 17 00:00:00 2001 From: Louis Jeckel Date: Thu, 30 Apr 2020 20:55:39 +0200 Subject: [PATCH] register flow --- .../Controllers/Auth/RegisterController.php | 41 +++- .../Auth/VerificationController.php | 10 + app/Notifications/EmailValidated.php | 62 ++++++ app/Observers/UserObserver.php | 5 +- app/Providers/AppServiceProvider.php | 3 + app/Providers/NovaServiceProvider.php | 1 - app/Providers/RouteServiceProvider.php | 2 +- app/User.php | 176 +++++++++++++----- ...020_04_30_093507_add_employer_to_users.php | 34 ++++ ...4_30_103918_add_prospect_flag_to_users.php | 32 ++++ resources/lang/fr/validation.php | 4 +- resources/views/account/index.blade.php | 9 +- resources/views/auth/register.blade.php | 43 ++++- routes/web.php | 3 +- 14 files changed, 363 insertions(+), 62 deletions(-) create mode 100644 app/Notifications/EmailValidated.php create mode 100644 database/migrations/2020_04_30_093507_add_employer_to_users.php create mode 100644 database/migrations/2020_04_30_103918_add_prospect_flag_to_users.php diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 56a9c22..88a7faf 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -5,10 +5,12 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use App\User; +use \Illuminate\Database\Query\Builder; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; class RegisterController extends Controller { @@ -51,8 +53,17 @@ class RegisterController extends Controller protected function validator(array $data) { return Validator::make($data, [ - 'name' => ['required', 'string', 'max:255'], - 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'first_name' => ['required', 'string', 'max:255'], + 'last_name' => ['required', 'string', 'max:255'], + 'employer' => ['required', 'string', 'max:255'], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + Rule::unique('users') + ->where(fn(Builder $builder) => $builder->where('is_prospect', false)) + ], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]); } @@ -65,11 +76,27 @@ class RegisterController extends Controller */ protected function create(array $data) { - return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => Hash::make($data['password']), - ]); + /** @var User $user */ + $user = User::query()->updateOrCreate( + [ + 'email' => $data['email'], + ], + [ + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'employer' => $data['employer'], + 'password' => Hash::make($data['password']), + 'reg_complete' => true, + 'is_prospect' => false, + 'self_registered' => true, + ] + ); + + $user->startTrial(); + + $user->sendEmailVerificationNotification(); + + return $user; } diff --git a/app/Http/Controllers/Auth/VerificationController.php b/app/Http/Controllers/Auth/VerificationController.php index 5e749af..cbb8e29 100644 --- a/app/Http/Controllers/Auth/VerificationController.php +++ b/app/Http/Controllers/Auth/VerificationController.php @@ -3,8 +3,10 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Notifications\EmailValidated; use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Auth\VerifiesEmails; +use Illuminate\Http\Request; class VerificationController extends Controller { @@ -39,4 +41,12 @@ class VerificationController extends Controller $this->middleware('signed')->only('verify'); $this->middleware('throttle:6,1')->only('verify', 'resend'); } + + /** + * @param Request $request + */ + protected function verified(Request $request) + { + $request->user()->notify(new EmailValidated); + } } diff --git a/app/Notifications/EmailValidated.php b/app/Notifications/EmailValidated.php new file mode 100644 index 0000000..d6038dd --- /dev/null +++ b/app/Notifications/EmailValidated.php @@ -0,0 +1,62 @@ +subject('Inscription réussie !') + ->line("Bienvenue ! Vous bénéficiez dès aujourd'hui d'une période d'essai de ".User::trialDurationDays." jours.") + ->line('Merci [....]'); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index 0266237..7131edb 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -35,9 +35,10 @@ class UserObserver public function updating(User $user) { if( + $user->self_registered === false && + $user->reg_complete && $user->isDirty('reg_complete') && - $user->getOriginal()['reg_complete'] === false && - $user->reg_complete + $user->getOriginal()['reg_complete'] === false ) { $user->notify(new RegistrationComplete); } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3280f56..93c6fd9 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -7,6 +7,7 @@ use App\Observers\UserObserver; use App\PdfFile; use App\User; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Support\Carbon; use Illuminate\Support\ServiceProvider; use League\HTMLToMarkdown\HtmlConverter; use Mailgun\Mailgun; @@ -49,6 +50,8 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { + setlocale(LC_TIME, 'fr_FR', 'fr', 'FR', 'French', 'fr_FR.UTF-8'); + Carbon::setLocale('fr'); Relation::morphMap([ 'PdfFiles' => PdfFile::class, ]); diff --git a/app/Providers/NovaServiceProvider.php b/app/Providers/NovaServiceProvider.php index 3b30524..d2697a9 100644 --- a/app/Providers/NovaServiceProvider.php +++ b/app/Providers/NovaServiceProvider.php @@ -67,7 +67,6 @@ class NovaServiceProvider extends NovaApplicationServiceProvider new FileAccess, new MailEvents('opened', 'Mails ouverts'), new MailEventsPartition, - ]; } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 540d17b..3a816d8 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -21,7 +21,7 @@ class RouteServiceProvider extends ServiceProvider * * @var string */ - public const HOME = '/home'; + public const HOME = '/'; /** * Define your route model bindings, pattern filters, etc. diff --git a/app/User.php b/app/User.php index cc895f7..3af8e89 100644 --- a/app/User.php +++ b/app/User.php @@ -3,6 +3,7 @@ namespace App; +use App\Notifications\EmailValidated; use DemeterChain\B; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Builder; @@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Arr; use Illuminate\Support\Carbon; use Laravel\Cashier\Billable; use Laravel\Scout\Searchable; @@ -23,8 +25,13 @@ use Laravel\Scout\Searchable; * @property-read $name * @property Organization $organization * @property string $position - * @property bool $isSubscribed - * @property Carbon $trial_ends_at + * @property bool $isSubscribed (is user has org subscribed) + * @property Carbon $trial_ends_at (is user is on trial) + * @property bool self_registered (if user used /register) + * @property string $employer (used if self registered) + * @property bool $is_prospect + * @property bool $reg_complete + * @property string $status */ class User extends Authenticatable { @@ -71,38 +78,15 @@ class User extends Authenticatable 'reg_complete' => 'bool', 'trial_ends_at' => 'datetime', 'active_until' => 'datetime', + 'self_registered' => 'bool', + 'is_prospect' => 'bool', ]; - public function toSearchableArray() - { - return [ - 'first_name' => $this->first_name, - 'last_name' => $this->last_name, - 'position' => $this->position, - 'organization' => $this->organization->name ?? null, - ]; - } - - public const trialDurationDays = 14; - - /** - * @return BelongsTo - */ - public function organization(): BelongsTo - { - return $this->belongsTo(Organization::class); - } - /** - * @return bool + * Trial duration in days */ - public function getIsSubscribedAttribute(): bool - { - return ($o = $this->organization) === null ? - false: - $o->isSubscribed(); - } + public const trialDurationDays = 14; /** * Possible Statuses @@ -117,36 +101,50 @@ class User extends Authenticatable 'label' => 'Abonnement actif (orga)' ], 'trial' => [ - 'badge' => 'warning', + 'badge' => 'info', 'label' => "Période d'essai" ], + 'prospect' => [ + 'badge' => 'warning', + 'label' => 'Prospect', + ] ]; + + + + + /** - * @return string + * @return array */ - public function getStatusAttribute(): string + public function toSearchableArray() { - $id = 'inactive'; + return [ + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'position' => $this->position, + 'organization' => $this->organization->name ?? null, + ]; + } + - if($this->isSubscribed) - $id = 'subscribed'; - if($this->onTrial()) - $id = 'trial'; - return $id; - } /** - * @return string - * Returns current status + * RELATIONSHIPS */ - public function getStatusLabelAttribute(): string + + /** + * @return BelongsTo + */ + public function organization(): BelongsTo { - return self::statuses[$this->status]['label']; + return $this->belongsTo(Organization::class); } + /** * @return HasMany */ @@ -155,6 +153,14 @@ class User extends Authenticatable return $this->hasMany(LoginToken::class); } + + + + + /** + * METHODS + */ + /** * @param $route * @param array $params @@ -178,32 +184,115 @@ class User extends Authenticatable $this->save(); } + /** + * @param $status + * @return bool + */ + public function hasStatus($status): bool + { + $status = Arr::wrap($status); + return in_array($this->status, $status, true); + } + /** + * SCOPES + */ + /** + * @param Builder $builder + */ public function scopeRecievesEmails(Builder $builder): void { $builder->hasActiveSubscription()->orWhere->isOnTrial(); } + /** + * @param Builder $builder + */ public function scopeIsOnTrial(Builder $builder): void { $builder->whereDate('trial_ends_at', '>', now()); } + /** + * @param Builder $builder + */ public function scopeHasActiveSubscription(Builder $builder): void { $builder->whereHas('organization', fn($builder) => $builder->subscribed()); } + /** + * @param Builder $builder + */ + public function scopeProspect(Builder $builder): void + { + $builder->where('is_prospect', true); + } + + /** + * @param Builder $builder + */ + public function scopeRegisteredUser(Builder $builder): void + { + $builder->where('is_prospect', false); + } + + /** + * ATTRIBUTES + */ + + /** + * @return bool + * Checks if affiliated organization has valid subscription + */ + public function getIsSubscribedAttribute(): bool + { + return ($o = $this->organization) === null ? + false: + $o->isSubscribed(); + } + + + /** + * @return string + * Returns status slug + */ + public function getStatusAttribute(): string + { + $id = 'inactive'; + + if($this->isSubscribed) + $id = 'subscribed'; + if($this->onTrial()) + $id = 'trial'; + if($this->is_prospect) + $id = 'prospect'; + + return $id; + + } + + /** + * @return string + * Returns current status + */ + public function getStatusLabelAttribute(): string + { + return self::statuses[$this->status]['label']; + } + + /** * @return string|null + * Get full name */ public function getNameAttribute(): ?string { @@ -217,6 +306,7 @@ class User extends Authenticatable /** * @return bool + * Checks if is affiliated to an organization */ public function getIsIndividualAttribute(): bool { diff --git a/database/migrations/2020_04_30_093507_add_employer_to_users.php b/database/migrations/2020_04_30_093507_add_employer_to_users.php new file mode 100644 index 0000000..d81e96c --- /dev/null +++ b/database/migrations/2020_04_30_093507_add_employer_to_users.php @@ -0,0 +1,34 @@ +string('employer')->nullable(); + $table->boolean('self_registered')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('employer'); + $table->dropColumn('self_registered'); + }); + } +} diff --git a/database/migrations/2020_04_30_103918_add_prospect_flag_to_users.php b/database/migrations/2020_04_30_103918_add_prospect_flag_to_users.php new file mode 100644 index 0000000..24d9e99 --- /dev/null +++ b/database/migrations/2020_04_30_103918_add_prospect_flag_to_users.php @@ -0,0 +1,32 @@ +boolean('is_prospect')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('is_prospect'); + }); + } +} diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index 4cb2705..44f9397 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -46,13 +46,13 @@ return [ '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.', + 'string' => 'Le champ :attribute doit contenir plus de :value caractères.', 'array' => 'Le tableau :attribute doit contenir plus de :value éléments.', ], '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.', + 'string' => 'Le champ :attribute doit contenir au moins :value caractères.', 'array' => 'Le tableau :attribute doit contenir au moins :value éléments.', ], 'image' => 'Le champ :attribute doit être une image.', diff --git a/resources/views/account/index.blade.php b/resources/views/account/index.blade.php index 28c0fcc..ca3cfaa 100644 --- a/resources/views/account/index.blade.php +++ b/resources/views/account/index.blade.php @@ -10,9 +10,14 @@ @else @if($user->isSubscribed) -
+
Votre compte est actif, vous pouvez accéder aux contenus Prescription Santé.
+ + @elseif($user->onTrial()) +
+ Vous bénéficiez d'une période d'évaluation jusqu'au {{$user->trial_ends_at->formatLocalized('%d %B %Y')}} +
@endif @endif @if(session()->has('message')) @@ -50,7 +55,7 @@
- + @error('last_name') diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index d236a48..a3bf374 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -4,7 +4,14 @@
+ +
+

Bienvenue sur {{config('app.name')}}

+

Vous bénéficierez de 2 semaines d'accès gratuit dès votre inscription !

+
+
+
{{ __('Register') }}
@@ -12,12 +19,26 @@ @csrf
- + + +
+ + + @error('first_name') + + {{ $message }} + + @enderror +
+
+ +
+
- + - @error('name') + @error('last_name') {{ $message }} @@ -39,6 +60,22 @@
+
+ + +
+ + + @error('employer') + + {{ $message }} + + @enderror +
+
+ + +
diff --git a/routes/web.php b/routes/web.php index 1d6f168..cc7e6ae 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,7 +16,8 @@ use Illuminate\Support\Facades\Route; -Auth::routes(); +Auth::routes(['verify' => true]); + -- 2.39.5