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"
@focus="isFocused = true"
@blur="isFocused = false"
>
/>
<kbd
class="hidden md:inline-block kbd kbd-sm"
:class="{ 'opacity-50': !isFocused }"

View file

@ -1,7 +1,29 @@
<script setup lang="ts">
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 url =
@ -49,10 +71,23 @@ const shareRecipe = async (recipe: Recipe) => {
<p class="prose prose-lg max-w-none w-full">
{{ recipe.instructions }}
</p>
<button class="btn btn-accent mt-4" @click="shareRecipe(recipe)">
<icon name="uil:share-alt" class="mr-2 w-6 h-6" />
Share Recipe
</button>
<div class="flex gap-4 mt-4">
<button class="btn btn-accent" @click="shareRecipe(recipe)">
<icon name="uil:share-alt" class="mr-2 w-6 h-6" />
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>
</template>

View file

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

View file

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

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 { authMiddleware } from "~/server/trpc/middleware";