authorize('plans.manage'); } public function save(): void { $this->authorize('plans.manage'); $this->key = strtolower(trim($this->key)); $data = $this->validate([ // The key is permanent once created — orders, instances and every // contract snapshot store it as a string — so it has to be a plain // identifier, not something anyone will want to prettify later. 'key' => ['required', 'string', 'max:32', 'regex:/^[a-z][a-z0-9_]*$/', 'unique:plan_families,key'], 'name' => 'required|string|max:255', 'tier' => 'required|integer|min:0|max:255', ]); PlanFamily::create([ 'key' => $data['key'], 'name' => $data['name'], 'tier' => $data['tier'], // Nothing to sell until a version is published, so it starts able // to sell and simply has nothing available. 'sales_enabled' => true, ]); $this->reset('key', 'name', 'tier'); $this->tier = 1; $this->dispatch('notify', message: __('plans.created')); } /** * The kill switch: stop selling this plan now, without touching a window. * * Existing customers keep their contract and their machine — this only * decides whether anyone new can buy it. */ public function toggleSales(string $uuid): void { $this->authorize('plans.manage'); $family = PlanFamily::query()->where('uuid', $uuid)->first(); $family?->update(['sales_enabled' => ! $family->sales_enabled]); $this->dispatch('notify', message: __($family?->sales_enabled ? 'plans.now_selling' : 'plans.withdrawn')); } public function render() { $now = now(); $families = PlanFamily::query() ->with(['versions.prices']) ->withCount('versions') ->orderBy('tier') ->get() ->map(fn (PlanFamily $family) => [ 'uuid' => $family->uuid, 'key' => $family->key, 'name' => $family->name, 'tier' => $family->tier, 'sales_enabled' => $family->sales_enabled, 'is_recommended' => $family->is_recommended, 'versions_count' => $family->versions_count, 'live' => $family->versions->first(fn ($v) => $v->isAvailableAt($now)), // What is queued up next, so a scheduled launch is visible // before a customer discovers it. 'next' => $family->versions ->filter(fn ($v) => $v->isPublished() && $v->available_from->greaterThan($now)) ->sortBy('available_from') ->first(), 'drafts' => $family->versions->filter(fn ($v) => ! $v->isPublished())->count(), ]); return view('livewire.admin.plans', [ 'families' => $families, // The shop's own answer, so this page cannot disagree with what a // customer actually sees. 'sellable' => array_keys(app(PlanCatalogue::class)->sellable()), ]); } }