Skip to content

Commit 395c2d6

Browse files
authored
Merge pull request #1713 from aalimsahin/refactor/client-react-query
refactor: client api
2 parents b79cd60 + 6d7012b commit 395c2d6

File tree

11 files changed

+119
-51
lines changed

11 files changed

+119
-51
lines changed

client/src/Agents.tsx

+2-14
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,11 @@
1-
import { useQuery } from "@tanstack/react-query";
21
import { Button } from "@/components/ui/button";
32
import { useNavigate } from "react-router-dom";
3+
import { useGetAgentsQuery } from "@/api";
44
import "./App.css";
55

6-
type Agent = {
7-
id: string;
8-
name: string;
9-
};
10-
116
function Agents() {
127
const navigate = useNavigate();
13-
const { data: agents, isLoading } = useQuery({
14-
queryKey: ["agents"],
15-
queryFn: async () => {
16-
const res = await fetch("/api/agents");
17-
const data = await res.json();
18-
return data.agents as Agent[];
19-
},
20-
});
8+
const { data: agents, isLoading } = useGetAgentsQuery()
219

2210
return (
2311
<div className="min-h-screen flex flex-col items-center justify-center p-4">

client/src/Chat.tsx

+9-36
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
import { Button } from "@/components/ui/button";
22
import { Input } from "@/components/ui/input";
3-
import { useMutation } from "@tanstack/react-query";
3+
import type { TextResponse } from "@/api";
4+
import { useSendMessageMutation } from "@/api";
45
import { ImageIcon } from "lucide-react";
56
import { useEffect, useRef, useState } from "react";
67
import { useParams } from "react-router-dom";
78
import "./App.css";
89

9-
type TextResponse = {
10-
text: string;
11-
user: string;
12-
attachments?: { url: string; contentType: string; title: string }[];
13-
};
14-
1510
export default function Chat() {
1611
const { agentId } = useParams();
1712
const [input, setInput] = useState("");
1813
const [messages, setMessages] = useState<TextResponse[]>([]);
1914
const [selectedFile, setSelectedFile] = useState<File | null>(null);
2015
const fileInputRef = useRef<HTMLInputElement>(null);
2116
const messagesEndRef = useRef<HTMLDivElement>(null);
17+
const { mutate: sendMessage, isPending } = useSendMessageMutation({ setMessages, setSelectedFile });
2218

2319
const scrollToBottom = () => {
2420
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -28,32 +24,9 @@ export default function Chat() {
2824
scrollToBottom();
2925
}, [messages]);
3026

31-
const mutation = useMutation({
32-
mutationFn: async (text: string) => {
33-
const formData = new FormData();
34-
formData.append("text", text);
35-
formData.append("userId", "user");
36-
formData.append("roomId", `default-room-${agentId}`);
37-
38-
if (selectedFile) {
39-
formData.append("file", selectedFile);
40-
}
41-
42-
const res = await fetch(`/api/${agentId}/message`, {
43-
method: "POST",
44-
body: formData,
45-
});
46-
return res.json() as Promise<TextResponse[]>;
47-
},
48-
onSuccess: (data) => {
49-
setMessages((prev) => [...prev, ...data]);
50-
setSelectedFile(null);
51-
},
52-
});
53-
5427
const handleSubmit = async (e: React.FormEvent) => {
5528
e.preventDefault();
56-
if (!input.trim() && !selectedFile) return;
29+
if ((!input.trim() && !selectedFile) || !agentId) return;
5730

5831
// Add user message immediately to state
5932
const userMessage: TextResponse = {
@@ -63,7 +36,7 @@ export default function Chat() {
6336
};
6437
setMessages((prev) => [...prev, userMessage]);
6538

66-
mutation.mutate(input);
39+
sendMessage({ text: input, agentId, selectedFile });
6740
setInput("");
6841
};
6942

@@ -142,19 +115,19 @@ export default function Chat() {
142115
onChange={(e) => setInput(e.target.value)}
143116
placeholder="Type a message..."
144117
className="flex-1"
145-
disabled={mutation.isPending}
118+
disabled={isPending}
146119
/>
147120
<Button
148121
type="button"
149122
variant="outline"
150123
size="icon"
151124
onClick={handleFileSelect}
152-
disabled={mutation.isPending}
125+
disabled={isPending}
153126
>
154127
<ImageIcon className="h-4 w-4" />
155128
</Button>
156-
<Button type="submit" disabled={mutation.isPending}>
157-
{mutation.isPending ? "..." : "Send"}
129+
<Button type="submit" disabled={isPending}>
130+
{isPending ? "..." : "Send"}
158131
</Button>
159132
</form>
160133
{selectedFile && (

client/src/api/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./mutations";
2+
export * from "./queries";

client/src/api/mutations/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./sendMessageMutation";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { CustomMutationResult } from "../types";
2+
3+
import { useMutation } from "@tanstack/react-query";
4+
import { ROUTES } from "../routes";
5+
import { SetStateAction } from "react";
6+
7+
export type TextResponse = {
8+
text: string;
9+
user: string;
10+
attachments?: { url: string; contentType: string; title: string }[];
11+
};
12+
13+
type SendMessageMutationProps = {
14+
text: string;
15+
agentId: string;
16+
selectedFile: File | null;
17+
};
18+
19+
type Props = Required<{
20+
setMessages: (value: SetStateAction<TextResponse[]>) => void;
21+
setSelectedFile: (value: SetStateAction<File | null>) => void;
22+
}>;
23+
24+
export const useSendMessageMutation = ({
25+
setMessages,
26+
setSelectedFile,
27+
}: Props): CustomMutationResult<TextResponse[], SendMessageMutationProps> => {
28+
const mutation = useMutation({
29+
mutationFn: async ({
30+
text,
31+
agentId,
32+
selectedFile,
33+
}: SendMessageMutationProps) => {
34+
const formData = new FormData();
35+
formData.append("text", text);
36+
formData.append("userId", "user");
37+
formData.append("roomId", `default-room-${agentId}`);
38+
39+
if (selectedFile) {
40+
formData.append("file", selectedFile);
41+
}
42+
43+
const res = await fetch(ROUTES.sendMessage(agentId), {
44+
method: "POST",
45+
body: formData,
46+
});
47+
48+
return res.json() as Promise<TextResponse[]>;
49+
},
50+
onSuccess: (data) => {
51+
setMessages((prev) => [...prev, ...data]);
52+
setSelectedFile(null);
53+
},
54+
onError: (error) => {
55+
console.error("[useSendMessageMutation]:", error);
56+
},
57+
});
58+
59+
return mutation;
60+
};

client/src/api/queries/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./useGetAgentsQuery";

client/src/api/queries/queries.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export enum Queries {
2+
AGENTS = "agents",
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import type { CustomQueryResult } from "../types";
3+
import { Queries } from "./queries";
4+
import { ROUTES } from "../routes";
5+
6+
export type Agent = {
7+
id: string;
8+
name: string;
9+
};
10+
11+
export const useGetAgentsQuery = (): CustomQueryResult<Agent[] | undefined> => {
12+
return useQuery({
13+
queryKey: [Queries.AGENTS],
14+
queryFn: async () => {
15+
const res = await fetch(ROUTES.getAgents());
16+
const data = await res.json();
17+
return data.agents as Agent[];
18+
},
19+
retry: (failureCount) => failureCount < 3,
20+
staleTime: 5 * 60 * 1000, // 5 minutes
21+
refetchOnWindowFocus: false,
22+
});
23+
};

client/src/api/routes.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const ROUTES = {
2+
sendMessage: (agentId: string): string => `/api/${agentId}/message`,
3+
getAgents: (): string => `/api/agents`,
4+
};

client/src/api/types.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { UseMutationResult, UseQueryResult } from "@tanstack/react-query";
2+
3+
export type CustomMutationResult<TData, TArgs = unknown> = UseMutationResult<
4+
TData,
5+
Error,
6+
TArgs,
7+
unknown
8+
>;
9+
10+
export type CustomQueryResult<TData, TArgs = unknown> = Omit<
11+
UseQueryResult<TData, TArgs>,
12+
"data" | "refetch" | "promise"
13+
> & { data: TData; refetch: () => void; promise: unknown };

client/src/components/app-sidebar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Calendar, Home, Inbox, Search, Settings } from "lucide-react";
1+
import { Calendar, Inbox } from "lucide-react";
22
import { useParams } from "react-router-dom";
33
import { ThemeToggle } from "@/components/theme-toggle";
44

0 commit comments

Comments
 (0)