Add Woo Manager Pro with multi-store switching and free-tier limits.
Deploy Ladill Woo Manager / deploy (push) Successful in 51s

Free: 1 store and 20 products. Pro/Business mirrors Invoice pricing (GHS 49/149)
with wallet renewals, Paystack prepay, session-based store switcher, and scoped UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-07 01:14:08 +00:00
co-authored by Cursor
parent 551473f8e5
commit cebf0d2e61
34 changed files with 1340 additions and 263 deletions
+33 -70
View File
@@ -3,8 +3,8 @@
namespace App\Http\Controllers\Woo;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Woo\Concerns\ResolvesWooContext;
use App\Models\WooCategory;
use App\Models\WooStore;
use App\Services\Woo\CatalogSyncService;
use App\Services\Woo\CatalogWriteService;
use Illuminate\Http\RedirectResponse;
@@ -13,6 +13,8 @@ use Illuminate\View\View;
class CategoryController extends Controller
{
use ResolvesWooContext;
public function __construct(
private CatalogWriteService $write,
private CatalogSyncService $sync,
@@ -20,105 +22,84 @@ class CategoryController extends Controller
public function index(Request $request): View
{
$user = $request->user();
$storeFilter = $request->query('store');
$store = $this->currentStore($request);
$search = trim((string) $request->query('q', ''));
$storeIds = WooStore::query()->where('user_id', $user->id)->pluck('id');
$categories = WooCategory::query()
->whereIn('woo_store_id', $storeIds)
->when($storeFilter, fn ($q) => $q->where('woo_store_id', $storeFilter))
->when($store, fn ($q) => $q->where('woo_store_id', $store->id))
->when(! $store, fn ($q) => $q->whereRaw('1 = 0'))
->when($search !== '', fn ($q) => $q->where('name', 'like', '%'.$search.'%'))
->with('store')
->orderBy('name')
->paginate(30)
->withQueryString();
$stores = WooStore::query()->where('user_id', $user->id)->orderBy('site_name')->get();
return view('woo.categories.index', compact('categories', 'stores', 'storeFilter', 'search'));
return view('woo.categories.index', compact('categories', 'search', 'store'));
}
public function create(Request $request): View
{
$stores = $this->activeStores($request);
$storeId = (int) $request->query('store', $stores->first()?->id ?? 0);
$parentCategories = $storeId > 0
? WooCategory::query()->where('woo_store_id', $storeId)->orderBy('name')->get()
: collect();
$store = $this->currentStoreOrFail($request);
return view('woo.categories.form', [
'category' => new WooCategory,
'stores' => $stores,
'parentCategories' => $parentCategories,
'selectedStoreId' => $storeId,
'category' => new WooCategory(['woo_store_id' => $store->id]),
'currentStore' => $store,
'parentCategories' => $store->categories()->orderBy('name')->get(),
]);
}
public function store(Request $request): RedirectResponse
{
$validated = $this->validateCategory($request);
$store = $this->authorizedStore($request, (int) $validated['woo_store_id']);
$store = $this->currentStoreOrFail($request);
$validated = $this->validateCategory($request, $store);
$this->write->createCategory($store, $validated);
return redirect()->route('woo.categories.index', ['store' => $store->id])
return redirect()->route('woo.categories.index')
->with('success', 'Category saved.');
}
public function edit(Request $request, WooCategory $category): View
{
$category->load('store');
abort_if($category->store?->user_id !== $request->user()->id, 403);
$stores = $this->activeStores($request);
$parentCategories = $category->store
? $category->store->categories()
->where('external_id', '!=', $category->external_id)
->orderBy('name')
->get()
: collect();
$store = $this->currentStoreOrFail($request);
abort_if($category->woo_store_id !== $store->id, 404);
return view('woo.categories.form', [
'category' => $category,
'stores' => $stores,
'parentCategories' => $parentCategories,
'selectedStoreId' => $category->woo_store_id,
'currentStore' => $store,
'parentCategories' => $store->categories()
->where('external_id', '!=', $category->external_id)
->orderBy('name')
->get(),
]);
}
public function update(Request $request, WooCategory $category): RedirectResponse
{
$category->load('store');
abort_if($category->store?->user_id !== $request->user()->id, 403);
$store = $this->currentStoreOrFail($request);
abort_if($category->woo_store_id !== $store->id, 404);
$validated = $this->validateCategory($request);
$validated = $this->validateCategory($request, $store);
$this->write->updateCategory($category, $validated);
return redirect()->route('woo.categories.index', ['store' => $category->woo_store_id])
return redirect()->route('woo.categories.index')
->with('success', 'Category updated.');
}
public function destroy(Request $request, WooCategory $category): RedirectResponse
{
$category->load('store');
abort_if($category->store?->user_id !== $request->user()->id, 403);
$store = $this->currentStoreOrFail($request);
abort_if($category->woo_store_id !== $store->id, 404);
$storeId = $category->woo_store_id;
$this->write->deleteCategory($category);
return redirect()->route('woo.categories.index', ['store' => $storeId])
return redirect()->route('woo.categories.index')
->with('success', 'Category removed.');
}
public function sync(Request $request): RedirectResponse
{
$validated = $request->validate([
'store' => ['required', 'integer'],
]);
$store = $this->authorizedStore($request, (int) $validated['store']);
$store = $this->currentStoreOrFail($request);
$count = $this->sync->syncCategories($store);
return back()->with('success', sprintf(
@@ -129,33 +110,15 @@ class CategoryController extends Controller
}
/** @return array<string, mixed> */
private function validateCategory(Request $request): array
private function validateCategory(Request $request, $store): array
{
return $request->validate([
'woo_store_id' => ['required', 'integer'],
return array_merge($request->validate([
'name' => ['required', 'string', 'max:255'],
'slug' => ['nullable', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:10000'],
'parent_external_id' => ['nullable', 'integer', 'min:0'],
]), [
'woo_store_id' => $store->id,
]);
}
private function authorizedStore(Request $request, int $storeId): WooStore
{
return WooStore::query()
->where('user_id', $request->user()->id)
->whereKey($storeId)
->where('status', WooStore::STATUS_ACTIVE)
->firstOrFail();
}
/** @return \Illuminate\Support\Collection<int, WooStore> */
private function activeStores(Request $request)
{
return WooStore::query()
->where('user_id', $request->user()->id)
->where('status', WooStore::STATUS_ACTIVE)
->orderBy('site_name')
->get();
}
}