From 8ce916731f3efbc47f015fd8c202131f4dda55a7 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Thu, 15 Aug 2024 12:14:43 -0500 Subject: [PATCH] Basic recipe editing page. --- src/api/types/FullRecipeView.ts | 12 +- src/pages/edit-recipe/EditRecipe.tsx | 186 ++++++++++++++++++ src/pages/edit-recipe/edit-recipe.module.css | 14 ++ src/routes/login.tsx | 4 +- src/routes/recipes_/$username.$slug_/edit.tsx | 8 +- 5 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 src/pages/edit-recipe/EditRecipe.tsx create mode 100644 src/pages/edit-recipe/edit-recipe.module.css diff --git a/src/api/types/FullRecipeView.ts b/src/api/types/FullRecipeView.ts index 0e3388a..f4a519f 100644 --- a/src/api/types/FullRecipeView.ts +++ b/src/api/types/FullRecipeView.ts @@ -7,9 +7,9 @@ export interface RawFullRecipeView { modified: string | null slug: string title: string - preparationTime: number - cookingTime: number - totalTime: number + preparationTime: number | null + cookingTime: number | null + totalTime: number | null text: string owner: UserInfoView starCount: number @@ -24,9 +24,9 @@ interface FullRecipeView { modified: Date | null slug: string title: string - preparationTime: number - cookingTime: number - totalTime: number + preparationTime: number | null + cookingTime: number | null + totalTime: number | null text: string owner: UserInfoView starCount: number diff --git a/src/pages/edit-recipe/EditRecipe.tsx b/src/pages/edit-recipe/EditRecipe.tsx new file mode 100644 index 0000000..c42aeac --- /dev/null +++ b/src/pages/edit-recipe/EditRecipe.tsx @@ -0,0 +1,186 @@ +import { useQuery, useQueryClient } from '@tanstack/react-query' +import { useNavigate } from '@tanstack/react-router' +import { PropsWithChildren, useEffect, useState } from 'react' +import { ApiError } from '../../api/ApiError' +import getRecipe from '../../api/getRecipe' +import { useAuth } from '../../auth' +import classes from './edit-recipe.module.css' + +interface ControlProps { + id: string + displayName: string +} + +const Control = ({ id, displayName, children }: PropsWithChildren) => { + return ( +
+ + {children} +
+ ) +} + +type TextInputProps = { + id: string +} & (NullableTextInputProps | NonNullableTextInputProps) + +interface NullableTextInputProps { + nullable: true + value: string | null + setValue(newValue: string | null): void +} + +interface NonNullableTextInputProps { + nullable?: false + value: string + setValue(newValue: string): void +} + +const TextInput = ({ nullable, id, value, setValue }: TextInputProps) => { + return ( + { + if (nullable) { + setValue(e.target.value === '' ? null : e.target.value) + } else { + setValue(e.target.value) + } + }} + /> + ) +} + +interface TimeInputProps { + id: string + value: number | null + setValue(newValue: number | null): void +} + +const TimeInput = ({ id, value, setValue }: TimeInputProps) => { + return ( + { + if (e.target.value === '') { + setValue(null) + } else { + const parsed = parseInt(e.target.value) + if (!Number.isNaN(parsed)) { + setValue(parsed) + } + } + }} + /> + ) +} + +export interface EditRecipeProps { + username: string + slug: string +} + +const EditRecipe = ({ username, slug }: EditRecipeProps) => { + const auth = useAuth() + const navigate = useNavigate() + + if (auth.token === null) { + navigate({ to: '/login', search: { reason: 'NOT_LOGGED_IN', redirect: `/recipes/${username}/${slug}/edit` } }) + } + + const queryClient = useQueryClient() + + const recipeQuery = useQuery( + { + queryKey: ['recipes', username, slug], + queryFn: ({ signal }) => + getRecipe({ + authContext: auth, + username, + slug, + abortSignal: signal + }) + }, + queryClient + ) + + const [isOwner, setIsOwner] = useState(false) + const [title, setTitle] = useState('') + const [mSlug, setMSlug] = useState('') + const [preparationTime, setPreparationTime] = useState(null) + const [cookingTime, setCookingTime] = useState(null) + const [totalTime, setTotalTime] = useState(null) + const [recipeText, setRecipeText] = useState('') + + useEffect(() => { + if (recipeQuery.isSuccess) { + const { isOwner, recipe } = recipeQuery.data + if (!isOwner) { + setIsOwner(false) + } else { + setIsOwner(true) + setTitle(recipe.title) + setMSlug(recipe.slug) + setPreparationTime(recipe.preparationTime) + setCookingTime(recipe.cookingTime) + setTotalTime(recipe.totalTime) + setRecipeText(recipe.text) + } + } + }, [recipeQuery.isSuccess, recipeQuery.data]) + + if (recipeQuery.isLoading) { + return 'Loading...' + } else if (recipeQuery.isError) { + const { error } = recipeQuery + if (error instanceof ApiError) { + if (error.status === 404) { + return 'No such recipe.' + } else { + return `ApiError: ${error.status} ${error.message}` + } + } else { + return `Error: ${error.name} ${error.message}` + } + } else if (!isOwner) { + return 'You do not have permission to edit this recipe.' + } else { + return ( +
+
+

Edit Recipe

+
+ + + + + + + + + + + + + + + + + + + + + +