From 096a7b384b693c3dcc6a26374e64d5b1a29166da Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sun, 4 Jan 2026 21:39:00 +0700 Subject: [PATCH] feat: Implement email verification, protect member routes, and improve registration with debounced inputs and unique phone numbers. --- app/Livewire/Auth/Login.php | 2 +- app/Livewire/Auth/VerifyEmail.php | 48 +++++++++++++++++++ app/Livewire/Forms/Auth/RegisterForm.php | 12 +++-- .../Forms/Studio/Loyalty/CustomerForm.php | 2 +- ...25_09_30_031349_create_customers_table.php | 2 +- .../views/livewire/auth/register.blade.php | 14 +++--- .../livewire/auth/verify-email.blade.php | 39 +++++++++++++++ routes/pages/auth.php | 7 ++- routes/pages/member.php | 2 +- 9 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 app/Livewire/Auth/VerifyEmail.php create mode 100644 resources/views/livewire/auth/verify-email.blade.php diff --git a/app/Livewire/Auth/Login.php b/app/Livewire/Auth/Login.php index 9525730..92db896 100644 --- a/app/Livewire/Auth/Login.php +++ b/app/Livewire/Auth/Login.php @@ -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()); } diff --git a/app/Livewire/Auth/VerifyEmail.php b/app/Livewire/Auth/VerifyEmail.php new file mode 100644 index 0000000..a2816f2 --- /dev/null +++ b/app/Livewire/Auth/VerifyEmail.php @@ -0,0 +1,48 @@ + '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'); + } +} diff --git a/app/Livewire/Forms/Auth/RegisterForm.php b/app/Livewire/Forms/Auth/RegisterForm.php index 483f988..df272c2 100644 --- a/app/Livewire/Forms/Auth/RegisterForm.php +++ b/app/Livewire/Forms/Auth/RegisterForm.php @@ -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; } } diff --git a/app/Livewire/Forms/Studio/Loyalty/CustomerForm.php b/app/Livewire/Forms/Studio/Loyalty/CustomerForm.php index 2f57a64..1bc89ed 100644 --- a/app/Livewire/Forms/Studio/Loyalty/CustomerForm.php +++ b/app/Livewire/Forms/Studio/Loyalty/CustomerForm.php @@ -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())], ]; } diff --git a/database/migrations/2025_09_30_031349_create_customers_table.php b/database/migrations/2025_09_30_031349_create_customers_table.php index d7ba3ba..d09714a 100644 --- a/database/migrations/2025_09_30_031349_create_customers_table.php +++ b/database/migrations/2025_09_30_031349_create_customers_table.php @@ -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(); diff --git a/resources/views/livewire/auth/register.blade.php b/resources/views/livewire/auth/register.blade.php index 55092aa..1825584 100644 --- a/resources/views/livewire/auth/register.blade.php +++ b/resources/views/livewire/auth/register.blade.php @@ -47,7 +47,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ? Pastikan email aktif karena akan digunakan untuk verifikasi. - @@ -56,7 +56,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ? Nama Pengguna * - + @@ -64,7 +64,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ? Kata Sandi * - @@ -77,7 +77,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ? Nama * - + @@ -86,7 +86,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ? Nomor Telepon * + wire:model.live.debounce.500ms="form.phone_number" autocomplete="off" /> @@ -94,7 +94,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ? Jenis Kelamin * - + @foreach (\App\Enums\Gender::cases() as $gender) {{ $gender->label() }} @@ -106,7 +106,7 @@ class="transition-all duration-500 ease-in-out {{ $currentStep === $step['id'] ? + wire:model.live.debounce.500ms="form.referral_code" autocomplete="off" /> @endif diff --git a/resources/views/livewire/auth/verify-email.blade.php b/resources/views/livewire/auth/verify-email.blade.php new file mode 100644 index 0000000..6e1f2e1 --- /dev/null +++ b/resources/views/livewire/auth/verify-email.blade.php @@ -0,0 +1,39 @@ +
+
+
+ + +
+ YD +
+
+
+ +
+ Verifikasi Email Anda + + Terima kasih telah mendaftar! Sebelum memulai, harap verifikasi email Anda dengan mengeklik tautan + yang baru saja kami kirimkan. + +
+
+ +
+
+ Jika Anda tidak menerima email tersebut, kami dengan senang hati akan mengirimkan email yang baru. +
+ +
+ + Kirim Ulang Email Verifikasi + + + + Keluar + +
+
+
+
diff --git a/routes/pages/auth.php b/routes/pages/auth.php index deb3476..58ca876 100644 --- a/routes/pages/auth.php +++ b/routes/pages/auth.php @@ -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) { diff --git a/routes/pages/member.php b/routes/pages/member.php index 7b9382d..5f66438 100644 --- a/routes/pages/member.php +++ b/routes/pages/member.php @@ -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 () {