The recipes.blue monorepo recipes.blue
recipes appview atproto

fix: update new recipe form for new lexicon structure

Changed files
+44 -43
apps
api
src
xrpc
web
src
routes
_.(app)
recipes
$author
libs
lexicons
+10 -5
apps/api/src/xrpc/index.ts
··· 35 handler: simpleFetchHandler({ 36 service: 'https://public.api.bsky.app', 37 }), 38 - }) 39 40 let authorInfo: BlueRecipesFeedDefs.AuthorInfo | null = null; 41 if (did) { ··· 94 }); 95 } 96 97 - const author = await getDidDoc(recipe.authorDid); 98 99 return ctx.json({ 100 recipe: { 101 - author: { 102 - handle: author.alsoKnownAs[0]?.substring(5), 103 - }, 104 title: recipe.title, 105 description: recipe.description, 106 ingredients: recipe.ingredients, 107 steps: recipe.steps,
··· 35 handler: simpleFetchHandler({ 36 service: 'https://public.api.bsky.app', 37 }), 38 + }); 39 40 let authorInfo: BlueRecipesFeedDefs.AuthorInfo | null = null; 41 if (did) { ··· 94 }); 95 } 96 97 + const rpc = new XRPC({ 98 + handler: simpleFetchHandler({ 99 + service: 'https://public.api.bsky.app', 100 + }), 101 + }); 102 + 103 + const authorInfo = await getAuthorInfo(recipe.authorDid, rpc); 104 105 return ctx.json({ 106 recipe: { 107 + author: authorInfo, 108 title: recipe.title, 109 + time: 5, 110 description: recipe.description, 111 ingredients: recipe.ingredients, 112 steps: recipe.steps,
+4 -4
apps/web/src/routes/_.(app)/recipes/$author/$rkey/index.lazy.tsx
··· 51 <BreadcrumbItem className="hidden md:block"> 52 <BreadcrumbLink asChild> 53 <Link to="/recipes/$author" params={{ author: recipe.author.handle }}> 54 - {recipe.author.handle} 55 </Link> 56 </BreadcrumbLink> 57 </BreadcrumbItem> ··· 65 </header> 66 <div className="flex flex-col gap-4 px-4 py-8 items-center max-w-2xl w-full mx-auto"> 67 <p className="text-muted-foreground"> 68 - By @{recipe.author.handle} 69 </p> 70 <h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl"> 71 {recipe.title} ··· 81 <CardTitle>Ingredients</CardTitle> 82 </CardHeader> 83 <CardContent> 84 - <ul className="list-disc flex flex-col ml-4"> 85 {recipe.ingredients.map((ing, idx) => ( 86 <li key={idx}> 87 - {ing.name} ({ing.amount} {ing.unit}) 88 </li> 89 ))} 90 </ul>
··· 51 <BreadcrumbItem className="hidden md:block"> 52 <BreadcrumbLink asChild> 53 <Link to="/recipes/$author" params={{ author: recipe.author.handle }}> 54 + {recipe.author.displayName} 55 </Link> 56 </BreadcrumbLink> 57 </BreadcrumbItem> ··· 65 </header> 66 <div className="flex flex-col gap-4 px-4 py-8 items-center max-w-2xl w-full mx-auto"> 67 <p className="text-muted-foreground"> 68 + By {recipe.author.displayName} 69 </p> 70 <h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl"> 71 {recipe.title} ··· 81 <CardTitle>Ingredients</CardTitle> 82 </CardHeader> 83 <CardContent> 84 + <ul className="flex flex-col"> 85 {recipe.ingredients.map((ing, idx) => ( 86 <li key={idx}> 87 + <b>{ing.amount}</b> {ing.name} 88 </li> 89 ))} 90 </ul>
+28 -32
apps/web/src/routes/_.(app)/recipes/new.tsx
··· 56 component: RouteComponent, 57 }); 58 59 - const schema = RecipeRecord.extend({ 60 - ingredients: z.array( 61 - IngredientObject.extend({ 62 - amount: z.coerce.number().nullable(), 63 - }), 64 - ), 65 - }); 66 67 function RouteComponent() { 68 const form = useForm<z.infer<typeof schema>>({ ··· 136 {...field} 137 /> 138 </FormControl> 139 <FormMessage /> 140 </FormItem> 141 )} ··· 151 <div className="flex w-full flex-col gap-2"> 152 {ingredients.fields.map((field, index) => ( 153 <SortableItem key={field.id} value={field.id} asChild> 154 - <div className="grid grid-cols-[2rem_1fr_0.2fr_0.2fr_2rem] items-center gap-2"> 155 <SortableDragHandle 156 type="button" 157 variant="outline" ··· 166 167 <FormField 168 control={form.control} 169 - name={`ingredients.${index}.name`} 170 - render={({ field }) => ( 171 - <FormItem> 172 - <FormControl> 173 - <Input 174 - placeholder="Ingredient" 175 - className="h-8" 176 - {...field} 177 - /> 178 - </FormControl> 179 - <FormMessage /> 180 - </FormItem> 181 - )} 182 - /> 183 - 184 - <FormField 185 - control={form.control} 186 name={`ingredients.${index}.amount`} 187 render={({ field: { value, ...field } }) => ( 188 <FormItem> 189 <FormControl> 190 <Input 191 - type="number" 192 - placeholder="#" 193 - value={value || "0"} 194 className="h-8" 195 {...field} 196 /> ··· 202 203 <FormField 204 control={form.control} 205 - name={`ingredients.${index}.unit`} 206 - render={({ field: { value, ...field } }) => ( 207 <FormItem> 208 <FormControl> 209 <Input 210 - placeholder="Unit" 211 className="h-8" 212 - value={value || ""} 213 {...field} 214 /> 215 </FormControl>
··· 56 component: RouteComponent, 57 }); 58 59 + const schema = RecipeRecord; 60 61 function RouteComponent() { 62 const form = useForm<z.infer<typeof schema>>({ ··· 130 {...field} 131 /> 132 </FormControl> 133 + <FormDescription>Describe your recipe, maybe tell the world how tasty it is? (Optional)</FormDescription> 134 + <FormMessage /> 135 + </FormItem> 136 + )} 137 + /> 138 + 139 + <FormField 140 + name="time" 141 + control={form.control} 142 + render={({ field: { value, ...field } }) => ( 143 + <FormItem> 144 + <FormLabel>Time</FormLabel> 145 + <FormControl> 146 + <Input 147 + type="number" 148 + className="resize-none" 149 + value={value || ""} 150 + {...field} 151 + /> 152 + </FormControl> 153 + <FormDescription>How long (in minutes) does your recipe take to complete?</FormDescription> 154 <FormMessage /> 155 </FormItem> 156 )} ··· 166 <div className="flex w-full flex-col gap-2"> 167 {ingredients.fields.map((field, index) => ( 168 <SortableItem key={field.id} value={field.id} asChild> 169 + <div className="grid grid-cols-[2rem_0.3fr_1fr_2rem] items-center gap-2"> 170 <SortableDragHandle 171 type="button" 172 variant="outline" ··· 181 182 <FormField 183 control={form.control} 184 name={`ingredients.${index}.amount`} 185 render={({ field: { value, ...field } }) => ( 186 <FormItem> 187 <FormControl> 188 <Input 189 + placeholder="Amount" 190 + value={value || ""} 191 className="h-8" 192 {...field} 193 /> ··· 199 200 <FormField 201 control={form.control} 202 + name={`ingredients.${index}.name`} 203 + render={({ field }) => ( 204 <FormItem> 205 <FormControl> 206 <Input 207 + placeholder="Ingredient" 208 className="h-8" 209 {...field} 210 /> 211 </FormControl>
+1 -2
libs/lexicons/src/defs.ts
··· 1 import { z } from 'zod'; 2 3 export const IngredientObject = z.object({ 4 name: z.string().max(3000, 'Ingredient names must be under 3000 characters.'), 5 - amount: z.number().nullable(), 6 - unit: z.string().max(3000, 'Ingredient units must be under 3000 characters.').nullable(), 7 }); 8 9 export type Ingredient = z.infer<typeof IngredientObject>;
··· 1 import { z } from 'zod'; 2 3 export const IngredientObject = z.object({ 4 + amount: z.string().nullable(), 5 name: z.string().max(3000, 'Ingredient names must be under 3000 characters.'), 6 }); 7 8 export type Ingredient = z.infer<typeof IngredientObject>;
+1
libs/lexicons/src/recipe.ts
··· 6 export const RecipeRecord = z.object({ 7 title: z.string().max(3000, 'Recipe titles must be under 3000 characters.'), 8 description: z.string().max(3000, 'Recipe descriptions must be under 3000 characters.').nullable(), 9 ingredients: z.array(IngredientObject), 10 steps: z.array(StepObject), 11 });
··· 6 export const RecipeRecord = z.object({ 7 title: z.string().max(3000, 'Recipe titles must be under 3000 characters.'), 8 description: z.string().max(3000, 'Recipe descriptions must be under 3000 characters.').nullable(), 9 + time: z.number({ message: 'Time must be a number.' }), 10 ingredients: z.array(IngredientObject), 11 steps: z.array(StepObject), 12 });