feat: Implement email verification, protect member routes, and improve registration with debounced inputs and unique phone numbers.
This commit is contained in:
parent
a9759f92c1
commit
096a7b384b
@ -61,7 +61,7 @@ public function auth(): void
|
||||
|
||||
session()->regenerate();
|
||||
|
||||
$this->toast('Hai, '.($user?->employee?->full_name ?? $user->name).'! Semoga harimu menyenangkan 😊');
|
||||
$this->toast('Hai, '.($user?->employee?->full_name ?? $user->customer?->name).'! Semoga harimu menyenangkan 😊');
|
||||
|
||||
$this->redirectByRole($user->roles->pluck('name')->toArray());
|
||||
}
|
||||
|
||||
48
app/Livewire/Auth/VerifyEmail.php
Normal file
48
app/Livewire/Auth/VerifyEmail.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Auth;
|
||||
|
||||
use App\Traits\Components\WithToast;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Layout('components.layouts.auth', [
|
||||
'title' => 'Verifikasi Email',
|
||||
])]
|
||||
class VerifyEmail extends Component
|
||||
{
|
||||
use WithToast;
|
||||
|
||||
/**
|
||||
* Send email verification notification to the user.
|
||||
*/
|
||||
public function resend(): void
|
||||
{
|
||||
if (Auth::user()->hasVerifiedEmail()) {
|
||||
$this->redirectIntended(route('member.overview'), navigate: true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Auth::user()->sendEmailVerificationNotification();
|
||||
|
||||
$this->toast('Tautan verifikasi baru telah dikirim ke alamat email Anda.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the user out of the application.
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
Auth::logout();
|
||||
|
||||
$this->redirect(route('login'), navigate: true);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.auth.verify-email');
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,7 @@
|
||||
use App\Models\Tier;
|
||||
use App\Models\User;
|
||||
use App\Rules\PhoneNumber;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
@ -56,7 +57,7 @@ public function rulesStep2(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:50'],
|
||||
'phone_number' => ['required', 'string', new PhoneNumber],
|
||||
'phone_number' => ['required', 'string', new PhoneNumber, Rule::unique('customers', 'phone_number')],
|
||||
'gender' => ['required', Rule::in(Gender::values())],
|
||||
'referral_code' => ['nullable', 'string', Rule::exists('referral_codes', 'code')],
|
||||
];
|
||||
@ -99,7 +100,8 @@ public function auth(): string
|
||||
$maxUser = User::max('id') + 1;
|
||||
$referralCode = ReferralCode::where('code', $this->referral_code)->first();
|
||||
|
||||
DB::transaction(function () use ($tier, $maxUser, $referralCode, &$customer): void {
|
||||
$user = null;
|
||||
DB::transaction(function () use ($tier, $maxUser, $referralCode, &$user): void {
|
||||
$user = User::create([
|
||||
'email' => $this->email,
|
||||
'username' => $this->username,
|
||||
@ -107,7 +109,7 @@ public function auth(): string
|
||||
'status' => UserStatus::ACTIVE,
|
||||
]);
|
||||
|
||||
$customer = Customer::create([
|
||||
Customer::create([
|
||||
'user_id' => $user->id,
|
||||
'name' => $this->name,
|
||||
'phone_number' => $this->phone_number,
|
||||
@ -144,11 +146,11 @@ public function auth(): string
|
||||
|
||||
$user->assignRole('Customer');
|
||||
|
||||
auth()->login($user);
|
||||
Auth::login($user);
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
});
|
||||
|
||||
return $customer->name;
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:50'],
|
||||
'phone_number' => ['nullable', 'string', new PhoneNumber],
|
||||
'phone_number' => ['nullable', 'string', new PhoneNumber, Rule::unique('customers', 'phone_number')],
|
||||
'gender' => ['required', Rule::in(Gender::cases())],
|
||||
];
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ public function up(): void
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete();
|
||||
$table->string('name', 50);
|
||||
$table->string('phone_number', 20)->nullable();
|
||||
$table->string('phone_number', 20)->nullable()->unique();
|
||||
$table->enum('gender', Gender::values())->comment(Gender::comment());
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
|
||||
@ -47,7 +47,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ?
|
||||
<flux:description>Pastikan email aktif karena akan digunakan untuk verifikasi.
|
||||
</flux:description>
|
||||
|
||||
<flux:input placeholder="info.pangestuyoga@gmail.com" wire:model.live="form.email" autofocus
|
||||
<flux:input placeholder="info.pangestuyoga@gmail.com" wire:model.live.debounce.500ms="form.email" autofocus
|
||||
autocomplete="off" />
|
||||
|
||||
<flux:error name="form.email" />
|
||||
@ -56,7 +56,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ?
|
||||
<flux:field>
|
||||
<flux:label>Nama Pengguna <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
|
||||
<flux:input placeholder="jonhdoe" wire:model.live="form.username" autocomplete="off" />
|
||||
<flux:input placeholder="jonhdoe" wire:model.live.debounce.500ms="form.username" autocomplete="off" />
|
||||
|
||||
<flux:error name="form.username" />
|
||||
</flux:field>
|
||||
@ -64,7 +64,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ?
|
||||
<flux:field>
|
||||
<flux:label>Kata Sandi <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
|
||||
<flux:input placeholder="********" type="password" wire:model.live="form.password"
|
||||
<flux:input placeholder="********" type="password" wire:model.live.debounce.500ms="form.password"
|
||||
autocomplete="off" viewable />
|
||||
|
||||
<flux:error name="form.password" />
|
||||
@ -77,7 +77,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ?
|
||||
<flux:field>
|
||||
<flux:label>Nama <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
|
||||
<flux:input placeholder="John Doe" wire:model.live="form.name" autofocus autocomplete="off" />
|
||||
<flux:input placeholder="John Doe" wire:model.live.debounce.500ms="form.name" autofocus autocomplete="off" />
|
||||
|
||||
<flux:error name="form.name" />
|
||||
</flux:field>
|
||||
@ -86,7 +86,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ?
|
||||
<flux:label>Nomor Telepon <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
|
||||
<flux:input placeholder="0821 xxxx xxxx" mask="9999 9999 99999"
|
||||
wire:model.live="form.phone_number" autocomplete="off" />
|
||||
wire:model.live.debounce.500ms="form.phone_number" autocomplete="off" />
|
||||
|
||||
<flux:error name="form.phone_number" />
|
||||
</flux:field>
|
||||
@ -94,7 +94,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ?
|
||||
<flux:field>
|
||||
<flux:label>Jenis Kelamin <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
|
||||
<flux:radio.group wire:model.live="form.gender" variant="buttons" class="w-full *:flex-1">
|
||||
<flux:radio.group wire:model.live.debounce.500ms="form.gender" variant="buttons" class="w-full *:flex-1">
|
||||
@foreach (\App\Enums\Gender::cases() as $gender)
|
||||
<flux:radio value="{{ $gender->value }}">
|
||||
{{ $gender->label() }}
|
||||
@ -106,7 +106,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ?
|
||||
</flux:field>
|
||||
|
||||
<flux:input label="Kode Referral" placeholder="Masukkan kode referral"
|
||||
wire:model.live="form.referral_code" autocomplete="off" />
|
||||
wire:model.live.debounce.500ms="form.referral_code" autocomplete="off" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
39
resources/views/livewire/auth/verify-email.blade.php
Normal file
39
resources/views/livewire/auth/verify-email.blade.php
Normal file
@ -0,0 +1,39 @@
|
||||
<div class="flex h-screen w-full items-center justify-center bg-zinc-50 dark:bg-zinc-950 p-6">
|
||||
<div class="w-full max-w-md space-y-8">
|
||||
<div class="flex flex-col items-center justify-center gap-4 opacity-50">
|
||||
<flux:brand href="{{ route('homepage') }}" name="Yadi Parfum" wire:navigate>
|
||||
<x-slot name="logo">
|
||||
<div
|
||||
class="size-12 rounded shrink-0 bg-accent text-accent-foreground flex items-center justify-center">
|
||||
<i class="font-serif font-bold text-xl">YD</i>
|
||||
</div>
|
||||
</x-slot>
|
||||
</flux:brand>
|
||||
|
||||
<div class="text-center">
|
||||
<flux:heading size="xl" class="font-semibold">Verifikasi Email Anda</flux:heading>
|
||||
<flux:subheading class="mt-2">
|
||||
Terima kasih telah mendaftar! Sebelum memulai, harap verifikasi email Anda dengan mengeklik tautan
|
||||
yang baru saja kami kirimkan.
|
||||
</flux:subheading>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-zinc-200 dark:bg-zinc-900 dark:ring-zinc-800 space-y-6">
|
||||
<div class="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
Jika Anda tidak menerima email tersebut, kami dengan senang hati akan mengirimkan email yang baru.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<flux:button wire:click="resend" variant="primary" class="w-full">
|
||||
Kirim Ulang Email Verifikasi
|
||||
</flux:button>
|
||||
|
||||
<flux:button wire:click="logout" variant="ghost" class="w-full">
|
||||
Keluar
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -5,6 +5,7 @@
|
||||
use App\Livewire\Auth\Logout as LogoutComponent;
|
||||
use App\Livewire\Auth\Register as RegisterComponent;
|
||||
use App\Livewire\Auth\ResetPassword as ResetPasswordComponent;
|
||||
use App\Livewire\Auth\VerifyEmail as VerifyEmailComponent;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -22,14 +23,12 @@
|
||||
|
||||
Route::post('logout', LogoutComponent::class)->name('logout')->middleware('auth');
|
||||
|
||||
Route::get('/email/verify', function () {
|
||||
return view('auth.verify-email');
|
||||
})->middleware('auth')->name('verification.notice');
|
||||
Route::get('/email/verify', VerifyEmailComponent::class)->middleware('auth')->name('verification.notice');
|
||||
|
||||
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
|
||||
$request->fulfill();
|
||||
|
||||
return redirect('/');
|
||||
return redirect()->intended(route('member.overview', absolute: false).'?verified=1');
|
||||
})->middleware(['auth', 'signed'])->name('verification.verify');
|
||||
|
||||
Route::post('/email/verification-notification', function (Request $request) {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
use App\Livewire\Member\Voucher;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware('auth')
|
||||
Route::middleware(['auth', 'verified'])
|
||||
->prefix('member')
|
||||
->name('member.')
|
||||
->group(function () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user