eletrotupi / tcc / frontend/components/ui/AvatarUpload.tsx master
7.4 KB Raw
import React, { useState, useRef } from 'react';
import {
  View,
  Image,
  TouchableOpacity,
  Text,
  Alert,
  ActivityIndicator,
  Animated,
  StyleSheet,
} from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { Ionicons } from '@expo/vector-icons';
import { apiClient } from '@/lib/api';
import { Colors } from '@/constants/theme';

interface AvatarUploadProps {
  currentAvatar?: string | null;
  onUploadSuccess?: () => void;
  onRemoveSuccess?: () => void;
}

const AvatarUpload: React.FC<AvatarUploadProps> = ({
  currentAvatar = null,
  onUploadSuccess,
  onRemoveSuccess,
}) => {
  const [preview, setPreview] = useState<string | null>(currentAvatar);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const scaleAnim = useRef(new Animated.Value(1)).current;

  const handleImagePick = async (useCamera: boolean = false): Promise<void> => {
    try {
      const result = useCamera
        ? await ImagePicker.launchCameraAsync({
            allowsEditing: true,
            aspect: [1, 1],
            quality: 0.8,
          })
        : await ImagePicker.launchImageLibraryAsync({
            allowsEditing: true,
            aspect: [1, 1],
            quality: 0.8,
          });

      if (!result.canceled && result.assets && result.assets.length > 0) {
        const asset = result.assets[0];
        setPreview(asset.uri);
        await uploadAvatar(asset.fileName, asset.uri, asset.mimeType);
      }
    } catch (error) {
      console.error('Image picker error:', error);
      Alert.alert('Error', 'Failed to select image');
    }
  };

  const uploadAvatar = async (fileName: string, imageUri: string, mimeType: string): Promise<void> => {
    setIsLoading(true);

    try {
      const formData = new FormData();
      formData.append('avatar', {
        uri: imageUri,
        type: mimeType,
        name: fileName,
      } as any);

      const response = await apiClient.avatar(formData);

      onUploadSuccess?.(response.user);
    } catch (error) {
      console.error('Upload error:', error);
      Alert.alert('Error', error instanceof Error ? error.message : 'Upload failed');
      setPreview(currentAvatar);
    } finally {
      setIsLoading(false);
    }
  };

  const handleRemove = (): void => {
    Alert.alert('Remover sua foto', 'Tem certeza que quer remover sua foto de perfil?', [
      {
        text: 'Cancelar',
        onPress: () => {},
        style: 'cancel',
      },
      {
        text: 'Remover',
        onPress: async () => {
          setIsLoading(true);

          try {
            const response = await apiClient.removeAvatar();

            setPreview(null);
            onRemoveSuccess?.(response.user);
          } catch (error) {
            console.error('Remove error:', error);
            Alert.alert('Error', error instanceof Error ? error.message : 'Failed to remove avatar');
          } finally {
            setIsLoading(false);
          }
        },
        style: 'destructive',
      },
    ]);
  };

  const handlePressIn = (): void => {
    Animated.spring(scaleAnim, {
      toValue: 0.95,
      useNativeDriver: true,
    }).start();
  };

  const handlePressOut = (): void => {
    Animated.spring(scaleAnim, {
      toValue: 1,
      useNativeDriver: true,
    }).start();
  };

  const showImageOptions = (): void => {
    Alert.alert('Selecionar uma foto de perfil', 'Escolha de onde você quer:', [
      {
        text: 'Câmera',
        onPress: () => handleImagePick(true),
      },
      {
        text: 'Galeria',
        onPress: () => handleImagePick(false),
      },
      {
        text: 'Cancelar',
        style: 'cancel',
      },
    ]);
  };

  return (
    <View style={styles.container}>
      <View style={styles.avatarSection}>
        {preview ? (
          <View style={styles.avatarWrapper}>
            <Image source={{ uri: preview }} style={styles.avatarImage} />
            {isLoading && (
              <View style={styles.loadingOverlay}>
                <ActivityIndicator color="#fff" size="large" />
              </View>
            )}
          </View>
        ) : (
          <TouchableOpacity
            style={styles.avatarPlaceholder}
            onPress={showImageOptions}
            activeOpacity={0.7}
            disabled={isLoading}
          >
            <Ionicons name="cloud-upload-outline" size={32} color="#666" />
            <Text style={styles.placeholderText}>Adicione uma foto</Text>
          </TouchableOpacity>
        )}
      </View>

      <View style={styles.avatarActions}>
        <Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
          <TouchableOpacity
            style={[styles.actionBtn, styles.editBtn]}
            onPress={showImageOptions}
            onPressIn={handlePressIn}
            onPressOut={handlePressOut}
            disabled={isLoading}
            activeOpacity={0.8}
          >
            {isLoading ? (
              <ActivityIndicator color="#fff" size="small" />
            ) : (
              <Text style={styles.editBtnText}>ALTERAR FOTO</Text>
            )}
          </TouchableOpacity>
        </Animated.View>

        {preview && (
          <Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
            <TouchableOpacity
              style={[styles.actionBtn, styles.removeBtn]}
              onPress={handleRemove}
              onPressIn={handlePressIn}
              onPressOut={handlePressOut}
              disabled={isLoading}
              activeOpacity={0.8}
            >
              <Text style={styles.removeBtnText}>REMOVER FOTO</Text>
            </TouchableOpacity>
          </Animated.View>
        )}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    gap: 32,
    paddingVertical: 32,
  },
  avatarSection: {
    position: 'relative',
    width: 160,
    height: 160,
  },
  avatarWrapper: {
    width: '100%',
    height: '100%',
    borderRadius: 80,
    overflow: 'hidden',
    backgroundColor: '#f0f0f0',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 8 },
    shadowOpacity: 0.15,
    shadowRadius: 12,
    elevation: 8,
  },
  avatarImage: {
    width: '100%',
    height: '100%',
    resizeMode: 'cover',
  },
  loadingOverlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0, 0, 0, 0.4)',
    alignItems: 'center',
    justifyContent: 'center',
  },
  avatarPlaceholder: {
    width: '100%',
    height: '100%',
    borderRadius: 80,
    borderWidth: 2,
    borderStyle: 'dashed',
    borderColor: '#d0d0d0',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#fafafa',
  },
  placeholderText: {
    marginTop: 8,
    fontSize: 12,
    fontWeight: '500',
    color: '#666',
    textAlign: 'center',
  },
  avatarActions: {
    display: 'flex',
    flexDirection: 'row',
    gap: 16,
    width: '100%',
    maxWidth: 320,
    justifyContent: 'center',
    flexWrap: 'wrap',
  },
  actionBtn: {
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 6,
    justifyContent: 'center',
    alignItems: 'center',
    minWidth: 140,
  },
  editBtn: {
  },
  editBtnText: {
    fontSize: 12,
    fontWeight: '600',
    color: Colors.light.textSecondary,
    letterSpacing: 0.5,
    textTransform: 'uppercase',
  },
  removeBtn: {
  },
  removeBtnText: {
    fontSize: 12,
    fontWeight: '600',
    color: Colors.light.danger,
    letterSpacing: 0.5,
    textTransform: 'uppercase',
  },
});

export default AvatarUpload;