97 lines
3.4 KiB
TypeScript

import { useEffect, useState } from 'react';
import { View, Text, Pressable, 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>
<Pressable
onPress={() => router.replace('/signin')}
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>
</Pressable>
</>
)}
</View>
</SafeAreaView>
);
}