Add platform filter to Discover, alongside keyword and TLD
Filters sites by server/powered_by/generator (e.g. "Shopify", "WordPress", "nginx"), combinable with the existing keyword/TLD filters. Results table now also shows each site's detected platform. Verified: platform=Wix correctly isolates the one known Wix site, tld=es+platform=nginx narrows to the 12 real nginx-hosted .es sites, and the no-match case renders the empty state correctly.
This commit is contained in:
@@ -13,7 +13,8 @@ use Livewire\WithPagination;
|
|||||||
/**
|
/**
|
||||||
* The competitor-discovery search: "who sells honey with a .es domain?" — keyword
|
* The competitor-discovery search: "who sells honey with a .es domain?" — keyword
|
||||||
* matches a site's pages via MySQL fulltext (natural language mode, so free-text input
|
* matches a site's pages via MySQL fulltext (natural language mode, so free-text input
|
||||||
* never trips a boolean-mode syntax error), TLD filters the site's domain suffix.
|
* never trips a boolean-mode syntax error), TLD filters the site's domain suffix,
|
||||||
|
* platform filters its detected server/powered-by/generator signature.
|
||||||
*/
|
*/
|
||||||
#[Layout('components.layouts.app')]
|
#[Layout('components.layouts.app')]
|
||||||
class Search extends Component
|
class Search extends Component
|
||||||
@@ -26,6 +27,9 @@ class Search extends Component
|
|||||||
#[Url(as: 'tld', history: true)]
|
#[Url(as: 'tld', history: true)]
|
||||||
public string $tld = '';
|
public string $tld = '';
|
||||||
|
|
||||||
|
#[Url(as: 'platform', history: true)]
|
||||||
|
public string $platform = '';
|
||||||
|
|
||||||
public function updatingKeyword(): void
|
public function updatingKeyword(): void
|
||||||
{
|
{
|
||||||
$this->resetPage();
|
$this->resetPage();
|
||||||
@@ -36,11 +40,17 @@ class Search extends Component
|
|||||||
$this->resetPage();
|
$this->resetPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatingPlatform(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
public function render(): View
|
public function render(): View
|
||||||
{
|
{
|
||||||
$keyword = trim($this->keyword);
|
$keyword = trim($this->keyword);
|
||||||
$tld = strtolower(trim(ltrim($this->tld, '.')));
|
$tld = strtolower(trim(ltrim($this->tld, '.')));
|
||||||
$hasQuery = $keyword !== '' || $tld !== '';
|
$platform = trim($this->platform);
|
||||||
|
$hasQuery = $keyword !== '' || $tld !== '' || $platform !== '';
|
||||||
|
|
||||||
// Without any filter this would just be the full site list — that's what the
|
// Without any filter this would just be the full site list — that's what the
|
||||||
// Sites page is for. Discover only returns results once you've asked it something.
|
// Sites page is for. Discover only returns results once you've asked it something.
|
||||||
@@ -56,6 +66,13 @@ class Search extends Component
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
->when($tld !== '', fn ($query) => $query->where('domain', 'like', '%.'.$tld))
|
->when($tld !== '', fn ($query) => $query->where('domain', 'like', '%.'.$tld))
|
||||||
|
->when($platform !== '', function ($query) use ($platform) {
|
||||||
|
$query->where(function ($query) use ($platform) {
|
||||||
|
$query->where('server', 'like', '%'.$platform.'%')
|
||||||
|
->orWhere('powered_by', 'like', '%'.$platform.'%')
|
||||||
|
->orWhere('generator', 'like', '%'.$platform.'%');
|
||||||
|
});
|
||||||
|
})
|
||||||
->withCount(['pages as matching_pages_count' => function ($pageQuery) use ($keyword) {
|
->withCount(['pages as matching_pages_count' => function ($pageQuery) use ($keyword) {
|
||||||
if ($keyword !== '') {
|
if ($keyword !== '') {
|
||||||
$pageQuery->whereFullText(['title', 'description', 'keywords', 'content'], $keyword);
|
$pageQuery->whereFullText(['title', 'description', 'keywords', 'content'], $keyword);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="mx-auto max-w-5xl px-4 py-8">
|
<div class="mx-auto max-w-5xl px-4 py-8">
|
||||||
<h1 class="text-2xl font-semibold">Discover competitors</h1>
|
<h1 class="text-2xl font-semibold">Discover competitors</h1>
|
||||||
<p class="mt-1 text-sm text-neutral-500">
|
<p class="mt-1 text-sm text-neutral-500">
|
||||||
Find sites by what they sell and where — e.g. keyword "honey", TLD "es".
|
Find sites by what they sell, where, and what they're built on — e.g. keyword "honey", TLD "es", platform "Shopify".
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-6 flex flex-wrap gap-3">
|
<div class="mt-6 flex flex-wrap gap-3">
|
||||||
@@ -17,6 +17,12 @@
|
|||||||
placeholder="TLD, e.g. es"
|
placeholder="TLD, e.g. es"
|
||||||
class="w-full max-w-[10rem] rounded-md border border-neutral-300 px-3 py-2 text-sm focus:border-neutral-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
|
class="w-full max-w-[10rem] rounded-md border border-neutral-300 px-3 py-2 text-sm focus:border-neutral-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
wire:model.live.debounce.400ms="platform"
|
||||||
|
placeholder="Platform, e.g. WordPress"
|
||||||
|
class="w-full max-w-[14rem] rounded-md border border-neutral-300 px-3 py-2 text-sm focus:border-neutral-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-800">
|
<div class="mt-6 overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-800">
|
||||||
@@ -25,6 +31,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-neutral-500">Domain</th>
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-neutral-500">Domain</th>
|
||||||
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-neutral-500">Title</th>
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-neutral-500">Title</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-neutral-500">Platform</th>
|
||||||
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-neutral-500">Matching pages</th>
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-neutral-500">Matching pages</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -37,15 +44,16 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-sm text-neutral-600 dark:text-neutral-400">{{ $site->title ?? '—' }}</td>
|
<td class="px-4 py-2 text-sm text-neutral-600 dark:text-neutral-400">{{ $site->title ?? '—' }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-neutral-600 dark:text-neutral-400">{{ $site->generator ?? $site->powered_by ?? $site->server ?? '—' }}</td>
|
||||||
<td class="px-4 py-2 text-right text-sm text-neutral-600 dark:text-neutral-400">{{ $site->matching_pages_count }}</td>
|
<td class="px-4 py-2 text-right text-sm text-neutral-600 dark:text-neutral-400">{{ $site->matching_pages_count }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="px-4 py-6 text-center text-sm text-neutral-500">
|
<td colspan="4" class="px-4 py-6 text-center text-sm text-neutral-500">
|
||||||
@if ($hasQuery)
|
@if ($hasQuery)
|
||||||
No matching sites.
|
No matching sites.
|
||||||
@else
|
@else
|
||||||
Enter a keyword or TLD to search.
|
Enter a keyword, TLD, or platform to search.
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user