From 8751ba26ebf7b7761b9e237f2bf3453623dd1018 Mon Sep 17 00:00:00 2001
From: polwex <polwex@sortug.com>
Date: Mon, 6 Oct 2025 10:13:39 +0700
Subject: added frontend WS connection for demonstration purposes

---
 gui/src/components/WsWidget.tsx | 123 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 gui/src/components/WsWidget.tsx

(limited to 'gui/src/components/WsWidget.tsx')

diff --git a/gui/src/components/WsWidget.tsx b/gui/src/components/WsWidget.tsx
new file mode 100644
index 0000000..75c773d
--- /dev/null
+++ b/gui/src/components/WsWidget.tsx
@@ -0,0 +1,123 @@
+import { useWebSocket } from "@/hooks/useWs";
+import { useState } from "react";
+
+type WidgetProps = {
+  url: string;
+  protocols?: string | string[];
+};
+
+export default function WebSocketWidget({ url, protocols }: WidgetProps) {
+  const {
+    status,
+    retryCount,
+    lastMessage,
+    error,
+    bufferedAmount,
+    send,
+    reconnectNow,
+    close,
+  } = useWebSocket({
+    url,
+    protocols,
+    onMessage: (ev) => {
+      // Example: auto reply to pings
+      console.log(ev.data, "ws event");
+      if (
+        typeof ev.data === "string" &&
+        // ev.data.toLowerCase().includes("ping")
+        ev.data.toLowerCase().trim() == "ping"
+      ) {
+        try {
+          console.log("sending pong");
+          send("pong");
+        } catch {}
+      }
+    },
+  });
+
+  const [outbound, setOutbound] = useState("");
+
+  return (
+    <div className="w-full max-w-xl mx-auto p-4 grid gap-3">
+      <header className="flex items-center justify-between">
+        <h1 className="text-xl font-semibold">WebSocketWidget</h1>
+        <span className="text-sm px-2 py-1 rounded-full border">
+          {status.toUpperCase()} {retryCount ? `(retry ${retryCount})` : ""}
+        </span>
+      </header>
+
+      <div className="text-sm text-gray-600">
+        <div>
+          <b>URL:</b> {url}
+        </div>
+        <div>
+          <b>Buffered:</b> {bufferedAmount} bytes
+        </div>
+        {error && (
+          <div className="text-red-600">
+            <b>Error:</b>{" "}
+            {"message" in error
+              ? (error as any).message
+              : String(error.type || "error")}
+          </div>
+        )}
+      </div>
+
+      <div className="p-3 rounded-2xl border bg-gray-50 min-h-[4rem] font-mono text-sm break-words">
+        <div className="opacity-70">Last message:</div>
+        <div>
+          {lastMessage
+            ? typeof lastMessage.data === "string"
+              ? lastMessage.data
+              : "(binary)"
+            : "—"}
+        </div>
+      </div>
+
+      <form
+        className="flex gap-2"
+        onSubmit={(e) => {
+          e.preventDefault();
+          if (!outbound) return;
+          send(outbound);
+          setOutbound("");
+        }}
+      >
+        <input
+          className="flex-1 px-3 py-2 rounded-xl border"
+          placeholder="Type message…"
+          value={outbound}
+          onChange={(e) => setOutbound(e.target.value)}
+        />
+        <button type="submit" className="px-3 py-2 rounded-xl border">
+          Send
+        </button>
+      </form>
+
+      <div className="flex gap-2">
+        <button className="px-3 py-2 rounded-xl border" onClick={reconnectNow}>
+          Reconnect
+        </button>
+        <button className="px-3 py-2 rounded-xl border" onClick={() => close()}>
+          Close
+        </button>
+      </div>
+
+      <details className="mt-2">
+        <summary className="cursor-pointer">Usage</summary>
+        <pre className="text-xs bg-gray-100 p-2 rounded-xl overflow-auto">
+          {`import WebSocketWidget from "./WebSocketWidget";
+
+export default function App() {
+  return (
+    <div className="p-6">
+      <WebSocketWidget url="wss://echo.websocket.events" />
+    </div>
+  );
+}
+`}
+        </pre>
+      </details>
+    </div>
+  );
+}
-- 
cgit v1.2.3

