From 3313dd84f3bac1ce793547efb99411a2558beca5 Mon Sep 17 00:00:00 2001 From: byGalax Date: Wed, 13 May 2026 23:58:52 +0200 Subject: [PATCH] feat(mobile): auth callback screen consumes magic-link tokens --- apps/mobile/app/auth/callback.tsx | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 apps/mobile/app/auth/callback.tsx diff --git a/apps/mobile/app/auth/callback.tsx b/apps/mobile/app/auth/callback.tsx new file mode 100644 index 0000000..6feb03c --- /dev/null +++ b/apps/mobile/app/auth/callback.tsx @@ -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(''); + + 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 ( + + {status === 'working' ? ( + <> + + Du wirst angemeldet… + + ) : ( + <> + Anmeldung fehlgeschlagen + {message} + + )} + + ); +} + +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' }, +});