// apps/mobile/app/login.tsx // Login screen — D3RO styled // Design ref: docs/v3/designs/login.html import { useState } from 'react' import { View, TextInput, StyleSheet, Alert, ScrollView, Pressable, Platform } from 'react-native' import * as WebBrowser from 'expo-web-browser' import * as Linking from 'expo-linking' import { d3roNativePalette, d3roNativeFonts, Led, PhosphorText } from '@d3ro/ui-native' import { useI18n } from '@d3ro/i18n' import { supabase, isSupabaseConfigured } from '../lib/supabase' import { useAuth } from '../lib/auth-context' import { useRouter } from 'expo-router' WebBrowser.maybeCompleteAuthSession() export default function LoginScreen(): React.ReactElement { const { t } = useI18n() const [busy, setBusy] = useState(false) const [email, setEmail] = useState('') const [password, setPassword] = useState('') const configured = isSupabaseConfigured() const { devBypass } = useAuth() const router = useRouter() function handleDevSkip(): void { devBypass() router.replace('/(tabs)/dash') } async function signInWithProvider(provider: 'google' | 'github' | 'apple'): Promise { if (!configured) { Alert.alert('Not Configured', t('mobile.login.notConfigured')) 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('Login Failed', error?.message ?? 'Could not get 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('Session Error', exchangeErr.message) } } } } finally { setBusy(false) } } async function signInWithEmail(): Promise { if (!configured) { Alert.alert('Not Configured', t('mobile.login.notConfigured')) return } if (!email.trim() || !password.trim()) return setBusy(true) try { const { error } = await supabase.auth.signInWithPassword({ email, password }) if (error) { Alert.alert('Login Failed', error.message) } } finally { setBusy(false) } } return ( {/* Logo & Branding */} {t('mobile.login.title')} {t('mobile.login.subtitle')} {/* OAuth Buttons */} [styles.oauthBtn, pressed && styles.oauthBtnPressed]} onPress={() => void signInWithProvider('google')} disabled={busy} > G {t('mobile.login.google')} [styles.oauthBtn, pressed && styles.oauthBtnPressed]} onPress={() => void signInWithProvider('apple')} disabled={busy} > {'\uF8FF'} {t('mobile.login.apple')} [styles.oauthBtn, pressed && styles.oauthBtnPressed]} onPress={() => void signInWithProvider('github')} disabled={busy} > {'\u2318'} {t('mobile.login.github')} {/* Divider */} {t('mobile.login.or')} {/* Email/Password Fields */} {t('mobile.login.email')} {t('mobile.login.password')} {/* Login Button */} [styles.loginBtn, pressed && { opacity: 0.9 }]} onPress={() => void signInWithEmail()} disabled={busy} > {t('mobile.login.signIn')} {/* Sign Up Link */} {t('mobile.login.noAccount')} {t('mobile.login.signUp')} {/* DEV Skip */} {__DEV__ && ( {'\u26A1'} DEV SKIP LOGIN )} {/* Supabase Warning */} {!configured && ( {'\u26A0'} {t('mobile.login.notConfigured')} )} ) } const P = d3roNativePalette const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: P.bg.app }, content: { flexGrow: 1, justifyContent: 'center', paddingHorizontal: 24, paddingBottom: 40, paddingTop: 60 }, branding: { alignItems: 'center', marginBottom: 40 }, leds: { flexDirection: 'row', gap: 10, marginBottom: 20 }, titleText: { letterSpacing: 5 }, subtitleText: { marginTop: 8, letterSpacing: 2 }, oauthSection: { gap: 12, marginBottom: 24 }, oauthBtn: { backgroundColor: P.bg.card, borderWidth: 1, borderColor: P.border.default, borderRadius: 12, paddingVertical: 14, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12 }, oauthBtnPressed: { backgroundColor: P.bg.cardHover, borderColor: 'rgba(113,113,122,0.4)' }, oauthLabel: { fontFamily: d3roNativeFonts.sans }, appleIcon: { fontSize: 18 }, divider: { flexDirection: 'row', alignItems: 'center', gap: 16, marginBottom: 24 }, dividerLine: { flex: 1, height: 1, backgroundColor: P.border.default }, dividerText: { letterSpacing: 2 }, formSection: { gap: 16, marginBottom: 24 }, fieldLabel: { letterSpacing: 4, marginBottom: 6, marginLeft: 4 }, input: { backgroundColor: P.bg.inset, borderWidth: 1, borderColor: P.border.default, borderRadius: 12, paddingVertical: 14, paddingHorizontal: 16, fontSize: 14, color: P.text.primary, fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto' }, loginBtn: { backgroundColor: P.accent.amber, borderRadius: 12, paddingVertical: 14, alignItems: 'center', shadowColor: P.accent.amber, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.3, shadowRadius: 10, elevation: 6, marginBottom: 24 }, loginBtnText: { color: P.bg.app, letterSpacing: 1 }, signupRow: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 6, marginBottom: 16 }, warningBox: { borderWidth: 1, borderColor: 'rgba(255,92,53,0.3)', backgroundColor: P.accent.amberDim, padding: 12, borderRadius: 8, marginTop: 8 }, warningText: { textAlign: 'center' }, devSkipBtn: { borderWidth: 1, borderColor: P.accent.green, borderRadius: 8, paddingVertical: 10, alignItems: 'center', marginBottom: 12, backgroundColor: 'rgba(74, 222, 128, 0.08)' }, devSkipText: { color: P.accent.green, letterSpacing: 2 } })