import { useState, useEffect } from 'react'; import { Text, View, TextInput, StyleSheet, Animated, Pressable, ActivityIndicator } from "react-native"; import { Ionicons } from '@expo/vector-icons'; import { useAuth } from '../context/AuthContext'; /** * LoginForm component handles user authentication with a Portainer instance. * It includes a multi-step form that first validates the domain URL, * then allows entering username/password credentials. */ export default function LoginForm() { // Form state and animation values const [formData, setFormData] = useState({ username: '', password: '', domain: '' }); const [isDomainValid, setIsDomainValid] = useState(false); const [isLoading, setIsLoading] = useState(false); const buttonScale = useState(new Animated.Value(1))[0]; const buttonRotation = useState(new Animated.Value(0))[0]; const fadeAnim = useState(new Animated.Value(0))[0]; const { setAuthState } = useAuth(); /** * Validates domain input and animates the login form appearance * when a valid domain is entered */ useEffect(() => { // Basic FQDN validation const fqdnRegex = /^(?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?::\d+)?(?:\/\S*)?$/; const isValid = fqdnRegex.test(formData.domain); setIsDomainValid(isValid); // Fade animation Animated.timing(fadeAnim, { toValue: isValid ? 1 : 0, duration: 500, useNativeDriver: true }).start(); }, [formData.domain]); /** * Handles the login form submission and authentication process * Includes success/failure animations for visual feedback */ const handleLogin = async () => { setIsLoading(true); try { const response = await fetch(`${formData.domain}/api/auth`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }, credentials: 'include', // Include cookies if needed body: JSON.stringify({ username: formData.username, password: formData.password, }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); // Store the domain and auth data await setAuthState(formData.domain, formData.username, data); // Success animation sequence: rotate -> scale up -> scale back Animated.sequence([ Animated.timing(buttonRotation, { toValue: 1, duration: 400, useNativeDriver: true }), Animated.spring(buttonScale, { toValue: 1.1, useNativeDriver: true }), Animated.spring(buttonScale, { toValue: 1, useNativeDriver: true }) ]).start(); } catch (error) { console.error('Login error:', error); } finally { setIsLoading(false); } }; return ( {/* Domain input section */} Please enter your Portainer domain Domain must be HTTPS setFormData({ ...formData, domain: text })} /> {/* Login credentials section - appears after valid domain */} Please enter your username and password setFormData({ ...formData, username: text })} /> setFormData({ ...formData, password: text })} /> {/* Animated login button */} { if (!isLoading) { // Button press animation Animated.sequence([ Animated.spring(buttonScale, { toValue: 0.95, useNativeDriver: true }), Animated.spring(buttonScale, { toValue: 1, useNativeDriver: true }) ]).start(); handleLogin(); } }} style={styles.loginButton} > {isLoading ? ( ) : ( <> Login )} ); } /** * Component styles * Includes styling for container, form inputs, buttons, and animations */ const styles = StyleSheet.create({ container: { width: '100%', padding: 10, backgroundColor: '#ffffff', borderRadius: 10, shadowColor: "#000", shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, }, loginSection: { marginTop: 20, paddingTop: 20, borderTopWidth: 1, borderTopColor: '#E9ECEF' }, domainInput: { backgroundColor: '#EDF2F7', borderRadius: 8, padding: 15, borderWidth: 1, borderColor: '#CBD5E0', fontSize: 16, color: '#4A5568', fontWeight: '500' }, title: { fontSize: 18, fontWeight: '600', color: '#2C3E50', marginBottom: 20, textAlign: 'center' }, input: { backgroundColor: '#F8F9FA', borderRadius: 8, padding: 15, marginBottom: 15, borderWidth: 1, borderColor: '#E9ECEF', fontSize: 16, color: '#2C3E50' }, loginButton: { backgroundColor: '#4299E1', borderRadius: 8, padding: 15, marginTop: 10, shadowColor: "#4299E1", shadowOffset: { width: 0, height: 4, }, shadowOpacity: 0.3, shadowRadius: 4.65, elevation: 8, }, buttonContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, buttonIcon: { marginRight: 8, }, buttonText: { color: '#fff', fontSize: 16, marginLeft: 8, fontWeight: '600' }, infoText: { fontSize: 14, color: '#718096', marginBottom: 15, textAlign: 'center', fontStyle: 'italic' }, });