2025-03-11 23:59:00 -04:00
|
|
|
// components/RelayManagement.tsx
|
2025-03-09 12:48:24 -04:00
|
|
|
import React, { useEffect, useState } from 'react';
|
2025-03-11 23:59:00 -04:00
|
|
|
import { View, Text, FlatList, TouchableOpacity, Modal, ActivityIndicator, TextInput, SafeAreaView, KeyboardAvoidingView, Platform } from 'react-native';
|
2025-03-09 11:15:28 -04:00
|
|
|
import { useRelayStore } from '@/lib/stores/relayStore';
|
2025-03-09 12:48:24 -04:00
|
|
|
import { Switch } from '@/components/ui/switch';
|
|
|
|
import { Button } from '@/components/ui/button';
|
2025-03-11 23:59:00 -04:00
|
|
|
import { X, RefreshCw, PlusCircle, AlertTriangle } from 'lucide-react-native';
|
2025-03-09 11:15:28 -04:00
|
|
|
import { RelayWithStatus } from '@/lib/db/services/RelayService';
|
|
|
|
|
2025-03-09 12:48:24 -04:00
|
|
|
interface RelayManagementProps {
|
2025-03-09 11:15:28 -04:00
|
|
|
isVisible: boolean;
|
|
|
|
onClose: () => void;
|
|
|
|
}
|
|
|
|
|
2025-03-09 12:48:24 -04:00
|
|
|
export default function RelayManagement({ isVisible, onClose }: RelayManagementProps) {
|
2025-03-09 11:15:28 -04:00
|
|
|
const relays = useRelayStore(state => state.relays);
|
|
|
|
const isLoading = useRelayStore(state => state.isLoading);
|
2025-03-11 23:59:00 -04:00
|
|
|
const isSaving = useRelayStore(state => state.isSaving);
|
2025-03-09 11:15:28 -04:00
|
|
|
const loadRelays = useRelayStore(state => state.loadRelays);
|
|
|
|
const updateRelay = useRelayStore(state => state.updateRelay);
|
2025-03-11 23:59:00 -04:00
|
|
|
const applyChanges = useRelayStore(state => state.applyChanges);
|
|
|
|
const resetToDefaults = useRelayStore(state => state.resetToDefaults);
|
|
|
|
const addRelay = useRelayStore(state => state.addRelay);
|
|
|
|
|
|
|
|
const [newRelayUrl, setNewRelayUrl] = useState('');
|
|
|
|
const [showAddRelay, setShowAddRelay] = useState(false);
|
|
|
|
const [confirmReset, setConfirmReset] = useState(false);
|
|
|
|
const [hasUnappliedChanges, setHasUnappliedChanges] = useState(false);
|
2025-03-09 11:15:28 -04:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (isVisible) {
|
|
|
|
loadRelays();
|
|
|
|
}
|
2025-03-09 12:48:24 -04:00
|
|
|
}, [isVisible]);
|
2025-03-09 11:15:28 -04:00
|
|
|
|
2025-03-11 23:59:00 -04:00
|
|
|
// Track if there are unapplied changes
|
|
|
|
const handleRelayUpdate = (url: string, changes: Partial<RelayWithStatus>) => {
|
|
|
|
updateRelay(url, changes);
|
|
|
|
setHasUnappliedChanges(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Handle applying changes
|
|
|
|
const handleApplyChanges = async () => {
|
|
|
|
const success = await applyChanges();
|
|
|
|
if (success) {
|
|
|
|
setHasUnappliedChanges(false);
|
|
|
|
// Success notification could be added here
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add new relay
|
|
|
|
const handleAddRelay = async () => {
|
|
|
|
if (!newRelayUrl || !newRelayUrl.startsWith('wss://')) {
|
|
|
|
alert('Please enter a valid relay URL starting with wss://');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
await addRelay(newRelayUrl);
|
|
|
|
setNewRelayUrl('');
|
|
|
|
setShowAddRelay(false);
|
|
|
|
setHasUnappliedChanges(true);
|
|
|
|
} catch (error) {
|
|
|
|
alert(`Failed to add relay: ${error instanceof Error ? error.message : String(error)}`);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Reset to defaults with confirmation
|
|
|
|
const handleResetToDefaults = async () => {
|
|
|
|
if (confirmReset) {
|
|
|
|
await resetToDefaults();
|
|
|
|
setConfirmReset(false);
|
|
|
|
setHasUnappliedChanges(true);
|
|
|
|
} else {
|
|
|
|
setConfirmReset(true);
|
|
|
|
// Auto-reset the confirmation after 3 seconds
|
|
|
|
setTimeout(() => setConfirmReset(false), 3000);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Status indicator color
|
2025-03-09 12:48:24 -04:00
|
|
|
const getStatusColor = (status: string): string => {
|
2025-03-09 11:15:28 -04:00
|
|
|
switch (status) {
|
|
|
|
case 'connected': return '#10b981'; // Green
|
|
|
|
case 'connecting': return '#f59e0b'; // Amber
|
2025-03-09 12:48:24 -04:00
|
|
|
case 'error':
|
|
|
|
case 'disconnected': return '#ef4444'; // Red
|
2025-03-09 11:15:28 -04:00
|
|
|
default: return '#6b7280'; // Gray
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-03-11 23:59:00 -04:00
|
|
|
// Render a relay item
|
2025-03-09 12:48:24 -04:00
|
|
|
const renderRelayItem = ({ item }: { item: RelayWithStatus }) => (
|
2025-03-11 23:59:00 -04:00
|
|
|
<View className="p-4 bg-card mb-2 rounded-lg border border-border">
|
|
|
|
<View className="flex-row items-center justify-between mb-2">
|
|
|
|
<View className="flex-row items-center flex-1">
|
|
|
|
<View
|
|
|
|
style={{
|
|
|
|
width: 10,
|
|
|
|
height: 10,
|
|
|
|
borderRadius: 5,
|
|
|
|
backgroundColor: getStatusColor(item.status),
|
|
|
|
marginRight: 8
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<Text className="text-foreground flex-1" numberOfLines={1}>{item.url}</Text>
|
2025-03-09 11:15:28 -04:00
|
|
|
</View>
|
|
|
|
|
2025-03-11 23:59:00 -04:00
|
|
|
<Text className="text-xs text-muted-foreground capitalize">
|
2025-03-09 12:48:24 -04:00
|
|
|
{item.status}
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
|
2025-03-11 23:59:00 -04:00
|
|
|
<View className="flex-row justify-between items-center mt-3 pt-3 border-t border-border/30">
|
|
|
|
<View className="flex-row items-center">
|
|
|
|
<Text className="text-foreground mr-2">Read</Text>
|
2025-03-09 12:48:24 -04:00
|
|
|
<Switch
|
|
|
|
checked={item.read}
|
2025-03-11 23:59:00 -04:00
|
|
|
onCheckedChange={() => handleRelayUpdate(item.url, { read: !item.read })}
|
2025-03-09 12:48:24 -04:00
|
|
|
/>
|
|
|
|
</View>
|
|
|
|
|
2025-03-11 23:59:00 -04:00
|
|
|
<View className="flex-row items-center">
|
|
|
|
<Text className="text-foreground mr-2">Write</Text>
|
2025-03-09 12:48:24 -04:00
|
|
|
<Switch
|
|
|
|
checked={item.write}
|
2025-03-11 23:59:00 -04:00
|
|
|
onCheckedChange={() => handleRelayUpdate(item.url, { write: !item.write })}
|
2025-03-09 12:48:24 -04:00
|
|
|
/>
|
2025-03-09 11:15:28 -04:00
|
|
|
</View>
|
|
|
|
</View>
|
2025-03-09 12:48:24 -04:00
|
|
|
</View>
|
|
|
|
);
|
2025-03-09 11:15:28 -04:00
|
|
|
|
2025-03-11 23:59:00 -04:00
|
|
|
// Main Render
|
2025-03-09 11:15:28 -04:00
|
|
|
return (
|
|
|
|
<Modal
|
|
|
|
visible={isVisible}
|
|
|
|
animationType="slide"
|
|
|
|
transparent={true}
|
|
|
|
onRequestClose={onClose}
|
|
|
|
>
|
2025-03-11 23:59:00 -04:00
|
|
|
<KeyboardAvoidingView
|
|
|
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
|
|
style={{ flex: 1 }}
|
|
|
|
>
|
|
|
|
<SafeAreaView style={{ flex: 1 }} className="bg-background/95">
|
|
|
|
{/* Header */}
|
|
|
|
<View className="p-4 flex-row items-center justify-between bg-card border-b border-border">
|
|
|
|
<Text className="text-xl font-semibold text-foreground">Relay Management</Text>
|
2025-03-09 11:15:28 -04:00
|
|
|
<TouchableOpacity onPress={onClose}>
|
2025-03-11 23:59:00 -04:00
|
|
|
<X size={24} className="text-foreground" />
|
2025-03-09 11:15:28 -04:00
|
|
|
</TouchableOpacity>
|
|
|
|
</View>
|
|
|
|
|
2025-03-11 23:59:00 -04:00
|
|
|
{/* Content */}
|
|
|
|
<View className="flex-1 px-4 pt-2">
|
|
|
|
{isLoading ? (
|
|
|
|
<View className="flex-1 items-center justify-center">
|
|
|
|
<ActivityIndicator size="large" />
|
|
|
|
<Text className="mt-2 text-foreground">Loading relays...</Text>
|
2025-03-09 12:48:24 -04:00
|
|
|
</View>
|
2025-03-11 23:59:00 -04:00
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
{/* Summary */}
|
|
|
|
<View className="flex-row justify-between mb-2 items-center">
|
|
|
|
<Text className="text-foreground">
|
|
|
|
{relays.length} Relays ({relays.filter(r => r.status === 'connected').length} Connected)
|
|
|
|
</Text>
|
|
|
|
<TouchableOpacity onPress={loadRelays} className="p-2">
|
|
|
|
<RefreshCw size={18} className="text-primary" />
|
|
|
|
</TouchableOpacity>
|
|
|
|
</View>
|
|
|
|
|
|
|
|
{/* Relay List */}
|
|
|
|
{relays.length === 0 ? (
|
|
|
|
<View className="items-center justify-center py-8">
|
|
|
|
<Text className="text-muted-foreground mb-4">No relays configured</Text>
|
|
|
|
<Button
|
|
|
|
variant="outline"
|
|
|
|
onPress={handleResetToDefaults}
|
|
|
|
>
|
|
|
|
<Text>Reset to Defaults</Text>
|
|
|
|
</Button>
|
|
|
|
</View>
|
|
|
|
) : (
|
|
|
|
<FlatList
|
|
|
|
data={relays}
|
|
|
|
renderItem={renderRelayItem}
|
|
|
|
keyExtractor={(item) => item.url}
|
|
|
|
ListEmptyComponent={
|
|
|
|
<View className="items-center justify-center py-8">
|
|
|
|
<Text className="text-muted-foreground">No relays found</Text>
|
|
|
|
</View>
|
|
|
|
}
|
|
|
|
contentContainerStyle={{ paddingBottom: showAddRelay ? 180 : 100 }}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{/* Add Relay Form */}
|
|
|
|
{showAddRelay && (
|
|
|
|
<View className="bg-card p-4 rounded-lg mb-4 mt-2 border border-border">
|
|
|
|
<Text className="text-foreground mb-2">Add New Relay</Text>
|
|
|
|
<TextInput
|
|
|
|
className="bg-background text-foreground p-2 rounded-md border border-border mb-2"
|
|
|
|
placeholder="wss://relay.example.com"
|
|
|
|
placeholderTextColor="#6b7280"
|
|
|
|
value={newRelayUrl}
|
|
|
|
onChangeText={setNewRelayUrl}
|
|
|
|
autoCapitalize="none"
|
|
|
|
autoCorrect={false}
|
|
|
|
/>
|
|
|
|
<View className="flex-row justify-end gap-2">
|
|
|
|
<Button
|
|
|
|
variant="outline"
|
|
|
|
onPress={() => setShowAddRelay(false)}
|
|
|
|
>
|
|
|
|
<Text>Cancel</Text>
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
onPress={handleAddRelay}
|
|
|
|
disabled={!newRelayUrl.startsWith('wss://')}
|
|
|
|
>
|
|
|
|
<Text className="text-primary-foreground">Add Relay</Text>
|
|
|
|
</Button>
|
|
|
|
</View>
|
|
|
|
</View>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</View>
|
|
|
|
|
|
|
|
{/* Footer */}
|
|
|
|
<View className="p-4 bg-card border-t border-border">
|
|
|
|
<View className="flex-row gap-2 mb-2">
|
|
|
|
<Button
|
|
|
|
className="flex-1"
|
|
|
|
onPress={handleApplyChanges}
|
|
|
|
disabled={!hasUnappliedChanges || isSaving}
|
|
|
|
>
|
|
|
|
{isSaving ? (
|
|
|
|
<ActivityIndicator size="small" color="#fff" />
|
|
|
|
) : (
|
|
|
|
<Text className="text-primary-foreground">Apply Changes</Text>
|
|
|
|
)}
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
variant="outline"
|
|
|
|
onPress={() => setShowAddRelay(true)}
|
|
|
|
disabled={showAddRelay}
|
|
|
|
>
|
|
|
|
<PlusCircle size={18} className="mr-1" />
|
|
|
|
<Text>Add Relay</Text>
|
|
|
|
</Button>
|
|
|
|
</View>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
variant={confirmReset ? "destructive" : "outline"}
|
|
|
|
onPress={handleResetToDefaults}
|
|
|
|
>
|
|
|
|
<Text className={confirmReset ? "text-destructive-foreground" : ""}>
|
|
|
|
{confirmReset ? "Confirm Reset" : "Reset to Defaults"}
|
|
|
|
</Text>
|
|
|
|
</Button>
|
|
|
|
</View>
|
|
|
|
</SafeAreaView>
|
|
|
|
</KeyboardAvoidingView>
|
2025-03-09 11:15:28 -04:00
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
}
|