generated from thegrind/laravel-dockerized
Started working on invites
This commit is contained in:
parent
8e0abedbbb
commit
1fd6f03a81
@ -21,6 +21,13 @@ class Register extends Component
|
||||
|
||||
public string $password_confirmation = '';
|
||||
|
||||
public string $code = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
dd($this->code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*/
|
||||
@ -28,7 +35,7 @@ class Register extends Component
|
||||
{
|
||||
$validated = $this->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class],
|
||||
'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
|
38
app/Livewire/ManageUsers.php
Normal file
38
app/Livewire/ManageUsers.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Invitation;
|
||||
use App\Models\User;
|
||||
use Flux\Flux;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class ManageUsers extends Component
|
||||
{
|
||||
public string $invite_email = '';
|
||||
public Collection $users;
|
||||
public Collection $invitations;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->users = User::all();
|
||||
$this->invitations = Invitation::all();
|
||||
}
|
||||
|
||||
public function inviteUser()
|
||||
{
|
||||
$inv = Invitation::create([
|
||||
'code' => str()->random(50),
|
||||
'email' => $this->invite_email,
|
||||
'invited_by' => auth()->user()->id,
|
||||
'expires_at' => now()->addDays(7),
|
||||
]);
|
||||
Flux::modal('invite-user')->close();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.manage-users');
|
||||
}
|
||||
}
|
53
app/Mail/InvitationMail.php
Normal file
53
app/Mail/InvitationMail.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvitationMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Invitation Mail',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
view: 'view.name',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
24
app/Models/Invitation.php
Normal file
24
app/Models/Invitation.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Invitation extends Model
|
||||
{
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'expires_at' => 'datetime',
|
||||
'accepted_at' => 'datetime'
|
||||
];
|
||||
|
||||
public function status(): string
|
||||
{
|
||||
return !empty($this->accepted_at) ? 'accepted' : 'pending';
|
||||
}
|
||||
|
||||
public function isPending(): bool
|
||||
{
|
||||
return empty($this->accepted_at);
|
||||
}
|
||||
}
|
60
database/factories/InvitationFactory.php
Normal file
60
database/factories/InvitationFactory.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Invitation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Invitation>
|
||||
*/
|
||||
class InvitationFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'code' => Invitation::generateCode(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'invited_by' => User::factory(),
|
||||
'expires_at' => now()->addDays(7),
|
||||
'email_sent' => fake()->boolean(30),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the invitation is expired.
|
||||
*/
|
||||
public function expired(): static
|
||||
{
|
||||
return $this->state(fn(array $attributes) => [
|
||||
'expires_at' => now()->subDay(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the invitation has been accepted.
|
||||
*/
|
||||
public function accepted(): static
|
||||
{
|
||||
return $this->state(fn(array $attributes) => [
|
||||
'accepted_at' => now(),
|
||||
'user_id' => User::factory(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the email was sent.
|
||||
*/
|
||||
public function emailSent(): static
|
||||
{
|
||||
return $this->state(fn(array $attributes) => [
|
||||
'email_sent' => true,
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('invitations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code')->unique();
|
||||
$table->string('email')->unique();
|
||||
$table->foreignId('invited_by')->constrained('users')->onDelete('cascade');
|
||||
$table->timestamp('expires_at');
|
||||
$table->timestamp('accepted_at')->nullable();
|
||||
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('cascade');
|
||||
$table->boolean('email_sent')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('invitations');
|
||||
}
|
||||
};
|
@ -1,6 +1,4 @@
|
||||
@props(['class' => ''])
|
||||
|
||||
<div {{ $attributes->merge(['class' => 'bg-white dark:bg-zinc-800 shadow-sm border border-gray-200 dark:border-zinc-700
|
||||
rounded-lg ' . $class]) }}>
|
||||
p-6'] ) }}>
|
||||
{{ $slot }}
|
||||
</div>
|
@ -1,5 +1,8 @@
|
||||
<x-layouts.app :title="__('Dashboard')">
|
||||
<div class="max-w-4xl mx-auto py-12">
|
||||
<div class="mb-4">
|
||||
<livewire:manage-users />
|
||||
</div>
|
||||
<div class="grid grid-cols-2">
|
||||
<livewire:forms.user-profile />
|
||||
</div>
|
||||
|
0
resources/views/emails/invitation.blade.php
Normal file
0
resources/views/emails/invitation.blade.php
Normal file
52
resources/views/livewire/manage-users.blade.php
Normal file
52
resources/views/livewire/manage-users.blade.php
Normal file
@ -0,0 +1,52 @@
|
||||
<div>
|
||||
<div class="flex justify-between items-center">
|
||||
<flux:heading size="xl">Users</flux:heading>
|
||||
</div>
|
||||
<flux:separator class="my-8" />
|
||||
@foreach ($users as $u)
|
||||
<x-card class="flex items-center justify-between p-6">
|
||||
<div class="flex gap-4">
|
||||
<flux:heading>{{$u->name}}</flux:heading>
|
||||
<flux:text>{{$u->email}}</flux:text>
|
||||
</div>
|
||||
</x-card>
|
||||
@endforeach
|
||||
<div class="flex justify-between items-center mt-8">
|
||||
<flux:heading size="xl">Invitations</flux:heading>
|
||||
<div>
|
||||
<flux:modal.trigger name="invite-user">
|
||||
<flux:button variant="primary" icon="plus">Create</flux:button>
|
||||
</flux:modal.trigger>
|
||||
</div>
|
||||
</div>
|
||||
<flux:separator class="my-8" />
|
||||
@foreach ($invitations as $inv)
|
||||
<x-card class="flex items-center justify-between p-6">
|
||||
<div class="flex gap-4 items-center flex-1">
|
||||
<flux:heading>{{$inv->email}}</flux:heading>
|
||||
@switch($inv->status())
|
||||
@case('accepted')
|
||||
<flux:badge color="green">Accepted</flux:badge>
|
||||
@break
|
||||
@case('pending')
|
||||
<flux:badge>Pending</flux:badge>
|
||||
@break
|
||||
@default
|
||||
<flux:badge>{{$inv->status}}</flux:badge>
|
||||
@endswitch
|
||||
</div>
|
||||
<flux:text>Invite link: {{route('register', ['code' => $inv->code])}}</flux:text>
|
||||
<div class="flex gap-4 items-center">
|
||||
<flux:button variant="primary" size="sm">Copy invite link</flux:button>
|
||||
</div>
|
||||
</x-card>
|
||||
@endforeach
|
||||
<flux:modal name="invite-user" class="w-96">
|
||||
<flux:heading>Invite User</flux:heading>
|
||||
<flux:separator class="my-4" />
|
||||
<form wire:submit="inviteUser" class="flex flex-col gap-4">
|
||||
<flux:input label="Email" wire:model="invite_email" />
|
||||
<flux:button type="submit" variant="primary">Create invitation</flux:button>
|
||||
</form>
|
||||
</flux:modal>
|
||||
</div>
|
7
tests/Feature/InvitationSystemTest.php
Normal file
7
tests/Feature/InvitationSystemTest.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
test('example', function () {
|
||||
$response = $this->get('/');
|
||||
|
||||
$response->assertStatus(200);
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user