fix(mobile/login): one-tap public pairing, manual URL/key behind Advanced

Pairing is automatic (App.tsx auto-connects to the public instance when no creds are
stored); the login screen only appears after an explicit Sign out. It defaulted to
localhost + empty key, forcing manual entry that no longer reflects how pairing works.
Now it prefills the public backend + shipped key (one-tap 'Connect to public instance')
and tucks the URL/API-key fields under an 'Advanced · self-hosted backend' toggle for
power users.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jtrzupek 2026-06-02 09:22:50 +02:00
parent bb5a97e288
commit 7981ba3408

View file

@ -12,6 +12,7 @@ import {
} from 'react-native'; } from 'react-native';
import { GoonClient } from '../api'; import { GoonClient } from '../api';
import { GoonMark, GoonWordmark } from '../components/GoonWordmark'; import { GoonMark, GoonWordmark } from '../components/GoonWordmark';
import { DEFAULT_API_KEY, DEFAULT_BACKEND_URL } from '../lib/backend';
import { saveCredentials } from '../storage'; import { saveCredentials } from '../storage';
import { theme } from '../theme'; import { theme } from '../theme';
@ -20,9 +21,14 @@ interface Props {
} }
export function LoginScreen({ onAuthenticated }: Props) { export function LoginScreen({ onAuthenticated }: Props) {
const [baseUrl, setBaseUrl] = useState('http://localhost:8000'); // Pairing jest domyślnie automatyczny (App.tsx auto-connectuje do publicznej instancji
const [apiKey, setApiKey] = useState(''); // gdy brak zapisanych creds). Ten ekran pojawia się tylko po explicit "Sign out" —
// prefillujemy publiczne defaulty, więc reconnect to JEDEN tap, bez wpisywania URL/klucza.
// Ręczne pola są pod "Advanced" dla self-hosterów.
const [baseUrl, setBaseUrl] = useState(DEFAULT_BACKEND_URL);
const [apiKey, setApiKey] = useState(DEFAULT_API_KEY);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [advanced, setAdvanced] = useState(false);
const [urlFocused, setUrlFocused] = useState(false); const [urlFocused, setUrlFocused] = useState(false);
const [keyFocused, setKeyFocused] = useState(false); const [keyFocused, setKeyFocused] = useState(false);
@ -67,10 +73,13 @@ export function LoginScreen({ onAuthenticated }: Props) {
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.cardLabel}>Connect</Text> <Text style={styles.cardLabel}>Connect</Text>
<Text style={styles.cardHint}> <Text style={styles.cardHint}>
Pair this device with your backend. Credentials are stored encrypted in {advanced
secure-storage on this device only. ? 'Point the app at your own self-hosted backend. Credentials are stored encrypted in secure-storage on this device only.'
: 'Connects to the public goon instance — no setup needed.'}
</Text> </Text>
{advanced && (
<>
<Text style={styles.label}>Backend URL</Text> <Text style={styles.label}>Backend URL</Text>
<TextInput <TextInput
style={[styles.input, urlFocused && styles.inputFocused]} style={[styles.input, urlFocused && styles.inputFocused]}
@ -80,7 +89,7 @@ export function LoginScreen({ onAuthenticated }: Props) {
onBlur={() => setUrlFocused(false)} onBlur={() => setUrlFocused(false)}
autoCapitalize="none" autoCapitalize="none"
keyboardType="url" keyboardType="url"
placeholder="http://localhost:8000" placeholder={DEFAULT_BACKEND_URL}
placeholderTextColor={theme.mutedDim} placeholderTextColor={theme.mutedDim}
/> />
@ -96,6 +105,8 @@ export function LoginScreen({ onAuthenticated }: Props) {
placeholder="paste your X-API-Key" placeholder="paste your X-API-Key"
placeholderTextColor={theme.mutedDim} placeholderTextColor={theme.mutedDim}
/> />
</>
)}
<Pressable <Pressable
style={({ pressed }) => [ style={({ pressed }) => [
@ -107,15 +118,34 @@ export function LoginScreen({ onAuthenticated }: Props) {
disabled={loading} disabled={loading}
> >
<Text style={styles.buttonText}> <Text style={styles.buttonText}>
{loading ? 'Connecting…' : 'Connect'} {loading ? 'Connecting…' : advanced ? 'Connect' : 'Connect to public instance'}
</Text>
</Pressable>
<Pressable
onPress={() => {
if (advanced) {
// Powrót do publicznej instancji — reset pól do defaultów.
setBaseUrl(DEFAULT_BACKEND_URL);
setApiKey(DEFAULT_API_KEY);
}
setAdvanced((v) => !v);
}}
hitSlop={8}
style={styles.advancedToggle}
>
<Text style={styles.advancedToggleText}>
{advanced ? '← Use public instance' : 'Advanced · self-hosted backend'}
</Text> </Text>
</Pressable> </Pressable>
</View> </View>
{advanced && (
<Text style={styles.footer}> <Text style={styles.footer}>
Need help? Check the <Text style={styles.footerEm}>X-API-Key</Text> in your Need help? Check the <Text style={styles.footerEm}>X-API-Key</Text> in your
backend .env file. backend .env file.
</Text> </Text>
)}
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
); );
@ -231,4 +261,12 @@ const styles = StyleSheet.create({
lineHeight: 18, lineHeight: 18,
}, },
footerEm: { color: theme.muted, fontWeight: '700' }, footerEm: { color: theme.muted, fontWeight: '700' },
advancedToggle: { marginTop: 16, alignItems: 'center' },
advancedToggleText: {
color: theme.muted,
fontSize: 12,
fontWeight: '600',
letterSpacing: 0.3,
},
}); });