Skip to content

Commit

Permalink
feat(invite): add confirmation step to invite process #39 (#48)
Browse files Browse the repository at this point in the history
* feat(invite): add confirmation step to invite process #39

* feat(invite): send emails to invitee and dealership #39

* fix: styling, wording and redirect issues
  • Loading branch information
Fenrikur authored Jan 31, 2024
1 parent c9bc2e3 commit ffc82c2
Show file tree
Hide file tree
Showing 16 changed files with 343 additions and 118 deletions.
81 changes: 58 additions & 23 deletions app/Http/Controllers/Applications/ApplicationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
use App\Notifications\AlternateTableOfferedShareNotification;
use App\Notifications\CanceledByDealershipNotification;
use App\Notifications\CanceledBySelfNotification;
use App\Notifications\JoinNotification;
use App\Notifications\TableAcceptanceReminderNotification;
use App\Notifications\TableOfferedNotification;
use App\Notifications\TableOfferedShareNotification;
use App\Notifications\WaitingListNotification;
use App\Notifications\WelcomeAssistantNotification;
use App\Notifications\WelcomeNotification;
use App\Notifications\WelcomeShareNotification;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
Expand All @@ -32,33 +34,45 @@ class ApplicationController extends Controller
{
public function create(Request $request)
{
InvitationController::verifyInvitationCodeConfirmation($request);

$application = Auth::user()->application ?? new Application();
$categories = Category::orderBy('name', 'asc')->get();
$applicationType = Application::determineApplicationTypeByCode($request->input('code')) ?? ApplicationType::Dealer;
$code = $request->input('code');
$applicationType = Application::determineApplicationTypeByCode($code) ?? ApplicationType::Dealer;
$invitingApplication = self::determineParentByCode($code);

return view('application.create', [
'table_types' => TableType::all(['id', 'name', 'price']),
'application' => $application,
'categories' => $categories,
'applicationType' => $applicationType,
'code' => $request->input('code'),
'code' => $code,
'invitingApplication' => $invitingApplication,
'confirmation' => $request->session()->get(InvitationController::SESSION_CONFIRMATION_KEY),
'profile' => ProfileController::getOrCreate($application->id)
]);
}

public function edit(Request $request)
{
InvitationController::verifyInvitationCodeConfirmation($request);

$application = Auth::user()->application;
abort_if(is_null($application), 404, 'Application not found');
$applicationType = Application::determineApplicationTypeByCode($request->input('code')) ?? $application->type;
$code = $request->input('code');
$applicationType = Application::determineApplicationTypeByCode($code) ?? $application->type;
$invitingApplication = self::determineParentByCode($code);

$categories = Category::orderBy('name', 'asc')->get();
return view('application.edit', [
'table_types' => TableType::all(['id', 'name', 'price']),
'application' => $application,
'categories' => $categories,
'applicationType' => $applicationType,
'code' => $request->input('code'),
'code' => $code,
'invitingApplication' => $invitingApplication,
'confirmation' => $request->session()->get(InvitationController::SESSION_CONFIRMATION_KEY),
'profile' => ProfileController::getByApplicationId($application->id)
]);
}
Expand Down Expand Up @@ -92,9 +106,11 @@ private static function determineParentByCode(string|null $code): Application|nu

public function store(ApplicationRequest $request)
{
InvitationController::verifyInvitationCodeConfirmation($request);
$code = $request->input('code');
$applicationType = self::determineApplicationTypeByCode($code) ?? ApplicationType::Dealer;

/** @var Application|null */
$parent = self::determineParentByCode($code);

$application = Application::updateOrCreate([
Expand Down Expand Up @@ -124,32 +140,40 @@ public function store(ApplicationRequest $request)
ProfileController::createOrUpdate($request, $application->id);
}

/** @var User $user */
$user = Auth::user();
if ($application && $application->getStatus() === ApplicationStatus::Open) {
switch ($application->type) {
case ApplicationType::Dealer:
$user->notify(new WelcomeNotification());
break;
case ApplicationType::Share:
$user->notify(new WelcomeNotification());
break;
case ApplicationType::Assistant:
$user->notify(new WelcomeAssistantNotification());
break;
default:
abort(400, 'Unknown application type.');
}
return Redirect::route('dashboard')->with('save-successful');
} else {
InvitationController::clearInvitationCodeConfirmation($request);

if (!$application || $application->getStatus() !== ApplicationStatus::Open) {
abort(400, 'Invalid application state');
}

/** @var User */
$user = Auth::user();

switch ($application->type) {
case ApplicationType::Dealer:
$user->notify(new WelcomeNotification());
break;
case ApplicationType::Share:
$user->notify(new WelcomeShareNotification($parent->getFullName()));
$parent->user->notify(new JoinNotification($application->type->value, $application->getFullName()));
break;
case ApplicationType::Assistant:
$user->notify(new WelcomeAssistantNotification($parent->getFullName()));
$parent->user->notify(new JoinNotification($application->type->value, $application->getFullName()));
break;
default:
abort(400, 'Unknown application type.');
}
return Redirect::route('dashboard')->with('save-successful');
}

public function update(ApplicationRequest $request)
{
InvitationController::verifyInvitationCodeConfirmation($request);
/** @var User */
$user = Auth::user();
/** @var Application */
$application = Auth::user()->application;
$application = $user->application;
abort_if(is_null($application), 404, 'Application not found');

$code = $request->input('code');
Expand All @@ -176,6 +200,17 @@ public function update(ApplicationRequest $request)
ProfileController::createOrUpdate($request, $application->id);
}

InvitationController::clearInvitationCodeConfirmation($request);

if ($newParent) {
$newParent->user->notify(new JoinNotification($newApplicationType->value, $user->name));
if ($newApplicationType === ApplicationType::Assistant) {
$user->notify(new WelcomeAssistantNotification($newParent->getFullName()));
} elseif ($newApplicationType === ApplicationType::Share) {
$user->notify(new WelcomeShareNotification($newParent->getFullName()));
}
}

return Redirect::route('applications.edit')->with('save-successful');
}

Expand Down
45 changes: 38 additions & 7 deletions app/Http/Controllers/Applications/InvitationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,19 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class InvitationController extends Controller
{
public function view()
{
return view('invitation.enter-code');
}
final public const SESSION_CONFIRMATION_KEY = 'join-confirmation';

public function store(Request $request)
public function view(Request $request)
{
$code = $request->get('code');
$code = $request->input('code');
$applicationType = Application::determineApplicationTypeByCode($code);

if (empty($code) && !is_null($applicationType)) {
if (empty($code) || is_null($applicationType)) {
throw ValidationException::withMessages([
"code" => "Please enter a valid invitation code.",
]);
Expand Down Expand Up @@ -55,6 +53,26 @@ public function store(Request $request)
]);
}

// Prevent people from sending direct join URLs
$confirmation = Str::random();
$request->session()->put(self::SESSION_CONFIRMATION_KEY, $confirmation);

$application = Auth::user()->application;

return view('invitation.confirm', [
'invitingApplication' => $invitingApplication,
'application' => $application,
'invitationType' => $applicationType,
'code' => $request->input('code'),
'confirmation' => $confirmation,
]);
}

public function store(Request $request)
{
InvitationController::verifyInvitationCodeConfirmation($request);

$code = $request->get('code');
$application = Auth::user()->application;
$action = 'edit';
if (
Expand All @@ -67,6 +85,19 @@ public function store(Request $request)

return Redirect::route('applications.' . $action, [
'code' => $code,
'confirmation' => $request->session()->get(self::SESSION_CONFIRMATION_KEY),
]);
}

public static function verifyInvitationCodeConfirmation(Request $request)
{
// Prevent people from sending direct join URLs
$confirmation = $request->session()->get(self::SESSION_CONFIRMATION_KEY);
abort_if(!empty($request->input('code')) && (!$request->session()->has(self::SESSION_CONFIRMATION_KEY) || $confirmation !== $request->input('confirmation')), 400, 'Invalid confirmation code');
}

public static function clearInvitationCodeConfirmation(Request $request)
{
$request->session()->forget(self::SESSION_CONFIRMATION_KEY);
}
}
6 changes: 3 additions & 3 deletions app/Http/Controllers/DashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
namespace App\Http\Controllers;

use App\Http\Controllers\Client\RegSysClientController;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;

class DashboardController extends Controller
{
public function __invoke()
{
$user = \Auth::user();
$user = Auth::user();

$application = $user->application;
$efRegistration = RegSysClientController::getSingleReg($user->reg_id);

return view('dashboard',[
return view('dashboard', [
"application" => $application,
"efRegistrationStatus" => $efRegistration ? $efRegistration['status'] : false,
]);
Expand Down
12 changes: 12 additions & 0 deletions app/Models/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,18 @@ public function type(): Attribute
);
}

/**
* Get full dealership name of either "display_name (userName)" or just "userName" if no display_name has been set.
*/
public function getFullName(): string
{
if (empty($this->display_name)) {
return $this->user->name;
} else {
return "{$this->display_name} ({$this->user->name})";
}
}

public function getStatus()
{
return ApplicationStatus::for($this);
Expand Down
44 changes: 44 additions & 0 deletions app/Notifications/JoinNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\HtmlString;

class JoinNotification extends Notification implements ShouldQueue
{
use Queueable;

private string $joinType;
private string $joinName;

public function __construct(string $joinType, string $joinName)
{
$this->joinType = ucfirst($joinType);
$this->joinName = $joinName;
}

public function via($notifiable): array
{
return ['mail'];
}

public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject(config('con.con_name') . ' Dealers\' Den - ' . $this->joinType . ' Joined')
->greeting("Dear $notifiable->name,")
->line('We wish to inform you that ' . $this->joinName . ' has successfully joined your dealership as ' . $this->joinType . ' via your invite code.')
->line('If you did not invite them, please go to "Shares & Assistants" in the Dealers\' Den Registration system to generate a new invite code or disable invitations and remove them from your dealership:')
->action('Manage Shares and Assistants', url('/applications/invitees'))
->salutation(new HtmlString('Best regards,<br />the Eurofurence Dealers\' Den Team'));
}

public function toArray($notifiable): array
{
return [];
}
}
7 changes: 5 additions & 2 deletions app/Notifications/WelcomeAssistantNotification.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ class WelcomeAssistantNotification extends Notification implements ShouldQueue
{
use Queueable;

public function __construct()
private string $dealershipName;

public function __construct(string $dealershipName)
{
$this->dealershipName = $dealershipName;
}

public function via($notifiable): array
Expand All @@ -27,7 +30,7 @@ public function toMail($notifiable): MailMessage
->subject(config('con.con_name') . ' Dealers\' Den - Dealer Assistant Information')
->greeting("Dear " . $notifiable->name . ",")
->line('We are delighted to welcome you as a Dealer Assistant at ' . config('con.con_name') . ' Dealers\' Den!')
->line('Thank you for accepting your dealerships\' invitation and entering the invitation code. Your support in helping your dealership during setup, teardown, and opening hours is greatly appreciated.')
->line('Thank you for accepting the invitation of ' . $this->dealershipName . ' to join their dealership by entering their invitation code. Your support in helping your dealership during setup, teardown, and opening hours is greatly appreciated.')
->line('As a Dealer Assistant, you play a vital role in ensuring the smooth operation and success of your dealer\'s experience at the Dealers\' Den. Your assistance will contribute significantly to the overall experience for both your dealer and the attendees.')
->line('In the coming weeks, we will be sending you more information about the Dealers\' Den setup, event schedules, and other important details to help you prepare for the convention. Please keep an eye on your email for these updates.')
->line(new HtmlString('If you have any questions or concerns, feel free to reach out to us at <a href="mailto:' . config('con.dealers_email') . '">' . config('con.dealers_email') . '</a>. We are here to help ensure a smooth and enjoyable experience for all our dealers and their assistants.'))
Expand Down
42 changes: 42 additions & 0 deletions app/Notifications/WelcomeShareNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\HtmlString;

class WelcomeShareNotification extends Notification implements ShouldQueue
{
use Queueable;

private string $dealershipName;

public function __construct(string $dealershipName)
{
$this->dealershipName = $dealershipName;
}

public function via($notifiable): array
{
return ['mail'];
}

public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject(config('con.con_name') . ' Dealers\' Den - Application Received')
->greeting('Dear ' . $notifiable->name . ',')
->line('Thank you for your application as part of your joint dealership with ' . $this->dealershipName . ' at the upcoming Eurofurence. Your interest in being a part of this year\'s Dealers\' Den is very much appreciated.')
->line('We have received your application and will review it once the Dealership application period has ended. We understand that waiting can be difficult, but please know that we are working hard to review all applications in a timely manner. Once we have reviewed all the applications, we will get in touch with you to provide you with all the necessary information about the next steps.')
->line('Thank you in advance for your patience. The Dealers\' Den management is looking forward to reviewing your application.')
->salutation(new HtmlString('Best regards,<br />the Eurofurence Dealers\' Den Team'));
}

public function toArray($notifiable): array
{
return [];
}
}
Loading

0 comments on commit ffc82c2

Please sign in to comment.