// apps/mobile/app/login.tsx // OAuth 로그인 화면 — @d3ro/ui-native 사용 import { useState } from 'react' import { View, StyleSheet, Alert } from 'react-native' import * as WebBrowser from 'expo-web-browser' import * as Linking from 'expo-linking' import { MetalCard, PhosphorText, PhysicalButton, d3roNativePalette } from '@d3ro/ui-native' import { supabase, isSupabaseConfigured } from '../lib/supabase' WebBrowser.maybeCompleteAuthSession() export default function LoginScreen(): React.ReactElement { const [busy, setBusy] = useState(false) const configured = isSupabaseConfigured() async function signInWithProvider(provider: 'google' | 'github'): Promise { if (!configured) { Alert.alert('미설정', 'Supabase 환경변수가 설정되지 않았습니다.') return } setBusy(true) try { const redirectTo = Linking.createURL('auth-callback') const { data, error } = await supabase.auth.signInWithOAuth({ provider, options: { redirectTo, skipBrowserRedirect: true } }) if (error || !data.url) { Alert.alert('로그인 실패', error?.message ?? 'OAuth URL을 받지 못했습니다') return } const result = await WebBrowser.openAuthSessionAsync(data.url, redirectTo) if (result.type === 'success' && result.url) { const url = new URL(result.url) const code = url.searchParams.get('code') if (code) { const { error: exchangeErr } = await supabase.auth.exchangeCodeForSession(code) if (exchangeErr) { Alert.alert('세션 교환 실패', exchangeErr.message) } } } } finally { setBusy(false) } } return ( D3RO VOICE AI 음성 어시스턴트 {!configured && ( Supabase가 설정되지 않았습니다. app.json의 extra 필드를 확인하세요. )} void signInWithProvider('google')} /> void signInWithProvider('github')} /> ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: d3roNativePalette.bg.app, justifyContent: 'center', padding: 24 }, card: { padding: 32 }, header: { alignItems: 'center', marginBottom: 32 }, subtitle: { marginTop: 8 }, warningBox: { borderWidth: 1, borderColor: d3roNativePalette.tag.orange, padding: 12, borderRadius: 8, marginBottom: 16 }, buttons: { gap: 12 } })