dev
Aji Kamaludin 1 year ago
parent 3f12da970a
commit 2f5faa840b
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -21,7 +21,7 @@ class GeneralController extends Controller
$total_customer_verified = Customer::where('identity_verified', Customer::VERIFIED)->count();
$total_deposit = DepositHistory::where('is_valid', DepositHistory::STATUS_VALID)->sum('debit');
$month = now()->locale('id')->translatedFormat('F');
$month = now()->translatedFormat('F');
$startOfMonth = now()->startOfMonth()->format('m/d/Y');
$endOfMonth = now()->endOfMonth()->format('m/d/Y');
$total_voucher_sale_this_month = SaleItem::whereBetween('created_at', [$startOfMonth, $endOfMonth])

@ -27,6 +27,7 @@ class SettingController extends Controller
'OPEN_WEBSITE_NAME' => 'required|string',
'SHARE_TEXT' => 'required|string',
'ENABLE_CASH_DEPOSIT' => 'required|in:0,1',
'TEXT_CASH_DEPOSIT' => 'required|string',
'ENABLE_MANUAL_TRANSFER' => 'required|in:0,1',
'MAX_MANUAL_TRANSFER_TIMEOUT' => 'required|numeric',
'MANUAL_TRANSFER_OPEN_HOUR' => 'required|string',

@ -107,6 +107,7 @@ class AuthController extends Controller
'google_oauth_response' => json_encode($user),
'status' => Customer::STATUS_ACTIVE,
]);
$this->process_referral($customer, session('referral_code', ''));
DB::commit();
} else {
$customer->update(['google_oauth_response' => json_encode($user)]);
@ -123,11 +124,21 @@ class AuthController extends Controller
return redirect()->route('home.index');
}
public function register()
public function register(Request $request)
{
session()->remove('carts');
return inertia('Auth/Register');
$code = '';
if ($request->referral_code != '') {
session()->put('referral_code', $request->referral_code);
$code = $request->referral_code;
} else {
$code = session('referral_code', ' ');
}
return inertia('Auth/Register', [
'referral_code' => $code,
]);
}
public function store(Request $request)
@ -158,25 +169,7 @@ class AuthController extends Controller
// send confirmation email
AsyncService::async(fn () => Mail::to($request->email)->send(new CustomerVerification($customer)));
if ($request->referral_code != '') {
$refferal = Customer::where('referral_code', $request->referral_code)->first();
$refferal->customerRefferals()->create([
'refferal_id' => $customer->id,
'customer_code' => $refferal->referral_code,
]);
$affilateEnabled = Setting::getByKey('AFFILATE_ENABLED');
if ($affilateEnabled == 1) {
$bonuspoin = Setting::getByKey('AFFILATE_poin_AMOUNT');
$poin = $refferal->poins()->create([
'debit' => $bonuspoin,
'description' => 'Bonus Refferal #' . Str::random(5),
]);
$poin->update_customer_balance();
}
}
$this->process_referral($customer, $request->referral_code);
DB::commit();
return redirect()->route('customer.login')
@ -207,4 +200,30 @@ class AuthController extends Controller
return redirect()->route('customer.login')
->with('message', ['type' => 'success', 'message' => 'Akun anda berhasil diaktifkan, silahkan login']);
}
private function process_referral(Customer $customer, $code)
{
if ($code != '') {
$refferal = Customer::where('referral_code', $code)->first();
if ($refferal == null) {
session()->forget('referral_code');
return;
}
$refferal->customerRefferals()->create([
'refferal_id' => $customer->id,
'customer_code' => $refferal->referral_code,
]);
$affilateEnabled = Setting::getByKey('AFFILATE_ENABLED');
if ($affilateEnabled == 1) {
$bonuspoin = Setting::getByKey('AFFILATE_poin_AMOUNT');
$poin = $refferal->poins()->create([
'debit' => $bonuspoin,
'description' => 'Bonus Refferal #' . Str::random(5),
]);
$poin->update_customer_balance();
}
}
}
}

@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\Customer;
use App\Models\DepositHistory;
use App\Models\DepositLocation;
use App\Models\Setting;
use App\Services\GeneralService;
use App\Services\MidtransService;
@ -77,8 +78,9 @@ class DepositController extends Controller
public function show(Request $request, DepositHistory $deposit)
{
return inertia('Deposit/Detail', [
'deposit' => $deposit->load(['account']),
'deposit' => $deposit->load(['account', 'depositLocation']),
'accounts' => Account::get(),
'deposit_locations' => DepositLocation::get(),
'midtrans_client_key' => Setting::getByKey('MIDTRANS_CLIENT_KEY'),
'is_production' => app()->isProduction(),
'direct' => $request->direct,
@ -88,15 +90,24 @@ class DepositController extends Controller
public function update(Request $request, DepositHistory $deposit)
{
$request->validate([
'account_id' => 'required|exists:accounts,id',
'image' => 'required|image',
]);
if ($deposit->payment_channel == Setting::PAYMENT_CASH_DEPOSIT) {
$request->validate(['deposit_location_id' => 'required|exists:deposit_locations,id']);
}
if ($deposit->payment_channel == Setting::PAYMENT_MANUAL) {
$request->validate(['account_id' => 'required|exists:accounts,id']);
}
$file = $request->file('image');
$file->store('uploads', 'public');
$deposit->update([
'image_prove' => $file->hashName('uploads'),
'account_id' => $request->account_id,
'deposit_location_id' => $request->deposit_location_id,
'is_valid' => DepositHistory::STATUS_WAIT_APPROVE,
]);

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
@ -10,7 +11,10 @@ class ProfileController extends Controller
{
public function index()
{
return inertia('Profile/Index');
$shareText = Setting::getByKey('AFFILATE_SHARE_REFFERAL_CODE');
return inertia('Profile/Index', [
'share_text' => $shareText
]);
}
public function show()

@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
class Customer extends Authenticatable
@ -70,6 +71,7 @@ class Customer extends Authenticatable
'is_allow_paylater',
'verification_status',
'status_text',
'poin_expired_text',
];
protected static function booted(): void
@ -208,6 +210,17 @@ class Customer extends Authenticatable
});
}
public function poinExpiredText(): Attribute
{
return Attribute::make(get: function () {
if ($this->poin_expired_at != null) {
$date = Carbon::parse($this->poin_expired_at)->translatedFormat('d F Y');
return "( kadaluarsa pada $date )";
}
return '';
});
}
public function level()
{
return $this->belongsTo(CustomerLevel::class, 'customer_level_id');

@ -35,6 +35,7 @@ class DepositHistory extends Model
'is_valid',
'image_prove',
'account_id',
'deposit_location_id',
'payment_token',
'payment_status',
'payment_response',
@ -67,7 +68,7 @@ class DepositHistory extends Model
return Attribute::make(get: function () {
return [
self::STATUS_VALID => ['text' => 'Success', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_WAIT_UPLOAD => ['text' => 'Upload bukti transfer', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
self::STATUS_WAIT_UPLOAD => ['text' => 'Upload bukti bayar', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
self::STATUS_WAIT_APPROVE => ['text' => 'Menunggu Approve', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_WAIT_PAYMENT => ['text' => 'Menunggu Pembayaran', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_INVALID => ['text' => 'Error', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
@ -79,14 +80,14 @@ class DepositHistory extends Model
public function formatHumanCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y');
return Carbon::parse($this->created_at)->translatedFormat('d F Y');
});
}
public function formatCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y H:i:s');
return Carbon::parse($this->created_at)->translatedFormat('d F Y H:i:s');
});
}
@ -118,6 +119,11 @@ class DepositHistory extends Model
return $this->belongsTo(Account::class);
}
public function depositLocation()
{
return $this->belongsTo(DepositLocation::class, 'deposit_location_id');
}
public function update_customer_balance()
{
$customer = Customer::find($this->customer_id);

@ -35,7 +35,7 @@ class Notification extends Model
public function formatCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y H:i:s');
return Carbon::parse($this->created_at)->translatedFormat('d F Y H:i:s');
});
}
}

@ -30,14 +30,14 @@ class PaylaterHistory extends Model
public function formatHumanCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y');
return Carbon::parse($this->created_at)->translatedFormat('d F Y');
});
}
public function formatCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y H:i:s');
return Carbon::parse($this->created_at)->translatedFormat('d F Y H:i:s');
});
}
@ -45,10 +45,10 @@ class PaylaterHistory extends Model
{
return Attribute::make(get: function () {
if ($this->credit == 0) {
return 'Rp'.number_format($this->debit, is_float($this->debit) ? 2 : 0, ',', '.');
return 'Rp' . number_format($this->debit, is_float($this->debit) ? 2 : 0, ',', '.');
}
return '-Rp'.number_format($this->credit, is_float($this->credit) ? 2 : 0, ',', '.');
return '-Rp' . number_format($this->credit, is_float($this->credit) ? 2 : 0, ',', '.');
});
}
}

@ -25,14 +25,14 @@ class PoinHistory extends Model
public function formatHumanCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y');
return Carbon::parse($this->created_at)->translatedFormat('d F Y');
});
}
public function formatCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y H:i:s');
return Carbon::parse($this->created_at)->translatedFormat('d F Y H:i:s');
});
}

@ -49,21 +49,21 @@ class Sale extends Model
public function formatHumanCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y');
return Carbon::parse($this->created_at)->translatedFormat('d F Y');
});
}
public function formatCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y H:i:s');
return Carbon::parse($this->created_at)->translatedFormat('d F Y H:i:s');
});
}
public function displayAmount(): Attribute
{
return Attribute::make(get: function () {
return 'Rp'.number_format($this->amount, is_float($this->amount) ? 2 : 0, ',', '.');
return 'Rp' . number_format($this->amount, is_float($this->amount) ? 2 : 0, ',', '.');
});
}
@ -72,12 +72,12 @@ class Sale extends Model
if ($this->payed_with == self::PAYED_WITH_POIN) {
Notification::create([
'entity_type' => User::class,
'description' => $this->customer->fullname.' melakukan penukaran '.$this->items()->count().' voucher sebesar '.$this->items->value('price').' poin',
'description' => $this->customer->fullname . ' melakukan penukaran ' . $this->items()->count() . ' voucher sebesar ' . $this->items->value('price') . ' poin',
]);
Notification::create([
'entity_id' => auth()->id(),
'description' => 'Transaksi '.$this->code.' berhasil',
'description' => 'Transaksi ' . $this->code . ' berhasil',
]);
return;
@ -85,12 +85,12 @@ class Sale extends Model
Notification::create([
'entity_type' => User::class,
'description' => $this->customer->fullname.' melakukan pembelian '.$this->items()->count().' voucher sebesar '.$this->display_amount,
'description' => $this->customer->fullname . ' melakukan pembelian ' . $this->items()->count() . ' voucher sebesar ' . $this->display_amount,
]);
Notification::create([
'entity_id' => auth()->id(),
'description' => 'Transaksi pembelian anda #'.$this->code.' sebesar '.$this->display_amount.' berhasil',
'description' => 'Transaksi pembelian anda #' . $this->code . ' sebesar ' . $this->display_amount . ' berhasil',
]);
}
}

@ -100,7 +100,7 @@ class GeneralService
$payment[] = [
'name' => Setting::PAYMENT_CASH_DEPOSIT,
'logo' => null,
'display_name' => 'Setor Tunai di Kantor WBB',
'display_name' => Setting::getByKey('TEXT_CASH_DEPOSIT'),
'admin_fee' => 0
];
}

@ -82,7 +82,7 @@ return [
|
*/
'locale' => 'en',
'locale' => 'id',
/*
|--------------------------------------------------------------------------

@ -20,6 +20,7 @@ return new class extends Migration
$table->text('note')->nullable();
$table->ulid('customer_id')->nullable();
$table->ulid('account_id')->nullable();
$table->ulid('deposit_location_id')->nullable();
$table->string('related_type')->nullable();
$table->string('related_id')->nullable();
$table->smallInteger('is_valid')->default(0);

@ -36,15 +36,16 @@ class InstallationSeed extends Seeder
['key' => 'MIDTRANS_CLIENT_KEY', 'value' => 'SB-Mid-client-xqqkspzoZOM10iUG', 'type' => 'text'],
['key' => 'MIDTRANS_MERCHANT_ID', 'value' => 'G561244367', 'type' => 'text'],
['key' => 'MIDTRANS_LOGO', 'value' => 'sample/midtrans_logo.png', 'type' => 'image'],
['key' => 'MIDTRANS_ENABLED', 'value' => '0', 'type' => 'text'],
['key' => 'MIDTRANS_ENABLED', 'value' => '1', 'type' => 'text'],
['key' => 'MIDTRANS_ADMIN_FEE', 'value' => '2500', 'type' => 'text'],
// deposit
['key' => 'ENABLE_CASH_DEPOSIT', 'value' => '0', 'type' => 'text'], // deposit by location (on/off)
['key' => 'ENABLE_MANUAL_TRANSFER', 'value' => '0', 'type' => 'text'], // transfer manual (on/off)
['key' => 'ENABLE_CASH_DEPOSIT', 'value' => '1', 'type' => 'text'], // deposit by location (on/off)
['key' => 'TEXT_CASH_DEPOSIT', 'value' => 'Setor Tunai di Kantor WBB', 'type' => 'text'],
['key' => 'ENABLE_MANUAL_TRANSFER', 'value' => '1', 'type' => 'text'], // transfer manual (on/off)
['key' => 'MAX_MANUAL_TRANSFER_TIMEOUT', 'value' => '2', 'type' => 'text'], // dalam jam
['key' => 'MANUAL_TRANSFER_OPEN_HOUR', 'value' => '06:00', 'type' => 'text'],
['key' => 'MANUAL_TRANSFER_CLOSE_HOUR', 'value' => '23:00', 'type' => 'text'],
['key' => 'MANUAL_TRANSFER_OPEN_HOUR', 'value' => '00:00', 'type' => 'text'],
['key' => 'MANUAL_TRANSFER_CLOSE_HOUR', 'value' => '23:59', 'type' => 'text'],
['key' => 'MAX_POINT_EXPIRED', 'value' => '90', 'type' => 'text'], //dalam hari
];

@ -9,7 +9,7 @@ import Alert from '@/Components/Alert'
import FormInputWith from '@/Components/FormInputWith'
import TextArea from '@/Components/TextArea'
export default function Index({ app_name, flash }) {
export default function Index({ referral_code, flash }) {
const { data, setData, post, processing, errors } = useForm({
username: '',
password: '',
@ -19,7 +19,7 @@ export default function Index({ app_name, flash }) {
address: '',
phone: '',
email: '',
referral_code: '',
referral_code: referral_code,
})
const handleOnChange = (event) => {

@ -31,9 +31,7 @@ export default function HeaderTrx({ enable = 'deposit' }) {
<div className="px-5 pb-5 border-b">
<div className="flex flex-row items-center text-gray-600 text-sm">
<div>{user.display_poin} poin</div>
<div className="pl-1 text-xs">
( kadaluarsa pada 20 juni 2023 )
</div>
<div className="pl-1 text-xs">{user.poin_expired_text}</div>
</div>
</div>
<div className="w-full">

@ -1,354 +1,17 @@
import React, { useState, useEffect } from 'react'
import { Head, router, useForm, usePage } from '@inertiajs/react'
import { isEmpty } from 'lodash'
import {
HiChevronLeft,
HiClipboardDocumentList,
HiOutlineClipboardDocumentCheck,
HiOutlineClipboardDocumentList,
} from 'react-icons/hi2'
import React from 'react'
import { Head, router } from '@inertiajs/react'
import { HiChevronLeft } from 'react-icons/hi2'
import { toastSuccess } from '../utils'
import CustomerLayout from '@/Layouts/CustomerLayout'
import FormFile from '@/Components/FormFile'
import Alert from '@/Components/Alert'
import { formatIDR } from '@/utils'
import { STATUS_REJECT } from '@/constant'
const PayButton = () => {
const {
props: { deposit, midtrans_client_key, is_production, direct, flash },
} = usePage()
const [loading, setLoading] = useState(false)
const handleResult = (result) => {
fetch(route('api.midtrans.payment', deposit), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ result }),
}).finally(() => {
router.get(route(route().current(), deposit))
})
}
const onClickPay = () => {
if (loading) {
return
}
setLoading(true)
window.snap.pay(deposit.payment_token, {
// Optional
onSuccess: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
// Optional
onPending: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
// Optional
onError: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
onClose: function () {
setLoading(false)
},
})
}
useEffect(() => {
//change this to the script source you want to load, for example this is snap.js sandbox env
let midtransScriptUrl = 'https://app.sandbox.midtrans.com/snap/snap.js'
if (is_production) {
midtransScriptUrl = 'https://app.midtrans.com/snap/snap.js'
}
//change this according to your client-key
let scriptTag = document.createElement('script')
scriptTag.src = midtransScriptUrl
// optional if you want to set script attribute
// for example snap.js have data-client-key attribute
scriptTag.setAttribute('data-client-key', midtrans_client_key)
document.body.appendChild(scriptTag)
if (direct === 'true') {
setTimeout(() => {
onClickPay()
}, 1000)
}
return () => {
document.body.removeChild(scriptTag)
}
}, [])
return (
<div className="w-full px-5 py-10">
<Alert type={flash.message.type}>
<span className="font-semibold">{flash.message.message}</span>
</Alert>
<div
className="px-4 py-2 bg-blue-700 text-white rounded-full border hover:bg-white hover:text-black"
onClick={onClickPay}
>
Bayar
</div>
</div>
)
}
const FormUpload = () => {
const {
props: { accounts, deposit, flash },
} = usePage()
const [imageUrl, setImageUrl] = useState(deposit.image_prove_url)
const [account, setAccount] = useState(null)
const { data, setData, errors, processing, post } = useForm({
account_id: '',
image: null,
image_url: deposit.image_prove_url,
})
const handleSelectAccount = (id) => {
if (id === '') {
setData('account_id', '')
setAccount(null)
return
}
const account = accounts.find((acc) => acc.id === id)
setData('account_id', account.id)
setAccount(account)
}
const handleCopyToClipboard = (text) => {
toastSuccess('copied to clipboard')
navigator.clipboard.writeText(text)
}
const handleSubmit = () => {
if (processing) {
return
}
post(route('transactions.deposit.update', deposit), {
replace: true,
preserveState: true,
onSuccess: () =>
setTimeout(
() => router.get(route(route().current(), deposit)),
3000
),
})
}
useEffect(() => {
if (deposit.account !== null) {
handleSelectAccount(deposit.account.id)
}
}, [deposit])
if (isEmpty(imageUrl) == false) {
return (
<div className="px-5 mt-2">
<div className="font-bold">Bukti Transfer</div>
<img
src={`${imageUrl}`}
className="w-full h-52 mb-1"
alt="bukti transfer"
/>
<div
className="mt-10 w-full px-4 py-2 border rounded-full bg-blue-600 text-white hover:bg-white hover:text-black"
onClick={() => setImageUrl(null)}
>
Ubah Bukti Transfer
</div>
</div>
)
}
return (
<div className="px-5 mt-4">
<div className="my-4">
<Alert type={flash.message.type}>
<span className="font-semibold">
{flash.message.message}
</span>
</Alert>
<div className="mb-1 font-bold">Bank</div>
{account !== null ? (
<div className="flex flex-row w-full gap-2">
<div
className="px-3 py-2 border rounded-md flex-1 flex flex-row items-center gap-1 shadow-md hover:bg-gray-100"
onClick={() => handleSelectAccount(account.id)}
>
<div className="w-1/3">
<img
src={account.logo_url}
alt="logo bank"
className="h-10"
/>
</div>
<div>
{account.name} - {account.bank_name}
</div>
</div>
<div
className="text-center border flex flex-row rounded-md shadow-md items-center justify-center px-3 py-2 hover:bg-gray-100"
onClick={() => handleSelectAccount('')}
>
<div>Ubah</div>
</div>
</div>
) : (
<div className="flex flex-col gap-2">
{accounts.map((account) => (
<div
key={account.id}
className="px-3 py-2 border rounded-md flex flex-row w-full items-center gap-1 shadow-md hover:bg-gray-100"
onClick={() => handleSelectAccount(account.id)}
>
<div className="w-1/3">
<img
src={account.logo_url}
alt="logo bank"
className="h-10"
/>
</div>
<div>
{account.name} - {account.bank_name}
</div>
</div>
))}
</div>
)}
</div>
{account !== null && (
<>
<div className="my-5">
<div className="bg-blue-50 text-blue-700 p-3 border rounded-md">
<div>
<span className="font-bold">
{account.bank_name}
</span>
</div>
<div>
Atas Nama :{' '}
<span className="font-bold">
{account.holder_name}
</span>
</div>
<div
className="flex flex-row items-center space-x-1"
onClick={() =>
handleCopyToClipboard(
account.account_number
)
}
>
<div>Nomor Rekening : </div>
<div className="font-bold pr-2">
{account.account_number}
</div>
<HiOutlineClipboardDocumentList className=" w-5 h-5 text-blue-600" />
</div>
<div className="font-bold mt-5">Rincian</div>
<table className="w-full">
<tbody>
<tr>
<td className="w-1/3">
Jumlah Deposit
</td>
<td>: </td>
<td className="text-right">
<span className="font-bold">
{deposit.amount}
</span>
</td>
<td className="w-5" />
</tr>
<tr>
<td>Biaya Admin</td>
<td>: </td>
<td className="text-right">
<span className="font-bold">
{+account.admin_fee === 0 ? (
'Gratis'
) : (
<>
Rp.{' '}
{formatIDR(
+account.admin_fee
)}
</>
)}
</span>
</td>
<td className="w-5" />
</tr>
<tr
onClick={() =>
handleCopyToClipboard(
+account.admin_fee +
+deposit.debit
)
}
>
<td>Total Transfer</td>
<td> : </td>
<td className="text-right">
<span className="font-bold">
Rp.{' '}
{formatIDR(
+account.admin_fee +
+deposit.debit
)}
</span>
</td>
<td className="w-5">
<HiOutlineClipboardDocumentList className=" w-5 h-5 text-blue-600" />
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="-mb-3">
<div className="font-bold">Bukti Transfer</div>
{isEmpty(data.image_url) == false && (
<img
src={`${data.image_url}`}
className="w-full h-52 mb-1"
alt="bukti transfer"
/>
)}
</div>
<FormFile
onChange={(e) => setData('image', e.target.files[0])}
error={errors.image}
/>
<div className="mb-1 -mt-4 text-sm font-medium text-gray-500">
upload gambar dalam ekstensi jpg, png, jpeg
</div>
<div
className="mt-10 w-full px-4 py-2 border rounded-full bg-blue-600 text-white hover:bg-white hover:text-black"
onClick={() => handleSubmit()}
>
Upload
</div>
</>
)}
</div>
)
}
import { PayButton } from './DetailPartials/PayButton'
import { FormUploadManual } from './DetailPartials/FormUploadManual'
import { FormUploadCashDeposit } from './DetailPartials/FormUploadCashDeposit'
import {
PAYMENT_CASH_DEPOSIT,
PAYMENT_MANUAL,
PAYMENT_MIDTRANS,
STATUS_REJECT,
} from '@/constant'
const ActionSection = ({ deposit }) => {
if (deposit.is_valid === STATUS_REJECT) {
@ -364,10 +27,10 @@ const ActionSection = ({ deposit }) => {
}
return (
<div className="w-full">
{deposit.payment_channel === 'MIDTRANS' ? (
<PayButton />
) : (
<FormUpload />
{deposit.payment_channel === PAYMENT_MIDTRANS && <PayButton />}
{deposit.payment_channel === PAYMENT_MANUAL && <FormUploadManual />}
{deposit.payment_channel === PAYMENT_CASH_DEPOSIT && (
<FormUploadCashDeposit />
)}
</div>
)
@ -388,8 +51,8 @@ export default function Detail({ deposit }) {
</div>
{/* detail */}
<div className="flex flex-row justify-between items-center pb-5 border-b px-5">
<div>
<div className="flex flex-row items-center pb-5 border-b px-5">
<div className="w-full">
<div className="font-semibold text-xl text-gray-400">
{deposit.description}
</div>

@ -0,0 +1,242 @@
import React, { useState, useEffect } from 'react'
import { router, useForm, usePage } from '@inertiajs/react'
import { isEmpty } from 'lodash'
import { HiOutlineClipboardDocumentList } from 'react-icons/hi2'
import { toastSuccess } from '@/Customer/utils'
import FormFile from '@/Components/FormFile'
import Alert from '@/Components/Alert'
export const FormUploadCashDeposit = () => {
const {
props: { deposit_locations, deposit, flash },
} = usePage()
const [imageUrl, setImageUrl] = useState(deposit.image_prove_url)
const [location, setLocation] = useState(null)
const { data, setData, errors, processing, post } = useForm({
deposit_location_id: '',
image: null,
image_url: deposit.image_prove_url,
})
const handleSelectLocation = (id) => {
if (id === '') {
setData('deposit_location_id', '')
setLocation(null)
return
}
const location = deposit_locations.find((acc) => acc.id === id)
setData('deposit_location_id', location.id)
setLocation(location)
}
const handleCopyToClipboard = (text) => {
toastSuccess('copied to clipboard')
navigator.clipboard.writeText(text)
}
const handleSubmit = () => {
if (processing) {
return
}
post(route('transactions.deposit.update', deposit), {
replace: true,
preserveState: true,
onSuccess: () =>
setTimeout(
() => router.get(route(route().current(), deposit)),
3000
),
})
}
useEffect(() => {
if (deposit.deposit_location !== null) {
handleSelectLocation(deposit.deposit_location_id)
}
}, [deposit])
if (isEmpty(imageUrl) == false) {
return (
<div className="px-5 mt-2">
<div className="font-bold">Bukti Pembayaran</div>
<img
src={`${imageUrl}`}
className="w-full h-52 mb-1"
alt="bukti Pembayaran"
/>
<div
className="mt-10 w-full px-4 py-2 border rounded-full bg-blue-600 text-white hover:bg-white hover:text-black"
onClick={() => setImageUrl(null)}
>
Ubah Bukti Pembayaran
</div>
</div>
)
}
return (
<div className="px-5 mt-4">
<div className="my-4">
<Alert type={flash.message.type}>
<span className="font-semibold">
{flash.message.message}
</span>
</Alert>
<div className="mb-1 font-bold">Lokasi Pembayaran</div>
{location !== null ? (
<div className="flex flex-row w-full gap-2">
<div
className="px-3 py-2 border rounded-md flex-1 flex flex-row items-center gap-1 shadow-md hover:bg-gray-100"
onClick={() => handleSelectLocation(location.id)}
>
<div>
<img
src={location.image_url}
alt="image location"
className="object-fill h-36 w-32"
/>
</div>
<div className="flex flex-col w-full">
<div className="font-bold">{location.name}</div>
<div>
<span>+62{location.phone}</span>
</div>
<div>
Alamat : <span>{location.address}</span>
</div>
<div>
Jam Buka :{' '}
<span className="font-bold">
{location.operational_hour}
</span>
</div>
</div>
</div>
<div
className="text-center border flex flex-row rounded-md shadow-md items-center justify-center px-3 py-2 hover:bg-gray-100 h-10"
onClick={() => handleSelectLocation('')}
>
<div>Ubah</div>
</div>
</div>
) : (
<div className="flex flex-col gap-2">
{deposit_locations.map((location) => (
<div
key={location.id}
className="px-3 py-2 border rounded-md flex flex-row w-full items-center gap-1 shadow-md hover:bg-gray-100"
onClick={() =>
handleSelectLocation(location.id)
}
>
<div>
<img
src={location.image_url}
alt="image location"
className="object-fill h-36 w-32"
/>
</div>
<div className="flex flex-col w-full">
<div className="font-bold">
{location.name}
</div>
<div>
<span>+62{location.phone}</span>
</div>
<div>
Alamat : <span>{location.address}</span>
</div>
<div>
Jam Buka :{' '}
<span className="font-bold">
{location.operational_hour}
</span>
</div>
</div>
</div>
))}
</div>
)}
</div>
{location !== null && (
<>
<div className="my-5">
<div className="bg-blue-50 text-blue-700 p-3 border rounded-md">
<div>
<span className="font-bold">
{location.name}
</span>
</div>
<div>Alamat : {location.address}</div>
<div
className="flex flex-row items-center space-x-1"
onClick={() =>
handleCopyToClipboard(
'+62' + location.phone
)
}
>
<div>Whatsapp : </div>
<div className="font-bold pr-2">
+62{location.phone}
</div>
<HiOutlineClipboardDocumentList className=" w-5 h-5 text-blue-600" />
</div>
<div className="font-bold mt-5">Rincian</div>
<table className="w-full">
<tbody>
<tr
onClick={() =>
handleCopyToClipboard(
deposit.amount
)
}
>
<td>Total Deposit</td>
<td> : </td>
<td className="text-right">
<span className="font-bold">
{deposit.amount}
</span>
</td>
<td className="w-5">
<HiOutlineClipboardDocumentList className=" w-5 h-5 text-blue-600" />
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="-mb-3">
<div className="font-bold">Bukti Pembayaran</div>
{isEmpty(data.image_url) == false && (
<img
src={`${data.image_url}`}
className="w-full h-52 mb-1"
alt="bukti Pembayaran"
/>
)}
</div>
<FormFile
onChange={(e) => setData('image', e.target.files[0])}
error={errors.image}
/>
<div className="mb-1 -mt-4 text-sm font-medium text-gray-500">
upload gambar dalam ekstensi jpg, png, jpeg
</div>
<div
className="mt-10 w-full px-4 py-2 border rounded-full bg-blue-600 text-white hover:bg-white hover:text-black"
onClick={() => handleSubmit()}
>
Upload
</div>
</>
)}
</div>
)
}

@ -0,0 +1,256 @@
import React, { useState, useEffect } from 'react'
import { router, useForm, usePage } from '@inertiajs/react'
import { HiOutlineClipboardDocumentList } from 'react-icons/hi2'
import { isEmpty } from 'lodash'
import { toastSuccess } from '@/Customer/utils'
import { formatIDR } from '@/utils'
import FormFile from '@/Components/FormFile'
import Alert from '@/Components/Alert'
export const FormUploadManual = () => {
const {
props: { accounts, deposit, flash },
} = usePage()
const [imageUrl, setImageUrl] = useState(deposit.image_prove_url)
const [account, setAccount] = useState(null)
const { data, setData, errors, processing, post } = useForm({
account_id: '',
image: null,
image_url: deposit.image_prove_url,
})
const handleSelectAccount = (id) => {
if (id === '') {
setData('account_id', '')
setAccount(null)
return
}
const account = accounts.find((acc) => acc.id === id)
setData('account_id', account.id)
setAccount(account)
}
const handleCopyToClipboard = (text) => {
toastSuccess('copied to clipboard')
navigator.clipboard.writeText(text)
}
const handleSubmit = () => {
if (processing) {
return
}
post(route('transactions.deposit.update', deposit), {
replace: true,
preserveState: true,
onSuccess: () =>
setTimeout(
() => router.get(route(route().current(), deposit)),
3000
),
})
}
useEffect(() => {
if (deposit.account !== null) {
handleSelectAccount(deposit.account.id)
}
}, [deposit])
if (isEmpty(imageUrl) == false) {
return (
<div className="px-5 mt-2">
<div className="font-bold">Bukti Transfer</div>
<img
src={`${imageUrl}`}
className="w-full h-52 mb-1"
alt="bukti transfer"
/>
<div
className="mt-10 w-full px-4 py-2 border rounded-full bg-blue-600 text-white hover:bg-white hover:text-black"
onClick={() => setImageUrl(null)}
>
Ubah Bukti Transfer
</div>
</div>
)
}
return (
<div className="px-5 mt-4">
<div className="my-4">
<Alert type={flash.message.type}>
<span className="font-semibold">
{flash.message.message}
</span>
</Alert>
<div className="mb-1 font-bold">Bank</div>
{account !== null ? (
<div className="flex flex-row w-full gap-2">
<div
className="px-3 py-2 border rounded-md flex-1 flex flex-row items-center gap-1 shadow-md hover:bg-gray-100"
onClick={() => handleSelectAccount(account.id)}
>
<div className="w-1/3">
<img
src={account.logo_url}
alt="logo bank"
className="h-10"
/>
</div>
<div>
{account.name} - {account.bank_name}
</div>
</div>
<div
className="text-center border flex flex-row rounded-md shadow-md items-center justify-center px-3 py-2 hover:bg-gray-100"
onClick={() => handleSelectAccount('')}
>
<div>Ubah</div>
</div>
</div>
) : (
<div className="flex flex-col gap-2">
{accounts.map((account) => (
<div
key={account.id}
className="px-3 py-2 border rounded-md flex flex-row w-full items-center gap-1 shadow-md hover:bg-gray-100"
onClick={() => handleSelectAccount(account.id)}
>
<div className="w-1/3">
<img
src={account.logo_url}
alt="logo bank"
className="h-10"
/>
</div>
<div>
{account.name} - {account.bank_name}
</div>
</div>
))}
</div>
)}
</div>
{account !== null && (
<>
<div className="my-5">
<div className="bg-blue-50 text-blue-700 p-3 border rounded-md">
<div>
<span className="font-bold">
{account.bank_name}
</span>
</div>
<div>
Atas Nama :{' '}
<span className="font-bold">
{account.holder_name}
</span>
</div>
<div
className="flex flex-row items-center space-x-1"
onClick={() =>
handleCopyToClipboard(
account.account_number
)
}
>
<div>Nomor Rekening : </div>
<div className="font-bold pr-2">
{account.account_number}
</div>
<HiOutlineClipboardDocumentList className=" w-5 h-5 text-blue-600" />
</div>
<div className="font-bold mt-5">Rincian</div>
<table className="w-full">
<tbody>
<tr>
<td className="w-1/3">
Jumlah Deposit
</td>
<td>: </td>
<td className="text-right">
<span className="font-bold">
{deposit.amount}
</span>
</td>
<td className="w-5" />
</tr>
<tr>
<td>Biaya Admin</td>
<td>: </td>
<td className="text-right">
<span className="font-bold">
{+account.admin_fee === 0 ? (
'Gratis'
) : (
<>
Rp.{' '}
{formatIDR(
+account.admin_fee
)}
</>
)}
</span>
</td>
<td className="w-5" />
</tr>
<tr
onClick={() =>
handleCopyToClipboard(
+account.admin_fee +
+deposit.debit
)
}
>
<td>Total Transfer</td>
<td> : </td>
<td className="text-right">
<span className="font-bold">
Rp.{' '}
{formatIDR(
+account.admin_fee +
+deposit.debit
)}
</span>
</td>
<td className="w-5">
<HiOutlineClipboardDocumentList className=" w-5 h-5 text-blue-600" />
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="-mb-3">
<div className="font-bold">Bukti Transfer</div>
{isEmpty(data.image_url) == false && (
<img
src={`${data.image_url}`}
className="w-full h-52 mb-1"
alt="bukti transfer"
/>
)}
</div>
<FormFile
onChange={(e) => setData('image', e.target.files[0])}
error={errors.image}
/>
<div className="mb-1 -mt-4 text-sm font-medium text-gray-500">
upload gambar dalam ekstensi jpg, png, jpeg
</div>
<div
className="mt-10 w-full px-4 py-2 border rounded-full bg-blue-600 text-white hover:bg-white hover:text-black"
onClick={() => handleSubmit()}
>
Upload
</div>
</>
)}
</div>
)
}

@ -0,0 +1,92 @@
import React, { useState, useEffect } from 'react'
import { router, usePage } from '@inertiajs/react'
import Alert from '@/Components/Alert'
export const PayButton = () => {
const {
props: { deposit, midtrans_client_key, is_production, direct, flash },
} = usePage()
const [loading, setLoading] = useState(false)
const handleResult = (result) => {
fetch(route('api.midtrans.payment', deposit), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ result }),
}).finally(() => {
router.get(route(route().current(), deposit))
})
}
const onClickPay = () => {
if (loading) {
return
}
setLoading(true)
window.snap.pay(deposit.payment_token, {
// Optional
onSuccess: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
// Optional
onPending: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
// Optional
onError: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
onClose: function () {
setLoading(false)
},
})
}
useEffect(() => {
//change this to the script source you want to load, for example this is snap.js sandbox env
let midtransScriptUrl = 'https://app.sandbox.midtrans.com/snap/snap.js'
if (is_production) {
midtransScriptUrl = 'https://app.midtrans.com/snap/snap.js'
}
//change this according to your client-key
let scriptTag = document.createElement('script')
scriptTag.src = midtransScriptUrl
// optional if you want to set script attribute
// for example snap.js have data-client-key attribute
scriptTag.setAttribute('data-client-key', midtrans_client_key)
document.body.appendChild(scriptTag)
if (direct === 'true') {
setTimeout(() => {
onClickPay()
}, 1000)
}
return () => {
document.body.removeChild(scriptTag)
}
}, [])
return (
<div className="w-full px-5 py-10">
<Alert type={flash.message.type}>
<span className="font-semibold">{flash.message.message}</span>
</Alert>
<div
className="px-4 py-2 bg-blue-700 text-white rounded-full border hover:bg-white hover:text-black"
onClick={onClickPay}
>
Bayar
</div>
</div>
)
}

@ -145,7 +145,7 @@ export default function Topup({ payments }) {
)}
className="flex flex-row items-center w-full text-sm text-gray-400 py-2 gap-1"
>
<div>Daftar lokasi setor tunai</div>
<div>Daftar lokasi pembayaran</div>
<div className="text-blue-400">
ada disini
</div>

@ -24,7 +24,7 @@ export default function Index({ locations: { data, next_page_url } }) {
}
return (
<CustomerLayout>
<Head title="Lokasi Setor Tunai" />
<Head title="Lokasi Pembayaran" />
<div className="flex flex-col min-h-[calc(90dvh)]">
<div
className="w-full px-5 py-5 flex flex-row items-center"
@ -36,7 +36,7 @@ export default function Index({ locations: { data, next_page_url } }) {
<HiChevronLeft className="font-bold h-5 w-5" />
</div>
<div className="pl-4 text-xl font-bold">
Lokasi Setor Tunai
Lokasi Pembayaran
</div>
</div>
<div className="w-full px-2 flex flex-col gap-1">

@ -13,7 +13,11 @@ import CustomerLayout from '@/Layouts/CustomerLayout'
import ModalConfirm from '@/Components/ModalConfirm'
import BalanceBanner from '../Index/Partials/BalanceBanner'
export default function Index({ auth: { user }, notification_count }) {
export default function Index({
auth: { user },
notification_count,
share_text,
}) {
const confirmModal = useModalState()
const handleLogoutClick = () => {
@ -24,8 +28,12 @@ export default function Index({ auth: { user }, notification_count }) {
router.post(route('customer.logout'))
}
const handleCopyToClipboard = (text) => {
const handleReferalCopyToClipboard = (code) => {
toastSuccess('copied to clipboard')
const text =
share_text +
'\n' +
route('customer.register', { referral_code: code })
navigator.clipboard.writeText(text)
}
@ -80,7 +88,7 @@ export default function Index({ auth: { user }, notification_count }) {
className="p-4 text-blue-800 rounded-lg bg-blue-50 flex flex-row space-x-2 w-full items-center"
role="alert"
onClick={() =>
handleCopyToClipboard(user.referral_code)
handleReferalCopyToClipboard(user.referral_code)
}
>
<div>Referral Code: </div>

@ -29,8 +29,6 @@ export default function Affilate(props) {
),
})
console.log(data)
const handleCheckLevel = (e, level) => {
if (e.target.checked) {
const isExists = data.AFFILATE_ALLOWED_LEVELS.find(

@ -17,6 +17,7 @@ export default function General(props) {
OPEN_WEBSITE_NAME: extractValue(setting, 'OPEN_WEBSITE_NAME'),
SHARE_TEXT: extractValue(setting, 'SHARE_TEXT'),
ENABLE_CASH_DEPOSIT: extractValue(setting, 'ENABLE_CASH_DEPOSIT'),
TEXT_CASH_DEPOSIT: extractValue(setting, 'TEXT_CASH_DEPOSIT'),
ENABLE_MANUAL_TRANSFER: extractValue(setting, 'ENABLE_MANUAL_TRANSFER'),
MAX_MANUAL_TRANSFER_TIMEOUT: extractValue(
setting,
@ -100,11 +101,18 @@ export default function General(props) {
</div>
<div className="p-2 border rounded-xl mt-2">
<Checkbox
label="Aktifkan Setor Tunai"
label="Aktifkan Cash / Setor Tunai"
value={+data.ENABLE_CASH_DEPOSIT === 1}
onChange={handleOnChange}
name="ENABLE_CASH_DEPOSIT"
/>
<FormInput
name="TEXT_CASH_DEPOSIT"
value={data.TEXT_CASH_DEPOSIT}
onChange={handleOnChange}
label="Nama Pilihan Pembayaran Cash / Setor Tunai"
error={errors.TEXT_CASH_DEPOSIT}
/>
<Checkbox
label="Aktifkan Transfer Manual"
value={+data.ENABLE_MANUAL_TRANSFER === 1}
@ -115,7 +123,7 @@ export default function General(props) {
name="MAX_MANUAL_TRANSFER_TIMEOUT"
value={data.MAX_MANUAL_TRANSFER_TIMEOUT}
onChange={handleOnChange}
label="Waktu Maksimal Transfer (Jam)"
label="Waktu Maksimal Transfer ( Jam )"
error={errors.MAX_MANUAL_TRANSFER_TIMEOUT}
/>
<div className="my-2 flex flex-row gap-2 items-center">

@ -3,3 +3,9 @@ export const DEFAULT_EXPIRED_UNIT = 'Hari'
export const STATUS_APPROVE = 0
export const STATUS_REJECT = 5
export const PAYMENT_MANUAL = 'MANUAL'
export const PAYMENT_MIDTRANS = 'MIDTRANS'
export const PAYMENT_CASH_DEPOSIT = 'CASH_DEPOSIT'

Loading…
Cancel
Save