Alle <Pressable style={({pressed}) => ({...})}> ersetzt — style-Funktion
droppt auf Android (New Arch) intermittierend width/height, führt zu 0×0
unsichtbaren Elementen. TouchableOpacity mit activeOpacity ist stabil.
Außerdem übrige Pressables (plain style) aus components/ und app/
migriert sowie zwei überschüssige </View>-Tags in chat.tsx + RoomCard.tsx
entfernt die TS-Fehler verursacht haben.
64 Dateien, typecheck sauber.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { View, Text, TouchableOpacity, ActivityIndicator } from 'react-native';
|
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { supabase } from '../../lib/supabase';
|
|
import { useAuthStore } from '../../stores/auth';
|
|
|
|
/**
|
|
* Deep-link landing screen for email-confirmation OAuth callbacks.
|
|
* Invoked when the system opens rebreak://auth/confirm?access_token=...&refresh_token=...
|
|
*
|
|
* Strategy: extract tokens from URL params (Expo Router surfaces them),
|
|
* call setSession, then route to app. If params are missing, poll getSession()
|
|
* (covers the case where supabase-js processed the hash before we got here).
|
|
*/
|
|
export default function ConfirmScreen() {
|
|
const router = useRouter();
|
|
const { t } = useTranslation();
|
|
const params = useLocalSearchParams<{
|
|
access_token?: string;
|
|
refresh_token?: string;
|
|
error?: string;
|
|
error_description?: string;
|
|
}>();
|
|
|
|
const [statusMsg, setStatusMsg] = useState(t('auth.confirming'));
|
|
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
handleConfirm();
|
|
}, []);
|
|
|
|
async function handleConfirm() {
|
|
if (params.error) {
|
|
setErrorMsg(params.error_description ?? params.error ?? t('auth.confirmFailed'));
|
|
return;
|
|
}
|
|
|
|
if (params.access_token && params.refresh_token) {
|
|
const { data, error } = await supabase.auth.setSession({
|
|
access_token: params.access_token,
|
|
refresh_token: params.refresh_token,
|
|
});
|
|
if (error) {
|
|
setErrorMsg(error.message);
|
|
return;
|
|
}
|
|
useAuthStore.setState({ session: data.session, user: data.session?.user ?? null });
|
|
setStatusMsg(t('auth.confirmSuccess'));
|
|
router.replace('/(app)');
|
|
return;
|
|
}
|
|
|
|
// Fallback: poll up to 20s for a session (supabase-js may have processed hash already)
|
|
const maxWait = 20_000;
|
|
const interval = 500;
|
|
const start = Date.now();
|
|
|
|
while (Date.now() - start < maxWait) {
|
|
const { data } = await supabase.auth.getSession();
|
|
if (data.session) {
|
|
useAuthStore.setState({ session: data.session, user: data.session.user });
|
|
setStatusMsg(t('auth.confirmSuccess'));
|
|
router.replace('/(app)');
|
|
return;
|
|
}
|
|
await new Promise((r) => setTimeout(r, interval));
|
|
}
|
|
|
|
setErrorMsg(t('auth.confirmTimeout'));
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView className="flex-1 bg-white">
|
|
<View className="flex-1 items-center justify-center px-6">
|
|
{!errorMsg ? (
|
|
<>
|
|
<ActivityIndicator color="#f59e0b" size="large" className="mb-4" />
|
|
<Text className="text-neutral-500 text-sm text-center" style={{ fontFamily: 'Nunito_400Regular' }}>{statusMsg}</Text>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Text className="text-red-600 text-sm text-center mb-6" style={{ fontFamily: 'Nunito_400Regular' }}>{errorMsg}</Text>
|
|
<TouchableOpacity
|
|
onPress={() => router.replace('/signin')}
|
|
activeOpacity={0.7}
|
|
className="bg-neutral-100 border border-neutral-200 px-6 py-3 rounded-xl"
|
|
>
|
|
<Text className="text-neutral-800 text-sm" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.backToLoginPlain')}</Text>
|
|
</TouchableOpacity>
|
|
</>
|
|
)}
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|