generated from thegrind/laravel-dockerized
Invitation email
This commit is contained in:
parent
6ebefb1120
commit
6771d84909
@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Mail\InvitationMail;
|
||||||
use App\Models\Invitation;
|
use App\Models\Invitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Flux\Flux;
|
use Flux\Flux;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class ManageUsers extends Component
|
class ManageUsers extends Component
|
||||||
{
|
{
|
||||||
public string $invite_email = '';
|
public string $invite_email = '';
|
||||||
|
public bool $send_email = false;
|
||||||
public Collection $users;
|
public Collection $users;
|
||||||
public Collection $invitations;
|
public Collection $invitations;
|
||||||
|
|
||||||
@ -19,7 +22,7 @@ class ManageUsers extends Component
|
|||||||
// Only load data if user is authorized to view it
|
// Only load data if user is authorized to view it
|
||||||
if (auth()->user()->can('viewAny', User::class)) {
|
if (auth()->user()->can('viewAny', User::class)) {
|
||||||
$this->users = User::all();
|
$this->users = User::all();
|
||||||
$this->invitations = Invitation::all();
|
$this->invitations = Invitation::orderBy('accepted_at', 'desc')->get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,11 +36,20 @@ class ManageUsers extends Component
|
|||||||
'invited_by' => auth()->user()->id,
|
'invited_by' => auth()->user()->id,
|
||||||
'expires_at' => now()->addDays(7),
|
'expires_at' => now()->addDays(7),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Send email if checkbox is checked
|
||||||
|
if ($this->send_email) {
|
||||||
|
Mail::to($inv->email)->send(new InvitationMail($inv));
|
||||||
|
}
|
||||||
|
|
||||||
Flux::modal('invite-user')->close();
|
Flux::modal('invite-user')->close();
|
||||||
|
|
||||||
// Refresh the data
|
// Refresh the data
|
||||||
$this->invitations = Invitation::all();
|
$this->invitations = Invitation::all();
|
||||||
$this->invite_email = '';
|
$this->invite_email = '';
|
||||||
|
$this->send_email = false;
|
||||||
|
|
||||||
|
session()->flash('success', 'Invitation created successfully' . ($emailSent ? ' and email sent' : '') . '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteUser(User $user)
|
public function deleteUser(User $user)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Mail;
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\Invitation;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Mail\Mailable;
|
use Illuminate\Mail\Mailable;
|
||||||
@ -16,7 +17,7 @@ class InvitationMail extends Mailable
|
|||||||
/**
|
/**
|
||||||
* Create a new message instance.
|
* Create a new message instance.
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct(public Invitation $invitation)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@ -37,7 +38,7 @@ class InvitationMail extends Mailable
|
|||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'view.name',
|
view: 'emails.invitation',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,4 +27,9 @@ class Invitation extends Model
|
|||||||
$this->accepted_at = now();
|
$this->accepted_at = now();
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getInviteUrl(): string
|
||||||
|
{
|
||||||
|
return route('register', ['code' => $this->code]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>You're invited to join AuthentiKate</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 30px 20px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 12px 30px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background: #5a6fd8;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.invite-details {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>= AuthentiKate</h1>
|
||||||
|
<p>You've been invited to join our authentication platform</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h2>Hello!</h2>
|
||||||
|
|
||||||
|
<p>You've been invited to create an account on <strong>AuthentiKate</strong>, our secure authentication platform. This will give you access to various applications and services.</p>
|
||||||
|
|
||||||
|
<div class="invite-details">
|
||||||
|
<h3>Invitation Details:</h3>
|
||||||
|
<p><strong>Email:</strong> {{ $invitation->email }}</p>
|
||||||
|
<p><strong>Invited by:</strong> Admin</p>
|
||||||
|
<p><strong>Expires:</strong> {{ $invitation->expires_at->format('F j, Y \a\t g:i A') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>To accept this invitation and create your account, click the button below:</p>
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a href="{{ $invitation->getInviteUrl() }}" class="button">Accept Invitation</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Or copy and paste this link into your browser:</p>
|
||||||
|
<p style="word-break: break-all; background: #f8f9fa; padding: 10px; border-radius: 4px; font-family: monospace;">
|
||||||
|
{{ $invitation->getInviteUrl() }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><strong>Note:</strong> This invitation will expire on {{ $invitation->expires_at->format('F j, Y') }}. If you don't create your account by then, you'll need to request a new invitation.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>This invitation was sent from AuthentiKate.</p>
|
||||||
|
<p>If you weren't expecting this invitation, you can safely ignore this email.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -78,6 +78,7 @@
|
|||||||
<flux:separator class="my-4" />
|
<flux:separator class="my-4" />
|
||||||
<form wire:submit="inviteUser" class="flex flex-col gap-4">
|
<form wire:submit="inviteUser" class="flex flex-col gap-4">
|
||||||
<flux:input label="Email" wire:model="invite_email" />
|
<flux:input label="Email" wire:model="invite_email" />
|
||||||
|
<flux:checkbox label="Send invitation email" wire:model="send_email" />
|
||||||
<flux:button type="submit" variant="primary">Create invitation</flux:button>
|
<flux:button type="submit" variant="primary">Create invitation</flux:button>
|
||||||
</form>
|
</form>
|
||||||
</flux:modal>
|
</flux:modal>
|
||||||
|
@ -5,6 +5,7 @@ use App\Livewire\ConsentScreen;
|
|||||||
use App\Livewire\Settings\Appearance;
|
use App\Livewire\Settings\Appearance;
|
||||||
use App\Livewire\Settings\Password;
|
use App\Livewire\Settings\Password;
|
||||||
use App\Livewire\Settings\Profile;
|
use App\Livewire\Settings\Profile;
|
||||||
|
use App\Mail\InvitationMail;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@ -42,4 +43,17 @@ Route::prefix('application/o')->group(function () {
|
|||||||
});
|
});
|
||||||
Route::get('.well-known/jwks.json', [OIDCController::class, 'jwks'])->name('auth.keys');
|
Route::get('.well-known/jwks.json', [OIDCController::class, 'jwks'])->name('auth.keys');
|
||||||
Route::get('.well-known/openid-configuration', [OIDCController::class, 'openidConfig'])->name('auth.openid-configuration');
|
Route::get('.well-known/openid-configuration', [OIDCController::class, 'openidConfig'])->name('auth.openid-configuration');
|
||||||
|
|
||||||
|
// Test route for invitation email preview
|
||||||
|
Route::get('/test-invitation-email', function () {
|
||||||
|
$invitation = new \App\Models\Invitation([
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
'code' => 'test-invitation-code-123',
|
||||||
|
'expires_at' => now()->addDays(7),
|
||||||
|
'invited_by' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new \App\Mail\InvitationMail($invitation);
|
||||||
|
})->middleware('auth');
|
||||||
|
|
||||||
require __DIR__ . '/auth.php';
|
require __DIR__ . '/auth.php';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user