From 96fde1e7d8bea6fb2e48a4e19f184ac05b46ed4a Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Wed, 7 Aug 2024 21:40:31 -0500 Subject: [PATCH] Fetching images with accessToken working. --- src/api/getImage.ts | 33 +++++++++++++++++++ src/pages/recipe/Recipe.tsx | 5 +-- src/pages/recipes/Recipes.tsx | 44 ++++++++++++++++++++++--- src/routes/recipes_/$username.$slug.tsx | 34 ++++++++++++++++--- 4 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 src/api/getImage.ts diff --git a/src/api/getImage.ts b/src/api/getImage.ts new file mode 100644 index 0000000..56e9ccd --- /dev/null +++ b/src/api/getImage.ts @@ -0,0 +1,33 @@ +import { ApiError } from './ApiError' +import ExpiredTokenError from './ExpiredTokenError' + +export interface GetImageDeps { + accessToken: string | null + signal: AbortSignal + url: string +} + +const getImage = async ({ + accessToken, + signal, + url +}: GetImageDeps): Promise => { + const headers = new Headers() + if (accessToken !== null) { + headers.set('Authorization', `Bearer ${accessToken}`) + } + const response = await fetch(url, { + headers, + mode: 'cors', + signal + }) + if (response.ok) { + return URL.createObjectURL(await response.blob()) + } else if (response.status === 401) { + throw new ExpiredTokenError() + } else { + throw new ApiError(response.status, response.statusText) + } +} + +export default getImage diff --git a/src/pages/recipe/Recipe.tsx b/src/pages/recipe/Recipe.tsx index c778729..df0b147 100644 --- a/src/pages/recipe/Recipe.tsx +++ b/src/pages/recipe/Recipe.tsx @@ -6,13 +6,14 @@ import classes from './recipe.module.css' export interface RecipeProps { recipe: FullRecipeView + imgUrl: string } -const Recipe = ({ recipe }: RecipeProps) => { +const Recipe = ({ recipe, imgUrl }: RecipeProps) => { return (
- +

{recipe.title}

diff --git a/src/pages/recipes/Recipes.tsx b/src/pages/recipes/Recipes.tsx index 8c1b2d8..5223451 100644 --- a/src/pages/recipes/Recipes.tsx +++ b/src/pages/recipes/Recipes.tsx @@ -1,8 +1,9 @@ -import { useQuery, useQueryClient } from '@tanstack/react-query' -import getRecipeInfos from '../../api/getRecipeInfos' +import { useQueries, useQuery, useQueryClient } from '@tanstack/react-query' import { useState } from 'react' -import { useAuth } from '../../auth' import { ApiError } from '../../api/ApiError' +import getImage from '../../api/getImage' +import getRecipeInfos from '../../api/getRecipeInfos' +import { useAuth } from '../../auth' import RecipeCard from '../../components/recipe-card/RecipeCard' import classes from './recipes.module.css' @@ -27,6 +28,32 @@ const Recipes = () => { queryClient ) + const slugsAndImgUrls = useQueries({ + queries: + data !== undefined + ? data.content.map(recipeInfoView => { + return { + queryKey: [ + 'images', + recipeInfoView.mainImage.owner.username, + recipeInfoView.mainImage.filename + ], + queryFn: async ({ signal }) => { + const imgUrl = await getImage({ + accessToken: token, + signal, + url: recipeInfoView.mainImage.url + }) + return { + slug: recipeInfoView.slug, + imgUrl + } + } + } + }) + : [] + }) + if (isPending) { return

Loading...

} else if (error) { @@ -50,7 +77,16 @@ const Recipes = () => { title={view.title} ownerUsername={view.ownerUsername} slug={view.slug} - mainImageUrl={view.mainImage.url} + mainImageUrl={ + slugsAndImgUrls.find( + ({ data: slugAndImgUrl }) => { + return ( + slugAndImgUrl !== undefined && + slugAndImgUrl.slug === view.slug + ) + } + )?.data!.imgUrl ?? '' // hacky workaround. should pass a kind of child which loads its own data + } mainImageAlt={ view.mainImage.alt ? view.mainImage.alt diff --git a/src/routes/recipes_/$username.$slug.tsx b/src/routes/recipes_/$username.$slug.tsx index 104427b..0a59ef2 100644 --- a/src/routes/recipes_/$username.$slug.tsx +++ b/src/routes/recipes_/$username.$slug.tsx @@ -3,6 +3,7 @@ import { createFileRoute, useParams } from '@tanstack/react-router' import getRecipe from '../../api/getRecipe' import { useAuth } from '../../auth' import Recipe from '../../pages/recipe/Recipe' +import getImage from '../../api/getImage' export const Route = createFileRoute('/recipes/$username/$slug')({ component() { @@ -29,12 +30,37 @@ export const Route = createFileRoute('/recipes/$username/$slug')({ }, queryClient ) - if (isLoading) { + + const { + isLoading: isImageLoading, + error: imageError, + data: imgUrl + } = useQuery( + { + enabled: recipe !== undefined, + queryKey: [ + 'images', + recipe?.mainImage.owner.username, + recipe?.mainImage.filename + ], + queryFn: ({ signal }) => + getImage({ + accessToken: authContext.token, + signal, + url: recipe!.mainImage.url + }) + }, + queryClient + ) + + if (isLoading || isImageLoading) { return 'Loading...' } else if (error !== null) { - return `Error: ${error.name}${error.message}` - } else if (recipe !== undefined) { - return + return `Error: ${error.name} ${error.message}` + } else if (imageError !== null) { + return `Image loading error: ${imageError} ${imageError.message}` + } else if (recipe !== undefined && imgUrl !== undefined) { + return } else { return null }