Skip to content

Commit 7cbfc7e

Browse files
authored
Merge pull request #69 from sreq-inc/feat/switch-message-for-toasts
feat: switch message for toasts
2 parents fffe42f + 896e869 commit 7cbfc7e

File tree

9 files changed

+1323
-120
lines changed

9 files changed

+1323
-120
lines changed

package-lock.json

Lines changed: 1216 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
"opener": "1.5.2",
2323
"react": "18.3.1",
2424
"react-dom": "18.3.1",
25+
"react-toastify": "11.0.5",
2526
"tailwindcss": "4.0.0"
2627
},
2728
"devDependencies": {
29+
"@tauri-apps/cli": "2",
2830
"@types/react": "18.3.1",
2931
"@types/react-dom": "18.3.1",
3032
"@vitejs/plugin-react": "4.3.4",
3133
"typescript": "~5.6.2",
32-
"vite": "6.0.3",
33-
"@tauri-apps/cli": "2"
34+
"vite": "6.0.3"
3435
}
3536
}

src/App.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { ResponseView } from "./components/ResponseView";
66
import "./App.css";
77
import Titlebar from "./components/TitleBar";
88
import { InputMethod } from "./components/InputMethod";
9+
import { ToastContainer } from "react-toastify";
10+
import "react-toastify/dist/ReactToastify.css";
911

1012
function App() {
1113
const { theme } = useTheme();
@@ -15,21 +17,32 @@ function App() {
1517
return (
1618
<div className="flex flex-col h-screen">
1719
<Titlebar />
20+
<ToastContainer />
1821
<div
19-
className={clsx("flex-1 flex transition-colors duration-200", appBg)}
22+
className={clsx(
23+
"flex-1 flex transition-colors duration-200 overflow-hidden",
24+
appBg
25+
)}
2026
>
21-
<div className={clsx("w-full flex rounded-xl shadow-lg", cardBg)}>
22-
<div className="flex w-full">
27+
<div
28+
className={clsx(
29+
"w-full flex rounded-xl shadow-lg overflow-hidden",
30+
cardBg
31+
)}
32+
>
33+
<div className="flex w-full overflow-hidden">
2334
<Sidebar />
24-
<section className="flex flex-col w-full px-4">
25-
<div className="p-4 space-y-4">
35+
<section className="flex flex-col w-full px-4 overflow-hidden">
36+
{/* Área fixa: InputMethod */}
37+
<div className="p-4 space-y-4 flex-shrink-0">
2638
<InputMethod />
2739
</div>
28-
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 flex-1">
29-
<div className="flex flex-col">
40+
{/* Área com scroll: RequestForm e ResponseView */}
41+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 flex-1 overflow-hidden pb-4">
42+
<div className="flex flex-col overflow-y-auto">
3043
<RequestForm />
3144
</div>
32-
<div className="flex flex-col">
45+
<div className="flex flex-col overflow-y-auto">
3346
<ResponseView />
3447
</div>
3548
</div>

src/components/GrpcEditor.tsx

Lines changed: 33 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRequest } from "../context/RequestContext";
33
import clsx from "clsx";
44
import { useState, useEffect } from "react";
55
import { invoke } from "@tauri-apps/api/core";
6+
import { useToast } from "../hooks/useToast";
67

78
interface ProtoSchema {
89
services: ProtoService[];
@@ -50,21 +51,15 @@ export const GrpcEditor = () => {
5051
setProtoContent,
5152
setGrpcSchema,
5253
} = useRequest();
54+
const toast = useToast();
5355

5456
const [services, setServices] = useState<ProtoService[]>([]);
5557
const [methods, setMethods] = useState<ProtoMethod[]>([]);
5658
const [isDiscovering, setIsDiscovering] = useState(false);
5759
const [isParsing, setIsParsing] = useState(false);
58-
const [parseError, setParseError] = useState<string>("");
59-
const [discoveryError, setDiscoveryError] = useState<string>("");
6060
const [jsonError, setJsonError] = useState<string>("");
6161
const [testingConnection, setTestingConnection] = useState(false);
6262
const [isJsonValid, setIsJsonValid] = useState(true);
63-
const [connectionStatus, setConnectionStatus] = useState<{
64-
connected: boolean;
65-
message: string;
66-
latency_ms?: number;
67-
} | null>(null);
6863

6964
// Validate JSON in real-time
7065
useEffect(() => {
@@ -85,10 +80,10 @@ export const GrpcEditor = () => {
8580
}
8681
}, [grpcMessage]);
8782

88-
// Parse proto content when it changes
83+
// Parse proto content when it changes (silently, without toasts)
8984
useEffect(() => {
9085
if (protoContent.trim()) {
91-
parseProtoContent();
86+
parseProtoContent(false);
9287
}
9388
}, [protoContent]);
9489

@@ -106,9 +101,8 @@ export const GrpcEditor = () => {
106101
}
107102
}, [grpcService, services]);
108103

109-
const parseProtoContent = async () => {
104+
const parseProtoContent = async (showToast: boolean = true) => {
110105
setIsParsing(true);
111-
setParseError("");
112106

113107
try {
114108
const result = (await invoke("grpc_parse_proto_file", {
@@ -129,16 +123,24 @@ export const GrpcEditor = () => {
129123
messages: schema.messages || [],
130124
});
131125

132-
if (schema.services.length === 0) {
133-
setParseError("No services found in proto file");
126+
if (showToast) {
127+
if (schema.services.length === 0) {
128+
toast.warning("No services found in proto file");
129+
} else {
130+
toast.success(`Found ${schema.services.length} service${schema.services.length !== 1 ? "s" : ""}`);
131+
}
134132
}
135133
} else {
136-
setParseError(result?.error || "Failed to parse proto file");
134+
if (showToast) {
135+
toast.error(result?.error || "Failed to parse proto file");
136+
}
137137
}
138138
} catch (error) {
139139
const errorMsg =
140140
error instanceof Error ? error.message : "Failed to parse proto file";
141-
setParseError(errorMsg);
141+
if (showToast) {
142+
toast.error(errorMsg);
143+
}
142144
console.error("Failed to parse proto file:", error);
143145
} finally {
144146
setIsParsing(false);
@@ -147,12 +149,11 @@ export const GrpcEditor = () => {
147149

148150
const discoverServices = async () => {
149151
if (!url) {
150-
setDiscoveryError("Please enter a URL first");
152+
toast.warning("Please enter a URL first");
151153
return;
152154
}
153155

154156
setIsDiscovering(true);
155-
setDiscoveryError("");
156157

157158
try {
158159
const result = (await invoke("grpc_discover_services", {
@@ -172,15 +173,17 @@ export const GrpcEditor = () => {
172173
});
173174

174175
if (schema.services.length === 0) {
175-
setDiscoveryError("No services found at this URL");
176+
toast.warning("No services found at this URL");
177+
} else {
178+
toast.success(`Discovered ${schema.services.length} service${schema.services.length !== 1 ? "s" : ""} via reflection`);
176179
}
177180
} else {
178-
setDiscoveryError(result?.error || "Failed to discover services");
181+
toast.error(result?.error || "Failed to discover services");
179182
}
180183
} catch (error) {
181184
const errorMsg =
182185
error instanceof Error ? error.message : "Failed to discover services";
183-
setDiscoveryError(errorMsg);
186+
toast.error(errorMsg);
184187
console.error("Failed to discover services:", error);
185188
} finally {
186189
setIsDiscovering(false);
@@ -189,29 +192,27 @@ export const GrpcEditor = () => {
189192

190193
const testConnection = async () => {
191194
if (!url) {
192-
setConnectionStatus({
193-
connected: false,
194-
message: "Please enter a URL first",
195-
});
195+
toast.warning("Please enter a URL first");
196196
return;
197197
}
198198

199199
setTestingConnection(true);
200-
setConnectionStatus(null);
201200

202201
try {
203202
const result = (await invoke("grpc_test_connection", {
204203
url: url,
205204
})) as { connected: boolean; message: string; latency_ms?: number };
206205

207-
setConnectionStatus(result);
206+
if (result.connected) {
207+
const latencyMsg = result.latency_ms ? ` (${result.latency_ms}ms)` : "";
208+
toast.success(`${result.message}${latencyMsg}`);
209+
} else {
210+
toast.error(result.message);
211+
}
208212
} catch (error) {
209213
const errorMsg =
210214
error instanceof Error ? error.message : "Failed to test connection";
211-
setConnectionStatus({
212-
connected: false,
213-
message: errorMsg,
214-
});
215+
toast.error(errorMsg);
215216
} finally {
216217
setTestingConnection(false);
217218
}
@@ -224,7 +225,7 @@ export const GrpcEditor = () => {
224225
setGrpcMessage(JSON.stringify(parsed, null, 2));
225226
} catch (error) {
226227
console.error("Invalid JSON");
227-
alert("Invalid JSON format. Please correct it before formatting.");
228+
toast.error("Invalid JSON format. Please correct it before formatting.");
228229
}
229230
};
230231

@@ -273,7 +274,7 @@ export const GrpcEditor = () => {
273274
<div className="space-y-2 mb-2">
274275
<div className="flex gap-2 flex-wrap">
275276
<button
276-
onClick={parseProtoContent}
277+
onClick={() => parseProtoContent(true)}
277278
disabled={!protoContent.trim() || isParsing}
278279
title="Parse proto file content to extract services and methods"
279280
className={clsx(
@@ -312,72 +313,6 @@ export const GrpcEditor = () => {
312313
{testingConnection ? "Testing..." : "Test Connection"}
313314
</button>
314315
</div>
315-
316-
{/* Error Messages */}
317-
{parseError && (
318-
<div
319-
className={clsx(
320-
"text-xs p-2 rounded border",
321-
theme === "dark"
322-
? "bg-red-900/20 border-red-700 text-red-300"
323-
: "bg-red-50 border-red-300 text-red-700"
324-
)}
325-
>
326-
{parseError}
327-
</div>
328-
)}
329-
330-
{discoveryError && (
331-
<div
332-
className={clsx(
333-
"text-xs p-2 rounded border",
334-
theme === "dark"
335-
? "bg-red-900/20 border-red-700 text-red-300"
336-
: "bg-red-50 border-red-300 text-red-700"
337-
)}
338-
>
339-
{discoveryError}
340-
</div>
341-
)}
342-
343-
{/* Success Message */}
344-
{services.length > 0 && !parseError && !discoveryError && (
345-
<div
346-
className={clsx(
347-
"text-xs p-2 rounded border",
348-
theme === "dark"
349-
? "bg-green-900/20 border-green-700 text-green-300"
350-
: "bg-green-50 border-green-300 text-green-700"
351-
)}
352-
>
353-
✅ Found {services.length} service
354-
{services.length !== 1 ? "s" : ""}
355-
</div>
356-
)}
357-
358-
{/* Connection Status */}
359-
{connectionStatus && (
360-
<div
361-
className={clsx(
362-
"text-xs p-2 rounded border",
363-
connectionStatus.connected
364-
? theme === "dark"
365-
? "bg-green-900/20 border-green-700 text-green-300"
366-
: "bg-green-50 border-green-300 text-green-700"
367-
: theme === "dark"
368-
? "bg-red-900/20 border-red-700 text-red-300"
369-
: "bg-red-50 border-red-300 text-red-700"
370-
)}
371-
>
372-
{connectionStatus.connected ? "🟢" : "🔴"}{" "}
373-
{connectionStatus.message}
374-
{connectionStatus.latency_ms && (
375-
<span className="ml-2 font-mono">
376-
({connectionStatus.latency_ms}ms)
377-
</span>
378-
)}
379-
</div>
380-
)}
381316
</div>
382317
<textarea
383318
value={protoContent}

src/components/InputMethod.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useVariables } from "../context/VariablesContext";
44
import { SelectMethod } from "./SelectMethod";
55
import { SmartUrlInput } from "./SmartUrlInput";
66
import { useTheme } from "../context/ThemeContext";
7+
import { useToast } from "../hooks/useToast";
78

89
export const InputMethod = () => {
910
const {
@@ -17,13 +18,14 @@ export const InputMethod = () => {
1718
} = useRequest();
1819
const { replaceVariablesInUrl } = useVariables();
1920
const { theme } = useTheme();
21+
const toast = useToast();
2022

2123
const handleRequestWithVariables = async () => {
2224
const processedUrl = replaceVariablesInUrl(url);
2325

2426
if (processedUrl.includes("{{")) {
2527
const unresolvedVars = processedUrl.match(/\{\{[^}]+\}\}/g);
26-
alert(
28+
toast.warning(
2729
`Some variables are not defined: ${unresolvedVars?.join(
2830
", "
2931
)}\nCheck the Variables tab.`
@@ -32,14 +34,14 @@ export const InputMethod = () => {
3234
}
3335

3436
if (!processedUrl.trim()) {
35-
alert("URL is required");
37+
toast.warning("URL is required");
3638
return;
3739
}
3840

3941
// Validate URL scheme by request type
4042
if (requestType === "grpc") {
4143
if (!processedUrl.startsWith("grpc://")) {
42-
alert(
44+
toast.warning(
4345
`For gRPC requests, URL must start with grpc://\nCurrent URL: "${processedUrl}"`
4446
);
4547
return;
@@ -49,7 +51,7 @@ export const InputMethod = () => {
4951
!processedUrl.startsWith("http://") &&
5052
!processedUrl.startsWith("https://")
5153
) {
52-
alert(
54+
toast.warning(
5355
`URL must start with http:// or https://\nCurrent URL: "${processedUrl}"`
5456
);
5557
return;

src/components/Sidebar.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { open } from "@tauri-apps/plugin-shell";
88
import { LatestRelease } from "./LatestRelease";
99
import clsx from "clsx";
1010
import { UpdateChecker } from "./UpdateChecker";
11+
import { useToast } from "../hooks/useToast";
1112

1213
export const Sidebar = () => {
1314
const [searchTerm, setSearchTerm] = useState("");
@@ -33,6 +34,7 @@ export const Sidebar = () => {
3334
createFolder,
3435
renameFile,
3536
} = useFile();
37+
const toast = useToast();
3638

3739
useLayoutEffect(() => {
3840
if (showModal) {
@@ -102,11 +104,11 @@ export const Sidebar = () => {
102104
setShowModal(false);
103105
} catch (error) {
104106
console.error("Error creating folder:", error);
105-
alert("Error creating folder. Please try again.");
107+
toast.error("Error creating folder. Please try again.");
106108
}
107109
} else {
108110
console.log("Folder name is empty");
109-
alert("Please enter a folder name.");
111+
toast.warning("Please enter a folder name.");
110112
}
111113
};
112114

0 commit comments

Comments
 (0)