atualizacao

This commit is contained in:
2026-04-23 10:42:46 +01:00
parent bba6d2de08
commit 8bf4fac5ed

View File

@@ -3,34 +3,40 @@ import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator, Animated,
ScrollView, StatusBar,
StyleSheet, Text, TextInput, TouchableOpacity, View
ActivityIndicator,
Animated,
Dimensions,
KeyboardAvoidingView,
Platform,
StatusBar,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from '../../themecontext';
import { supabase } from '../lib/supabase';
const { width } = Dimensions.get('window');
export default function PerfilAluno() {
const { isDarkMode } = useTheme();
const router = useRouter();
const insets = useSafeAreaInsets();
// --- ESTADOS ---
const [loading, setLoading] = useState(true);
const [isEditing, setIsEditing] = useState(false);
const [perfil, setPerfil] = useState<any>(null);
const [estagio, setEstagio] = useState<any>(null);
const [saving, setSaving] = useState(false);
// --- ANIMAÇÕES ---
const [alertConfig, setAlertConfig] = useState<{ msg: string, type: 'success' | 'error' | 'info' } | null>(null);
const alertOpacity = useMemo(() => new Animated.Value(0), []);
const showAlert = useCallback((msg: string, type: 'success' | 'error' | 'info' = 'info') => {
setAlertConfig({ msg, type });
Animated.sequence([
Animated.timing(alertOpacity, { toValue: 1, duration: 300, useNativeDriver: true }),
Animated.delay(3000),
Animated.timing(alertOpacity, { toValue: 0, duration: 300, useNativeDriver: true })
]).start(() => setAlertConfig(null));
}, []);
const fadeAnim = useMemo(() => new Animated.Value(0), []);
const azulPetroleo = '#2390a6';
@@ -45,20 +51,19 @@ export default function PerfilAluno() {
vermelho: '#EF4444',
borda: isDarkMode ? '#2D2D2D' : '#E2E8F0',
verde: '#10B981',
sombra: isDarkMode ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0.05)',
}), [isDarkMode]);
const formatarParaExibir = (data: string) => {
if (!data) return '';
const [ano, mes, dia] = data.split('-');
return `${dia}-${mes}-${ano}`;
};
const formatarParaSalvar = (data: string) => {
if (!data || data.length < 10) return null;
const [dia, mes, ano] = data.split('-');
return `${ano}-${mes}-${dia}`;
};
const showAlert = useCallback((msg: string, type: 'success' | 'error' | 'info' = 'info') => {
setAlertConfig({ msg, type });
Animated.sequence([
Animated.timing(alertOpacity, { toValue: 1, duration: 400, useNativeDriver: true }),
Animated.delay(3000),
Animated.timing(alertOpacity, { toValue: 0, duration: 400, useNativeDriver: true })
]).start(() => setAlertConfig(null));
}, []);
// --- LÓGICA DE FORMATAÇÃO ---
const aplicarMascaraData = (text: string) => {
const cleaned = text.replace(/\D/g, '');
let formatted = cleaned;
@@ -67,214 +72,314 @@ export default function PerfilAluno() {
return formatted;
};
const carregarDados = async () => {
// --- CARREGAMENTO DE DADOS ---
const buscarDados = async () => {
try {
setLoading(true);
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data: profile, error } = await supabase
// Consulta à Tabela Profiles
const { data: pData, error: pError } = await supabase
.from('profiles')
.select('*')
.eq('id', user.id)
.single();
if (pError) throw pError;
if (error) throw error;
if (profile?.data_nascimento) {
profile.data_nascimento = formatarParaExibir(profile.data_nascimento);
// Consulta à Tabela Alunos
let aData = {};
if (pData.n_escola) {
const { data: alunoRes } = await supabase
.from('alunos')
.select('ano, n_escola, nome, turma_curso')
.eq('n_escola', pData.n_escola)
.single();
if (alunoRes) aData = alunoRes;
}
setPerfil({ ...profile, email: user.email });
} catch (e) {
showAlert('Erro ao carregar perfil.', 'error');
// Consulta à Tabela Estágios
const { data: eData } = await supabase
.from('estagios')
.select('*, empresas(nome, tutor_nome)')
.eq('aluno_id', user.id)
.single();
setPerfil({ ...pData, ...aData, email: user.email });
setEstagio(eData);
Animated.timing(fadeAnim, { toValue: 1, duration: 600, useNativeDriver: true }).start();
} catch (err) {
showAlert('Erro ao sincronizar com a base de dados.', 'error');
} finally {
setLoading(false);
}
};
useEffect(() => { carregarDados(); }, []);
useEffect(() => { buscarDados(); }, []);
const salvarPerfil = async () => {
const salvarDados = async () => {
try {
const dataBD = formatarParaSalvar(perfil.data_nascimento);
setSaving(true);
const { error } = await supabase.from('profiles').update({
nome: perfil.nome,
telefone: perfil.telefone,
residencia: perfil.residencia,
data_nascimento: dataBD,
curso: perfil.curso,
n_escola: perfil.n_escola
idade: perfil.idade,
data_nascimento: perfil.data_nascimento
}).eq('id', perfil.id);
if (error) throw error;
setIsEditing(false);
showAlert('Perfil guardado!', 'success');
showAlert('Perfil atualizado!', 'success');
buscarDados();
} catch (e) {
showAlert('Erro ao salvar. Verifica os campos.', 'error');
showAlert('Falha ao guardar alterações.', 'error');
} finally {
setSaving(false);
}
};
const terminarSessao = async () => {
await supabase.auth.signOut();
router.replace('/');
};
if (loading) return <View style={[styles.centered, { backgroundColor: cores.fundo }]}><ActivityIndicator size="large" color={cores.azul} /></View>;
if (loading) {
return (
<View style={[styles.centered, { backgroundColor: cores.fundo }]}>
<ActivityIndicator size="large" color={cores.azul} />
</View>
);
}
return (
<View style={{ flex: 1, backgroundColor: cores.fundo }}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} translucent backgroundColor="transparent" />
<KeyboardAvoidingView
style={{ flex: 1, backgroundColor: cores.fundo }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
{alertConfig && (
<Animated.View style={[styles.alertBar, { opacity: alertOpacity, backgroundColor: alertConfig.type === 'error' ? cores.vermelho : alertConfig.type === 'success' ? cores.verde : cores.azul, top: insets.top + 10 }]}>
<Ionicons name={alertConfig.type === 'error' ? "alert-circle" : "checkmark-circle"} size={20} color="#fff" />
<Animated.View style={[styles.alert, {
opacity: alertOpacity,
backgroundColor: alertConfig.type === 'error' ? cores.vermelho : cores.verde,
top: insets.top + 10
}]}>
<Ionicons name="information-circle" size={20} color="#fff" />
<Text style={styles.alertText}>{alertConfig.msg}</Text>
</Animated.View>
)}
<SafeAreaView style={styles.safe} edges={['top']}>
<View style={styles.topBar}>
<TouchableOpacity style={[styles.backBtn, { backgroundColor: cores.card }]} onPress={() => router.back()}>
<SafeAreaView style={{ flex: 1 }} edges={['top']}>
<View style={styles.headerContainer}>
<TouchableOpacity
style={[styles.roundBtn, { backgroundColor: cores.card }]}
onPress={() => router.back()}
>
<Ionicons name="arrow-back" size={22} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.topTitle, { color: cores.texto }]}>O Meu Perfil</Text>
<Text style={[styles.headerTitle, { color: cores.texto }]}>Ficha de Aluno</Text>
<TouchableOpacity
style={[styles.editBtn, { backgroundColor: isEditing ? cores.azul : cores.card }]}
onPress={() => isEditing ? salvarPerfil() : setIsEditing(true)}
style={[styles.roundBtn, { backgroundColor: isEditing ? cores.azul : cores.card }]}
onPress={() => isEditing ? salvarDados() : setIsEditing(true)}
disabled={saving}
>
<Ionicons name={isEditing ? "checkmark" : "create-outline"} size={20} color={isEditing ? "#fff" : cores.azul} />
{saving ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Ionicons name={isEditing ? "checkmark-sharp" : "create-outline"} size={22} color={isEditing ? "#fff" : cores.azul} />
)}
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
<View style={styles.profileHeader}>
<View style={[styles.avatarContainer, { borderColor: cores.azulSuave }]}>
<View style={[styles.avatar, { backgroundColor: cores.azul }]}>
<Text style={styles.avatarLetter}>{perfil?.nome?.charAt(0).toUpperCase()}</Text>
<Animated.ScrollView
style={{ opacity: fadeAnim }}
contentContainerStyle={styles.scrollContainer}
showsVerticalScrollIndicator={false}
>
{/* HEADER PERFIL */}
<View style={styles.avatarSection}>
<View style={[styles.avatarFrame, { borderColor: cores.azulSuave }]}>
<View style={[styles.avatarCircle, { backgroundColor: cores.azul }]}>
<Text style={styles.avatarInitial}>{perfil?.nome?.charAt(0).toUpperCase()}</Text>
</View>
</View>
<Text style={[styles.userName, { color: cores.texto }]}>{perfil?.nome}</Text>
<Text style={[styles.userRole, { color: cores.secundario }]}>{perfil?.curso || 'Sem Curso'} {perfil?.n_escola || '---'}</Text>
</View>
{/* DADOS ACADÉMICOS */}
<Text style={[styles.sectionTitle, { color: cores.secundario }]}>Informação Académica</Text>
<View style={[styles.card, { backgroundColor: cores.card, marginBottom: 20 }]}>
<View style={styles.row}>
<View style={{ flex: 1, marginRight: 10 }}>
<ModernInput
label="Nº Aluno" icon="card-outline"
value={perfil?.n_escola || ''}
editable={isEditing}
onChangeText={(v: string) => setPerfil({...perfil, n_escola: v})}
cores={cores} keyboardType="numeric"
/>
</View>
<View style={{ flex: 1 }}>
<ModernInput
label="Curso (Sigla)" icon="school-outline"
value={perfil?.curso || ''}
editable={isEditing}
onChangeText={(v: string) => setPerfil({...perfil, curso: v})}
cores={cores} autoCapitalize="characters"
/>
</View>
<Text style={[styles.profileName, { color: cores.texto }]}>{perfil?.nome}</Text>
<View style={[styles.badge, { backgroundColor: cores.azulSuave }]}>
<Text style={[styles.badgeText, { color: cores.azul }]}>
{perfil?.tipo?.toUpperCase()}
</Text>
</View>
<ModernInput label="E-mail Institucional" icon="mail-outline" value={perfil?.email || ''} editable={false} cores={cores} />
</View>
{/* DADOS PESSOAIS */}
<Text style={[styles.sectionTitle, { color: cores.secundario }]}>Dados Pessoais</Text>
<View style={[styles.card, { backgroundColor: cores.card }]}>
<ModernInput label="Nome Completo" icon="person-outline" value={perfil?.nome || ''} editable={isEditing} onChangeText={(v: string) => setPerfil({...perfil, nome: v})} cores={cores} />
{/* SECÇÃO ACADÉMICA (TABLE ALUNOS) */}
<Text style={[styles.sectionHeader, { color: cores.secundario }]}>Registo Escolar</Text>
<View style={[styles.infoCard, { backgroundColor: cores.card, shadowColor: cores.sombra }]}>
<View style={styles.inputRow}>
<View style={{ flex: 1, marginRight: 12 }}>
<PerfilInput label="Nº Escola" icon="id-card-outline" value={perfil?.n_escola?.toString()} editable={false} cores={cores} />
</View>
<View style={{ flex: 1 }}>
<PerfilInput label="Ano Letivo" icon="calendar-outline" value={perfil?.ano?.toString() + 'º Ano'} editable={false} cores={cores} />
</View>
</View>
<PerfilInput
label="Turma e Curso"
icon="school-outline"
value={perfil?.turma_curso}
editable={false}
cores={cores}
/>
</View>
{/* SECÇÃO PESSOAL (TABLE PROFILES) */}
<Text style={[styles.sectionHeader, { color: cores.secundario }]}>Dados de Contacto</Text>
<View style={[styles.infoCard, { backgroundColor: cores.card, shadowColor: cores.sombra }]}>
<PerfilInput label="Email" icon="mail-outline" value={perfil?.email} editable={false} cores={cores} />
<View style={styles.row}>
<View style={{ flex: 1, marginRight: 10 }}>
<ModernInput
label="Nascimento" icon="calendar-outline"
value={perfil?.data_nascimento || ''}
editable={isEditing}
onChangeText={(v: string) => setPerfil({...perfil, data_nascimento: aplicarMascaraData(v)})}
cores={cores} maxLength={10} keyboardType="numeric"
/>
</View>
<View style={{ flex: 1.2 }}>
<ModernInput label="Telefone" icon="call-outline" value={perfil?.telefone || ''} editable={isEditing} onChangeText={(v: string) => setPerfil({...perfil, telefone: v})} keyboardType="phone-pad" cores={cores} />
</View>
<View style={styles.inputRow}>
<View style={{ flex: 0.8, marginRight: 12 }}>
<PerfilInput label="Idade" icon="time-outline" value={perfil?.idade?.toString()} editable={isEditing} onChangeText={(v:string) => setPerfil({...perfil, idade: v})} cores={cores} keyboardType="numeric" />
</View>
<View style={{ flex: 1.2 }}>
<PerfilInput label="Telemóvel" icon="call-outline" value={perfil?.telefone} editable={isEditing} onChangeText={(v:string) => setPerfil({...perfil, telefone: v})} cores={cores} keyboardType="phone-pad" maxLength={9} />
</View>
</View>
<ModernInput label="Residência" icon="location-outline" value={perfil?.residencia || ''} editable={isEditing} onChangeText={(v: string) => setPerfil({...perfil, residencia: v})} cores={cores} />
<PerfilInput
label="Data de Nascimento"
icon="gift-outline"
value={perfil?.data_nascimento}
editable={isEditing}
onChangeText={(v:string) => setPerfil({...perfil, data_nascimento: aplicarMascaraData(v)})}
cores={cores}
placeholder="DD-MM-AAAA"
/>
<PerfilInput
label="Localidade / Morada"
icon="location-outline"
value={perfil?.residencia}
editable={isEditing}
onChangeText={(v:string) => setPerfil({...perfil, residencia: v})}
cores={cores}
/>
</View>
{/* ACÇÕES */}
<View style={[styles.actionsContainer, { marginTop: 30 }]}>
<TouchableOpacity style={[styles.menuItem, { backgroundColor: cores.card }]} onPress={() => router.push('/Aluno/redefenirsenha')}>
<View style={[styles.menuIcon, { backgroundColor: cores.azulSuave }]}>
<Ionicons name="lock-closed-outline" size={20} color={cores.azul} />
{/* SECÇÃO ESTÁGIO (CORREÇÃO DE LAYOUT) */}
{estagio && (
<>
<Text style={[styles.sectionHeader, { color: cores.secundario }]}>Informação de Estágio</Text>
<View style={[styles.infoCard, { backgroundColor: cores.card, borderLeftWidth: 4, borderLeftColor: cores.azul, shadowColor: cores.sombra }]}>
<PerfilInput label="Entidade Acolhedora" icon="business-outline" value={estagio.empresas?.nome} editable={false} cores={cores} />
<View style={styles.inputRow}>
<View style={{ flex: 1, marginRight: 12 }}>
<PerfilInput label="Total Horas" icon="timer-outline" value={estagio.horas_totais?.toString() + 'h'} editable={false} cores={cores} />
</View>
<View style={{ flex: 2 }}>
<PerfilInput
label="Horário"
icon="watch-outline"
value={estagio.horario}
editable={false}
cores={cores}
multiline={true} // Evita que o texto seja cortado
/>
</View>
</View>
{estagio.empresas?.tutor_nome && (
<PerfilInput label="Tutor na Empresa" icon="person-circle-outline" value={estagio.empresas.tutor_nome} editable={false} cores={cores} />
)}
</View>
<Text style={[styles.menuText, { color: cores.texto }]}>Alterar Palavra-passe</Text>
</>
)}
{/* BOTÕES DE ACÇÃO */}
<View style={styles.footer}>
<TouchableOpacity
style={[styles.actionMenuItem, { backgroundColor: cores.card }]}
onPress={() => router.push('/Aluno/redefenirsenha')}
>
<View style={[styles.actionIcon, { backgroundColor: cores.azulSuave }]}>
<Ionicons name="key-outline" size={20} color={cores.azul} />
</View>
<Text style={[styles.actionText, { color: cores.texto }]}>Alterar Credenciais</Text>
<Ionicons name="chevron-forward" size={18} color={cores.secundario} />
</TouchableOpacity>
<TouchableOpacity style={[styles.menuItem, { backgroundColor: cores.card }]} onPress={terminarSessao}>
<View style={[styles.menuIcon, { backgroundColor: cores.vermelhoSuave }]}>
<TouchableOpacity
style={[styles.actionMenuItem, { backgroundColor: cores.card, marginTop: 12 }]}
onPress={() => supabase.auth.signOut().then(() => router.replace('/'))}
>
<View style={[styles.actionIcon, { backgroundColor: cores.vermelhoSuave }]}>
<Ionicons name="log-out-outline" size={20} color={cores.vermelho} />
</View>
<Text style={[styles.menuText, { color: cores.vermelho }]}>Terminar Sessão</Text>
<Text style={[styles.actionText, { color: cores.vermelho }]}>Terminar Sessão</Text>
</TouchableOpacity>
{isEditing && (
<TouchableOpacity style={styles.cancelLink} onPress={() => { setIsEditing(false); buscarDados(); }}>
<Text style={[styles.cancelLinkText, { color: cores.secundario }]}>Cancelar edições pendentes</Text>
</TouchableOpacity>
)}
</View>
{isEditing && (
<TouchableOpacity style={styles.cancelBtn} onPress={() => { setIsEditing(false); carregarDados(); }}>
<Text style={[styles.cancelText, { color: cores.secundario }]}>Cancelar Edição</Text>
</TouchableOpacity>
)}
</ScrollView>
</Animated.ScrollView>
</SafeAreaView>
</View>
</KeyboardAvoidingView>
);
}
const ModernInput = ({ label, icon, cores, editable, ...props }: any) => (
<View style={styles.inputWrapper}>
// --- COMPONENTE DE INPUT CUSTOMIZADO ---
const PerfilInput = ({ label, icon, cores, editable, multiline, ...props }: any) => (
<View style={styles.inputGroup}>
<Text style={[styles.inputLabel, { color: cores.secundario }]}>{label}</Text>
<View style={[styles.inputContainer, { backgroundColor: cores.fundo, borderColor: editable ? cores.azul : cores.borda, opacity: editable ? 1 : 0.8 }]}>
<Ionicons name={icon} size={18} color={cores.azul} style={{ marginRight: 8 }} />
<TextInput {...props} editable={editable} style={[styles.textInput, { color: cores.texto }]} placeholderTextColor={cores.secundario} />
<View style={[styles.inputContainer, {
backgroundColor: cores.fundo,
borderColor: editable ? cores.azul : cores.borda,
height: multiline ? undefined : 52,
minHeight: multiline ? 52 : undefined,
paddingVertical: multiline ? 10 : 0
}]}>
<Ionicons name={icon} size={18} color={cores.azul} style={{ marginHorizontal: 12 }} />
<TextInput
{...props}
editable={editable}
multiline={multiline}
style={[styles.textInput, { color: cores.texto }]}
placeholderTextColor={cores.secundario}
/>
</View>
</View>
);
const styles = StyleSheet.create({
safe: { flex: 1 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
alertBar: { position: 'absolute', left: 20, right: 20, padding: 15, borderRadius: 15, flexDirection: 'row', alignItems: 'center', zIndex: 9999, elevation: 10 },
alertText: { color: '#fff', fontWeight: '700', marginLeft: 10, flex: 1 },
topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 15 },
backBtn: { width: 42, height: 42, borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
editBtn: { width: 42, height: 42, borderRadius: 12, justifyContent: 'center', alignItems: 'center', elevation: 2 },
topTitle: { fontSize: 18, fontWeight: '800' },
scrollContent: { paddingHorizontal: 20, paddingBottom: 40 },
profileHeader: { alignItems: 'center', marginVertical: 15 },
avatarContainer: { padding: 6, borderRadius: 100, borderWidth: 2, borderStyle: 'dashed' },
avatar: { width: 80, height: 80, borderRadius: 40, alignItems: 'center', justifyContent: 'center', elevation: 4 },
avatarLetter: { color: '#fff', fontSize: 32, fontWeight: '800' },
userName: { fontSize: 22, fontWeight: '900', marginTop: 12 },
userRole: { fontSize: 14, fontWeight: '600' },
sectionTitle: { fontSize: 11, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 1.2, marginLeft: 10, marginBottom: 10, marginTop: 10 },
card: { borderRadius: 24, padding: 20, elevation: 2, shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 10 },
inputWrapper: { marginBottom: 15 },
inputLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 6, marginLeft: 4 },
inputContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, height: 50, borderRadius: 16, borderWidth: 1.5 },
textInput: { flex: 1, fontSize: 14, fontWeight: '600' },
row: { flexDirection: 'row' },
actionsContainer: { gap: 10 },
menuItem: { flexDirection: 'row', alignItems: 'center', padding: 12, borderRadius: 18, elevation: 1 },
menuIcon: { width: 38, height: 38, borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
menuText: { flex: 1, marginLeft: 12, fontSize: 14, fontWeight: '700' },
cancelBtn: { marginTop: 20, alignItems: 'center' },
cancelText: { fontSize: 13, fontWeight: '600', textDecorationLine: 'underline' }
headerContainer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 15 },
headerTitle: { fontSize: 19, fontWeight: '900' },
roundBtn: { width: 48, height: 48, borderRadius: 16, justifyContent: 'center', alignItems: 'center', elevation: 2, shadowOpacity: 0.1, shadowRadius: 4 },
scrollContainer: { paddingHorizontal: 20, paddingBottom: 50 },
avatarSection: { alignItems: 'center', marginVertical: 25 },
avatarFrame: { padding: 6, borderRadius: 100, borderWidth: 2, borderStyle: 'dashed' },
avatarCircle: { width: 95, height: 95, borderRadius: 48, alignItems: 'center', justifyContent: 'center', elevation: 5 },
avatarInitial: { color: '#fff', fontSize: 40, fontWeight: '800' },
profileName: { fontSize: 24, fontWeight: '900', marginTop: 15 },
badge: { paddingHorizontal: 12, paddingVertical: 4, borderRadius: 8, marginTop: 8 },
badgeText: { fontSize: 11, fontWeight: '800', letterSpacing: 1 },
sectionHeader: { fontSize: 12, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 1.5, marginLeft: 8, marginBottom: 12, marginTop: 15 },
infoCard: { borderRadius: 28, padding: 22, elevation: 3, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 10 },
inputGroup: { marginBottom: 18 },
inputLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 8, marginLeft: 4 },
inputContainer: { flexDirection: 'row', alignItems: 'center', borderRadius: 16, borderWidth: 1.5 },
textInput: { flex: 1, fontSize: 15, fontWeight: '700' },
inputRow: { flexDirection: 'row', justifyContent: 'space-between' },
footer: { marginTop: 20 },
actionMenuItem: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 22, elevation: 2 },
actionIcon: { width: 42, height: 42, borderRadius: 14, justifyContent: 'center', alignItems: 'center' },
actionText: { flex: 1, marginLeft: 15, fontSize: 16, fontWeight: '800' },
cancelLink: { marginTop: 25, alignItems: 'center' },
cancelLinkText: { fontSize: 14, fontWeight: '700', textDecorationLine: 'underline' },
alert: { position: 'absolute', left: 20, right: 20, padding: 18, borderRadius: 20, flexDirection: 'row', alignItems: 'center', zIndex: 999, elevation: 10 },
alertText: { color: '#fff', fontWeight: '800', marginLeft: 10, flex: 1 }
});