feat: implement member referral system including code generation, usage tracking, and UI integration.
This commit is contained in:
parent
716fb3448b
commit
33d11cedea
55
app/Livewire/Member/Referral.php
Normal file
55
app/Livewire/Member/Referral.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Member;
|
||||||
|
|
||||||
|
use App\Models\ReferralUsage;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Attributes\Layout;
|
||||||
|
use Livewire\Attributes\Title;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
#[Title('Referral')]
|
||||||
|
#[Layout('components.layouts.member')]
|
||||||
|
class Referral extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Target spending for referral reward.
|
||||||
|
*/
|
||||||
|
public const TARGET_SPENDING = 50000;
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$referralCode = $user->referralCode;
|
||||||
|
|
||||||
|
$usages = ReferralUsage::with(['user', 'user.customer', 'user.membership'])
|
||||||
|
->where('referral_code_id', $referralCode->id)
|
||||||
|
->latest()
|
||||||
|
->get()
|
||||||
|
->map(function ($usage) {
|
||||||
|
$spending = $usage->user?->membership?->total_spending ?? 0;
|
||||||
|
$progress = min(100, ($spending / self::TARGET_SPENDING) * 100);
|
||||||
|
$isRewardEarned = $spending >= self::TARGET_SPENDING;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'name' => $usage->user?->customer?->name ?? 'Perfumers',
|
||||||
|
'joined_at' => $usage->created_at,
|
||||||
|
'spending' => $spending,
|
||||||
|
'progress' => $progress,
|
||||||
|
'is_reward_earned' => $isRewardEarned,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
$message = 'Halo! Ayo bergabung di Yadi Parfum dan dapatkan keuntungan eksklusif. Gunakan kode referral saya: ' . ($referralCode?->code ?? '') . ' saat mendaftar! ' . url('/auth/register');
|
||||||
|
$whatsappUrl = 'https://wa.me/?text=' . urlencode($message);
|
||||||
|
|
||||||
|
return view('livewire.member.referral', [
|
||||||
|
'referralCode' => $referralCode,
|
||||||
|
'usages' => $usages,
|
||||||
|
'whatsappUrl' => $whatsappUrl,
|
||||||
|
'targetSpending' => self::TARGET_SPENDING,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,7 +5,6 @@
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Veelasky\LaravelHashId\Eloquent\HashableId;
|
use Veelasky\LaravelHashId\Eloquent\HashableId;
|
||||||
|
|
||||||
@ -27,8 +26,8 @@ public function user(): BelongsTo
|
|||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function referralUsage(): HasOne
|
public function usages(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
{
|
{
|
||||||
return $this->hasOne(ReferralUsage::class);
|
return $this->hasMany(ReferralUsage::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,9 @@ class="bg-zinc-50 dark:bg-zinc-900 border-r rtl:border-r-0 rtl:border-l border-z
|
|||||||
<flux:navlist.item icon="check-badge" href="{{ route('member.redeem_point') }}"
|
<flux:navlist.item icon="check-badge" href="{{ route('member.redeem_point') }}"
|
||||||
:current="request()->routeIs('member.redeem_point')" wire:navigate>
|
:current="request()->routeIs('member.redeem_point')" wire:navigate>
|
||||||
Penukaran Poin</flux:navlist.item>
|
Penukaran Poin</flux:navlist.item>
|
||||||
|
<flux:navlist.item icon="user-group" href="{{ route('member.referral') }}"
|
||||||
|
:current="request()->routeIs('member.referral')" wire:navigate>
|
||||||
|
Referral</flux:navlist.item>
|
||||||
|
|
||||||
@can('view studio overview')
|
@can('view studio overview')
|
||||||
<div class="mt-3 mb-1">
|
<div class="mt-3 mb-1">
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
<flux:heading size="xl" level="1">{{ $pageTitle }}</flux:heading>
|
<flux:heading size="xl" level="1">{{ $pageTitle }}</flux:heading>
|
||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
@if ($membership)
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||||
@foreach ($stats as $stat)
|
@foreach ($stats as $stat)
|
||||||
<div class="rounded-lg px-6 py-4 bg-zinc-200 dark:bg-zinc-700">
|
<div class="rounded-lg px-6 py-4 bg-zinc-200 dark:bg-zinc-700">
|
||||||
@ -29,17 +28,6 @@
|
|||||||
<flux:heading class="mt-2">{{ $progress }} %</flux:heading>
|
<flux:heading class="mt-2">{{ $progress }} %</flux:heading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
|
||||||
<div class="flex justify-center flex-col">
|
|
||||||
<dotlottie-wc src="https://lottie.host/4282c234-6d0f-4a8d-ad04-3d82f2783814/FuhTCVgPGi.lottie" autoplay
|
|
||||||
loop style="width: 200px; height: 200px; margin: 0 auto;">
|
|
||||||
</dotlottie-wc>
|
|
||||||
<flux:text class="block text-center italic mt-5">
|
|
||||||
Apa? Anda belum menjadi member, ayo sini <flux:link href="{{ route('member.overview') }}"
|
|
||||||
wire:navigate>daftar.</flux:link>
|
|
||||||
</flux:text>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</flux:main>
|
</flux:main>
|
||||||
@assets
|
@assets
|
||||||
|
|||||||
@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
<x-slot name="actions">
|
<x-slot name="actions">
|
||||||
<flux:button href="{{ $whatsappUrl }}" target="_blank">Undang teman</flux:button>
|
<flux:button href="{{ $whatsappUrl }}" target="_blank">Undang teman</flux:button>
|
||||||
<flux:button href="{{ route('member.redeem_point') }}">Ambil Hadiah</flux:button>
|
<flux:button href="{{ route('member.referral') }}" wire:navigate>Riwayat</flux:button>
|
||||||
|
<flux:button href="{{ route('member.redeem_point') }}" wire:navigate>Ambil Hadiah</flux:button>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
</flux:callout>
|
</flux:callout>
|
||||||
|
|
||||||
|
|||||||
80
resources/views/livewire/member/referral.blade.php
Normal file
80
resources/views/livewire/member/referral.blade.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<flux:main>
|
||||||
|
<flux:heading size="xl" level="1">Referral</flux:heading>
|
||||||
|
<flux:text class="mb-6 mt-2 text-base">Ajak temanmu bergabung dan dapatkan hadiah menarik dengan kode referralmu.
|
||||||
|
</flux:text>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<flux:card class="mb-8">
|
||||||
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<flux:heading size="lg">Kode Referral Anda</flux:heading>
|
||||||
|
<div class="flex items-center gap-2 mt-2" x-data="{ copied: false }">
|
||||||
|
<div
|
||||||
|
class="px-4 py-2 bg-zinc-100 dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 font-mono text-xl font-bold tracking-wider">
|
||||||
|
{{ $referralCode->code }}
|
||||||
|
</div>
|
||||||
|
<flux:button x-bind:icon="copied ? 'check' : 'clipboard'"
|
||||||
|
@click="navigator.clipboard.writeText('{{ $referralCode->code }}'); copied = true; setTimeout(() => copied = false, 2000)">
|
||||||
|
<span x-text="copied ? 'Tersalin' : 'Salin'"></span>
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<flux:button href="{{ $whatsappUrl }}" target="_blank" color="emerald" icon="share">
|
||||||
|
Undang Teman via WhatsApp
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</flux:card>
|
||||||
|
|
||||||
|
<flux:heading size="lg" class="mb-4">Riwayat Penggunaan</flux:heading>
|
||||||
|
|
||||||
|
@if ($usages->isEmpty())
|
||||||
|
<flux:card class="flex flex-col items-center justify-center py-12">
|
||||||
|
@include('components.animations.lottie.not-found', [
|
||||||
|
'message' => 'Belum ada yang menggunakan kode referral Anda. Ayo ajak teman-temanmu!',
|
||||||
|
])
|
||||||
|
</flux:card>
|
||||||
|
@else
|
||||||
|
<div class="grid grid-cols-1 gap-4">
|
||||||
|
@foreach ($usages as $usage)
|
||||||
|
<flux:card>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<flux:heading size="md">{{ $usage['name'] }}</flux:heading>
|
||||||
|
<flux:text size="sm">Bergabung pada
|
||||||
|
{{ formatDateLocalized($usage['joined_at'], 'd M Y') }}
|
||||||
|
</flux:text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($usage['is_reward_earned'])
|
||||||
|
<flux:badge color="emerald" inset="top bottom">Target Tercapai</flux:badge>
|
||||||
|
@else
|
||||||
|
<flux:badge color="amber" inset="top bottom">Dalam Proses</flux:badge>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between text-sm">
|
||||||
|
<flux:text>Akumulasi Belanja: {{ formatCurrencyNumber($usage['spending'], 'Rp') }}
|
||||||
|
</flux:text>
|
||||||
|
<flux:text>Target: {{ formatCurrencyNumber($targetSpending, 'Rp') }}</flux:text>
|
||||||
|
</div>
|
||||||
|
<div class="h-2 bg-zinc-100 dark:bg-zinc-800 rounded-full overflow-hidden">
|
||||||
|
<div class="h-full bg-emerald-500 transition-all duration-500"
|
||||||
|
style="width: {{ $usage['progress'] }}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</flux:card>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</flux:main>
|
||||||
|
|
||||||
|
@assets
|
||||||
|
<script src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.8.5/dist/dotlottie-wc.js" type="module"></script>
|
||||||
|
@endassets
|
||||||
@ -3,8 +3,8 @@
|
|||||||
use App\Livewire\Member\Membership;
|
use App\Livewire\Member\Membership;
|
||||||
use App\Livewire\Member\Overview;
|
use App\Livewire\Member\Overview;
|
||||||
use App\Livewire\Member\RedeemPoint;
|
use App\Livewire\Member\RedeemPoint;
|
||||||
|
use App\Livewire\Member\Referral;
|
||||||
use App\Livewire\Member\Setting\Account;
|
use App\Livewire\Member\Setting\Account;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::middleware(['auth', 'verified'])
|
Route::middleware(['auth', 'verified'])
|
||||||
@ -15,5 +15,6 @@
|
|||||||
|
|
||||||
Route::get('memberships', Membership::class)->name('membership');
|
Route::get('memberships', Membership::class)->name('membership');
|
||||||
Route::get('redeem-points', RedeemPoint::class)->name('redeem_point');
|
Route::get('redeem-points', RedeemPoint::class)->name('redeem_point');
|
||||||
|
Route::get('referrals', Referral::class)->name('referral');
|
||||||
Route::get('settings', Account::class)->name('setting');
|
Route::get('settings', Account::class)->name('setting');
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user