feat(mobile): auth callback screen consumes magic-link tokens
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { supabase } from '../../lib/supabase';
|
||||||
|
import { colors } from '../../theme/colors';
|
||||||
|
|
||||||
|
// Supabase magic-link emails redirect to netralax://auth/callback with
|
||||||
|
// the tokens in either the URL fragment (#access_token=...&refresh_token=...)
|
||||||
|
// or the query string depending on the provider. Expo Router parses the
|
||||||
|
// query string into useLocalSearchParams. The hash portion would require
|
||||||
|
// expo-linking; we accept both shapes for safety.
|
||||||
|
export default function AuthCallback() {
|
||||||
|
const params = useLocalSearchParams<{
|
||||||
|
access_token?: string;
|
||||||
|
refresh_token?: string;
|
||||||
|
error?: string;
|
||||||
|
error_description?: string;
|
||||||
|
}>();
|
||||||
|
const router = useRouter();
|
||||||
|
const [status, setStatus] = useState<'working' | 'error'>('working');
|
||||||
|
const [message, setMessage] = useState<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void (async () => {
|
||||||
|
if (params.error) {
|
||||||
|
setStatus('error');
|
||||||
|
setMessage(params.error_description ?? params.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!params.access_token || !params.refresh_token) {
|
||||||
|
setStatus('error');
|
||||||
|
setMessage('Magic-Link-URL enthielt keine Tokens.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { error } = await supabase.auth.setSession({
|
||||||
|
access_token: params.access_token,
|
||||||
|
refresh_token: params.refresh_token,
|
||||||
|
});
|
||||||
|
if (error) {
|
||||||
|
setStatus('error');
|
||||||
|
setMessage(error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.replace('/(app)/chats');
|
||||||
|
})();
|
||||||
|
}, [params, router]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{status === 'working' ? (
|
||||||
|
<>
|
||||||
|
<ActivityIndicator color={colors.accent} size="large" />
|
||||||
|
<Text style={styles.text}>Du wirst angemeldet…</Text>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text style={styles.errorTitle}>Anmeldung fehlgeschlagen</Text>
|
||||||
|
<Text style={styles.text}>{message}</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: colors.bg,
|
||||||
|
padding: 24,
|
||||||
|
gap: 16,
|
||||||
|
},
|
||||||
|
text: { color: colors.textMuted, fontSize: 14, textAlign: 'center' },
|
||||||
|
errorTitle: { color: colors.danger, fontSize: 18, fontWeight: '600' },
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user