diff options
Diffstat (limited to 'apps/mobile/components/CustomHeadersModal.tsx')
| -rw-r--r-- | apps/mobile/components/CustomHeadersModal.tsx | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/apps/mobile/components/CustomHeadersModal.tsx b/apps/mobile/components/CustomHeadersModal.tsx new file mode 100644 index 00000000..6d022eb8 --- /dev/null +++ b/apps/mobile/components/CustomHeadersModal.tsx @@ -0,0 +1,200 @@ +import { useState } from "react"; +import { Modal, Pressable, ScrollView, View } from "react-native"; +import { KeyboardAwareScrollView } from "react-native-keyboard-controller"; +import { Plus, Trash2, X } from "lucide-react-native"; +import { useColorScheme } from "nativewind"; + +import { Button } from "./ui/Button"; +import { Input } from "./ui/Input"; +import { Text } from "./ui/Text"; + +interface CustomHeadersModalProps { + visible: boolean; + customHeaders: Record<string, string>; + onClose: () => void; + onSave: (headers: Record<string, string>) => void; +} + +export function CustomHeadersModal({ + visible, + customHeaders, + onClose, + onSave, +}: CustomHeadersModalProps) { + const { colorScheme } = useColorScheme(); + const iconColor = colorScheme === "dark" ? "#d1d5db" : "#374151"; + + // Convert headers object to array of entries for easier manipulation + const [headers, setHeaders] = useState<{ key: string; value: string }[]>( + Object.entries(customHeaders).map(([key, value]) => ({ key, value })), + ); + const [newHeaderKey, setNewHeaderKey] = useState(""); + const [newHeaderValue, setNewHeaderValue] = useState(""); + + 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>, + ); + + onSave(headersObject); + onClose(); + }; + + const handleCancel = () => { + // Reset to original headers + setHeaders( + Object.entries(customHeaders).map(([key, value]) => ({ key, value })), + ); + setNewHeaderKey(""); + setNewHeaderValue(""); + onClose(); + }; + + return ( + <Modal + visible={visible} + transparent + animationType="slide" + onRequestClose={handleCancel} + > + <View className="flex-1 justify-end"> + <Pressable + className="absolute inset-0 bg-black/50" + onPress={handleCancel} + /> + <View className="max-h-[85%] rounded-t-3xl bg-card"> + <KeyboardAwareScrollView + contentContainerClassName="p-6" + bottomOffset={20} + keyboardShouldPersistTaps="handled" + > + {/* Header */} + <View className="mb-4 flex flex-row items-center justify-between"> + <Text className="text-lg font-semibold">Custom Headers</Text> + <Pressable onPress={handleCancel} className="p-2"> + <X size={24} color={iconColor} /> + </Pressable> + </View> + + <Text className="mb-4 text-sm text-gray-600 dark:text-gray-400"> + Add custom HTTP headers that will be sent with every API request. + </Text> + + {/* Existing Headers List */} + <View className="mb-4 max-h-64"> + {headers.length === 0 ? ( + <Text className="py-4 text-center text-sm text-gray-500 dark:text-gray-400"> + No custom headers configured + </Text> + ) : ( + <ScrollView> + {headers.map((header, index) => ( + <View + key={index} + className="mb-2 flex-row items-center gap-2 rounded-lg border border-border bg-background p-3" + > + <View className="flex-1"> + <Text className="text-sm font-semibold"> + {header.key} + </Text> + <Text + className="text-xs text-gray-600 dark:text-gray-400" + numberOfLines={1} + > + {header.value} + </Text> + </View> + <Pressable + onPress={() => handleRemoveHeader(index)} + className="p-2" + > + <Trash2 size={18} color="#ef4444" /> + </Pressable> + </View> + ))} + </ScrollView> + )} + </View> + + {/* Add New Header */} + <View className="gap-2 border-t border-border pt-4"> + <Text className="text-sm font-semibold">Add New Header</Text> + <Input + placeholder="Header Name (e.g., X-Custom-Header)" + value={newHeaderKey} + onChangeText={setNewHeaderKey} + autoCapitalize="none" + inputClasses="bg-background" + /> + <Input + placeholder="Header Value" + value={newHeaderValue} + onChangeText={setNewHeaderValue} + autoCapitalize="none" + inputClasses="bg-background" + /> + <Button + variant="secondary" + onPress={handleAddHeader} + disabled={!newHeaderKey.trim() || !newHeaderValue.trim()} + > + <Plus size={16} color={iconColor} /> + <Text className="text-sm">Add Header</Text> + </Button> + </View> + + {/* Action Buttons */} + <View className="mt-4 flex flex-row gap-2 border-t border-border pt-4"> + <Button + variant="secondary" + onPress={handleCancel} + androidRootClassName="flex-1" + > + <Text>Cancel</Text> + </Button> + <Button + variant="primary" + onPress={handleSave} + androidRootClassName="flex-1" + > + <Text>Save</Text> + </Button> + </View> + </KeyboardAwareScrollView> + </View> + </View> + </Modal> + ); +} |
