Lupa Password

This commit is contained in:
hildaaaevs 2025-06-22 12:59:49 +07:00
parent f5f2056e86
commit 0657270b87
11 changed files with 204 additions and 40 deletions

View File

@ -79,7 +79,9 @@ public static function form(Form $form): Form
'full' => 'Full',
'dp' => 'DP',
])
->required(),
->required()
->default('full')
->inline(),
Select::make('status_pembayaran')
->label('Status Pembayaran')
->options([
@ -94,10 +96,6 @@ public static function form(Form $form): Form
->image()
->directory('bukti-pembayaran')
->visibility('public')
->imageResizeMode('cover')
->imageCropAspectRatio('16:9')
->imageResizeTargetWidth('1920')
->imageResizeTargetHeight('1080')
->preserveFilenames()
->downloadable()
->openable()
@ -171,7 +169,7 @@ public static function form(Form $form): Form
'cream' => 'Cream',
'spotlight' => 'Spotlight'
])
->columnSpan(2), // Lebih kecil karena opsinya sedikit
->columnSpan(2),
TextInput::make('jumlah')
->numeric()
@ -208,11 +206,33 @@ public static function form(Form $form): Form
foreach ($repeaters as $key => $repeater){
$total += $get("detail.{$key}.total_harga");
}
// Hitung diskon jika ada promo
$diskon = 0;
if ($promoId = $get('promo_id')) {
$promo = \App\Models\Promo::find($promoId);
if ($promo && $promo->aktif) {
if ($promo->tipe === 'fix') {
$diskon = $promo->diskon;
} else if ($promo->tipe === 'persen') {
$diskon = ($total * $promo->diskon) / 100;
}
}
}
$totalSetelahDiskon = $total - $diskon;
$set('total', $total);
return 'Rp ' . number_format($total, 0, ',', '.');
$set('diskon', $diskon);
$set('total_setelah_diskon', $totalSetelahDiskon);
return 'Rp ' . number_format($totalSetelahDiskon, 0, ',', '.');
}),
Hidden::make('total')
->default(0)
->default(0),
Hidden::make('diskon')
->default(0),
Hidden::make('total_setelah_diskon')
->default(0),
])->columnSpanFull(),
])
]);
@ -221,6 +241,7 @@ public static function form(Form $form): Form
public static function table(Table $table): Table
{
return $table
->defaultSort('created_at', 'desc')
->columns([
Tables\Columns\TextColumn::make('nama')
->label('Nama'),

View File

@ -23,18 +23,17 @@ public function save(){
]);
// save to database
$user = User::create([
User::create([
'name' => $this->name,
'email' => $this->email,
'password' => Hash::make($this->password)
]);
// login user
auth()->login($user);
// redirect to home page
return redirect()->intended();
// set success message
session()->flash('success', 'Registrasi berhasil! Silakan login untuk melanjutkan.');
// redirect to login page
return redirect()->route('login');
}
public function render()
{

View File

@ -3,9 +3,58 @@
namespace App\Livewire\Auth;
use Livewire\Component;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use App\Models\User;
class ResetPasswordPage extends Component
{
public $token;
#[Url]
public $email;
public $password;
public $password_confirmation;
public function mount($token = null)
{
$this->token = $token ?? request()->route('token');
$this->email = request()->query('email');
}
public function save()
{
$this->validate([
'email' => 'required|email|exists:users,email',
'password' => 'required|min:6|confirmed',
]);
$status = Password::reset(
[
'email' => $this->email,
'password' => $this->password,
'password_confirmation' => $this->password_confirmation,
'token' => $this->token,
],
function (User $user, string $password) {
$password = $this->password;
$user->forceFill([
'password' => Hash::make($password)
])->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
}
);
if ($status == Password::PASSWORD_RESET) {
session()->flash('success', 'Password berhasil diubah!');
return redirect('/login');
} else {
session()->flash('error', 'Terjadi kesalahan. Silakan coba lagi.');
}
}
public function render()
{
return view('livewire.auth.reset-password-page');

View File

@ -67,7 +67,7 @@ public function getTimeLeftProperty()
}
$createdAt = Carbon::parse($this->booking->created_at);
$expiryTime = $createdAt->addMinutes(5);
$expiryTime = $createdAt->addMinutes(1);
return max(0, Carbon::now()->diffInSeconds($expiryTime));
}

View File

@ -4,10 +4,13 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Notifications\Notifiable;
use App\Notifications\ReservasiApproved;
use Illuminate\Support\Facades\Log;
class Reservasii extends Model
{
use HasFactory;
use HasFactory, Notifiable;
protected $fillable = [
'user_id',
@ -24,9 +27,8 @@ class Reservasii extends Model
protected $casts = [
'tanggal' => 'date',
'waktu' => 'datetime',
'total' => 'decimal:2',
'bukti_pembayaran' => 'array'
'waktu' => 'string',
'total' => 'decimal:2'
];
public function user()
@ -45,6 +47,21 @@ public function paketFoto()
{
return $this->belongsTo(PaketFoto::class);
}
protected static function booted()
{
static::updated(function ($reservasi) {
if ($reservasi->isDirty('status_pembayaran') && $reservasi->status_pembayaran === 'approved') {
Log::info('Mencoba mengirim notifikasi email untuk reservasi ID: ' . $reservasi->id);
try {
$reservasi->notify(new ReservasiApproved($reservasi));
Log::info('Notifikasi email berhasil dikirim untuk reservasi ID: ' . $reservasi->id);
} catch (\Exception $e) {
Log::error('Gagal mengirim notifikasi email: ' . $e->getMessage());
}
}
});
}
}

View File

@ -54,4 +54,9 @@ public function canAccessPanel(Panel $panel): bool
{
return $this->email == 'admin@gmail.com';
}
public function routeNotificationForMail()
{
return $this->email;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use App\Models\Reservasii;
use Illuminate\Support\Facades\Log;
class ReservasiApproved extends Notification implements ShouldQueue
{
use Queueable;
protected $reservasi;
public function __construct(Reservasii $reservasi)
{
$this->reservasi = $reservasi;
Log::info('Notifikasi ReservasiApproved dibuat untuk reservasi ID: ' . $reservasi->id);
}
public function via($notifiable)
{
Log::info('Mengirim notifikasi via email untuk reservasi ID: ' . $this->reservasi->id);
return ['mail'];
}
public function toMail($notifiable)
{
Log::info('Menyiapkan email untuk reservasi ID: ' . $this->reservasi->id);
return (new MailMessage)
->subject('Reservasi Anda Telah Disetujui')
->greeting('Halo ' . $this->reservasi->nama . '!')
->line('Reservasi Anda dengan nomor ID: ' . $this->reservasi->id . ' telah disetujui.')
->line('Detail Reservasi:')
->line('Tanggal: ' . $this->reservasi->tanggal->format('d F Y'))
->line('Waktu: ' . $this->reservasi->waktu)
->line('Total Pembayaran: Rp ' . number_format($this->reservasi->total, 0, ',', '.'))
->line('Silahkan datang sesuai dengan jadwal yang telah ditentukan.')
->line('Terima kasih telah memilih layanan kami!')
->salutation('Salam, Tim SiKolaself');
}
}

View File

@ -23,6 +23,8 @@ public function up(): void
$table->string('metode_pembayaran');
$table->string('bukti_pembayaran')->nullable();
$table->enum('status_pembayaran', ['pending', 'approved', 'rejected'])->default('pending');
$table->decimal('diskon', 10, 2)->default(0);
$table->decimal('total_setelah_diskon', 10, 2)->default(0);
$table->timestamps();
});
}

View File

@ -29,7 +29,7 @@
<div class="grid gap-y-4">
<!-- Form Group -->
<div>
<label for="email" class="block text-sm mb-2 dark:text-white">Email address</label>
<label for="email" class="block text-sm mb-2 dark:text-white">Alamat Email</label>
<div class="relative">
<input type="email" id="email" wire:model="email" class="py-3 px-4 block w-full border border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500
disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:ring-gray-600" aria-describedby="email-error">

View File

@ -13,6 +13,12 @@
<h1 class="text-2xl font-bold text-center text-gray-800 mb-6">Login</h1>
<form wire:submit.prevent="save" class="space-y-6">
@if (session('success'))
<div class="bg-green-500 text-white p-4 rounded-lg text-sm mb-4" role="alert">
{{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="bg-red-500 text-white p-4 rounded-lg text-sm mb-4" role="alert">
{{ session('error') }}
@ -32,7 +38,7 @@
<div>
<div class="flex justify-between items-center mb-1">
<label for="password" class="block text-sm font-medium text-gray-700 dark:text-white">Password</label>
{{--<a href="/forgot" class="text-sm text-blue-600 hover:underline">Lupa password?</a>--}}
<a href="/forgot" class="text-sm text-blue-600 hover:underline">Lupa password?</a>
</div>
<input type="password" id="password" wire:model="password" class="py-3 px-4 block w-full border border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:ring-gray-600">
@error('password')
@ -45,6 +51,5 @@
<p class="text-sm text-center text-gray-600">Belum punya akun? <a href="/register" class="text-blue-600 hover:underline">Daftar</a></p>
</form>
</>
</div>
</div>

View File

@ -7,43 +7,64 @@
<h1 class="block text-2xl font-bold text-gray-800 dark:text-white">Reset password</h1>
</div>
@if (session()->has('error'))
<div class="mt-4 p-4 text-sm text-red-800 border border-red-300 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800" role="alert">
{{ session('error') }}
</div>
@endif
@if (session()->has('success'))
<div class="mt-4 p-4 text-sm text-green-800 border border-green-300 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400 dark:border-green-800" role="alert">
{{ session('success') }}
</div>
@endif
<div class="mt-5">
<!-- Form -->
<form wire:submit.prevent="save">
<div class="grid gap-y-4">
<!-- Form Group -->
<div>
<label for="password" class="block text-sm mb-2 dark:text-white">Password</label>
<label for="password" class="block text-sm mb-2 dark:text-white">Password Baru</label>
<div class="relative">
<input type="password" id="password" wire:model="password" class="py-3 px-4 block w-full border border-gray-200 rounded-lg text-sm
focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:ring-gray-600" aria-describedby="email-error">
<div class=" absolute inset-y-0 end-0 flex items-center pointer-events-none pe-3">
<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
</svg>
</div>
<input type="password" id="password" wire:model.live="password" class="py-3 px-4 block w-full border border-gray-200 rounded-lg text-sm
focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:ring-gray-600 @error('password') border-red-500 @enderror" aria-describedby="password-error">
@error('password')
<div class="absolute inset-y-0 end-0 flex items-center pointer-events-none pe-3">
<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
</svg>
</div>
@enderror
</div>
<p class="text-red-600 mt-2" id="password-error">Password error message</p>
@error('password')
<p class="text-red-600 text-xs mt-2" id="password-error">{{ $message }}</p>
@enderror
</div>
<!-- End Form Group -->
<div>
<label for="password_confirmation" class="block text-sm mb-2 dark:text-white">Confirm Password</label>
<label for="password_confirmation" class="block text-sm mb-2 dark:text-white">Konfirmasi Password</label>
<div class="relative">
<input type="password" id="password_confirmation" wire:model="password_confirmation" class="py-3 px-4 block w-full border border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:ring-gray-600" required aria-describedby="email-error">
<input type="password" id="password_confirmation" wire:model.live="password_confirmation" class="py-3 px-4 block w-full border border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:ring-gray-600 @error('password_confirmation') border-red-500 @enderror" required aria-describedby="password_confirmation-error">
<div class="hidden inset-y-0 end-0 flex items-center pointer-events-none pe-3">
<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
</svg>
</div>
@error('password_confirmation')
<div class="absolute inset-y-0 end-0 flex items-center pointer-events-none pe-3">
<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
</svg>
</div>
@enderror
</div>
<p class="text-xs text-red-600 mt-2" id="password_confirmation-error">Confirm Password Error</p>
@error('password_confirmation')
<p class="text-xs text-red-600 mt-2" id="password_confirmation-error">{{ $message }}</p>
@enderror
</div>
<button type="submit" class="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600">
Save password
<span wire:loading.remove>Simpan Password</span>
<span wire:loading>Simpan Password</span>
</button>
</div>
</form>