aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/components/CustomHeadersModal.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'apps/mobile/components/CustomHeadersModal.tsx')
-rw-r--r--apps/mobile/components/CustomHeadersModal.tsx200
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>
+ );
+}