mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-06 02:26:49 +00:00
feat: search feature with debounce
This commit is contained in:
parent
a5d328a133
commit
2d9fdd07c2
7 changed files with 76 additions and 3 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -48,7 +48,8 @@ const handleRandomClick = async () => {
|
|||
<li><a>Categories</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-end gap-2">
|
||||
<recipe-search />
|
||||
<button class="btn btn-primary" @click="handleRandomClick">Random</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
|||
48
components/recipe/search.vue
Normal file
48
components/recipe/search.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<label
|
||||
class="input input-bordered input-primary flex items-center gap-2 container mx-auto px-4 lg:px-8 my-4"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="grow"
|
||||
placeholder="Search"
|
||||
v-model="searchQuery"
|
||||
@focus="isFocused = true"
|
||||
@blur="isFocused = false"
|
||||
/>
|
||||
<kbd class="kbd kbd-sm" :class="{ 'opacity-50': !isFocused }">⌘</kbd>
|
||||
<kbd class="kbd kbd-sm" :class="{ 'opacity-50': !isFocused }">K</kbd>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const searchQuery = ref("");
|
||||
const isFocused = ref(false);
|
||||
|
||||
// Debounced search function
|
||||
const debouncedSearch = useDebounceFn(async (query: string) => {
|
||||
const { data, status, error } = await useRecipeSearch(query);
|
||||
console.log("result", data.value, status.value, error.value);
|
||||
}, 500);
|
||||
|
||||
// Watch for changes in searchQuery
|
||||
watch(searchQuery, (newQuery) => {
|
||||
debouncedSearch(newQuery);
|
||||
});
|
||||
|
||||
// Optional: Handle keyboard shortcut
|
||||
onMounted(() => {
|
||||
const handleKeydown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
||||
e.preventDefault();
|
||||
const inputEl = document.querySelector("input");
|
||||
inputEl?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeydown);
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("keydown", handleKeydown);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
4
composables/useRecipeSearch.ts
Normal file
4
composables/useRecipeSearch.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export default function useRecipeSearch(query: string) {
|
||||
const { $client } = useNuxtApp();
|
||||
return $client.recipeSearch.useQuery(query);
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ export default defineNuxtConfig({
|
|||
"nuxt-icon",
|
||||
"nuxt-delay-hydration",
|
||||
"@nuxtjs/robots",
|
||||
"@vueuse/nuxt",
|
||||
],
|
||||
|
||||
app: {
|
||||
|
|
@ -59,5 +60,4 @@ export default defineNuxtConfig({
|
|||
|
||||
ssr: true,
|
||||
compatibilityDate: "2024-12-13",
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
"@nuxtjs/robots": "5.0.1",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"@vueuse/nuxt": "12.0.0",
|
||||
"nuxt": "^3.14.1592",
|
||||
"nuxt-icon": "^0.6.10",
|
||||
"trpc-nuxt": "^0.10.22",
|
||||
|
|
|
|||
|
|
@ -42,4 +42,23 @@ export const recipeRouter = router({
|
|||
const recipes = parseRecipeData(data);
|
||||
return recipes[0];
|
||||
}),
|
||||
recipeSearch: publicProcedure
|
||||
.input(
|
||||
z.string({
|
||||
required_error: "search query is required",
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const data = await $fetch<{ meals: Meal[] }>(
|
||||
new URL(`search.php?s=${input}`, apiUrl).href,
|
||||
);
|
||||
if (!data?.meals) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: "Recipe not found",
|
||||
});
|
||||
}
|
||||
const recipes = parseRecipeData(data);
|
||||
return recipes;
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue