- Replace hardcoded Bearer token default with empty string - Remove auto-save in useEffect that wrote defaults to SecureStore without user consent on first app launch Fixes #2
145 lines
4.9 KiB
TypeScript
145 lines
4.9 KiB
TypeScript
import React, { useEffect, useState } from 'react'
|
|
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, ScrollView } from 'react-native'
|
|
import * as SecureStore from 'expo-secure-store'
|
|
import { ApiConfig } from '../types'
|
|
|
|
const CONFIG_KEY = 'weegschaal_api_config'
|
|
|
|
async function loadConfig(): Promise<ApiConfig | null> {
|
|
try {
|
|
const json = await SecureStore.getItemAsync(CONFIG_KEY)
|
|
return json ? JSON.parse(json) : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function saveConfig(config: ApiConfig): Promise<void> {
|
|
await SecureStore.setItemAsync(CONFIG_KEY, JSON.stringify(config))
|
|
}
|
|
|
|
async function deleteConfig(): Promise<void> {
|
|
await SecureStore.deleteItemAsync(CONFIG_KEY)
|
|
}
|
|
|
|
export async function getApiConfig(): Promise<ApiConfig | null> {
|
|
return loadConfig()
|
|
}
|
|
|
|
export default function SettingsScreen() {
|
|
const [url, setUrl] = useState('https://weegschaal.eddydevink.nl')
|
|
const [token, setToken] = useState('')
|
|
const [showToken, setShowToken] = useState(false)
|
|
const [syncing, setSyncing] = useState(false)
|
|
|
|
useEffect(() => {
|
|
loadConfig().then(c => {
|
|
if (c) {
|
|
setUrl(c.url)
|
|
setToken(c.token)
|
|
}
|
|
})
|
|
}, [])
|
|
|
|
const handleSave = async () => {
|
|
await saveConfig({ url, token })
|
|
Alert.alert('Opgeslagen', 'API instellingen zijn opgeslagen')
|
|
}
|
|
|
|
const handleClear = () => {
|
|
Alert.alert('Instellingen wissen?', 'API configuratie definitief verwijderen?', [
|
|
{ text: 'Annuleren', style: 'cancel' },
|
|
{
|
|
text: 'Ja, wissen',
|
|
style: 'destructive',
|
|
onPress: async () => {
|
|
await deleteConfig()
|
|
setUrl('')
|
|
setToken('')
|
|
},
|
|
},
|
|
])
|
|
}
|
|
|
|
const handleSync = async () => {
|
|
setSyncing(true)
|
|
try {
|
|
const { syncAll } = await import('../sync/syncService')
|
|
const result = await syncAll()
|
|
let msg = `Opgehaald: ${result.pulled}, gepusht: ${result.pushed}, verwijderd: ${result.deleted}`
|
|
if (result.errors.length > 0) {
|
|
msg += `\n\nFouten: ${result.errors.join(', ')}`
|
|
}
|
|
Alert.alert('Sync voltooid', msg)
|
|
} catch (e: any) {
|
|
Alert.alert('Sync fout', e?.message ?? 'Onbekende fout')
|
|
} finally {
|
|
setSyncing(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
|
<Text style={styles.sectionTitle}>REST API</Text>
|
|
|
|
<Text style={styles.label}>API URL</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={url}
|
|
onChangeText={setUrl}
|
|
placeholder="https://weegschaal.eddydevink.nl"
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
/>
|
|
|
|
<Text style={styles.label}>Token</Text>
|
|
<View style={styles.passwordRow}>
|
|
<TextInput
|
|
style={styles.passwordInput}
|
|
value={token}
|
|
onChangeText={setToken}
|
|
secureTextEntry={!showToken}
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
/>
|
|
<TouchableOpacity style={styles.eyeButton} onPress={() => setShowToken(!showToken)}>
|
|
<Text style={styles.eyeIcon}>{showToken ? '👁' : '🔒'}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<TouchableOpacity style={styles.saveButton} onPress={handleSave}>
|
|
<Text style={styles.buttonText}>Instellingen opslaan</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity style={styles.clearButton} onPress={handleClear}>
|
|
<Text style={styles.clearButtonText} numberOfLines={1}>Instellingen wissen</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
style={[styles.syncButton, syncing && styles.syncButtonDisabled]}
|
|
onPress={handleSync}
|
|
disabled={syncing}
|
|
>
|
|
<Text style={styles.buttonText}>{syncing ? 'Bezig met sync...' : 'Nu synchroniseren'}</Text>
|
|
</TouchableOpacity>
|
|
</ScrollView>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: '#f5f5f5' },
|
|
content: { padding: 16 },
|
|
sectionTitle: { fontSize: 18, fontWeight: '700', marginBottom: 16 },
|
|
label: { fontSize: 14, fontWeight: '600', color: '#333', marginBottom: 4, marginTop: 12 },
|
|
input: { backgroundColor: '#fff', borderRadius: 8, padding: 12, fontSize: 16, borderWidth: 1, borderColor: '#ddd' },
|
|
passwordRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 8, borderWidth: 1, borderColor: '#ddd' },
|
|
passwordInput: { flex: 1, padding: 12, fontSize: 16 },
|
|
eyeButton: { padding: 12 },
|
|
eyeIcon: { fontSize: 20 },
|
|
saveButton: { backgroundColor: '#4CAF50', padding: 14, borderRadius: 8, alignItems: 'center', marginTop: 24 },
|
|
clearButton: { padding: 14, borderRadius: 8, alignItems: 'center', alignSelf: 'stretch', marginTop: 8 },
|
|
clearButtonText: { color: '#e53935', fontSize: 14 },
|
|
syncButton: { backgroundColor: '#2196F3', padding: 14, borderRadius: 8, alignItems: 'center', marginTop: 24 },
|
|
syncButtonDisabled: { opacity: 0.6 },
|
|
buttonText: { color: '#fff', fontSize: 16, fontWeight: '700' },
|
|
})
|