Files
carry-your-live/carry-your-live/src/components/ServerUrlModal.tsx
T

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',
},
});