mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-06 02:26:49 +00:00
feat: Add category details page with image header, description, and related recipes
This commit is contained in:
parent
2010270bcf
commit
fb84b4bfc6
3 changed files with 87 additions and 0 deletions
4
composables/useCategoryRecipes.ts
Normal file
4
composables/useCategoryRecipes.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export function useCategoryRecipes(category: string) {
|
||||||
|
const { $client } = useNuxtApp();
|
||||||
|
return $client.recipesByCategory.useQuery(category);
|
||||||
|
}
|
||||||
69
pages/category/[name].vue
Normal file
69
pages/category/[name].vue
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const route = useRoute();
|
||||||
|
const categoryName = route.params.name as string;
|
||||||
|
|
||||||
|
const { data: categories } = useCategories();
|
||||||
|
const { data: recipes, status } = useCategoryRecipes(categoryName);
|
||||||
|
|
||||||
|
const category = computed(() =>
|
||||||
|
categories.value?.find((c) => c.name === categoryName)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!category.value) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 404,
|
||||||
|
message: 'Category not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="hero h-[40vh] bg-cover bg-center relative"
|
||||||
|
:style="`background-image: url(${category.value.picture})`"
|
||||||
|
>
|
||||||
|
<div class="hero-overlay bg-opacity-60"></div>
|
||||||
|
<div class="hero-content text-center text-neutral-content">
|
||||||
|
<h1 class="text-5xl font-bold">{{ category.value.name }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="prose max-w-none mb-12">
|
||||||
|
<p>{{ category.value.description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="status === 'pending'" class="flex justify-center my-8">
|
||||||
|
<span class="loading loading-spinner loading-lg text-primary" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="recipes?.length"
|
||||||
|
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="recipe in recipes"
|
||||||
|
:key="recipe.id"
|
||||||
|
class="card bg-base-100 shadow-xl"
|
||||||
|
>
|
||||||
|
<figure>
|
||||||
|
<img :src="recipe.pictureUrl" :alt="recipe.title" />
|
||||||
|
</figure>
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">{{ recipe.title }}</h2>
|
||||||
|
<div class="card-actions justify-end">
|
||||||
|
<nuxt-link :to="`/recipe/${recipe.id}`" class="btn btn-primary">
|
||||||
|
View Recipe
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="alert alert-info">
|
||||||
|
<span>No recipes found in this category.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -13,6 +13,20 @@ type Category = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const recipeRouter = router({
|
export const recipeRouter = router({
|
||||||
|
recipesByCategory: publicProcedure
|
||||||
|
.input(z.string())
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const data = await $fetch<{ meals: Meal[] }>(
|
||||||
|
new URL(`filter.php?c=${input}`, apiUrl).href,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!data?.meals) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipes = parseRecipeData(data);
|
||||||
|
return recipes;
|
||||||
|
}),
|
||||||
recipeGet: publicProcedure
|
recipeGet: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.coerce
|
z.coerce
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue