mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-12 13:26:45 +00:00
add error handling
This commit is contained in:
parent
7c3bfb0ea8
commit
f19874e4c7
8 changed files with 147 additions and 42 deletions
76
README.md
76
README.md
|
|
@ -29,45 +29,45 @@ Free meal planner for cooks short on ideas! (like me …)
|
||||||
- Search by name: you look for a recipe? Ours are easy to make and Yummy! ✓
|
- Search by name: you look for a recipe? Ours are easy to make and Yummy! ✓
|
||||||
- What's in the fridge ? Choose your main ingredient and get a meal suggestion
|
- What's in the fridge ? Choose your main ingredient and get a meal suggestion
|
||||||
- Choose by a category: ✓
|
- Choose by a category: ✓
|
||||||
- Beef
|
- Beef
|
||||||
- Breakfast
|
- Breakfast
|
||||||
- Chicken
|
- Chicken
|
||||||
- Dessert
|
- Dessert
|
||||||
- Goat
|
- Goat
|
||||||
- Lamb
|
- Lamb
|
||||||
- Miscellaneous
|
- Miscellaneous
|
||||||
- Pasta
|
- Pasta
|
||||||
- Pork
|
- Pork
|
||||||
- Seafood
|
- Seafood
|
||||||
- Side
|
- Side
|
||||||
- Starter
|
- Starter
|
||||||
- Vegan
|
- Vegan
|
||||||
- Vegetarian
|
- Vegetarian
|
||||||
- Choose by area:
|
- Choose by area:
|
||||||
- American
|
- American
|
||||||
- British
|
- British
|
||||||
- Canadian
|
- Canadian
|
||||||
- Chinese
|
- Chinese
|
||||||
- Dutch
|
- Dutch
|
||||||
- Egyptian
|
- Egyptian
|
||||||
- French
|
- French
|
||||||
- Greek
|
- Greek
|
||||||
- Indian
|
- Indian
|
||||||
- Irish
|
- Irish
|
||||||
- Italian
|
- Italian
|
||||||
- Jamaican
|
- Jamaican
|
||||||
- Japanese
|
- Japanese
|
||||||
- Kenyan
|
- Kenyan
|
||||||
- Malaysian
|
- Malaysian
|
||||||
- Mexican
|
- Mexican
|
||||||
- Moroccan
|
- Moroccan
|
||||||
- Russian
|
- Russian
|
||||||
- Spanish
|
- Spanish
|
||||||
- Thai
|
- Thai
|
||||||
- Tunisian
|
- Tunisian
|
||||||
- Turkish
|
- Turkish
|
||||||
- Unknown
|
- Unknown
|
||||||
- Vietnamese
|
- Vietnamese
|
||||||
- Cocktail selection
|
- Cocktail selection
|
||||||
- Create a profile and save your favourite meals ✓
|
- Create a profile and save your favourite meals ✓
|
||||||
- Notation system: know what are the most loved meals
|
- Notation system: know what are the most loved meals
|
||||||
|
|
|
||||||
7
TODO.md
7
TODO.md
|
|
@ -7,7 +7,12 @@
|
||||||
- [x] deploy
|
- [x] deploy
|
||||||
- [x] nuxt image
|
- [x] nuxt image
|
||||||
- [x] prettier and eslint
|
- [x] prettier and eslint
|
||||||
- [ ] transition
|
- [ ] transition and loading times
|
||||||
- [ ] pwa
|
- [ ] pwa
|
||||||
- [ ] seo, robots.txt
|
- [ ] seo, robots.txt
|
||||||
- [x] update the README
|
- [x] update the README
|
||||||
|
- [ ] create image provider
|
||||||
|
- [ ] fetch recipe per id
|
||||||
|
- [ ] add mood section
|
||||||
|
- [ ] store recipes into my db (SQLite)
|
||||||
|
- [ ] process them using AI
|
||||||
|
|
|
||||||
13
app.vue
13
app.vue
|
|
@ -7,3 +7,16 @@
|
||||||
<AppFooter />
|
<AppFooter />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page-enter-active,
|
||||||
|
.page-leave-active {
|
||||||
|
transition: all 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-enter-from,
|
||||||
|
.page-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
filter: blur(1rem);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
58
error.vue
Normal file
58
error.vue
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-base-200">
|
||||||
|
<div class="text-center max-w-md p-8">
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Error Icon -->
|
||||||
|
<div class="text-error text-6xl mb-4">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mx-auto h-24 w-24"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Details -->
|
||||||
|
<h1 class="text-4xl font-bold mb-4">
|
||||||
|
{{ error?.statusCode || "Error" }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl mb-6">
|
||||||
|
{{ error?.message || "Something went wrong" }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="flex justify-center gap-4">
|
||||||
|
<button class="btn btn-primary" @click="handleError">
|
||||||
|
Try Again
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-ghost" @click="navigateToHome">
|
||||||
|
Go Home
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const error = useError();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const handleError = () => {
|
||||||
|
clearError({ redirect: route.redirectedFrom?.fullPath ?? "/" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigateToHome = () => {
|
||||||
|
clearError({ redirect: "/" });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -1,6 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { idSchema } from "~/types/id";
|
||||||
|
|
||||||
const { params } = useRoute();
|
const { params } = useRoute();
|
||||||
const id = params.id;
|
const routeParam = params.id;
|
||||||
|
|
||||||
|
const parsed = idSchema.safeParse(routeParam);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: "Invalid recipe id",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: recipe,
|
data: recipe,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,22 @@
|
||||||
import { parseRecipeData } from "~/utils/recipes";
|
import { parseRecipeData } from "~/utils/recipes";
|
||||||
import type { Meal } from "~/types/recipe";
|
import type { Meal } from "~/types/recipe";
|
||||||
|
import { idSchema } from "~/types/id";
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const { apiUrl } = useRuntimeConfig();
|
const { apiUrl } = useRuntimeConfig();
|
||||||
const id = getRouterParam(event, "id");
|
const routeParam = getRouterParam(event, "id");
|
||||||
|
|
||||||
|
const parsed = idSchema.safeParse(routeParam);
|
||||||
|
if (!parsed.success) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: "Invalid recipe id",
|
||||||
|
message: parsed.error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const data = await $fetch<{ meals: Meal[] }>(
|
const data = await $fetch<{ meals: Meal[] }>(
|
||||||
new URL(`lookup.php?i=${id}`, apiUrl).toString(),
|
new URL(`lookup.php?i=${parsed.data}`, apiUrl).toString(),
|
||||||
);
|
);
|
||||||
if (!data?.meals) {
|
if (!data?.meals) {
|
||||||
throw createError({
|
throw createError({
|
||||||
|
|
|
||||||
8
types/id.ts
Normal file
8
types/id.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const idSchema = z
|
||||||
|
.number({
|
||||||
|
required_error: "recipe id is required",
|
||||||
|
invalid_type_error: "recipe id must be a number",
|
||||||
|
})
|
||||||
|
.positive("recipe id must be positive");
|
||||||
Loading…
Reference in a new issue