165 lines
4.4 KiB
TypeScript
165 lines
4.4 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Modal, View, Text, StyleSheet, TextInput, TouchableOpacity, KeyboardAvoidingView } from 'react-native';
|
|
import { useSettings } from '@/theme';
|
|
import { DEFAULT_API_BASE_URL } from '@/services/auth';
|
|
import Svg, { Path } from 'react-native-svg';
|
|
|
|
interface ServerUrlModalProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function ServerUrlModal({ visible, onClose }: ServerUrlModalProps) {
|
|
const { theme, apiUrl, setApiUrl } = useSettings();
|
|
const [value, setValue] = useState(apiUrl);
|
|
const [prevVisible, setPrevVisible] = useState(visible);
|
|
|
|
if (prevVisible !== visible) {
|
|
setPrevVisible(visible);
|
|
if (visible) {
|
|
setValue(apiUrl);
|
|
}
|
|
}
|
|
|
|
const handleSave = () => {
|
|
const trimmed = value.trim().replace(/\/+$/, '');
|
|
if (trimmed && /^https?:\/\/.+/.test(trimmed)) {
|
|
setApiUrl(trimmed);
|
|
}
|
|
onClose();
|
|
};
|
|
|
|
const isValid = /^https?:\/\/.+/.test(value.trim());
|
|
|
|
return (
|
|
<Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
|
|
<KeyboardAvoidingView
|
|
style={styles.overlay}
|
|
behavior="padding"
|
|
>
|
|
<View style={[styles.sheet, { backgroundColor: theme.sheetBg }]}>
|
|
<View style={styles.header}>
|
|
<Text style={[styles.title, { color: theme.text }]}>Backend URL</Text>
|
|
<TouchableOpacity onPress={onClose} style={styles.closeButton} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
|
|
<Svg width={18} height={18} viewBox="0 0 24 24">
|
|
<Path d="M18 6L6 18M6 6l12 12" stroke={theme.textMuted} strokeWidth={2} strokeLinecap="round" />
|
|
</Svg>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<Text style={[styles.label, { color: theme.textSecondary }]}>API base URL</Text>
|
|
<TextInput
|
|
style={[
|
|
styles.input,
|
|
{ backgroundColor: theme.inputBg, borderColor: theme.borderStrong, color: theme.text },
|
|
]}
|
|
placeholder={DEFAULT_API_BASE_URL}
|
|
placeholderTextColor={theme.textMuted}
|
|
value={value}
|
|
onChangeText={setValue}
|
|
autoFocus
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
keyboardType="url"
|
|
/>
|
|
<Text style={[styles.hint, { color: theme.textMuted }]}>
|
|
Include the /api suffix, e.g. https://example.com/api
|
|
</Text>
|
|
|
|
<View style={styles.actions}>
|
|
<TouchableOpacity
|
|
style={[styles.cancelButton, { borderColor: theme.borderStrong }]}
|
|
onPress={onClose}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={[styles.cancelButtonText, { color: theme.textSecondary }]}>Cancel</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[styles.saveButton, { backgroundColor: theme.accent }, !isValid && styles.saveButtonDisabled]}
|
|
onPress={handleSave}
|
|
disabled={!isValid}
|
|
activeOpacity={0.8}
|
|
>
|
|
<Text style={[styles.saveButtonText, { color: theme.accentText }]}>Save</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</KeyboardAvoidingView>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
overlay: {
|
|
flex: 1,
|
|
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
padding: 24,
|
|
},
|
|
sheet: {
|
|
width: '100%',
|
|
maxWidth: 380,
|
|
borderRadius: 16,
|
|
padding: 20,
|
|
gap: 8,
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 4,
|
|
},
|
|
title: {
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
},
|
|
closeButton: {
|
|
padding: 4,
|
|
},
|
|
label: {
|
|
fontSize: 13,
|
|
fontWeight: '600',
|
|
marginTop: 8,
|
|
},
|
|
input: {
|
|
height: 48,
|
|
paddingHorizontal: 14,
|
|
borderRadius: 12,
|
|
borderWidth: 1,
|
|
fontSize: 16,
|
|
},
|
|
hint: {
|
|
fontSize: 12,
|
|
},
|
|
actions: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
marginTop: 16,
|
|
},
|
|
cancelButton: {
|
|
flex: 1,
|
|
paddingVertical: 12,
|
|
borderRadius: 12,
|
|
borderWidth: 1,
|
|
alignItems: 'center',
|
|
},
|
|
cancelButtonText: {
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
},
|
|
saveButton: {
|
|
flex: 1,
|
|
paddingVertical: 12,
|
|
borderRadius: 12,
|
|
alignItems: 'center',
|
|
},
|
|
saveButtonDisabled: {
|
|
opacity: 0.5,
|
|
},
|
|
saveButtonText: {
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
},
|
|
});
|