feat: Implement member's "My Vouchers" page with new Livewire component, view, sidebar navigation, and route.

This commit is contained in:
Yoga Pangestu 2026-01-06 20:27:17 +07:00
parent 6706c191ed
commit ca92fc9089
4 changed files with 169 additions and 0 deletions

View File

@ -0,0 +1,74 @@
<?php
namespace App\Livewire\Member;
use App\Enums\VoucherType;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Voucher Saya')]
#[Layout('components.layouts.member')]
class MyVouchers extends Component
{
public array $myVouchers = [];
public function mount()
{
$this->myVouchers = auth()->user()->vouchers()
->withPivot('quantity', 'created_at')
->orderBy('user_voucher.created_at', 'desc')
->get()
->map(function ($voucher) {
// Status Logic
$now = now();
$isActive = $voucher->start_date <= $now && ($voucher->end_date === null || $voucher->end_date >= $now);
$isExpired = $voucher->end_date && $voucher->end_date < $now;
$isUpcoming = $voucher->start_date > $now;
$status = 'active';
if ($isExpired) {
$status = 'expired';
} elseif ($isUpcoming) {
$status = 'upcoming';
}
// Discount Display Logic
if ($voucher->type === VoucherType::PERCENTAGE) {
$discountDisplay = "Diskon {$voucher->discount_amount}%";
if ($voucher->max_discount) {
$discountDisplay .= ' (Max Rp'.formatCurrencyNumber($voucher->max_discount).')';
}
} else {
$discountDisplay = 'Potongan Rp'.formatCurrencyNumber($voucher->discount_amount);
}
// Date Display Logic
$dateDisplay = formatDateLocalized($voucher->start_date);
if ($voucher->end_date) {
$dateDisplay .= ' - '.formatDateLocalized($voucher->end_date);
} else {
$dateDisplay .= ' - Tidak ada batas waktu';
}
$voucher->claimed_at_display = formatDateTimeLocalized($voucher->pivot->created_at);
$voucher->date_display = $dateDisplay;
$voucher->min_purchase_display = $voucher->min_purchase ? 'Min. Belanja: Rp'.formatCurrencyNumber($voucher->min_purchase) : null;
$voucher->discount_display = $discountDisplay;
$voucher->status = $status;
$voucher->quantity = $voucher->pivot->quantity;
return $voucher;
})
->toArray();
}
public function render(): View
{
return view('livewire.member.my-vouchers', [
'pageTitle' => 'Voucher Saya',
'pageDescription' => 'Daftar voucher yang telah Anda klaim dan dapat digunakan ketika belanja di toko.',
]);
}
}

View File

@ -13,6 +13,9 @@ class="bg-zinc-50 dark:bg-zinc-900 border-r rtl:border-r-0 rtl:border-l border-z
<flux:navlist.item icon="user-circle" href="{{ route('member.membership') }}"
:current="request()->routeIs('member.membership')" wire:navigate>
Membership</flux:navlist.item>
<flux:navlist.item icon="ticket" href="{{ route('member.my_vouchers') }}"
:current="request()->routeIs('member.my_vouchers')" wire:navigate>
Voucher Saya</flux:navlist.item>
<flux:navlist.item icon="check-badge" href="{{ route('member.redeem_point') }}"
:current="request()->routeIs('member.redeem_point')" wire:navigate>
Penukaran Poin</flux:navlist.item>

View File

@ -0,0 +1,90 @@
<flux:main>
<flux:heading size="xl" level="1">{{ $pageTitle }}</flux:heading>
<flux:text class="mb-6 mt-2 text-base">{{ $pageDescription }}</flux:text>
@if (empty($myVouchers))
<flux:card class="text-center py-12">
<div class="flex flex-col items-center justify-center">
<flux:icon.ticket variant="outline" class="size-16 text-zinc-400 mb-4" />
<flux:heading size="lg" class="mb-2">Belum Ada Voucher</flux:heading>
<flux:text class="text-zinc-500 mb-6">
Anda belum memiliki voucher. Klaim voucher di halaman utama untuk mendapatkan diskon menarik!
</flux:text>
<flux:button href="{{ route('homepage') }}" wire:navigate>
Klaim Voucher Sekarang
</flux:button>
</div>
</flux:card>
@else
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach ($myVouchers as $voucher)
<flux:card class="relative overflow-hidden">
{{-- Badge Status --}}
<div class="absolute top-4 right-4">
@if ($voucher['status'] === 'active')
<flux:badge color="emerald" size="sm">Aktif</flux:badge>
@elseif ($voucher['status'] === 'expired')
<flux:badge color="red" size="sm">Kadaluarsa</flux:badge>
@elseif ($voucher['status'] === 'upcoming')
<flux:badge color="blue" size="sm">Segera</flux:badge>
@endif
</div>
{{-- Voucher Content --}}
<div class="mb-4">
<flux:heading size="lg" class="mb-2">{{ $voucher['name'] }}</flux:heading>
{{-- Voucher Code --}}
<div class="flex items-center gap-2 mb-3">
<flux:badge color="zinc" size="lg" class="font-mono text-lg">
{{ $voucher['code'] }}
</flux:badge>
@if ($voucher['quantity'] > 1)
<flux:badge color="blue" size="sm">
{{ $voucher['quantity'] }}x
</flux:badge>
@endif
</div>
{{-- Summary --}}
<flux:text class="text-sm mb-3">
{{ $voucher['summary'] }}
</flux:text>
{{-- Discount Info --}}
<div class="flex items-center gap-2 mb-2">
<flux:icon.tag class="size-4 text-emerald-600" />
<flux:text class="text-sm font-semibold text-emerald-600">
{{ $voucher['discount_display'] }}
</flux:text>
</div>
{{-- Min Purchase --}}
@if ($voucher['min_purchase_display'])
<div class="flex items-center gap-2 mb-2">
<flux:icon.shopping-cart class="size-4" />
<flux:text class="text-sm">
{{ $voucher['min_purchase_display'] }}
</flux:text>
</div>
@endif
{{-- Valid Period --}}
<div class="flex items-center gap-2">
<flux:icon.calendar class="size-4" />
<flux:text class="text-sm">
{{ $voucher['date_display'] }}
</flux:text>
</div>
</div>
{{-- Claimed Date --}}
<flux:separator class="my-4" />
<flux:text class="text-xs">
Diklaim pada: {{ $voucher['claimed_at_display'] }}
</flux:text>
</flux:card>
@endforeach
</div>
@endif
</flux:main>

View File

@ -1,6 +1,7 @@
<?php
use App\Livewire\Member\Membership;
use App\Livewire\Member\MyVouchers;
use App\Livewire\Member\Overview;
use App\Livewire\Member\RedeemPoint;
use App\Livewire\Member\Referral;
@ -14,6 +15,7 @@
Route::get('overview', Overview::class)->name('overview');
Route::get('memberships', Membership::class)->name('membership');
Route::get('my-vouchers', MyVouchers::class)->name('my_vouchers');
Route::get('redeem-points', RedeemPoint::class)->name('redeem_point');
Route::get('referrals', Referral::class)->name('referral');
Route::get('settings', Account::class)->name('setting');