loadNewest(); } private function loadNewest(): void { try { $this->recipes = Cache::remember('trmnl_recipes_newest', 43200, function () { $response = Http::get('https://usetrmnl.com/recipes.json', [ 'sort-by' => 'newest', ]); if (!$response->successful()) { throw new \RuntimeException('Failed to fetch TRMNL recipes'); } $json = $response->json(); $data = $json['data'] ?? []; return $this->mapRecipes($data); }); } catch (\Throwable $e) { Log::error('TRMNL catalog load error: ' . $e->getMessage()); $this->recipes = []; } } private function searchRecipes(string $term): void { $this->isSearching = true; try { $cacheKey = 'trmnl_recipes_search_' . md5($term); $this->recipes = Cache::remember($cacheKey, 300, function () use ($term) { $response = Http::get('https://usetrmnl.com/recipes.json', [ 'search' => $term, 'sort-by' => 'newest', ]); if (!$response->successful()) { throw new \RuntimeException('Failed to search TRMNL recipes'); } $json = $response->json(); $data = $json['data'] ?? []; return $this->mapRecipes($data); }); } catch (\Throwable $e) { Log::error('TRMNL catalog search error: ' . $e->getMessage()); $this->recipes = []; } finally { $this->isSearching = false; } } public function updatedSearch(): void { $term = trim($this->search); if ($term === '') { $this->loadNewest(); return; } if (strlen($term) < 2) { // Require at least 2 chars to avoid noisy calls return; } $this->searchRecipes($term); } public function installPlugin(string $recipeId, PluginImportService $pluginImportService): void { abort_unless(auth()->user() !== null, 403); $this->installingPlugin = $recipeId; try { $zipUrl = "https://usetrmnl.com/api/plugin_settings/{$recipeId}/archive"; $recipe = collect($this->recipes)->firstWhere('id', $recipeId); $plugin = $pluginImportService->importFromUrl( $zipUrl, auth()->user(), null, config('services.trmnl.liquid_enabled') ? 'trmnl-liquid' : null, $recipe['icon_url'] ?? null ); $this->dispatch('plugin-installed'); Flux::modal('import-from-trmnl-catalog')->close(); } catch (\Exception $e) { Log::error('Plugin installation failed: ' . $e->getMessage()); $this->addError('installation', 'Error installing plugin: ' . $e->getMessage()); } finally { $this->installingPlugin = ''; } } /** * @param array> $items * @return array> */ private function mapRecipes(array $items): array { return collect($items) ->map(function (array $item) { return [ 'id' => $item['id'] ?? null, 'name' => $item['name'] ?? 'Untitled', 'icon_url' => $item['icon_url'] ?? null, 'screenshot_url' => $item['screenshot_url'] ?? null, 'author_bio' => is_array($item['author_bio'] ?? null) ? strip_tags($item['author_bio']['description'] ?? null) : null, 'stats' => [ 'installs' => data_get($item, 'stats.installs'), 'forks' => data_get($item, 'stats.forks'), ], 'detail_url' => isset($item['id']) ? ('https://usetrmnl.com/recipes/' . $item['id']) : null, ]; }) ->toArray(); } }; ?>
Newest
@error('installation') @enderror @if(empty($recipes))
No recipes found Try a different search term
@else
@foreach($recipes as $recipe)
@php($thumb = $recipe['icon_url'] ?? $recipe['screenshot_url']) @if($thumb) {{ $recipe['name'] }} @else
@endif

{{ $recipe['name'] }}

@if(data_get($recipe, 'stats.installs'))

Installs: {{ data_get($recipe, 'stats.installs') }} ยท Forks: {{ data_get($recipe, 'stats.forks') }}

@endif
@if($recipe['detail_url']) @endif
@if($recipe['author_bio'])

{{ $recipe['author_bio'] }}

@endif
@if($recipe['id']) @if($installingPlugin === $recipe['id']) @else Install @endif @endif @if($recipe['detail_url']) View on TRMNL @endif
@endforeach
@endif