Basic FullRecipeView and Recipe page.
This commit is contained in:
parent
5815ea5879
commit
fa7d104bd2
@ -1,5 +1,7 @@
|
|||||||
|
import { QueryClient } from '@tanstack/react-query'
|
||||||
import { AuthContextType } from './auth'
|
import { AuthContextType } from './auth'
|
||||||
|
|
||||||
export default interface RouterContext {
|
export default interface RouterContext {
|
||||||
auth: AuthContextType
|
auth: AuthContextType
|
||||||
|
queryClient: QueryClient
|
||||||
}
|
}
|
||||||
|
53
src/api/getRecipe.ts
Normal file
53
src/api/getRecipe.ts
Normal file
@ -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<FullRecipeView> => {
|
||||||
|
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
|
@ -1,4 +1,5 @@
|
|||||||
import { ApiError } from './ApiError'
|
import { ApiError } from './ApiError'
|
||||||
|
import { toImageView } from './types/ImageView'
|
||||||
import RecipeInfosView, { RawRecipeInfosView } from './types/RecipeInfosView'
|
import RecipeInfosView, { RawRecipeInfosView } from './types/RecipeInfosView'
|
||||||
|
|
||||||
const getRecipeInfos = async (
|
const getRecipeInfos = async (
|
||||||
@ -43,19 +44,7 @@ const getRecipeInfos = async (
|
|||||||
ownerUsername,
|
ownerUsername,
|
||||||
isPublic,
|
isPublic,
|
||||||
starCount,
|
starCount,
|
||||||
mainImage: {
|
mainImage: toImageView(rawMainImage),
|
||||||
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
|
|
||||||
},
|
|
||||||
slug
|
slug
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
31
src/api/types/FullRecipeView.ts
Normal file
31
src/api/types/FullRecipeView.ts
Normal file
@ -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
|
@ -4,7 +4,7 @@ export interface RawImageView {
|
|||||||
url: string
|
url: string
|
||||||
created: string
|
created: string
|
||||||
modified: string | null
|
modified: string | null
|
||||||
fileName: string
|
filename: string
|
||||||
mimeType: string
|
mimeType: string
|
||||||
alt: string | null
|
alt: string | null
|
||||||
caption: string | null
|
caption: string | null
|
||||||
@ -24,4 +24,26 @@ interface ImageView {
|
|||||||
isPublic: boolean
|
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
|
export default ImageView
|
||||||
|
@ -10,17 +10,18 @@ import { fas } from '@fortawesome/free-solid-svg-icons'
|
|||||||
// Font-Awesome: load icons
|
// Font-Awesome: load icons
|
||||||
library.add(fas)
|
library.add(fas)
|
||||||
|
|
||||||
|
// Create queryClient
|
||||||
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
// Create router
|
// Create router
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
context: {
|
context: {
|
||||||
auth: undefined!
|
auth: undefined!,
|
||||||
|
queryClient
|
||||||
},
|
},
|
||||||
routeTree
|
routeTree
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create queryClient
|
|
||||||
const queryClient = new QueryClient()
|
|
||||||
|
|
||||||
// Register the router instance for type safety
|
// Register the router instance for type safety
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface Register {
|
interface Register {
|
||||||
|
@ -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 Recipe = ({ recipe }: RecipeProps) => {
|
||||||
const { username, slug } = Route.useParams()
|
|
||||||
return (
|
return (
|
||||||
<>
|
<article>
|
||||||
Hello, {username}/{slug}
|
<h1>{recipe.title}</h1>
|
||||||
</>
|
<div dangerouslySetInnerHTML={{ __html: recipe.text }} />
|
||||||
|
</article>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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'
|
import Recipe from '../../pages/recipe/Recipe'
|
||||||
|
|
||||||
export const Route = createFileRoute('/recipes/$username/$slug')({
|
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 <Recipe {...{ recipe }} />
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user