From 5fb2de31c88bf2be9bb2817d127b4dc0d09e9aa5 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Sun, 7 Jun 2026 15:37:58 +0000 Subject: [PATCH] Add mobile and desktop search for QR Plus. Introduce a dedicated /search page, wire the bottom nav and topbar to it, and expose JSON results for live lookup of QR codes. Co-authored-by: Cursor --- app/Http/Controllers/SearchController.php | 51 +++++++++ resources/js/app.js | 12 ++- .../components/mobile-page-header.blade.php | 28 +++++ resources/views/layouts/user.blade.php | 59 +---------- .../views/partials/search-screen.blade.php | 100 ++++++++++++++++++ resources/views/partials/topbar-qr.blade.php | 46 ++++++++ resources/views/search/index.blade.php | 13 +++ routes/web.php | 2 + 8 files changed, 253 insertions(+), 58 deletions(-) create mode 100644 app/Http/Controllers/SearchController.php create mode 100644 resources/views/components/mobile-page-header.blade.php create mode 100644 resources/views/partials/search-screen.blade.php create mode 100644 resources/views/search/index.blade.php diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php new file mode 100644 index 0000000..e063440 --- /dev/null +++ b/app/Http/Controllers/SearchController.php @@ -0,0 +1,51 @@ +query('q')); + $results = mb_strlen($q) >= 2 ? $this->results($q) : []; + + if ($request->expectsJson() || $request->ajax() || $request->wantsJson()) { + return response()->json(['results' => $results]); + } + + return view('search.index', [ + 'query' => $q, + 'results' => $results, + ]); + } + + /** @return list */ + private function results(string $q): array + { + $like = '%'.$q.'%'; + + return ladill_account()->qrCodes() + ->whereIn('type', QrTypeCatalog::plusTypes()) + ->where(function ($query) use ($like): void { + $query->where('label', 'like', $like) + ->orWhere('short_code', 'like', $like) + ->orWhere('destination_url', 'like', $like); + }) + ->orderByDesc('updated_at') + ->limit(15) + ->get() + ->map(fn (QrCode $code): array => [ + 'type' => 'qr', + 'title' => $code->label, + 'subtitle' => $code->typeLabel().' · '.$code->publicUrl(), + 'url' => route('user.qr-codes.show', $code), + ]) + ->all(); + } +} diff --git a/resources/js/app.js b/resources/js/app.js index 3b5c12a..61cc8be 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -140,14 +140,20 @@ Alpine.data('afia', (config = {}) => ({ })); Alpine.data('topbarSearch', (config = {}) => ({ - query: '', - results: [], - open: false, + query: config.initialQuery || '', + results: Array.isArray(config.initialResults) ? config.initialResults : [], + open: !!config.openOnInit, loading: false, active: 0, _abort: null, searchUrl: config.searchUrl || '/search', + init() { + if (config.autoFocus) { + this.$nextTick(() => this.$refs.input?.focus()); + } + }, + onFocus() { if (this.results.length > 0 || this.query.trim().length >= 2) this.open = true; }, diff --git a/resources/views/components/mobile-page-header.blade.php b/resources/views/components/mobile-page-header.blade.php new file mode 100644 index 0000000..acd3927 --- /dev/null +++ b/resources/views/components/mobile-page-header.blade.php @@ -0,0 +1,28 @@ +@props([ + 'title', + 'subtitle' => null, + 'backUrl' => null, + 'badge' => null, +]) + +
+
+ @if ($backUrl) + + + + @endif +
+ @if ($subtitle) +

{{ $subtitle }}

+ @endif +

{{ $title }}

+
+ @if ($badge) + {{ $badge }} + @endif + {{ $actions ?? '' }} +
+ {{ $slot }} +
diff --git a/resources/views/layouts/user.blade.php b/resources/views/layouts/user.blade.php index 2b12cd1..d9925d4 100644 --- a/resources/views/layouts/user.blade.php +++ b/resources/views/layouts/user.blade.php @@ -1,7 +1,8 @@ @php $mobileFullScreenPage = request()->routeIs('account.settings') || request()->routeIs('user.qr-codes.create') - || request()->routeIs('user.qr-codes.show'); + || request()->routeIs('user.qr-codes.show') + || request()->routeIs('qr.search'); $qrMobilePage = request()->routeIs('user.qr-codes.create') || request()->routeIs('user.qr-codes.show'); @endphp @@ -65,8 +66,8 @@ @include('partials.mobile-bottom-nav', [ 'homeUrl' => route('qr.dashboard'), 'homeActive' => request()->routeIs('qr.dashboard'), - 'searchUrl' => route('user.qr-codes.index'), - 'searchActive' => request()->routeIs('user.qr-codes.*'), + 'searchUrl' => route('qr.search'), + 'searchActive' => request()->routeIs('qr.search'), 'notificationsUrl' => route('notifications.index'), 'notificationsActive' => request()->routeIs('notifications.*'), 'unreadUrl' => route('notifications.unread'), @@ -90,58 +91,6 @@