feat: implement owner verification workflow for marketplace settings and restrict administrative settings access

This commit is contained in:
Yoga Pangestu 2026-06-27 17:06:54 +07:00
parent acdd7ebb63
commit 8c026dda1c
10 changed files with 251 additions and 35 deletions

View File

@ -195,6 +195,9 @@ public function permissions(): array
Permission::PAYROLL_VIEW,
Permission::PAYROLL_ADJUST,
Permission::SETTINGS_VIEW,
Permission::SETTINGS_UPDATE,
],
self::CASHIER => [

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin\System;
use App\Enums\Permission;
use App\Http\Controllers\Concerns\FlashesEntityMessage;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\System\Setting\HomepageSettingRequest;
@ -9,11 +10,13 @@
use App\Http\Requests\Admin\System\Setting\MarketplaceRequest;
use App\Http\Requests\Admin\System\Setting\SocialMediaRequest;
use App\Http\Requests\Admin\System\Setting\SystemRequest;
use App\Models\OwnerVerificationRequest;
use App\Services\System\Setting\HomepageSettingService;
use App\Services\System\Setting\HrSettingService;
use App\Services\System\Setting\MarketplaceService;
use App\Services\System\Setting\SocialMediaService;
use App\Services\System\Setting\SystemService;
use App\Settings\MarketplaceSettings;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
@ -38,11 +41,19 @@ public function index(): Response
'marketplace' => $this->marketplaceService->marketplaceData(),
'hr' => $this->hrSettingService->hrData(),
'homepage' => $this->homepageSettingService->homepageData(),
'hasPendingMarketplaceVerification' => OwnerVerificationRequest::query()
->where('subject_type', MarketplaceSettings::class)
->pending()
->exists(),
]);
}
public function updateSystem(SystemRequest $request): RedirectResponse
{
if (! $request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
abort(403, 'Aksi ini tidak diizinkan.');
}
$this->systemService->updateSystem($request->validated());
$this->flashSuccess('Pengaturan sistem berhasil disimpan.');
@ -52,6 +63,10 @@ public function updateSystem(SystemRequest $request): RedirectResponse
public function updateSocialMedia(SocialMediaRequest $request): RedirectResponse
{
if (! $request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
abort(403, 'Aksi ini tidak diizinkan.');
}
$this->socialMediaService->updateSocialMedia($request->validated());
$this->flashSuccess('Pengaturan media sosial berhasil disimpan.');
@ -61,15 +76,28 @@ public function updateSocialMedia(SocialMediaRequest $request): RedirectResponse
public function updateMarketplace(MarketplaceRequest $request): RedirectResponse
{
$this->marketplaceService->updateMarketplace($request->validated());
$this->marketplaceService->updateMarketplace($request->validated(), $request->user());
$this->flashSuccess('Pengaturan marketplace berhasil disimpan.');
$hasPending = OwnerVerificationRequest::query()
->where('subject_type', MarketplaceSettings::class)
->pending()
->exists();
if ($hasPending) {
$this->flashSuccess('Perubahan pengaturan marketplace berhasil diajukan dan menunggu verifikasi owner.');
} else {
$this->flashSuccess('Pengaturan marketplace berhasil disimpan.');
}
return redirect()->route('admin.system.settings.index');
}
public function updateHr(HrSettingRequest $request): RedirectResponse
{
if (! $request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
abort(403, 'Aksi ini tidak diizinkan.');
}
$this->hrSettingService->updateHr($request->validated());
$this->flashSuccess('Pengaturan HR berhasil disimpan.');
@ -79,6 +107,10 @@ public function updateHr(HrSettingRequest $request): RedirectResponse
public function updateHomepage(HomepageSettingRequest $request): RedirectResponse
{
if (! $request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
abort(403, 'Aksi ini tidak diizinkan.');
}
$this->homepageSettingService->updateHomepage($request->validated());
$this->flashSuccess('Pengaturan homepage berhasil disimpan.');

View File

@ -16,6 +16,8 @@
use App\Services\System\PushNotificationService;
use App\Support\ActivityLog\ModelLabel;
use App\Support\OwnerVerification\VerificationChangeFormatter;
use App\Services\System\Setting\MarketplaceService;
use App\Settings\MarketplaceSettings;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator as Paginator;
@ -32,6 +34,7 @@ public function __construct(
private readonly RawMaterialService $rawMaterialService,
private readonly PurchaseService $purchaseService,
private readonly PushNotificationService $pushNotificationService,
private readonly MarketplaceService $marketplaceService,
) {}
/**
@ -278,6 +281,7 @@ private function rejectVerificationRequest(OwnerVerificationRequest $request): v
Product::class => $this->productService->rejectVerificationRequest($request),
RawMaterial::class => $this->rawMaterialService->rejectVerificationRequest($request),
Purchase::class => $this->purchaseService->rejectVerificationRequest($request),
MarketplaceSettings::class => null,
default => throw ValidationException::withMessages([
'subject_type' => 'Tipe data verifikasi tidak didukung.',
]),
@ -290,6 +294,7 @@ private function applyVerificationRequest(OwnerVerificationRequest $request): vo
Product::class => $this->productService->applyVerificationRequest($request),
RawMaterial::class => $this->rawMaterialService->applyVerificationRequest($request),
Purchase::class => $this->purchaseService->applyVerificationRequest($request),
MarketplaceSettings::class => $this->marketplaceService->applyVerificationRequest($request),
default => throw ValidationException::withMessages([
'subject_type' => 'Tipe data verifikasi tidak didukung.',
]),
@ -469,6 +474,10 @@ private function makePaginator(
private function requestTitle(OwnerVerificationRequest $request): string
{
if ($request->subject_type === MarketplaceSettings::class) {
return 'Pengaturan Marketplace';
}
if ($request->subject instanceof Product || $request->subject instanceof RawMaterial) {
return $request->subject->name;
}
@ -517,6 +526,7 @@ private function notifyRequestSubmitter(
Product::class => route('admin.master.products.index'),
RawMaterial::class => route('admin.master.raw_materials.index'),
Purchase::class => route('admin.manage.purchases.index'),
MarketplaceSettings::class => route('admin.system.settings.index'),
default => route('admin.manage.owner_verifications.index'),
};

View File

@ -3,12 +3,23 @@
namespace App\Services\System\Setting;
use App\Enums\OrderChannel;
use App\Enums\OwnerVerificationAction;
use App\Enums\OwnerVerificationStatus;
use App\Enums\Permission;
use App\Models\OwnerVerificationRequest;
use App\Models\User;
use App\Services\System\PushNotificationService;
use App\Settings\MarketplaceSettings;
use App\Support\Marketplace\MarketplaceFeeCalculator;
use App\Support\Marketplace\MarketplaceFeeRule;
use Illuminate\Validation\ValidationException;
class MarketplaceService
{
public function __construct(
private readonly PushNotificationService $pushNotificationService,
) {}
/**
* @return list<string>
*/
@ -65,7 +76,67 @@ public function marketplaceData(): array
];
}
public function updateMarketplace(array $validated): void
public function updateMarketplace(array $validated, User $user): void
{
if ($user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
$this->saveSettings($validated);
return;
}
$hasPending = OwnerVerificationRequest::query()
->where('subject_type', MarketplaceSettings::class)
->pending()
->exists();
if ($hasPending) {
throw ValidationException::withMessages([
'system' => 'Perubahan pengaturan marketplace sedang menunggu verifikasi owner.',
]);
}
$settings = app(MarketplaceSettings::class);
$oldPayload = [];
$newPayload = [];
foreach ($this->tiktokFeeKeys() as $key) {
$oldPayload[$key] = $this->presentFeeRule($settings->{$key});
$newPayload[$key] = $this->normalizeFeeRule($validated[$key]);
}
foreach ($this->shopeeFeeKeys() as $key) {
$oldPayload[$key] = $this->presentFeeRule($settings->{$key});
$newPayload[$key] = $this->normalizeFeeRule($validated[$key]);
}
OwnerVerificationRequest::create([
'action' => OwnerVerificationAction::UPDATE,
'status' => OwnerVerificationStatus::PENDING,
'subject_type' => MarketplaceSettings::class,
'subject_id' => null,
'submitted_by_id' => $user->id,
'payload' => [
'old' => $oldPayload,
'new' => $newPayload,
],
]);
$this->pushNotificationService->sendToRoles(
'⚙️ Pengaturan Marketplace Menunggu Persetujuan Owner',
"Pengajuan ubah pengaturan marketplace oleh '{$user->username}' menunggu verifikasi owner.",
['owner', 'developer'],
route('admin.manage.owner_verifications.index'),
);
$this->pushNotificationService->sendToUser(
'📤 Pengajuan Terkirim',
'Pengajuan ubah pengaturan marketplace menunggu verifikasi owner.',
$user->id,
route('admin.system.settings.index'),
);
}
public function saveSettings(array $validated): void
{
$settings = app(MarketplaceSettings::class);
@ -80,6 +151,12 @@ public function updateMarketplace(array $validated): void
$settings->save();
}
public function applyVerificationRequest(OwnerVerificationRequest $request): void
{
$newPayload = $request->payload['new'] ?? [];
$this->saveSettings($newPayload);
}
/**
* @param list<array{quantity: int, subtotal: int}> $lineItems
* @return array<string, mixed>|null

View File

@ -67,6 +67,7 @@ class ModelLabel
SystemConfiguration::class => 'Konfigurasi Sistem',
User::class => 'Pengguna',
UserProfile::class => 'Profil Pengguna',
\App\Settings\MarketplaceSettings::class => 'Pengaturan Marketplace',
];
public static function for(?string $modelClass): string

View File

@ -74,6 +74,21 @@ private static function label(string $field): string
'is_active' => 'Status Aktif',
'variants' => 'Varian',
'prices' => 'Varian Harga',
'tiktok_shop_platform_commission' => 'TikTok Shop - Komisi Platform',
'tiktok_shop_logistics_service_fee' => 'TikTok Shop - Biaya Logistik',
'tiktok_shop_dynamic_commission' => 'TikTok Shop - Komisi Dinamis',
'tiktok_shop_order_processing_fee' => 'TikTok Shop - Biaya Proses Pesanan',
'tiktok_shop_affiliate' => 'TikTok Shop - Komisi Affiliate',
'tiktok_shop_pre_order_service_fee' => 'TikTok Shop - Biaya Pre Order',
'shopee_admin_fee' => 'Shopee - Biaya Admin',
'shopee_program_fee' => 'Shopee - Biaya Program',
'shopee_shipping_savings' => 'Shopee - Hemat Biaya Kirim',
'shopee_premium' => 'Shopee - Premi',
'shopee_service_fee' => 'Shopee - Biaya Layanan',
'shopee_order_processing_fee' => 'Shopee - Biaya Proses Pesanan',
'shopee_ams_commission_fee' => 'Shopee - Komisi AMS',
'shopee_pre_order' => 'Shopee - Pre Order',
'shopee_live_extra' => 'Shopee - Live Extra',
default => ucfirst(str_replace('_', ' ', $field)),
};
}
@ -88,6 +103,39 @@ private static function presentValue(string $field, mixed $value): mixed
return (bool) $value;
}
$marketplaceKeys = [
'tiktok_shop_platform_commission',
'tiktok_shop_logistics_service_fee',
'tiktok_shop_dynamic_commission',
'tiktok_shop_order_processing_fee',
'tiktok_shop_affiliate',
'tiktok_shop_pre_order_service_fee',
'shopee_admin_fee',
'shopee_program_fee',
'shopee_shipping_savings',
'shopee_premium',
'shopee_service_fee',
'shopee_order_processing_fee',
'shopee_ams_commission_fee',
'shopee_pre_order',
'shopee_live_extra',
];
if (in_array($field, $marketplaceKeys, true)) {
if (! is_array($value) || ! isset($value['scope'], $value['value_type'], $value['value'])) {
return is_array($value) ? json_encode($value) : (string) $value;
}
$val = $value['value'];
$formattedValue = $value['value_type'] === 'percent'
? rtrim(rtrim(number_format($val, 2, ',', '.'), '0'), ',') . '%'
: 'Rp ' . number_format($val, 0, ',', '.');
$scopeStr = $value['scope'] === 'item' ? 'per Item' : 'per Transaksi';
return "{$formattedValue} ({$scopeStr})";
}
if ($field === 'variants' && is_array($value)) {
return collect($value)
->map(function (array $variant): string {

View File

@ -1,23 +1,30 @@
<script setup lang="ts">
import { computed } from 'vue';
import { Home, Settings2, Share2, ShoppingBag, Users } from '@lucide/vue';
import { SettingSection as SettingSectionConst } from '@/constants/setting-section';
import { useCan } from '@/composables/useCan';
import AdminLayout from '@/layouts/AdminLayout.vue';
import { cn } from '@/lib/utils';
import type { SettingSection } from '@/types/setting';
const activeSection = defineModel<SettingSection>('section', { required: true });
const navItems: Array<{
key: SettingSection;
label: string;
icon: typeof Settings2;
}> = [
{ key: SettingSectionConst.SYSTEM, label: 'Sistem', icon: Settings2 },
{ key: SettingSectionConst.HOMEPAGE, label: 'Homepage', icon: Home },
{ key: SettingSectionConst.SOCIAL, label: 'Media Sosial', icon: Share2 },
{ key: SettingSectionConst.MARKETPLACE, label: 'Marketplace', icon: ShoppingBag },
{ key: SettingSectionConst.HR, label: 'HR / Pegawai', icon: Users },
];
const { hasRole } = useCan();
const navItems = [
{ key: SettingSectionConst.SYSTEM, label: 'Sistem', icon: Settings2 },
{ key: SettingSectionConst.HOMEPAGE, label: 'Homepage', icon: Home },
{ key: SettingSectionConst.SOCIAL, label: 'Media Sosial', icon: Share2 },
{ key: SettingSectionConst.MARKETPLACE, label: 'Marketplace', icon: ShoppingBag },
{ key: SettingSectionConst.HR, label: 'HR / Pegawai', icon: Users },
];
const filteredNavItems = computed(() => {
if (hasRole('admin-toko')) {
return navItems.filter((item) => item.key === SettingSectionConst.MARKETPLACE);
}
return navItems;
});
</script>
<template>
@ -31,7 +38,7 @@ const navItems: Array<{
<div class="flex flex-col gap-6 lg:flex-row">
<nav class="flex shrink-0 flex-row gap-1 overflow-x-auto lg:w-56 lg:flex-col lg:overflow-visible">
<button v-for="item in navItems" :key="item.key" type="button" :class="cn(
<button v-for="item in filteredNavItems" :key="item.key" type="button" :class="cn(
'inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium whitespace-nowrap transition-colors',
activeSection === item.key
? 'bg-accent text-accent-foreground'

View File

@ -2,6 +2,7 @@
import { Head } from '@inertiajs/vue3';
import { ref } from 'vue';
import { SettingSection as SettingSectionConst } from '@/constants/setting-section';
import { useCan } from '@/composables/useCan';
import SettingLayout from '@/layouts/SettingLayout.vue';
import type {
HomepageSettingsData,
@ -23,9 +24,14 @@ defineProps<{
marketplace: MarketplaceSettingsData;
hr: HrSettingsData;
homepage: HomepageSettingsData;
hasPendingMarketplaceVerification: boolean;
}>();
const activeSection = ref<SettingSection>(SettingSectionConst.SYSTEM);
const { hasRole } = useCan();
const activeSection = ref<SettingSection>(
hasRole('admin-toko') ? SettingSectionConst.MARKETPLACE : SettingSectionConst.SYSTEM
);
</script>
<template>
@ -36,7 +42,7 @@ const activeSection = ref<SettingSection>(SettingSectionConst.SYSTEM);
<SystemSection v-if="activeSection === SettingSectionConst.SYSTEM" :data="system" />
<HomepageSection v-else-if="activeSection === SettingSectionConst.HOMEPAGE" :data="homepage" />
<SocialMediaSection v-else-if="activeSection === SettingSectionConst.SOCIAL" :data="socialMedia" />
<MarketplaceSection v-else-if="activeSection === SettingSectionConst.MARKETPLACE" :data="marketplace" />
<MarketplaceSection v-else-if="activeSection === SettingSectionConst.MARKETPLACE" :data="marketplace" :has-pending-verification="hasPendingMarketplaceVerification" />
<HrSection v-else-if="activeSection === SettingSectionConst.HR" :data="hr" />
</SettingLayout>
</template>

View File

@ -23,6 +23,7 @@ const props = defineProps<{
label: string;
modelValue: MarketplaceFeeRule;
errors?: string[];
disabled?: boolean;
}>();
const emit = defineEmits<{
@ -57,6 +58,7 @@ function updateValue(value: string | number) {
Dasar
</FieldLabel>
<Select :model-value="modelValue.scope"
:disabled="disabled"
@update:model-value="update({ scope: $event as MarketplaceFeeRule['scope'] })">
<SelectTrigger :id="`${id}-scope`">
<SelectValue placeholder="Pilih dasar" />
@ -77,6 +79,7 @@ function updateValue(value: string | number) {
Tipe
</FieldLabel>
<Select :model-value="modelValue.value_type"
:disabled="disabled"
@update:model-value="update({ value_type: $event as MarketplaceFeeRule['value_type'] })">
<SelectTrigger :id="`${id}-value-type`">
<SelectValue placeholder="Pilih tipe" />
@ -97,9 +100,10 @@ function updateValue(value: string | number) {
Nilai
</FieldLabel>
<RupiahInput v-if="!isPercent" :id="`${id}-value`" :model-value="modelValue.value" placeholder="0"
:disabled="disabled"
@update:model-value="updateValue" />
<Input v-else :id="`${id}-value`" :model-value="modelValue.value" type="number" step="0.01" min="0"
max="100" @update:model-value="updateValue" />
max="100" :disabled="disabled" @update:model-value="updateValue" />
</Field>
</div>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useForm } from '@inertiajs/vue3';
import { Save, ShoppingBag, Store } from '@lucide/vue';
import { Save, ShoppingBag, Store, TriangleAlert } from '@lucide/vue';
import { ref } from 'vue';
import { toast } from 'vue-sonner';
import { Button } from '@/components/ui/button';
@ -24,6 +24,7 @@ import MarketplaceFeeField from './MarketplaceFeeField.vue';
const props = defineProps<{
data: MarketplaceSettingsData;
hasPendingVerification: boolean;
}>();
const activePlatform = ref<MarketplacePlatform>(MarketplacePlatformConst.TIKTOK_SHOP);
@ -78,6 +79,18 @@ function submit() {
<template>
<form @submit.prevent="submit">
<div v-if="props.hasPendingVerification" class="mb-6 rounded-lg border border-yellow-200 bg-yellow-50 p-4 dark:border-yellow-900/30 dark:bg-yellow-950/20">
<div class="flex gap-3">
<TriangleAlert class="size-5 text-yellow-600 dark:text-yellow-400 shrink-0 mt-0.5" />
<div>
<h5 class="font-medium text-yellow-800 dark:text-yellow-300">Menunggu Verifikasi Owner</h5>
<p class="mt-1 text-sm text-yellow-700/90 dark:text-yellow-400/90">
Perubahan pengaturan marketplace sedang menunggu verifikasi owner. Anda tidak dapat melakukan perubahan baru hingga pengajuan ini diproses.
</p>
</div>
</div>
</div>
<div class="flex flex-col gap-6 lg:flex-row">
<nav class="flex shrink-0 flex-row gap-1 overflow-x-auto lg:w-44 lg:flex-col lg:overflow-visible">
<button v-for="item in platformItems" :key="item.key" type="button" :class="cn(
@ -98,26 +111,32 @@ function submit() {
<FieldGroup class="grid gap-4 sm:grid-cols-2">
<MarketplaceFeeField id="tiktok_shop_platform_commission"
v-model="form.tiktok_shop_platform_commission" label="Biaya Komisi Platform"
:errors="feeErrors('tiktok_shop_platform_commission')" />
:errors="feeErrors('tiktok_shop_platform_commission')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="tiktok_shop_logistics_service_fee"
v-model="form.tiktok_shop_logistics_service_fee" label="Biaya Layanan Logistik"
:errors="feeErrors('tiktok_shop_logistics_service_fee')" />
:errors="feeErrors('tiktok_shop_logistics_service_fee')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="tiktok_shop_dynamic_commission"
v-model="form.tiktok_shop_dynamic_commission" label="Komisi Dinamis"
:errors="feeErrors('tiktok_shop_dynamic_commission')" />
:errors="feeErrors('tiktok_shop_dynamic_commission')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="tiktok_shop_order_processing_fee"
v-model="form.tiktok_shop_order_processing_fee" label="Biaya Pemrosesan Pesanan"
:errors="feeErrors('tiktok_shop_order_processing_fee')" />
:errors="feeErrors('tiktok_shop_order_processing_fee')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="tiktok_shop_affiliate" v-model="form.tiktok_shop_affiliate"
label="Affiliate" :errors="feeErrors('tiktok_shop_affiliate')" />
label="Affiliate" :errors="feeErrors('tiktok_shop_affiliate')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="tiktok_shop_pre_order_service_fee"
v-model="form.tiktok_shop_pre_order_service_fee" label="Biaya Layanan Pre Order"
:errors="feeErrors('tiktok_shop_pre_order_service_fee')" />
:errors="feeErrors('tiktok_shop_pre_order_service_fee')"
:disabled="props.hasPendingVerification" />
</FieldGroup>
</FieldSet>
</CardContent>
@ -128,40 +147,49 @@ function submit() {
<FieldSet>
<FieldGroup class="grid gap-4 sm:grid-cols-2">
<MarketplaceFeeField id="shopee_admin_fee" v-model="form.shopee_admin_fee"
label="Biaya Administrasi" :errors="feeErrors('shopee_admin_fee')" />
label="Biaya Administrasi" :errors="feeErrors('shopee_admin_fee')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="shopee_program_fee" v-model="form.shopee_program_fee"
label="Biaya Program" :errors="feeErrors('shopee_program_fee')" />
label="Biaya Program" :errors="feeErrors('shopee_program_fee')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="shopee_shipping_savings" v-model="form.shopee_shipping_savings"
label="Hemat Biaya Kirim" :errors="feeErrors('shopee_shipping_savings')" />
label="Hemat Biaya Kirim" :errors="feeErrors('shopee_shipping_savings')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="shopee_premium" v-model="form.shopee_premium" label="Premi"
:errors="feeErrors('shopee_premium')" />
:errors="feeErrors('shopee_premium')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="shopee_service_fee" v-model="form.shopee_service_fee"
label="Biaya Layanan" :errors="feeErrors('shopee_service_fee')" />
label="Biaya Layanan" :errors="feeErrors('shopee_service_fee')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="shopee_order_processing_fee"
v-model="form.shopee_order_processing_fee" label="Biaya Proses Pesanan"
:errors="feeErrors('shopee_order_processing_fee')" />
:errors="feeErrors('shopee_order_processing_fee')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="shopee_ams_commission_fee"
v-model="form.shopee_ams_commission_fee" label="Biaya Komisi AMS"
:errors="feeErrors('shopee_ams_commission_fee')" />
:errors="feeErrors('shopee_ams_commission_fee')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="shopee_pre_order" v-model="form.shopee_pre_order"
label="Pre Order" :errors="feeErrors('shopee_pre_order')" />
label="Pre Order" :errors="feeErrors('shopee_pre_order')"
:disabled="props.hasPendingVerification" />
<MarketplaceFeeField id="shopee_live_extra" v-model="form.shopee_live_extra"
label="Live Extra" :errors="feeErrors('shopee_live_extra')" />
label="Live Extra" :errors="feeErrors('shopee_live_extra')"
:disabled="props.hasPendingVerification" />
</FieldGroup>
</FieldSet>
</CardContent>
</Card>
<div class="flex justify-end">
<Button type="submit" :disabled="form.processing">
<Button type="submit" :disabled="form.processing || props.hasPendingVerification">
<Save class="size-4" />
{{ form.processing ? 'Menyimpan...' : 'Simpan' }}
</Button>