Add Products page backed by the CRM products API + storefront catalog picker
Deploy Ladill Merchant / deploy (push) Successful in 30s

- New Products page (merchant.products.*) with full CRUD proxied to the Ladill
  CRM products API via a new CrmClient + config/crm.php (owner-scoped, type=product).
- Sidebar gains a Products entry.
- The new storefront form loads the merchant's catalog: each shop/menu section
  gets an "Add from products…" picker that drops a CRM product in as an item
  (name, price, description). Catalog fetch is resilient — empty if CRM is down.

Wires CRM_API_URL + CRM_API_KEY_MERCHANT on the merchant env (matches CRM).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
isaacclad
2026-06-24 07:32:58 +00:00
co-authored by Claude Opus 4.8
parent 08d3c99996
commit b19e2654a1
12 changed files with 435 additions and 9 deletions
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Merchant;
use App\Http\Controllers\Controller;
use App\Models\QrCode;
use App\Services\Crm\CrmClient;
use App\Services\Qr\QrCodeManagerService;
use App\Services\Qr\QrImageGeneratorService;
use App\Services\Qr\QrPdfExporter;
@@ -59,9 +60,38 @@ class StorefrontController extends Controller
return view('merchant.storefronts.create', [
'requestedType' => $requestedType,
'prefill' => ($prefill['kind'] ?? null) === 'merchant_storefront' ? $prefill : null,
'catalog' => $this->catalogProducts(),
]);
}
/**
* Active products from the merchant's Ladill CRM catalog, shaped for the
* storefront editor's "add from catalog" picker. Resilient: returns [] if
* CRM is unreachable so the form still works.
*
* @return array<int,array{name:string,price:string,currency:string,description:string}>
*/
private function catalogProducts(): array
{
try {
$response = CrmClient::for((string) ladill_account()->public_id)
->products(['type' => 'product', 'active' => 1, 'per_page' => 200]);
} catch (\Throwable) {
return [];
}
return collect($response['data'] ?? [])
->map(fn ($p) => [
'name' => (string) ($p['name'] ?? ''),
'price' => number_format(((int) ($p['unit_price_minor'] ?? 0)) / 100, 2, '.', ''),
'currency' => strtoupper((string) ($p['currency'] ?? 'GHS')),
'description' => (string) ($p['description'] ?? ''),
])
->filter(fn ($p) => $p['name'] !== '')
->values()
->all();
}
public function store(Request $request): RedirectResponse
{
$account = ladill_account();