mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-06 02:26:49 +00:00
feat: Add Zod validation for categories API response
This commit is contained in:
parent
296b2048e9
commit
2010270bcf
5 changed files with 64 additions and 39 deletions
|
|
@ -1,30 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import type { Category } from "~/types/category";
|
||||
|
||||
defineProps<{
|
||||
category: Category;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
:to="`/category/${category.strCategory}`"
|
||||
<nuxt-link
|
||||
:to="`/category/${category.name}`"
|
||||
class="block max-w-sm rounded-lg border border-gray-200 bg-white p-6 shadow hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700"
|
||||
>
|
||||
<img
|
||||
:src="category.strCategoryThumb"
|
||||
:alt="category.strCategory"
|
||||
:src="category.picture"
|
||||
:alt="category.name"
|
||||
class="mb-4 h-48 w-full object-cover rounded"
|
||||
/>
|
||||
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||
{{ category.strCategory }}
|
||||
<h5
|
||||
class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white"
|
||||
>
|
||||
{{ category.name }}
|
||||
</h5>
|
||||
<p class="font-normal text-gray-700 dark:text-gray-400">
|
||||
{{ category.strCategoryDescription }}
|
||||
{{ category.description }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Category {
|
||||
strCategory: string
|
||||
strCategoryThumb: string
|
||||
strCategoryDescription: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
category: Category
|
||||
}>()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
const { data: categories, status, error } = useCategories();
|
||||
|
||||
console.log(categories.value);
|
||||
|
||||
if (error.value) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
|
|
@ -15,25 +17,26 @@ if (error.value) {
|
|||
<span class="loading loading-spinner loading-lg text-primary" />
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
v-else-if="categories?.length > 0"
|
||||
v-else-if="categories!.length > 0"
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 my-8"
|
||||
>
|
||||
<div
|
||||
v-for="category in categories"
|
||||
:key="category.strCategory"
|
||||
:key="category.name"
|
||||
class="card bg-base-100 shadow-xl h-[28rem] sm:h-[32rem] md:h-[36rem] lg:h-[32rem]"
|
||||
>
|
||||
<figure>
|
||||
<img :src="category.strCategoryThumb" :alt="category.strCategory" />
|
||||
<img :src="category.picture" :alt="category.name" />
|
||||
</figure>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ category.strCategory }}</h2>
|
||||
<p class="line-clamp-6 text-sm">{{ category.strCategoryDescription }}</p>
|
||||
<h2 class="card-title">{{ category.name }}</h2>
|
||||
<p class="line-clamp-6 text-sm">
|
||||
{{ category.description }}
|
||||
</p>
|
||||
<div class="card-actions justify-end">
|
||||
<nuxt-link
|
||||
:to="`/category/${category.strCategory}`"
|
||||
:to="`/category/${category.name}`"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
View Recipes
|
||||
|
|
@ -1,27 +1,28 @@
|
|||
import { publicProcedure, router } from "../trpc";
|
||||
import {} from "~/types/recipe";
|
||||
import {
|
||||
categoriesResponseSchema,
|
||||
type CategoriesResponse,
|
||||
} from "~/types/category";
|
||||
|
||||
const { apiUrl } = useRuntimeConfig();
|
||||
|
||||
type Category = {
|
||||
idCategory: string;
|
||||
strCategory: string;
|
||||
strCategoryThumb: string;
|
||||
strCategoryDescription: string;
|
||||
};
|
||||
|
||||
export const categoryRouter = router({
|
||||
listCategories: publicProcedure.query(async () => {
|
||||
const data = await $fetch<{ categories: Category[] }>(
|
||||
new URL("categories.php", apiUrl).toString(),
|
||||
const response = await $fetch<CategoriesResponse>(
|
||||
new URL("categories.php", apiUrl).href,
|
||||
);
|
||||
|
||||
if (!data?.categories) {
|
||||
const result = categoriesResponseSchema.safeParse(response);
|
||||
|
||||
if (!result.success) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "Failed to fetch categories",
|
||||
statusMessage: "Invalid API response format",
|
||||
data: result.error,
|
||||
});
|
||||
}
|
||||
|
||||
return data.categories;
|
||||
return result.data.categories.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
22
types/category.ts
Normal file
22
types/category.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { z } from "zod";
|
||||
|
||||
const categorySchema = z
|
||||
.object({
|
||||
idCategory: z.string(),
|
||||
strCategory: z.string(),
|
||||
strCategoryThumb: z.string().url(),
|
||||
strCategoryDescription: z.string(),
|
||||
})
|
||||
.transform((c) => ({
|
||||
identity: c.idCategory,
|
||||
name: c.strCategory,
|
||||
picture: c.strCategoryThumb,
|
||||
description: c.strCategoryDescription,
|
||||
}));
|
||||
|
||||
export const categoriesResponseSchema = z.object({
|
||||
categories: z.array(categorySchema),
|
||||
});
|
||||
|
||||
export type Category = z.infer<typeof categorySchema>;
|
||||
export type CategoriesResponse = z.infer<typeof categoriesResponseSchema>;
|
||||
|
|
@ -72,4 +72,5 @@ export const apiResponseSchema = z.object({
|
|||
});
|
||||
|
||||
export type Meal = z.infer<typeof mealSchema>;
|
||||
|
||||
export type ApiResponse = z.infer<typeof apiResponseSchema>;
|
||||
|
|
|
|||
Loading…
Reference in a new issue