To create the invitation system, we need to save the invitations themselves in the database.
Model and DB Fields
So, let's create a Model with Migration.
php artisan make:model Invitation -m
For the invitation, we need these fields:
- Tenant ID to know for which tenant user is being invited
- Some random token for security
- And whether it was accepted
database/migrations/xxx_create_invitations_table.php:
Schema::create('invitations', function (Blueprint $table) { $table->id(); $table->foreignId('tenant_id')->constrained(); $table->string('email'); $table->string('token'); $table->timestamp('accepted_at')->nullable(); $table->timestamps();});
app/Models/Invitation.php:
class Invitation extends Model{ protected $fillable = [ 'tenant_id', 'email', 'token', 'accepted_at', ];}
Form to Invite Users
Next, we need a form on the user's page with one email input.
resources/views/users/index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Users') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 bg-white border-b border-gray-200"> Coming soon. <form method="POST" action="{{ route('users.store') }}"> @csrf <!-- Email Address --> <div class="mt-4"> <x-input-label for="email" :value="__('Email')" /> <x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" /> <x-input-error :messages="$errors->get('email')" class="mt-2" /> </div> <div class="mt-4"> <x-primary-button> {{ __('Send Invitation') }} </x-primary-button> </div> </form> </div> </div> </div> </div></x-app-layout>
Process the Invitation Submit
The form submission will lead to the store()
method in the UserController
. We will need validation, and we will use a Form Request.
php artisan make:request StoreUserRequest
app/Http/Requests/StoreUserRequest.php:
class StoreUserRequest extends FormRequest{ public function authorize(): bool { return true; } public function rules(): array { return [ 'email' => ['required', 'unique:invitations,email'], ]; }}
For now, we will create the invitation in the Controller and redirect it back to the users list page.
app/Http/Controllers/UserController.php:
use App\Models\Invitation;use Illuminate\Support\Str;use Illuminate\Contracts\View\View;use Illuminate\Http\RedirectResponse;use App\Http\Requests\StoreUserRequest; class UserController extends Controller{ public function index(): View { return view('users.index'); } public function store(StoreUserRequest $request): RedirectResponse { $invitation = Invitation::create([ 'tenant_id' => auth()->user()->current_tenant_id, 'email' => $request->input('email'), 'token' => Str::random(32), ]); return redirect()->route('users.index'); } }
We have a record in the database after entering some email in the invitation form and sending the invitation.
Showing the Invitations
And finally, let's show all the invitations. First, in the Controller, we must pass the invitations on to the View.
app/Http/Controllers/UserController.php:
class UserController extends Controller{ public function index(): View { $invitations = Invitation::where('tenant_id', auth()->user()->current_tenant_id)->latest()->get(); return view('users.index'); return view('users.index', compact('invitations')); } // ...}
resources/views/users/index.blade.php:
// ... <div class="border-b border-gray-200 bg-white p-6"> <form method="POST" action="{{ route('users.store') }}"> @csrf <!-- Email Address --> <div class="mt-4"> <x-input-label for="email" :value="__('Email')" /> <x-text-input id="email" class="mt-1 block w-full" type="email" name="email" :value="old('email')" required autocomplete="username" /> <x-input-error :messages="$errors->get('email')" class="mt-2" /> </div> <div class="mt-4"> <x-primary-button> {{ __('Send Invitation') }} </x-primary-button> </div> </form> <table class="mt-8 w-full text-left text-sm text-gray-500"> <thead class="bg-gray-50 text-xs uppercase text-gray-700 dark:bg-gray-700"> <th scope="col" class="px-6 py-3 text-left">{{ __('Email') }}</th> <th scope="col" class="px-6 py-3 text-left">{{ __('Sent on') }}</th> </thead> <tbody> @foreach ($invitations as $invitation) <tr class="border-b bg-white dark:bg-gray-800"> <td class="px-6 py-4 font-medium text-gray-900whitespace-nowrap"> {{ $invitation->email }} </td> <td class="px-6 py-4 font-medium text-gray-900whitespace-nowrap"> {{ $invitation->created_at }} </td> </tr> @endforeach </tbody> </table> </div> // ...
Thank you for this course I have some points :) :
StoreUserRequest
Rule I think the email should not be unique beacuse the user can be invited to many tenat, more than that even if the user is registerd before he may get invitation to other tenat 'I think it is covered on the next topics', so I think it well be beter if we make ['tenat_id', 'email'] unique.Yes it can be how you say, but every application is unique. What works for one doesn't mean it will be best for other. This is only a tutorial, a starting point to build application.