this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve API error messages, retry logic, and toast rich colors

authored by

James Blair and committed by
James Blair
4db4aa59 ad50a76f

+44 -26
+11 -9
apps/api/src/routes/chats.ts
··· 65 65 with: { messages: true }, 66 66 }); 67 67 68 - if (!res) return c.json({ message: "Not found" }, 404); 68 + if (!res) return c.json({ message: "Chat not found" }, 404); 69 69 70 70 return c.json(res, 200); 71 71 }) ··· 82 82 where: and(eq(schema.chats.id, id), eq(schema.chats.userId, userId)), 83 83 }); 84 84 85 - if (!chat) return c.json({ message: "Not found" }, 404); 85 + if (!chat) return c.json({ message: "Chat not found" }, 404); 86 86 87 87 return c.json(chat, 201); 88 88 }) ··· 95 95 where: and(eq(schema.chats.id, id), eq(schema.chats.userId, userId)), 96 96 }); 97 97 98 - if (!chat) return c.json({ message: "Not found" }, 404); 98 + if (!chat) return c.json({ message: "Chat not found" }, 404); 99 99 100 100 const character = characters.find((c) => c.id === chat.characterId); 101 101 102 - if (!character) return c.json({ message: "Invalid character" }, 400); 102 + if (!character) return c.json({ message: "Character not found" }, 400); 103 103 104 104 const messages = await db.query.messages.findMany({ 105 105 where: and( ··· 197 197 where: and(eq(schema.chats.id, id), eq(schema.chats.userId, userId)), 198 198 }); 199 199 200 - if (!chat) return c.json({ message: "Not found" }, 404); 200 + if (!chat) return c.json({ message: "Chat not found" }, 404); 201 201 202 202 const character = characters.find( 203 203 (character) => character.id === chat.characterId, 204 204 ); 205 205 206 - if (!character) return c.json({ message: "Invalid character" }, 400); 206 + if (!character) return c.json({ message: "Character not found" }, 400); 207 207 208 208 const messages = await db.query.messages.findMany({ 209 209 where: and( ··· 279 279 where: and(eq(schema.chats.id, id), eq(schema.chats.userId, userId)), 280 280 }); 281 281 282 - if (!chat) return c.json({ message: "Not found" }, 404); 282 + if (!chat) return c.json({ message: "Chat not found" }, 404); 283 283 284 284 const character = characters.find( 285 285 (character) => character.id === chat.characterId, 286 286 ); 287 287 288 - if (!character) return c.json({ message: "Invalid character" }, 400); 288 + if (!character) { 289 + return c.json({ message: "Character not found" }, 400); 290 + } 289 291 290 292 const messages = await db.query.messages.findMany({ 291 293 where: and( ··· 387 389 where: and(eq(schema.chats.id, id), eq(schema.chats.userId, userId)), 388 390 }); 389 391 390 - if (!chat) return c.json({ message: "Not found" }, 404); 392 + if (!chat) return c.json({ message: "Chat not found" }, 404); 391 393 392 394 return c.json(chat, 200); 393 395 })
+1
apps/web/src/components/ui/sonner.tsx
··· 19 19 error: <OctagonXIcon className="size-4" />, 20 20 loading: <Loader2Icon className="size-4 animate-spin" />, 21 21 }} 22 + richColors 22 23 style={ 23 24 { 24 25 "--normal-bg": "var(--popover)",
+8 -16
apps/web/src/hooks/use-chat.ts
··· 1 1 import { useAuth } from "@clerk/clerk-react"; 2 2 import { useQuery } from "@tanstack/react-query"; 3 3 import { client } from "#/lib/api"; 4 - import { ApiError, expectApiResponse } from "#/lib/api-error"; 4 + import { expectApiResponse } from "#/lib/api-error"; 5 5 import { chatQueryKey } from "#/lib/chat-query"; 6 6 7 7 interface UseChatOptions { ··· 15 15 enabled: isLoaded && isSignedIn, 16 16 queryKey: chatQueryKey(userId, id), 17 17 queryFn: async () => { 18 - try { 19 - const response = await expectApiResponse( 20 - client.api.chats[":id"].$get({ 21 - param: { id }, 22 - }), 23 - "Failed to load chat", 24 - ); 25 - 26 - return response.json(); 27 - } catch (error) { 28 - if (error instanceof ApiError && error.status === 404) { 29 - return undefined; 30 - } 18 + const response = await expectApiResponse( 19 + client.api.chats[":id"].$get({ 20 + param: { id }, 21 + }), 22 + "Failed to load chat", 23 + ); 31 24 32 - throw error; 33 - } 25 + return response.json(); 34 26 }, 35 27 }); 36 28
+12
apps/web/src/lib/api-error.ts
··· 59 59 60 60 return null; 61 61 } 62 + 63 + export function shouldRetryApiError(error: unknown) { 64 + if (!(error instanceof ApiError)) { 65 + return true; 66 + } 67 + 68 + if (error.status === undefined) { 69 + return true; 70 + } 71 + 72 + return error.status >= 500; 73 + }
+12 -1
apps/web/src/lib/query.ts
··· 1 1 import { MutationCache, QueryCache, QueryClient } from "@tanstack/react-query"; 2 2 import { toast } from "sonner"; 3 - import { getApiErrorMessage } from "./api-error"; 3 + import { getApiErrorMessage, shouldRetryApiError } from "./api-error"; 4 4 5 5 function toastApiError(error: unknown) { 6 6 const message = getApiErrorMessage(error); ··· 11 11 } 12 12 13 13 export const queryClient = new QueryClient({ 14 + defaultOptions: { 15 + queries: { 16 + retry: (failureCount, error) => { 17 + if (!shouldRetryApiError(error)) { 18 + return false; 19 + } 20 + 21 + return failureCount < 2; 22 + }, 23 + }, 24 + }, 14 25 mutationCache: new MutationCache({ 15 26 onError: (error) => { 16 27 toastApiError(error);