From ec87813a257e63f8a161e7bc04679e9fab6fbcf6 Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Sun, 9 Nov 2025 12:21:54 +0000
Subject: feat(extension): Add custom header support for extension (#2111)

Fixes #1287
---
 apps/browser-extension/src/CustomHeadersPage.tsx | 155 +++++++++++++++++++++++
 1 file changed, 155 insertions(+)
 create mode 100644 apps/browser-extension/src/CustomHeadersPage.tsx

(limited to 'apps/browser-extension/src/CustomHeadersPage.tsx')

diff --git a/apps/browser-extension/src/CustomHeadersPage.tsx b/apps/browser-extension/src/CustomHeadersPage.tsx
new file mode 100644
index 00000000..1b3fd8df
--- /dev/null
+++ b/apps/browser-extension/src/CustomHeadersPage.tsx
@@ -0,0 +1,155 @@
+import { useEffect, useState } from "react";
+import { Plus, Trash2 } from "lucide-react";
+import { useNavigate } from "react-router-dom";
+
+import { Button } from "./components/ui/button";
+import { Input } from "./components/ui/input";
+import Logo from "./Logo";
+import usePluginSettings from "./utils/settings";
+
+export default function CustomHeadersPage() {
+  const navigate = useNavigate();
+  const { settings, setSettings } = usePluginSettings();
+
+  // Convert headers object to array of entries for easier manipulation
+  const [headers, setHeaders] = useState<{ key: string; value: string }[]>([]);
+  const [newHeaderKey, setNewHeaderKey] = useState("");
+  const [newHeaderValue, setNewHeaderValue] = useState("");
+
+  // Update headers when settings change (e.g., when loaded from storage)
+  useEffect(() => {
+    setHeaders(
+      Object.entries(settings.customHeaders || {}).map(([key, value]) => ({
+        key,
+        value,
+      })),
+    );
+  }, [settings.customHeaders]);
+
+  const handleAddHeader = () => {
+    if (!newHeaderKey.trim() || !newHeaderValue.trim()) {
+      return;
+    }
+
+    // Check if header already exists
+    const existingIndex = headers.findIndex((h) => h.key === newHeaderKey);
+    if (existingIndex >= 0) {
+      // Update existing header
+      const updatedHeaders = [...headers];
+      updatedHeaders[existingIndex].value = newHeaderValue;
+      setHeaders(updatedHeaders);
+    } else {
+      // Add new header
+      setHeaders([...headers, { key: newHeaderKey, value: newHeaderValue }]);
+    }
+
+    setNewHeaderKey("");
+    setNewHeaderValue("");
+  };
+
+  const handleRemoveHeader = (index: number) => {
+    setHeaders(headers.filter((_, i) => i !== index));
+  };
+
+  const handleSave = () => {
+    // Convert array back to object
+    const headersObject = headers.reduce(
+      (acc, { key, value }) => {
+        if (key.trim() && value.trim()) {
+          acc[key] = value;
+        }
+        return acc;
+      },
+      {} as Record<string, string>,
+    );
+
+    setSettings((s) => ({ ...s, customHeaders: headersObject }));
+    navigate(-1);
+  };
+
+  const handleCancel = () => {
+    navigate(-1);
+  };
+
+  return (
+    <div className="flex flex-col space-y-2">
+      <Logo />
+      <span className="text-lg">Custom Headers</span>
+      <p className="text-sm text-muted-foreground">
+        Add custom HTTP headers that will be sent with every API request.
+      </p>
+      <hr />
+
+      {/* Existing Headers List */}
+      <div className="max-h-64 space-y-2 overflow-y-auto">
+        {headers.length === 0 ? (
+          <p className="py-4 text-center text-sm text-muted-foreground">
+            No custom headers configured
+          </p>
+        ) : (
+          headers.map((header, index) => (
+            <div
+              key={index}
+              className="flex items-center gap-2 rounded-lg border bg-background p-3"
+            >
+              <div className="flex-1 space-y-1">
+                <p className="text-sm font-semibold">{header.key}</p>
+                <p className="truncate text-xs text-muted-foreground">
+                  {header.value}
+                </p>
+              </div>
+              <Button
+                variant="ghost"
+                size="sm"
+                onClick={() => handleRemoveHeader(index)}
+                className="h-8 w-8 p-0 text-destructive hover:text-destructive"
+              >
+                <Trash2 className="h-4 w-4" />
+              </Button>
+            </div>
+          ))
+        )}
+      </div>
+
+      <hr />
+
+      {/* Add New Header */}
+      <div className="space-y-2">
+        <p className="text-sm font-semibold">Add New Header</p>
+        <Input
+          placeholder="Header Name (e.g., X-Custom-Header)"
+          value={newHeaderKey}
+          onChange={(e) => setNewHeaderKey(e.target.value)}
+          autoCapitalize="none"
+        />
+        <Input
+          placeholder="Header Value"
+          value={newHeaderValue}
+          onChange={(e) => setNewHeaderValue(e.target.value)}
+          autoCapitalize="none"
+        />
+        <Button
+          variant="secondary"
+          onClick={handleAddHeader}
+          disabled={!newHeaderKey.trim() || !newHeaderValue.trim()}
+          className="w-full"
+        >
+          <Plus className="mr-2 h-4 w-4" />
+          Add Header
+        </Button>
+      </div>
+
+      <hr />
+
+      {/* Action Buttons */}
+      <div className="flex gap-2">
+        <Button variant="outline" onClick={handleCancel} className="flex-1">
+          Cancel
+        </Button>
+        <Button onClick={handleSave} className="flex-1">
+          Save
+        </Button>
+      </div>
+    </div>
+  );
+}
-- 
cgit v1.2.3-70-g09d2

