From 501069e3afb3de33e7a9fa90a35acc058ca58b25 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Thu, 18 Jul 2024 18:25:48 -0500 Subject: [PATCH] Fetching/query of RecipeInfo working at a basic level. --- src/api/ApiError.ts | 9 ++++ src/api/getRecipeInfos.ts | 43 ++++++++++++++++++ src/components/recipe-card/RecipeCard.tsx | 22 ++++++++-- src/pages/recipes/Recipes.tsx | 44 +++++++++++++++++++ src/routeTree.gen.ts | 20 ++++++++- src/routes/recipes.tsx | 6 +++ src/stories/recipe-card/RecipeCard.stories.ts | 3 +- 7 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/api/ApiError.ts create mode 100644 src/api/getRecipeInfos.ts create mode 100644 src/pages/recipes/Recipes.tsx create mode 100644 src/routes/recipes.tsx diff --git a/src/api/ApiError.ts b/src/api/ApiError.ts new file mode 100644 index 0000000..dad75ca --- /dev/null +++ b/src/api/ApiError.ts @@ -0,0 +1,9 @@ +export class ApiError extends Error { + status: number + + constructor(status: number, message?: string) { + super(message) + this.status = status + Object.setPrototypeOf(this, ApiError.prototype) + } +} diff --git a/src/api/getRecipeInfos.ts b/src/api/getRecipeInfos.ts new file mode 100644 index 0000000..3a49fc2 --- /dev/null +++ b/src/api/getRecipeInfos.ts @@ -0,0 +1,43 @@ +import { ApiError } from './ApiError' + +export interface RecipeInfoView { + id: number + updated: Date + title: string + ownerId: number + ownerUsername: string + isPublic: boolean + starCount: number +} + +export interface RecipeInfosView { + pageNumber: number + pageSize: number + content: RecipeInfoView[] +} + +const getRecipeInfos = async ( + token: string | null, + pageNumber: number, + pageSize: number +): Promise => { + const headers = new Headers() + if (token !== null) { + headers.set('Authorization', `Bearer ${token}`) + } + const response = await fetch( + import.meta.env.VITE_MME_API_URL + + `/recipes?page=${pageNumber}&size=${pageSize}`, + { + headers, + mode: 'cors' + } + ) + if (response.ok) { + return (await response.json()) as RecipeInfosView + } else { + throw new ApiError(response.status, response.statusText) + } +} + +export default getRecipeInfos diff --git a/src/components/recipe-card/RecipeCard.tsx b/src/components/recipe-card/RecipeCard.tsx index c37f342..2c934b0 100644 --- a/src/components/recipe-card/RecipeCard.tsx +++ b/src/components/recipe-card/RecipeCard.tsx @@ -2,20 +2,36 @@ export interface RecipeCardProps { title: string owner?: string imgUrl?: string - rating?: number + starCount?: number + isPublic?: boolean } -const RecipeCard = ({ title, owner, imgUrl, rating }: RecipeCardProps) => { +const RecipeCard = ({ + title, + owner, + imgUrl, + starCount, + isPublic +}: RecipeCardProps) => { return (
{imgUrl ? : null}

{title}

- {rating ? {Math.round(rating)}/5 : null} + {starCount ? {starCount} : null}
{owner ?

@{owner}

: null}
+
+ {isPublic !== undefined ? ( + isPublic ? ( +

public

+ ) : ( +

not public

+ ) + ) : null} +
) } diff --git a/src/pages/recipes/Recipes.tsx b/src/pages/recipes/Recipes.tsx new file mode 100644 index 0000000..7f2c6a0 --- /dev/null +++ b/src/pages/recipes/Recipes.tsx @@ -0,0 +1,44 @@ +import { useQuery } from '@tanstack/react-query' +import getRecipeInfos from '../../api/getRecipeInfos' +import { useState } from 'react' +import { useAuth } from '../../auth' +import { ApiError } from '../../api/ApiError' +import RecipeCard from '../../components/recipe-card/RecipeCard' + +const Recipes = () => { + const [pageNumber, setPageNumber] = useState(0) + const [pageSize, setPageSize] = useState(20) + + const { token } = useAuth() + + const { data, isPending, error } = useQuery({ + queryKey: ['recipeInfos'], + queryFn: () => getRecipeInfos(token, pageNumber, pageSize) + }) + + if (isPending) { + return

Loading...

+ } else if (error) { + if (error instanceof ApiError) { + return ( +

+ ApiError: {error.status} {error.message} +

+ ) + } else { + return

Error: {error.message}

+ } + } else { + return data.content.map(view => ( + + )) + } +} + +export default Recipes diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index a06436f..1f7c332 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -11,12 +11,18 @@ // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as RecipesImport } from './routes/recipes' import { Route as LoginImport } from './routes/login' import { Route as AuthImport } from './routes/_auth' import { Route as AuthIndexImport } from './routes/_auth/index' // Create/Update Routes +const RecipesRoute = RecipesImport.update({ + path: '/recipes', + getParentRoute: () => rootRoute, +} as any) + const LoginRoute = LoginImport.update({ path: '/login', getParentRoute: () => rootRoute, @@ -50,6 +56,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginImport parentRoute: typeof rootRoute } + '/recipes': { + id: '/recipes' + path: '/recipes' + fullPath: '/recipes' + preLoaderRoute: typeof RecipesImport + parentRoute: typeof rootRoute + } '/_auth/': { id: '/_auth/' path: '/' @@ -65,6 +78,7 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren({ AuthRoute: AuthRoute.addChildren({ AuthIndexRoute }), LoginRoute, + RecipesRoute, }) /* prettier-ignore-end */ @@ -76,7 +90,8 @@ export const routeTree = rootRoute.addChildren({ "filePath": "__root.tsx", "children": [ "/_auth", - "/login" + "/login", + "/recipes" ] }, "/_auth": { @@ -88,6 +103,9 @@ export const routeTree = rootRoute.addChildren({ "/login": { "filePath": "login.tsx" }, + "/recipes": { + "filePath": "recipes.tsx" + }, "/_auth/": { "filePath": "_auth/index.tsx", "parent": "/_auth" diff --git a/src/routes/recipes.tsx b/src/routes/recipes.tsx new file mode 100644 index 0000000..59f9c47 --- /dev/null +++ b/src/routes/recipes.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from '@tanstack/react-router' +import Recipes from '../pages/recipes/Recipes' + +export const Route = createFileRoute('/recipes')({ + component: Recipes +}) diff --git a/src/stories/recipe-card/RecipeCard.stories.ts b/src/stories/recipe-card/RecipeCard.stories.ts index e1bb6db..8d7b2b5 100644 --- a/src/stories/recipe-card/RecipeCard.stories.ts +++ b/src/stories/recipe-card/RecipeCard.stories.ts @@ -14,7 +14,8 @@ export const Primary: Story = { args: { title: 'My Recipe', owner: 'JesseBrault', - rating: 5, + starCount: 7, + isPublic: true, imgUrl: 'https://www.simplyrecipes.com/thmb/L4nBpdZCubnbGtVbOW90JQSBVWc=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/Simply-Recipes-Easy-Banana-Bread-LEAD-2-2-63dd39af009945d58f5bf4c2ae8d6070.jpg' } }