Skip to content
57 changes: 31 additions & 26 deletions app/pages/search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { normalizeSearchParam } from '#shared/utils/url'

const route = useRoute()
const router = useRouter()

Check failure on line 11 in app/pages/search.vue

View workflow job for this annotation

GitHub Actions / 💪 Type check

'router' is declared but its value is never read.

// Preferences (persisted to localStorage)
const {
Expand All @@ -21,13 +21,16 @@
} = usePackageListPreferences()

// Debounced URL update for page (less aggressive to avoid too many URL changes)
//Use History API directly to update URL without triggering Router's scroll-to-top
const updateUrlPage = debounce((page: number) => {
router.replace({
query: {
...route.query,
page: page > 1 ? page : undefined,
},
})
const url = new URL(window.location.href)
if (page > 1) {
url.searchParams.set('page', page.toString())
} else {
url.searchParams.delete('page')
}
// This updates the address bar "silently"
window.history.replaceState({}, '', url)
}, 500)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const { model: searchQuery, provider: searchProvider } = useGlobalSearch()
Expand Down Expand Up @@ -276,7 +279,8 @@
}

// Reset page when query changes
watch(query, () => {
watch(query, (newQuery, oldQuery) => {
if (newQuery.trim() === (oldQuery || '').trim()) return
currentPage.value = 1
hasInteracted.value = true
})
Expand Down Expand Up @@ -536,7 +540,7 @@
</script>

<template>
<main class="flex-1 py-8" :class="{ 'overflow-x-hidden': viewMode !== 'table' }">
<main class="flex-1 py-8 search-page" :class="{ 'overflow-x-hidden': viewMode !== 'table' }">
<div class="container-sm">
<div class="flex items-center justify-between gap-4 mb-4">
<h1 class="font-mono text-2xl sm:text-3xl font-medium">
Expand All @@ -545,12 +549,13 @@
<SearchProviderToggle />
</div>

<section v-if="query">
<!-- Initial loading (only after user interaction, not during view transition) -->
<LoadingSpinner v-if="showSearching" :text="$t('search.searching')" />
<section v-if="query" class="results-layout">
<LoadingSpinner
v-if="showSearching && displayResults.length === 0"
:text="$t('search.searching')"
/>

<div v-else-if="visibleResults">
<!-- User/Org search suggestions -->
<div v-show="results || displayResults.length > 0">
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
<div v-if="validatedSuggestions.length > 0" class="mb-6 space-y-3">
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
<SearchSuggestionCard
v-for="(suggestion, idx) in validatedSuggestions"
Expand All @@ -565,9 +570,8 @@
/>
</div>

<!-- Claim prompt - shown at top when valid name but no exact match -->
<div
v-if="showClaimPrompt && visibleResults.total > 0"
v-if="showClaimPrompt && visibleResults && visibleResults.total > 0"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
class="mb-6 p-4 bg-bg-subtle border border-border rounded-lg sm:flex hidden flex-row sm:items-center gap-3 sm:gap-4"
>
<div class="flex-1 min-w-0">
Expand All @@ -585,15 +589,13 @@
</button>
</div>

<!-- Rate limited by npm - check FIRST before showing any results -->
<div v-if="isRateLimited" role="status" class="py-12">
<p class="text-fg-muted font-mono mb-6 text-center">
{{ $t('search.rate_limited') }}
</p>
</div>

<!-- Enhanced toolbar -->
<div v-else-if="visibleResults.total > 0" class="mb-6">
<div v-else-if="visibleResults && visibleResults.total > 0" class="mb-6">
<PackageListToolbar
:filters="filters"
v-model:sort-option="sortOption"
Expand All @@ -618,7 +620,6 @@
@update:updated-within="setUpdatedWithin"
@toggle-keyword="toggleKeyword"
/>
<!-- Show count status (infinite scroll mode only) -->
<p
v-if="viewMode === 'cards' && paginationMode === 'infinite'"
role="status"
Expand All @@ -642,7 +643,6 @@
$t('search.updating')
}}</span>
</p>
<!-- Show "x of y" (paginated/table mode only) -->
<p
v-if="viewMode === 'table' || paginationMode === 'paginated'"
role="status"
Expand All @@ -664,13 +664,11 @@
</p>
</div>

<!-- No results found -->
<div v-else-if="status === 'success' || status === 'error'" role="status" class="py-12">
<p class="text-fg-muted font-mono mb-6 text-center">
{{ $t('search.no_results', { query }) }}
</p>

<!-- User/Org suggestions when no packages found -->
<div v-if="validatedSuggestions.length > 0" class="max-w-md mx-auto mb-6 space-y-3">
<SearchSuggestionCard
v-for="(suggestion, idx) in validatedSuggestions"
Expand All @@ -685,7 +683,6 @@
/>
</div>

<!-- Offer to claim the package name if it's valid -->
<div v-if="showClaimPrompt" class="max-w-md mx-auto text-center hidden sm:block">
<div class="p-4 bg-bg-subtle border border-border rounded-lg">
<p class="text-sm text-fg-muted mb-3">{{ $t('search.want_to_claim') }}</p>
Expand All @@ -701,7 +698,7 @@
</div>

<PackageList
v-if="displayResults.length > 0 && !isRateLimited"
v-show="displayResults.length > 0 && !isRateLimited"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
:results="displayResults"
:search-query="query"
:filters="filters"
Expand All @@ -722,7 +719,6 @@
@click-keyword="toggleKeyword"
/>

<!-- Pagination controls -->
<PaginationControls
v-if="displayResults.length > 0 && !isRateLimited"
v-model:mode="paginationMode"
Expand All @@ -739,7 +735,6 @@
</section>
</div>

<!-- Claim package modal -->
<PackageClaimPackageModal
ref="claimPackageModalRef"
:package-name="query"
Expand All @@ -748,3 +743,13 @@
/>
</main>
</template>

<style scoped>
.search-page {
overflow-anchor: none;
}

.results-layout {
min-height: 100vh;
}
</style>
Loading