add cookbook

This commit is contained in:
Ruidy 2025-04-13 01:08:22 +02:00
parent 35af888724
commit 3e34609fcf
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
5 changed files with 74 additions and 27 deletions

View file

@ -10,7 +10,7 @@
:autofocus="autofocus" :autofocus="autofocus"
@focus="isFocused = true" @focus="isFocused = true"
@blur="isFocused = false" @blur="isFocused = false"
> />
<kbd <kbd
class="hidden md:inline-block kbd kbd-sm" class="hidden md:inline-block kbd kbd-sm"
:class="{ 'opacity-50': !isFocused }" :class="{ 'opacity-50': !isFocused }"

View file

@ -1,7 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Recipe } from "~/types/recipe"; import type { Recipe } from "~/types/recipe";
defineProps<{ recipe: Recipe }>(); import { useLocalStorage } from "@vueuse/core";
const { recipe } = defineProps<{ recipe: Recipe }>();
const cookbook = useLocalStorage<Recipe[]>("cookbook", []);
const likedRecipes = ref(new Set<string>());
onMounted(() => {
likedRecipes.value = new Set(cookbook.value.map((recipe) => recipe.id));
console.log("cook", likedRecipes.value);
});
const toggleLike = (recipeId: string) => {
if (likedRecipes.value.has(recipeId)) {
likedRecipes.value.delete(recipeId);
cookbook.value = cookbook.value.filter((recipe) => recipe.id !== recipeId);
} else {
likedRecipes.value.add(recipeId);
const recipeToAdd = recipe;
if (recipeToAdd) {
cookbook.value = [...cookbook.value, recipeToAdd];
}
}
};
const shareRecipe = async (recipe: Recipe) => { const shareRecipe = async (recipe: Recipe) => {
const url = const url =
@ -49,10 +71,23 @@ const shareRecipe = async (recipe: Recipe) => {
<p class="prose prose-lg max-w-none w-full"> <p class="prose prose-lg max-w-none w-full">
{{ recipe.instructions }} {{ recipe.instructions }}
</p> </p>
<button class="btn btn-accent mt-4" @click="shareRecipe(recipe)"> <div class="flex gap-4 mt-4">
<icon name="uil:share-alt" class="mr-2 w-6 h-6" /> <button class="btn btn-accent" @click="shareRecipe(recipe)">
Share Recipe <icon name="uil:share-alt" class="mr-2 w-6 h-6" />
</button> Share Recipe
</button>
<button
class="btn btn-ghost"
@click="toggleLike(recipe.id)"
:class="{ 'text-red-500': likedRecipes.has(recipe.id) }"
>
<icon
:name="likedRecipes.has(recipe.id) ? 'uil:heart' : 'uil:heart-alt'"
class="w-6 h-6"
/>
Like
</button>
</div>
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Recipe } from "~/types/recipe"; import type { Recipe } from "~/types/recipe";
import { useStorage } from "@vueuse/core";
const cookbook = [] as Recipe[]; const cookbook = useStorage<Recipe[]>("cookbook", [], localStorage);
</script> </script>
<template> <template>
@ -16,7 +16,7 @@ const cookbook = [] as Recipe[];
</div> </div>
<ul> <ul>
<li v-for="recipe in cookbook" :key="recipe.id"> <li v-for="recipe in cookbook" :key="recipe.id">
<nuxt-link :to="`/recipes/${recipe.id}`"> <nuxt-link :to="`/${recipe.id}`">
<recipe-card <recipe-card
:title="recipe.title" :title="recipe.title"
:picture-url="recipe.pictureUrl" :picture-url="recipe.pictureUrl"

View file

@ -11,20 +11,24 @@ import { publicProcedure, router } from "../trpc";
const { apiUrl } = useRuntimeConfig(); const { apiUrl } = useRuntimeConfig();
export const recipeRouter = router({ export const recipeRouter = router({
recipesByCategory: publicProcedure.input(z.string()).query(async ({ input }) => { recipesByCategory: publicProcedure
const data = await $fetch<{ meals: Meal[] }>(new URL(`filter.php?c=${input}`, apiUrl).href); .input(z.string())
.query(async ({ input }) => {
const data = await $fetch<{ meals: Meal[] }>(
new URL(`filter.php?c=${input}`, apiUrl).href,
);
const result = categoryRecipesResponseSchema.safeParse(data); const result = categoryRecipesResponseSchema.safeParse(data);
if (!result.success) { if (!result.success) {
throw createError({ throw createError({
statusCode: 404, statusCode: 404,
statusMessage: "Recipes for category not found", statusMessage: "Recipes for category not found",
}); });
} }
return result.data.recipes; return result.data.recipes;
}), }),
recipeGet: publicProcedure recipeGet: publicProcedure
.input( .input(
@ -33,10 +37,12 @@ export const recipeRouter = router({
required_error: "recipe id is required", required_error: "recipe id is required",
invalid_type_error: "recipe id must be a number", invalid_type_error: "recipe id must be a number",
}) })
.positive("recipe id must be positive") .positive("recipe id must be positive"),
) )
.query(async ({ input }) => { .query(async ({ input }) => {
const data = await $fetch<{ meals: Meal[] }>(new URL(`lookup.php?i=${input}`, apiUrl).href); const data = await $fetch<{ meals: Meal[] }>(
new URL(`lookup.php?i=${input}`, apiUrl).href,
);
if (!data?.meals) { if (!data?.meals) {
throw createError({ throw createError({
statusCode: 404, statusCode: 404,
@ -49,7 +55,9 @@ export const recipeRouter = router({
}), }),
recipeRandom: publicProcedure.query(async () => { recipeRandom: publicProcedure.query(async () => {
const data = await $fetch<{ meals: Meal[] }>(new URL("random.php", apiUrl).toString()); const data = await $fetch<{ meals: Meal[] }>(
new URL("random.php", apiUrl).toString(),
);
if (!data?.meals) { if (!data?.meals) {
throw createError({ throw createError({
statusCode: 500, statusCode: 500,
@ -64,10 +72,12 @@ export const recipeRouter = router({
.input( .input(
z.string({ z.string({
required_error: "search query is required", required_error: "search query is required",
}) }),
) )
.query(async ({ input }) => { .query(async ({ input }) => {
const data = await $fetch<{ meals: Meal[] }>(new URL(`search.php?s=${input}`, apiUrl).href); const data = await $fetch<{ meals: Meal[] }>(
new URL(`search.php?s=${input}`, apiUrl).href,
);
if (!data?.meals) { if (!data?.meals) {
return []; return [];
} }
@ -76,7 +86,9 @@ export const recipeRouter = router({
}), }),
listCategories: publicProcedure.query(async () => { listCategories: publicProcedure.query(async () => {
const response = await $fetch<CategoriesResponse>(new URL("categories.php", apiUrl).href); const response = await $fetch<CategoriesResponse>(
new URL("categories.php", apiUrl).href,
);
const result = categoriesResponseSchema.safeParse(response); const result = categoriesResponseSchema.safeParse(response);

View file

@ -1,4 +1,4 @@
import { initTRPC , TRPCError } from "@trpc/server"; import { initTRPC, TRPCError } from "@trpc/server";
import type { Context } from "~/server/trpc/context"; import type { Context } from "~/server/trpc/context";
// import { authMiddleware } from "~/server/trpc/middleware"; // import { authMiddleware } from "~/server/trpc/middleware";