From fa7d104bd290ef60f1ee2c9964132f04142569b4 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Wed, 31 Jul 2024 11:15:02 -0500 Subject: [PATCH] Basic FullRecipeView and Recipe page. --- src/RouterContext.ts | 2 + src/api/getRecipe.ts | 53 +++++++++++++++++++++++++ src/api/getRecipeInfos.ts | 15 +------ src/api/types/FullRecipeView.ts | 31 +++++++++++++++ src/api/types/ImageView.ts | 24 ++++++++++- src/main.tsx | 9 +++-- src/pages/recipe/Recipe.tsx | 16 ++++---- src/routes/recipes_/$username.$slug.tsx | 16 +++++++- 8 files changed, 139 insertions(+), 27 deletions(-) create mode 100644 src/api/getRecipe.ts create mode 100644 src/api/types/FullRecipeView.ts diff --git a/src/RouterContext.ts b/src/RouterContext.ts index 7d71095..c2d31b6 100644 --- a/src/RouterContext.ts +++ b/src/RouterContext.ts @@ -1,5 +1,7 @@ +import { QueryClient } from '@tanstack/react-query' import { AuthContextType } from './auth' export default interface RouterContext { auth: AuthContextType + queryClient: QueryClient } diff --git a/src/api/getRecipe.ts b/src/api/getRecipe.ts new file mode 100644 index 0000000..8e0b8e1 --- /dev/null +++ b/src/api/getRecipe.ts @@ -0,0 +1,53 @@ +import { ApiError } from './ApiError' +import FullRecipeView, { RawFullRecipeView } from './types/FullRecipeView' +import { toImageView } from './types/ImageView' + +const getRecipe = async ( + token: string | null, + ownerUsername: string, + slug: string +): 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/${ownerUsername}/${slug}`, + { + headers, + mode: 'cors' + } + ) + if (response.ok) { + const { + id, + created: rawCreated, + modified: rawModified, + slug, + title, + text, + ownerId, + ownerUsername, + starCount, + viewerCount, + mainImage: rawMainImage + } = (await response.json()) as RawFullRecipeView + return { + id, + created: new Date(rawCreated), + modified: rawModified ? new Date(rawModified) : null, + slug, + title, + text, + ownerId, + ownerUsername, + starCount, + viewerCount, + mainImage: toImageView(rawMainImage) + } + } else { + throw new ApiError(response.status, response.statusText) + } +} + +export default getRecipe diff --git a/src/api/getRecipeInfos.ts b/src/api/getRecipeInfos.ts index 9d3b2df..99dd013 100644 --- a/src/api/getRecipeInfos.ts +++ b/src/api/getRecipeInfos.ts @@ -1,4 +1,5 @@ import { ApiError } from './ApiError' +import { toImageView } from './types/ImageView' import RecipeInfosView, { RawRecipeInfosView } from './types/RecipeInfosView' const getRecipeInfos = async ( @@ -43,19 +44,7 @@ const getRecipeInfos = async ( ownerUsername, isPublic, starCount, - mainImage: { - url: rawMainImage.url, - created: new Date(rawMainImage.created), - modified: rawMainImage.modified - ? new Date(rawMainImage.modified) - : null, - filename: rawMainImage.fileName, - mimeType: rawMainImage.mimeType, - alt: rawMainImage.alt, - caption: rawMainImage.caption, - owner: rawMainImage.owner, - isPublic: rawMainImage.isPublic - }, + mainImage: toImageView(rawMainImage), slug }) ) diff --git a/src/api/types/FullRecipeView.ts b/src/api/types/FullRecipeView.ts new file mode 100644 index 0000000..1a62977 --- /dev/null +++ b/src/api/types/FullRecipeView.ts @@ -0,0 +1,31 @@ +import ImageView, { RawImageView } from './ImageView' + +export interface RawFullRecipeView { + id: number + created: string + modified: string | null + slug: string + title: string + text: string + ownerId: number + ownerUsername: string + starCount: number + viewerCount: number + mainImage: RawImageView +} + +interface FullRecipeView { + id: number + created: Date + modified: Date | null + slug: string + title: string + text: string + ownerId: number + ownerUsername: string + starCount: number + viewerCount: number + mainImage: ImageView +} + +export default FullRecipeView diff --git a/src/api/types/ImageView.ts b/src/api/types/ImageView.ts index 38b6d97..d3ef46f 100644 --- a/src/api/types/ImageView.ts +++ b/src/api/types/ImageView.ts @@ -4,7 +4,7 @@ export interface RawImageView { url: string created: string modified: string | null - fileName: string + filename: string mimeType: string alt: string | null caption: string | null @@ -24,4 +24,26 @@ interface ImageView { isPublic: boolean } +export const toImageView = ({ + url, + created: rawCreated, + modified: rawModified, + filename, + mimeType, + alt, + caption, + owner, + isPublic +}: RawImageView): ImageView => ({ + url, + created: new Date(rawCreated), + modified: rawModified ? new Date(rawModified) : null, + filename, + mimeType, + alt, + caption, + owner, + isPublic +}) + export default ImageView diff --git a/src/main.tsx b/src/main.tsx index 70bcbe2..7d6e8e8 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -10,17 +10,18 @@ import { fas } from '@fortawesome/free-solid-svg-icons' // Font-Awesome: load icons library.add(fas) +// Create queryClient +const queryClient = new QueryClient() + // Create router const router = createRouter({ context: { - auth: undefined! + auth: undefined!, + queryClient }, routeTree }) -// Create queryClient -const queryClient = new QueryClient() - // Register the router instance for type safety declare module '@tanstack/react-router' { interface Register { diff --git a/src/pages/recipe/Recipe.tsx b/src/pages/recipe/Recipe.tsx index e0e2103..8b982f5 100644 --- a/src/pages/recipe/Recipe.tsx +++ b/src/pages/recipe/Recipe.tsx @@ -1,13 +1,15 @@ -import { Route } from '../../routes/recipes_/$username.$slug' +import FullRecipeView from '../../api/types/FullRecipeView' -export interface RecipeProps {} +export interface RecipeProps { + recipe: FullRecipeView +} -const Recipe = ({}: RecipeProps) => { - const { username, slug } = Route.useParams() +const Recipe = ({ recipe }: RecipeProps) => { return ( - <> - Hello, {username}/{slug} - +
+

{recipe.title}

+
+
) } diff --git a/src/routes/recipes_/$username.$slug.tsx b/src/routes/recipes_/$username.$slug.tsx index 74d6f77..929c590 100644 --- a/src/routes/recipes_/$username.$slug.tsx +++ b/src/routes/recipes_/$username.$slug.tsx @@ -1,6 +1,18 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, useLoaderData } from '@tanstack/react-router' +import getRecipe from '../../api/getRecipe' import Recipe from '../../pages/recipe/Recipe' export const Route = createFileRoute('/recipes/$username/$slug')({ - component: Recipe + loader: ({ context, params }) => + context.queryClient.ensureQueryData({ + queryKey: ['recipe', params.username, params.slug], + queryFn: () => + getRecipe(context.auth.token, params.username, params.slug) + }), + component: () => { + const recipe = useLoaderData({ + from: '/recipes/$username/$slug' + }) + return + } })