import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { ChangeEventHandler, FormEventHandler, PropsWithChildren, useEffect, useState } from 'react' import { ApiError } from '../../api/ApiError' import getRecipe from '../../api/getRecipe' import UpdateRecipeSpec, { fromFullRecipeView } from '../../api/types/UpdateRecipeSpec' import updateRecipe from '../../api/updateRecipe' import { useAuth } from '../../AuthProvider' import { useRefresh } from '../../RefreshProvider' 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 { accessToken } = useAuth() const refresh = useRefresh() const queryClient = useQueryClient() const [spec, setSpec] = useState({ title: '', preparationTime: null, cookingTime: null, totalTime: null, rawText: '', mainImage: null, isPublic: false }) const recipeQuery = useQuery( { queryKey: ['recipes', username, slug], queryFn: ({ signal }) => getRecipe({ accessToken, includeRawText: true, refresh, slug, signal, username }) }, queryClient ) useEffect(() => { if (recipeQuery.isSuccess) { setSpec(fromFullRecipeView(recipeQuery.data.recipe)) } }, [recipeQuery.isSuccess, recipeQuery.data]) const mutation = useMutation( { mutationFn: () => { if (accessToken !== null) { return updateRecipe({ spec, accessToken, refresh, username, slug }) } else { return Promise.reject('Must be logged in.') } }, onSuccess: data => { console.log(data) setSpec(fromFullRecipeView(data.recipe)) queryClient.setQueryData(['recipes', username, slug], data) } }, queryClient ) const onSubmit: FormEventHandler = e => { e.preventDefault() mutation.mutate() return false } const getSetSpecText = (prop: keyof UpdateRecipeSpec) => (value: string): void => { const next = { ...spec } ;(next as any)[prop] = value setSpec(next) } const getSetTimeSpec = (prop: keyof UpdateRecipeSpec) => (value: number | null): void => { const next = { ...spec } ;(next as any)[prop] = value setSpec(next) } const getSetSpecTextAsHandler = (prop: keyof UpdateRecipeSpec): ChangeEventHandler => (event): void => { const next = { ...spec } ;(next as any)[prop] = event.target.value setSpec(next) } if (recipeQuery.isPending) { console.log('we are pending') return 'Loading...' } else if (recipeQuery.isError) { console.log('we had an error') 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 (recipeQuery.isSuccess && !recipeQuery.data.isOwner) { console.log('we are not the owner') return 'You do not have permission to edit this recipe.' } else { console.log('doing whole page') return (

Edit Recipe