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>
|
<li><a>Categories</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end gap-2">
|
||||||
|
<recipe-search />
|
||||||
<button class="btn btn-primary" @click="handleRandomClick">Random</button>
|
<button class="btn btn-primary" @click="handleRandomClick">Random</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</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-icon",
|
||||||
"nuxt-delay-hydration",
|
"nuxt-delay-hydration",
|
||||||
"@nuxtjs/robots",
|
"@nuxtjs/robots",
|
||||||
|
"@vueuse/nuxt",
|
||||||
],
|
],
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
|
|
@ -59,5 +60,4 @@ export default defineNuxtConfig({
|
||||||
|
|
||||||
ssr: true,
|
ssr: true,
|
||||||
compatibilityDate: "2024-12-13",
|
compatibilityDate: "2024-12-13",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"@nuxtjs/robots": "5.0.1",
|
"@nuxtjs/robots": "5.0.1",
|
||||||
"@trpc/client": "^10.45.2",
|
"@trpc/client": "^10.45.2",
|
||||||
"@trpc/server": "^10.45.2",
|
"@trpc/server": "^10.45.2",
|
||||||
|
"@vueuse/nuxt": "12.0.0",
|
||||||
"nuxt": "^3.14.1592",
|
"nuxt": "^3.14.1592",
|
||||||
"nuxt-icon": "^0.6.10",
|
"nuxt-icon": "^0.6.10",
|
||||||
"trpc-nuxt": "^0.10.22",
|
"trpc-nuxt": "^0.10.22",
|
||||||
|
|
|
||||||
|
|
@ -42,4 +42,23 @@ export const recipeRouter = router({
|
||||||
const recipes = parseRecipeData(data);
|
const recipes = parseRecipeData(data);
|
||||||
return recipes[0];
|
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