Skip to content

Commit 29b9c37

Browse files
simonhampclaude
andcommitted
Deferred repo loading, cached GitHub API, Flux Pro searchable selects
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e1fc7e4 commit 29b9c37

File tree

2 files changed

+27
-69
lines changed

2 files changed

+27
-69
lines changed

app/Livewire/Customer/Plugins/Create.php

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Notifications\PluginSubmitted;
1010
use App\Services\GitHubUserService;
1111
use App\Services\PluginSyncService;
12+
use Illuminate\Support\Facades\Cache;
1213
use Laravel\Pennant\Feature;
1314
use Livewire\Attributes\Computed;
1415
use Livewire\Attributes\Layout;
@@ -65,13 +66,13 @@ public function updatedSelectedOwner(): void
6566
public function mount(): void
6667
{
6768
if (auth()->user()->github_id) {
68-
$this->loadRepositories();
69+
$this->loadingRepos = true;
6970
}
7071
}
7172

7273
public function loadRepositories(): void
7374
{
74-
if ($this->loadingRepos || $this->reposLoaded) {
75+
if ($this->reposLoaded) {
7576
return;
7677
}
7778

@@ -81,16 +82,23 @@ public function loadRepositories(): void
8182
$user = auth()->user();
8283

8384
if ($user->hasGitHubToken()) {
84-
$githubService = GitHubUserService::for($user);
85-
$this->repositories = $githubService->getRepositories()
86-
->map(fn ($repo) => [
87-
'id' => $repo['id'],
88-
'full_name' => $repo['full_name'],
89-
'name' => $repo['name'],
90-
'owner' => explode('/', $repo['full_name'])[0],
91-
'private' => $repo['private'] ?? false,
92-
])
93-
->toArray();
85+
$cacheKey = "github_repos_{$user->id}";
86+
87+
$repos = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($user) {
88+
$githubService = GitHubUserService::for($user);
89+
90+
return $githubService->getRepositories()
91+
->map(fn ($repo) => [
92+
'id' => $repo['id'],
93+
'full_name' => $repo['full_name'],
94+
'name' => $repo['name'],
95+
'owner' => explode('/', $repo['full_name'])[0],
96+
'private' => $repo['private'] ?? false,
97+
])
98+
->all();
99+
});
100+
101+
$this->repositories = collect($repos)->values()->all();
94102
}
95103

96104
$this->reposLoaded = true;

resources/views/livewire/customer/plugins/create.blade.php

Lines changed: 7 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div>
1+
<div wire:init="loadRepositories">
22
<div class="mb-6">
33
<nav class="flex" aria-label="Breadcrumb">
44
<ol class="flex items-center gap-2">
@@ -112,68 +112,18 @@
112112
<span>Loading repositories...</span>
113113
</div>
114114
@elseif($reposLoaded)
115-
<flux:select wire:model.live="selectedOwner" label="Account" placeholder="Select an account...">
115+
<flux:select variant="listbox" searchable wire:model.live="selectedOwner" label="Account" placeholder="Select an account...">
116116
@foreach($this->owners as $owner)
117117
<flux:select.option value="{{ $owner }}">{{ $owner }}</flux:select.option>
118118
@endforeach
119119
</flux:select>
120120

121121
@if($selectedOwner)
122-
<div
123-
wire:key="repo-search-{{ $selectedOwner }}"
124-
x-data="{
125-
search: '',
126-
open: false,
127-
repos: @js($this->ownerRepositories),
128-
get filtered() {
129-
if (!this.search) return this.repos;
130-
const q = this.search.toLowerCase();
131-
return this.repos.filter(r => r.name.toLowerCase().includes(q));
132-
},
133-
select(repo) {
134-
$wire.set('repository', repo.full_name);
135-
this.search = repo.name + (repo.private ? ' (private)' : '');
136-
this.open = false;
137-
},
138-
}"
139-
@click.outside="open = false"
140-
class="relative"
141-
>
142-
<flux:input
143-
x-model="search"
144-
@focus="open = true"
145-
@keydown.escape.prevent="open = false"
146-
label="Repository"
147-
placeholder="Search repositories..."
148-
icon="magnifying-glass"
149-
autocomplete="off"
150-
/>
151-
152-
<div
153-
x-show="open && filtered.length > 0"
154-
x-cloak
155-
class="absolute z-20 mt-1 max-h-60 w-full overflow-auto rounded-lg border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-800"
156-
>
157-
<template x-for="repo in filtered" :key="repo.id">
158-
<button
159-
type="button"
160-
@click="select(repo)"
161-
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700"
162-
>
163-
<span x-text="repo.name" class="font-medium text-zinc-900 dark:text-white"></span>
164-
<template x-if="repo.private">
165-
<span class="rounded-full bg-zinc-100 px-2 py-0.5 text-xs text-zinc-500 dark:bg-zinc-700 dark:text-zinc-400">private</span>
166-
</template>
167-
</button>
168-
</template>
169-
</div>
170-
171-
<template x-if="open && search && filtered.length === 0">
172-
<div class="absolute z-20 mt-1 w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-500 shadow-lg dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-400">
173-
No repositories found.
174-
</div>
175-
</template>
176-
</div>
122+
<flux:select wire:key="repo-search-{{ $selectedOwner }}" variant="combobox" wire:model.live="repository" label="Repository" placeholder="Search repositories...">
123+
@foreach($this->ownerRepositories as $repo)
124+
<flux:select.option value="{{ $repo['full_name'] }}">{{ $repo['name'] }}@if($repo['private']) (private)@endif</flux:select.option>
125+
@endforeach
126+
</flux:select>
177127
@endif
178128
@endif
179129
@error('repository')

0 commit comments

Comments
 (0)