diff --git a/app/Http/Controllers/ForgotPasswordController.php b/app/Http/Controllers/ForgotPasswordController.php new file mode 100644 index 0000000..6fc136e --- /dev/null +++ b/app/Http/Controllers/ForgotPasswordController.php @@ -0,0 +1,69 @@ +validate(['email' => 'required|email', 'role' => 'required|in:petani,pembeli']); + + $broker = $request->role == 'petani' ? 'petanis' : 'pembelis'; + + $status = Password::broker($broker)->sendResetLink( + $request->only('email') + ); + + return $status == Password::RESET_LINK_SENT + ? back()->with(['status' => __($status)]) + : back()->withErrors(['email' => __($status)]); + } + + // Tampilkan Form Reset Password (Link dari Email) + public function showResetForm(Request $request, $token = null) + { + return view('auth.reset-password')->with( + ['token' => $token, 'email' => $request->email, 'role' => $request->role] + ); + } + + // Proses Simpan Password Baru + public function reset(Request $request) + { + $request->validate([ + 'token' => 'required', + 'email' => 'required|email', + 'password' => 'required|min:6|confirmed', + ]); + + $broker = 'pembelis'; + if (\App\Models\Petani::where('email', $request->email)->exists()) { + $broker = 'petanis'; + } + + $status = Password::broker($broker)->reset( + $request->only('email', 'password', 'password_confirmation', 'token'), + function ($user, $password) { + $user->forceFill([ + 'password' => Hash::make($password) + ])->save(); + } + ); + + return $status == Password::PASSWORD_RESET + ? redirect()->route('login')->with('success', 'Password berhasil direset! Silakan login.') + : back()->withErrors(['email' => __($status)]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/LandingController.php b/app/Http/Controllers/LandingController.php index b105ad6..a21e26e 100644 --- a/app/Http/Controllers/LandingController.php +++ b/app/Http/Controllers/LandingController.php @@ -7,17 +7,31 @@ class LandingController extends Controller { - public function index() + public function index(Request $request) { - $produks = Produk::latest()->take(8)->get(); - return view('landing.home', compact('produks')); + $query = Produk::with('petani')->latest(); + if ($request->has('kategori') && $request->kategori != '' && $request->kategori != 'Semua') { + $query->where('kategori', $request->kategori); + } + + $produks = $query->take(8)->get(); + if ($request->ajax()) { + return view('landing.partials.product_list', compact('produks'))->render(); + } + + $produkTerlaris = Produk::with('petani') + ->inRandomOrder() + ->take(4) + ->get(); + + return view('landing.home', compact('produks', 'produkTerlaris')); } public function shop(Request $request) { - $query = Produk::query(); + $query = Produk::with('petani'); - if ($request->has('search')) { + if ($request->has('search') && $request->search != '') { $query->where('nama_produk', 'like', '%' . $request->search . '%'); } diff --git a/app/Http/Controllers/PesanController.php b/app/Http/Controllers/PesanController.php index 4da0108..3e5ad57 100644 --- a/app/Http/Controllers/PesanController.php +++ b/app/Http/Controllers/PesanController.php @@ -10,30 +10,23 @@ class PesanController extends Controller { - // Menampilkan Daftar Percakapan (Inbox Utama) - public function index() + // Helper untuk mengambil daftar chat + private function getChatList($user) { - $user = $this->getAuthenticatedUser(); - $isPetani = Auth::guard('petani')->check(); - $allMessages = Pesan::where(function ($q) use ($user) { $q->where('pengirim_id', $user->id)->where('pengirim_type', get_class($user)); })->orWhere(function ($q) use ($user) { $q->where('penerima_id', $user->id)->where('penerima_type', get_class($user)); })->orderBy('created_at', 'desc')->get(); - // Kelompokkan berdasarkan ID Lawan Bicara $conversations = $allMessages->groupBy(function ($pesan) use ($user) { return $pesan->pengirim_id == $user->id ? $pesan->penerima_type . '_' . $pesan->penerima_id : $pesan->pengirim_type . '_' . $pesan->pengirim_id; }); - // Format data untuk view - $chatList = $conversations->map(function ($msgs) use ($user) { + return $conversations->map(function ($msgs) use ($user) { $lastMsg = $msgs->first(); - - // Tentukan siapa lawan bicaranya if ($lastMsg->pengirim_id == $user->id) { $lawan = $lastMsg->penerima; } else { @@ -44,28 +37,38 @@ public function index() 'lawan_id' => $lawan->id ?? 0, 'lawan_type' => get_class($lawan ?? new \stdClass), 'nama' => $lawan->nama_lengkap ?? 'User Terhapus', - 'foto' => $lawan->foto ?? null, + 'foto' => $lawan->foto ?? null, // Pastikan ada accessor url foto 'last_message' => $lastMsg->isi_pesan, 'time' => $lastMsg->created_at->diffForHumans(), 'unread' => $msgs->where('penerima_id', $user->id)->where('sudah_dibaca', false)->count() ]; }); + } + + public function index() + { + $user = $this->getAuthenticatedUser(); + $isPetani = Auth::guard('petani')->check(); + + // Panggil helper + $chatList = $this->getChatList($user); $view = $isPetani ? 'petani.pesan.index' : 'landing.pesan.index'; return view($view, compact('chatList')); } - // Menampilkan Detail Chat public function show($id) { $user = $this->getAuthenticatedUser(); $isPetani = Auth::guard('petani')->check(); - // Tentukan model lawan bicara + // Ambil List Chat juga (untuk Sidebar Kiri) + $chatList = $this->getChatList($user); + + // Logika Detail Chat (Kanan) $lawanType = $isPetani ? Pembeli::class : Petani::class; $lawan = $lawanType::findOrFail($id); - // Ambil percakapan antara User Login & Lawan Bicara $chats = Pesan::where(function ($q) use ($user, $lawan, $lawanType) { $q->where('pengirim_id', $user->id)->where('pengirim_type', get_class($user)) ->where('penerima_id', $lawan->id)->where('penerima_type', $lawanType); @@ -74,22 +77,21 @@ public function show($id) ->where('penerima_id', $user->id)->where('penerima_type', get_class($user)); })->orderBy('created_at', 'asc')->get(); - // Tandai pesan masuk sebagai "Sudah Dibaca" + // Tandai dibaca Pesan::where('pengirim_id', $lawan->id)->where('pengirim_type', $lawanType) ->where('penerima_id', $user->id)->update(['sudah_dibaca' => true]); $view = $isPetani ? 'petani.pesan.show' : 'landing.pesan.show'; - return view($view, compact('chats', 'lawan')); + + // Kirim $chatList, $chats, dan $lawan + return view($view, compact('chatList', 'chats', 'lawan')); } - // Proses Kirim Pesan public function store(Request $request) { $request->validate(['isi_pesan' => 'required']); $user = $this->getAuthenticatedUser(); $isPetani = Auth::guard('petani')->check(); - - // Menentukan tipe penerima $penerimaType = $isPetani ? 'App\Models\Pembeli' : 'App\Models\Petani'; Pesan::create([ diff --git a/app/Models/Pembeli.php b/app/Models/Pembeli.php index 8163fe2..4a86f06 100644 --- a/app/Models/Pembeli.php +++ b/app/Models/Pembeli.php @@ -4,10 +4,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Contracts\Auth\CanResetPassword; +use Illuminate\Auth\Passwords\CanResetPassword as CanResetPasswordTrait; -class Pembeli extends Authenticatable +class Pembeli extends Authenticatable implements CanResetPassword { - use Notifiable; + use Notifiable, CanResetPasswordTrait; protected $table = 'pembelis'; diff --git a/app/Models/Petani.php b/app/Models/Petani.php index 7397fc9..78c6e76 100644 --- a/app/Models/Petani.php +++ b/app/Models/Petani.php @@ -4,10 +4,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Contracts\Auth\CanResetPassword; +use Illuminate\Auth\Passwords\CanResetPassword as CanResetPasswordTrait; -class Petani extends Authenticatable +class Petani extends Authenticatable implements CanResetPassword { - use Notifiable; + use Notifiable, CanResetPasswordTrait; protected $table = 'petanis'; diff --git a/app/Models/Produk.php b/app/Models/Produk.php index e8c7ae7..8e495b9 100644 --- a/app/Models/Produk.php +++ b/app/Models/Produk.php @@ -14,6 +14,7 @@ class Produk extends Model protected $fillable = [ 'petani_id', 'nama_produk', + 'kategori', 'harga', 'stok', 'deskripsi', diff --git a/config/auth.php b/config/auth.php index 3d593ed..5324a45 100644 --- a/config/auth.php +++ b/config/auth.php @@ -114,7 +114,23 @@ 'passwords' => [ 'users' => [ 'provider' => 'users', - 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'table' => 'password_reset_tokens', + 'expire' => 60, + 'throttle' => 60, + ], + + // untuk PETANI + 'petanis' => [ + 'provider' => 'petanis', + 'table' => 'password_reset_tokens', + 'expire' => 60, + 'throttle' => 60, + ], + + // untuk PEMBELI + 'pembelis' => [ + 'provider' => 'pembelis', + 'table' => 'password_reset_tokens', 'expire' => 60, 'throttle' => 60, ], diff --git a/database/migrations/2025_11_25_123429_create_produks_table.php b/database/migrations/2025_11_25_123429_create_produks_table.php index 561b5b2..6f1b948 100644 --- a/database/migrations/2025_11_25_123429_create_produks_table.php +++ b/database/migrations/2025_11_25_123429_create_produks_table.php @@ -10,20 +10,19 @@ * Run the migrations. */ public function up(): void -{ - Schema::create('produks', function (Blueprint $table) { - $table->id(); - $table->foreignId('petani_id')->constrained('petanis')->onDelete('cascade'); - - $table->string('nama_produk'); - $table->decimal('harga', 12, 0); - $table->integer('stok'); - $table->text('deskripsi'); - $table->string('foto_produk')->nullable(); - - $table->timestamps(); - }); -} + { + Schema::create('produks', function (Blueprint $table) { + $table->id(); + $table->foreignId('petani_id')->constrained('petanis')->onDelete('cascade'); + $table->string('nama_produk'); + $table->string('kategori')->default('Lainnya'); + $table->decimal('harga', 12, 0); + $table->integer('stok'); + $table->text('deskripsi'); + $table->string('foto_produk')->nullable(); + $table->timestamps(); + }); + } /** * Reverse the migrations. diff --git a/database/migrations/2025_12_11_060158_create_password_reset_tokens_table.php b/database/migrations/2025_12_11_060158_create_password_reset_tokens_table.php new file mode 100644 index 0000000..a6d7c00 --- /dev/null +++ b/database/migrations/2025_12_11_060158_create_password_reset_tokens_table.php @@ -0,0 +1,28 @@ +string('email')->index(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('password_reset_tokens'); + } +}; diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php new file mode 100644 index 0000000..95e63f2 --- /dev/null +++ b/resources/views/auth/forgot-password.blade.php @@ -0,0 +1,43 @@ + + + + Lupa Password - TaniDesa + + + +
+
+

Lupa Password?

+

Masukkan email dan peran Anda untuk reset password.

+
+ + @if (session('status')) +
{{ session('status') }}
+ @endif + @if ($errors->any()) +
{{ $errors->first() }}
+ @endif + +
+ @csrf +
+ + +
+
+ + +
+
+ +
+
+
+ Kembali ke Login +
+
+ + \ No newline at end of file diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 90fe3e9..6ce4104 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -1,96 +1,62 @@ - - + Masuk - TaniDesa - -
-

TaniDesa

+

TaniDesa

-

Masuk untuk melanjutkan

+

Masuk untuk melanjutkan

- {{-- Pesan Sukses --}} - @if (session('success')) - - @endif - - {{-- Pesan Error --}} - @if ($errors->any()) -
-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
- @endif + @if(session('success'))
{{ session('success') }}
@endif + @if($errors->any())
{{ $errors->first() }}
@endif
@csrf
- +
-
+
- - + + + {{-- Icon Mata --}} + + +
+ +
@@ -102,5 +68,22 @@
+ {{-- Script Show/Hide Password --}} + \ No newline at end of file diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 97d0af4..089422d 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -1,95 +1,139 @@ - + Daftar - TaniDesa - - + -
+
- -

Daftar Akun

-
-

Bergabunglah dengan TaniDesa

+

Daftar Akun

+

Gabung komunitas TaniDesa

@csrf - - {{-- Pilihan Role --}}
- - + +
-
+
-
+
+ + +
+ + {{-- PASSWORD DENGAN VALIDASI --}} +
- +
+ + + + +
+
+ + {{-- Indikator Kekuatan Password --}} +
+
Minimal 8 Karakter
+
Ada Angka (0-9)
+
Ada Huruf Kapital (A-Z)
- +
@@ -98,34 +142,77 @@
- {{-- Input Khusus Petani --}} - - \ No newline at end of file + + diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php new file mode 100644 index 0000000..04ba791 --- /dev/null +++ b/resources/views/auth/reset-password.blade.php @@ -0,0 +1,133 @@ + + + + + Reset Password - TaniDesa + + + + + + +
+
+

Buat Password Baru

+
+ + @if ($errors->any()) +
{{ $errors->first() }}
+ @endif + +
+ @csrf + + +
+ + +
+ + {{-- Password Baru --}} +
+ +
+ + + + +
+
+ + {{-- Indikator Validasi --}} +
+
Minimal 8 Karakter
+
Ada Angka (0-9)
+
Ada Huruf Kapital (A-Z)
+
+ +
+ +
+ + + + +
+
+ +
+ +
+
+
+ + + + + diff --git a/resources/views/landing/cart.blade.php b/resources/views/landing/cart.blade.php index c679476..6b324b2 100644 --- a/resources/views/landing/cart.blade.php +++ b/resources/views/landing/cart.blade.php @@ -3,180 +3,168 @@ @section('title', 'Keranjang Belanja') @section('content') - +
+

Keranjang Belanja

-
-
- {{-- Alert Notification --}} - @if(session('success')) - - @endif - -
- - - - - - - - - - - - - @php $total_belanja = 0; @endphp - @forelse($cart as $id => $details) - @php - $total = $details['price'] * $details['quantity']; - $total_belanja += $total; - @endphp - - - - - - - - - @empty - - - - @endforelse - -
ProdukNamaHargaJumlahTotalAksi
-
- {{ $details['name'] }} -
-
-

{{ $details['name'] }}

-
-

Rp {{ number_format($details['price'], 0, ',', '.') }}

-
-
-
- -
- - - -
- -
-
-
-

Rp {{ number_format($total, 0, ',', '.') }}

-
-
- @csrf - @method('DELETE') - - -
-
-
- -

Keranjang belanja masih kosong.

- Mulai Belanja -
-
+ {{-- Alert Notification --}} + @if (session('success')) + + @endif - @if (count($cart) > 0) -
-
-
-
-

Total Belanja

-
-
Subtotal:
-

Rp {{ number_format($total_belanja, 0, ',', '.') }}

-
+ @if (count($cart) > 0) +
+
+
+
+
+ + + + + + + + + + + + @php $total_belanja = 0; @endphp + @foreach ($cart as $id => $details) + @php + $total = $details['price'] * $details['quantity']; + $total_belanja += $total; + @endphp + + + + + + + + @endforeach + +
ProdukHargaJumlahTotal
+
+ + {{ $details['name'] }} +
+
Rp + {{ number_format($details['price'], 0, ',', '.') }} +
+ + + +
+
Rp {{ number_format($total, 0, ',', '.') }} + +
+ @csrf + @method('DELETE') + + +
+
-
-
Total
-

Rp {{ number_format($total_belanja, 0, ',', '.') }}

-
- - {{-- Button Checkout --}} - Proses Checkout
- @endif -
+ +
+
+
+
Ringkasan Belanja
+
+ Subtotal + Rp {{ number_format($total_belanja, 0, ',', '.') }} +
+
+ Total + Rp + {{ number_format($total_belanja, 0, ',', '.') }} +
+ + Checkout Sekarang + + + Lanjut Belanja + +
+
+
+
+ @else +
+
+ +
+

Keranjang Kosong

+

Yuk isi dengan beras berkualitas kami.

+ + Mulai Belanja + +
+ @endif
@endsection @section('js') - -@endsection \ No newline at end of file + + function updateCart(id, qty) { + $.ajax({ + url: "{{ route('cart.update') }}", + method: "patch", + data: { + _token: '{{ csrf_token() }}', + id: id, + quantity: qty + }, + success: function(response) { + window.location.reload(); + } + }); + } + }); + +@endsection diff --git a/resources/views/landing/checkout.blade.php b/resources/views/landing/checkout.blade.php index 18b4536..20ff068 100644 --- a/resources/views/landing/checkout.blade.php +++ b/resources/views/landing/checkout.blade.php @@ -46,11 +46,30 @@ placeholder="Tulis alamat lengkap Anda...">{{ Auth::guard('pembeli')->user()->alamat }}
-
- - +
+
+
Metode Pembayaran
+
+
+ + + + + +
diff --git a/resources/views/landing/detail.blade.php b/resources/views/landing/detail.blade.php index fae9d48..f8bb95f 100644 --- a/resources/views/landing/detail.blade.php +++ b/resources/views/landing/detail.blade.php @@ -3,178 +3,196 @@ @section('title', $produk->nama_produk) @section('content') - -