import { Link, router } from "expo-router"; import * as Linking from "expo-linking"; import { useEffect, useState } from "react"; import { KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, View, } from "react-native"; import { AuthBackground } from "@/components/AppBackground"; import { AuthServerPicker } from "@/components/AuthServerPicker"; import { HeadingText, Logo } from "@/components/Logo"; import { FullScreen } from "@/components/Screen"; import { Button } from "@/components/ui/Button"; import { Card } from "@/components/ui/Card"; import { Input } from "@/components/ui/Input"; import { fonts, spacing } from "@/constants/theme"; import { useAccounts } from "@/contexts/AccountsContext"; import { useAuthClient } from "@/contexts/AuthContext"; import { useAppTheme } from "@/contexts/ThemeContext"; import { fetchAuthCapabilities } from "@/lib/auth-capabilities"; import { signInWithAuthentik } from "@/lib/auth-oauth"; import { completeSignInAfterAuth } from "@/lib/complete-sign-in"; import { isRequiredString, isValidEmail, useFieldVisibility } from "@/lib/form-validation"; export default function SignInScreen() { const authClient = useAuthClient(); const { apiUrl, activeAccountId, registerAccount } = useAccounts(); const { colors } = useAppTheme(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [serverReady, setServerReady] = useState(true); const [authentikEnabled, setAuthentikEnabled] = useState(false); const [signupsDisabled, setSignupsDisabled] = useState(false); const { touch, visible, markSubmitted } = useFieldVisibility(); useEffect(() => { let cancelled = false; void fetchAuthCapabilities(apiUrl).then((capabilities) => { if (cancelled) return; setAuthentikEnabled(capabilities.authentik); setSignupsDisabled(capabilities.signupsDisabled); }); return () => { cancelled = true; }; }, [apiUrl]); const emailValidationError = !email.trim() ? "Email is required" : isValidEmail(email) ? undefined : "Enter a valid email"; const passwordValidationError = password.trim() ? undefined : "Password is required"; const canSignIn = isValidEmail(email) && isRequiredString(password) && serverReady; async function finishSignIn() { const completed = await completeSignInAfterAuth(authClient, { apiUrl, activeAccountId, registerAccount, }); if (!completed) { setError("Signed in but session was not available. Try again."); } } async function handleSignIn() { markSubmitted(); if (!canSignIn) return; setError(null); setLoading(true); try { const { error: signInError } = await authClient.signIn.email({ email: email.trim(), password, }); if (signInError) { const message = signInError.message ?? ""; if (message.toLowerCase().includes("internal") || message.includes("500")) { setError("Server error — is the API running with Postgres? Check beenvoice dev + docker."); } else { setError(message || "Invalid email or password"); } return; } await finishSignIn(); } finally { setLoading(false); } } async function handleAuthentikSignIn() { if (!serverReady) return; setError(null); setLoading(true); try { const { error: oauthError } = await signInWithAuthentik( authClient, Linking.createURL("/"), ); if (oauthError) { setError(oauthError.message ?? "Could not sign in with Authentik"); return; } await finishSignIn(); } finally { setLoading(false); } } return ( Welcome back Sign in to manage invoices on the go {signupsDisabled ? ( New account registration is currently disabled on this server. ) : null} {authentikEnabled ? (