validate([ 'q' => ['nullable', 'string', 'max:120'], 'category' => ['nullable', 'string', 'max:140'], 'difficulty' => ['nullable', 'string', 'max:40'], 'tag' => ['nullable', 'string', 'max:60'], ]); $query = AcademyPromptTemplate::query() ->with('category') ->active() ->published() ->latest('published_at'); if (filled($filters['q'] ?? null)) { $query->where(function ($builder) use ($filters): void { $builder->where('title', 'like', '%' . $filters['q'] . '%') ->orWhere('excerpt', 'like', '%' . $filters['q'] . '%'); }); } if (filled($filters['category'] ?? null)) { $query->whereHas('category', fn ($builder) => $builder->where('slug', $filters['category'])); } if (filled($filters['difficulty'] ?? null)) { $query->where('difficulty', $filters['difficulty']); } if (filled($filters['tag'] ?? null)) { $tag = $filters['tag']; $query->whereJsonContains('tags', $tag); } $prompts = $query->paginate(12)->withQueryString(); $prompts->getCollection()->transform(fn (AcademyPromptTemplate $prompt): array => $this->access->promptPayload($prompt, $request->user())); $seo = app(SeoFactory::class) ->collectionListing( 'Academy Prompts — Skinbase', 'Browse AI prompt templates for wallpapers, worlds, editorial covers, robots, pixel art, and creator workflows.', route('academy.prompts.index', $request->query()), ) ->toArray(); return Inertia::render('Academy/List', [ 'pageType' => 'prompts', 'title' => 'Prompt library', 'description' => 'Reusable prompt templates for wallpapers, worlds, mascots, covers, and digital art workflows.', 'seo' => $seo, 'items' => $prompts, 'filters' => $filters, 'categories' => $this->cache->categoriesByType('prompt'), 'pricingUrl' => route('academy.pricing'), ])->rootView('collections'); } public function show(Request $request, string $slug): Response { abort_unless((bool) config('academy.enabled', true), 404); $prompt = AcademyPromptTemplate::query() ->with('category') ->active() ->published() ->where('slug', $slug) ->firstOrFail(); $payload = $this->access->promptPayload($prompt, $request->user(), true); $canonical = route('academy.prompts.show', ['slug' => $prompt->slug]); $description = Str::limit(trim((string) ($prompt->seo_description ?? $prompt->excerpt ?? 'Skinbase Academy prompt template.')), 160, '...'); $seo = app(SeoFactory::class)->collectionPage( (string) ($prompt->seo_title ?? ($prompt->title . ' — Skinbase Academy')), $description, $canonical, $prompt->preview_image, )->toArray(); return Inertia::render('Academy/Show', [ 'pageType' => 'prompt', 'item' => $payload, 'seo' => $seo, 'pricingUrl' => route('academy.pricing'), 'saveUrl' => $request->user() ? route('academy.prompts.save', ['prompt' => $prompt->id]) : null, 'unsaveUrl' => $request->user() ? route('academy.prompts.unsave', ['prompt' => $prompt->id]) : null, 'saved' => $request->user()?->academySavedPrompts()->where('prompt_template_id', $prompt->id)->exists() ?? false, ])->rootView('collections'); } }