-
Notifications
You must be signed in to change notification settings - Fork 826
Add support for custom headers and migrate from legacy auth #751
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+810
−106
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
c5acc63
Add support for custom headers and migrate from legacy auth
Jeetul-samaiya cb8c454
refactor: improve code formatting and readability across multiple files
Jeetul-samaiya 6f727b6
fix: update error handling in JSON parsing and add ESLint rule for un…
Jeetul-samaiya fde55ef
fix: standardize formatting in bug report issue template
Jeetul-samaiya 90ab419
fix: enhance error handling for custom headers and improve test cover…
Jeetul-samaiya 743bb52
Merge branch 'main' into main
Jeetulsamaiya f36c649
Merge branch 'main' into main
Jeetulsamaiya e08ce8c
feat: add default Authorization Bearer header and clean up legacy auth
Jeetul-samaiya File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
import { useState } from "react"; | ||
import { Button } from "@/components/ui/button"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Textarea } from "@/components/ui/textarea"; | ||
import { Switch } from "@/components/ui/switch"; | ||
import { Plus, Trash2, Eye, EyeOff } from "lucide-react"; | ||
import { | ||
CustomHeaders as CustomHeadersType, | ||
CustomHeader, | ||
createEmptyHeader, | ||
} from "@/lib/types/customHeaders"; | ||
|
||
interface CustomHeadersProps { | ||
headers: CustomHeadersType; | ||
onChange: (headers: CustomHeadersType) => void; | ||
className?: string; | ||
} | ||
|
||
const CustomHeaders = ({ | ||
headers, | ||
onChange, | ||
className, | ||
}: CustomHeadersProps) => { | ||
const [isJsonMode, setIsJsonMode] = useState(false); | ||
const [jsonValue, setJsonValue] = useState(""); | ||
const [jsonError, setJsonError] = useState<string | null>(null); | ||
const [visibleValues, setVisibleValues] = useState<Set<number>>(new Set()); | ||
|
||
const updateHeader = ( | ||
index: number, | ||
field: keyof CustomHeader, | ||
value: string | boolean, | ||
) => { | ||
const newHeaders = [...headers]; | ||
newHeaders[index] = { ...newHeaders[index], [field]: value }; | ||
onChange(newHeaders); | ||
}; | ||
|
||
const addHeader = () => { | ||
onChange([...headers, createEmptyHeader()]); | ||
}; | ||
|
||
const removeHeader = (index: number) => { | ||
const newHeaders = headers.filter((_, i) => i !== index); | ||
onChange(newHeaders); | ||
}; | ||
|
||
const toggleValueVisibility = (index: number) => { | ||
const newVisible = new Set(visibleValues); | ||
if (newVisible.has(index)) { | ||
newVisible.delete(index); | ||
} else { | ||
newVisible.add(index); | ||
} | ||
setVisibleValues(newVisible); | ||
}; | ||
|
||
const switchToJsonMode = () => { | ||
const jsonObject: Record<string, string> = {}; | ||
headers.forEach((header) => { | ||
if (header.enabled && header.name.trim() && header.value.trim()) { | ||
jsonObject[header.name.trim()] = header.value.trim(); | ||
} | ||
}); | ||
setJsonValue(JSON.stringify(jsonObject, null, 2)); | ||
setJsonError(null); | ||
setIsJsonMode(true); | ||
}; | ||
|
||
const switchToFormMode = () => { | ||
try { | ||
const parsed = JSON.parse(jsonValue); | ||
if ( | ||
typeof parsed !== "object" || | ||
parsed === null || | ||
Array.isArray(parsed) | ||
) { | ||
setJsonError("JSON must be an object with string key-value pairs"); | ||
return; | ||
} | ||
|
||
const newHeaders: CustomHeadersType = Object.entries(parsed).map( | ||
([name, value]) => ({ | ||
name, | ||
value: String(value), | ||
enabled: true, | ||
}), | ||
); | ||
|
||
onChange(newHeaders); | ||
setJsonError(null); | ||
setIsJsonMode(false); | ||
} catch { | ||
setJsonError("Invalid JSON format"); | ||
} | ||
}; | ||
|
||
const handleJsonChange = (value: string) => { | ||
setJsonValue(value); | ||
setJsonError(null); | ||
}; | ||
|
||
if (isJsonMode) { | ||
return ( | ||
<div className={`space-y-3 ${className}`}> | ||
<div className="flex justify-between items-center gap-2"> | ||
<h4 className="text-sm font-semibold flex-shrink-0"> | ||
Custom Headers (JSON) | ||
</h4> | ||
<Button | ||
type="button" | ||
variant="outline" | ||
size="sm" | ||
onClick={switchToFormMode} | ||
className="flex-shrink-0" | ||
> | ||
Switch to Form | ||
</Button> | ||
</div> | ||
<div className="space-y-2"> | ||
<Textarea | ||
value={jsonValue} | ||
onChange={(e) => handleJsonChange(e.target.value)} | ||
placeholder='{\n "Authorization": "Bearer token123",\n "X-Tenant-ID": "acme-inc",\n "X-Environment": "staging"\n}' | ||
className="font-mono text-sm min-h-[100px] resize-none" | ||
/> | ||
{jsonError && <p className="text-sm text-red-600">{jsonError}</p>} | ||
<p className="text-xs text-muted-foreground"> | ||
Enter headers as a JSON object with string key-value pairs. | ||
</p> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div className={`space-y-3 ${className}`}> | ||
<div className="flex justify-between items-center gap-2"> | ||
<h4 className="text-sm font-semibold flex-shrink-0">Custom Headers</h4> | ||
<div className="flex gap-1 flex-shrink-0"> | ||
<Button | ||
type="button" | ||
variant="outline" | ||
size="sm" | ||
onClick={switchToJsonMode} | ||
className="text-xs px-2" | ||
> | ||
JSON | ||
</Button> | ||
<Button | ||
type="button" | ||
variant="outline" | ||
size="sm" | ||
onClick={addHeader} | ||
className="text-xs px-2" | ||
data-testid="add-header-button" | ||
> | ||
<Plus className="w-3 h-3 mr-1" /> | ||
Add | ||
</Button> | ||
</div> | ||
</div> | ||
|
||
{headers.length === 0 ? ( | ||
<div className="text-center py-4 text-muted-foreground"> | ||
<p className="text-sm">No custom headers configured</p> | ||
<p className="text-xs mt-1">Click "Add" to get started</p> | ||
</div> | ||
) : ( | ||
<div className="space-y-2 max-h-[300px] overflow-y-auto"> | ||
{headers.map((header, index) => ( | ||
<div | ||
key={index} | ||
className="flex items-start gap-2 p-2 border rounded-md" | ||
> | ||
<Switch | ||
checked={header.enabled} | ||
onCheckedChange={(enabled) => | ||
updateHeader(index, "enabled", enabled) | ||
} | ||
className="shrink-0 mt-2" | ||
/> | ||
<div className="flex-1 min-w-0 space-y-2"> | ||
<Input | ||
placeholder="Header Name" | ||
value={header.name} | ||
onChange={(e) => updateHeader(index, "name", e.target.value)} | ||
className="font-mono text-xs" | ||
data-testid={`header-name-input-${index}`} | ||
/> | ||
<div className="relative"> | ||
<Input | ||
placeholder="Header Value" | ||
value={header.value} | ||
onChange={(e) => | ||
updateHeader(index, "value", e.target.value) | ||
} | ||
type={visibleValues.has(index) ? "text" : "password"} | ||
className="font-mono text-xs pr-8" | ||
data-testid={`header-value-input-${index}`} | ||
/> | ||
<Button | ||
type="button" | ||
variant="ghost" | ||
size="sm" | ||
onClick={() => toggleValueVisibility(index)} | ||
className="absolute right-1 top-1/2 -translate-y-1/2 h-6 w-6 p-0" | ||
> | ||
{visibleValues.has(index) ? ( | ||
<EyeOff className="w-3 h-3" /> | ||
) : ( | ||
<Eye className="w-3 h-3" /> | ||
)} | ||
</Button> | ||
</div> | ||
</div> | ||
<Button | ||
type="button" | ||
variant="ghost" | ||
size="sm" | ||
onClick={() => removeHeader(index)} | ||
className="shrink-0 text-red-600 hover:text-red-700 hover:bg-red-50 h-6 w-6 p-0 mt-2" | ||
> | ||
<Trash2 className="w-3 h-3" /> | ||
</Button> | ||
</div> | ||
))} | ||
</div> | ||
)} | ||
|
||
{headers.length > 0 && ( | ||
<p className="text-xs text-muted-foreground"> | ||
Use the toggle to enable/disable headers. Only enabled headers with | ||
both name and value will be sent. | ||
</p> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default CustomHeaders; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.