image handle in whole project
This commit is contained in:
@@ -8,11 +8,15 @@ import {
|
||||
Platform,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
Image,
|
||||
} from "react-native";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { CustomInput } from "../components/common/Custom";
|
||||
import CustomButton from "../components/CustomButton";
|
||||
import colors from "../components/colors";
|
||||
import { launchImageLibrary } from "react-native-image-picker";
|
||||
import Ionicons from "@react-native-vector-icons/ionicons";
|
||||
import { UPLOAD_GROUP_IMAGE } from "../utils/ApiUrls";
|
||||
|
||||
export default function GroupCreationForm() {
|
||||
const navigation = useNavigation<any>();
|
||||
@@ -31,6 +35,7 @@ export default function GroupCreationForm() {
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState<any>({});
|
||||
const [selectedImage, setSelectedImage] = useState()
|
||||
|
||||
const handleChange = (key: string, value: string) => {
|
||||
setForm({ ...form, [key]: value });
|
||||
@@ -74,7 +79,9 @@ export default function GroupCreationForm() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<KeyboardAvoidingView
|
||||
style={{ flex: 1, backgroundColor: "#f5f6fa", paddingTop: Platform.OS === "android" ? 40 : 0 }}
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
@@ -196,6 +203,31 @@ const styles = StyleSheet.create({
|
||||
paddingBottom: 10,
|
||||
},
|
||||
|
||||
avatarContainer: {
|
||||
position: "relative",
|
||||
marginBottom: 16,
|
||||
},
|
||||
avatar: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
borderWidth: 4,
|
||||
borderColor: "#e2e8f0",
|
||||
},
|
||||
cameraIcon: {
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: colors.pastelBlue,
|
||||
borderRadius: 20,
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderWidth: 3,
|
||||
borderColor: "#fff",
|
||||
elevation: 2,
|
||||
},
|
||||
settingRow: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
Share,
|
||||
Image,
|
||||
ToastAndroid,
|
||||
} from 'react-native';
|
||||
import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path
|
||||
import { apiCall } from '../../utils/ApiHelper'; // Adjust path
|
||||
@@ -20,6 +22,11 @@ import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { CompositeScreenProps } from '@react-navigation/native';
|
||||
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
|
||||
import { ManagementTabParamList } from './ManagementTabNavigation'; // Adjust path
|
||||
import { UPLOAD_GROUP_IMAGE } from '../../utils/ApiUrls';
|
||||
import { launchImageLibrary } from 'react-native-image-picker';
|
||||
import *as Keychain from 'react-native-keychain'
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
import { CustomButton } from '../../components/common/Custom';
|
||||
|
||||
// --- Type Definitions ---
|
||||
interface Participant {
|
||||
@@ -51,6 +58,7 @@ export interface GroupDetails {
|
||||
organizerId: string;
|
||||
totalParticipants: number;
|
||||
participants: Participant[];
|
||||
imageBlob:string
|
||||
}
|
||||
|
||||
export interface EditGroupPayload {
|
||||
@@ -98,6 +106,8 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
const [starting, setStarting] = useState(false);
|
||||
const [inviting, setInviting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>()
|
||||
const [imageUpdating, setImageUpdating] = useState<boolean>(false)
|
||||
|
||||
// ... (All your handler functions: initializeEditableData, fetchGroupDetails, handleCancel, handleSave, etc. remain the same)
|
||||
const initializeEditableData = (data: GroupDetails) => {
|
||||
@@ -313,6 +323,84 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
const isInvitable = groupDetails.status === 'APPROVED';
|
||||
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
|
||||
|
||||
const pickImage = () => {
|
||||
launchImageLibrary({ mediaType: "photo", quality: 0.5 }, (response) => {
|
||||
if (response.didCancel) return;
|
||||
if (response.errorCode) {
|
||||
Alert.alert("Error", response.errorMessage || "Something went wrong");
|
||||
} else if (response.assets && response.assets.length > 0) {
|
||||
const selectedImage = response.assets[0];
|
||||
console.log(selectedImage?.uri)
|
||||
setSelectedImage(selectedImage?.uri)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateImage = async () => {
|
||||
try {
|
||||
const credentials = await Keychain.getGenericPassword();
|
||||
if (!credentials) {
|
||||
Alert.alert("Error", "You are not logged in");
|
||||
return;
|
||||
}
|
||||
const token = credentials.password;
|
||||
|
||||
if (!selectedImage) {
|
||||
Alert.alert("Error", "Please select an image first");
|
||||
return;
|
||||
}
|
||||
|
||||
setImageUpdating(true);
|
||||
|
||||
// Get file extension and mime type dynamically
|
||||
const fileExtension = selectedImage.split('.').pop();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", {
|
||||
uri: selectedImage,
|
||||
type: "image/jpeg",
|
||||
name: `group_${groupId}_profile.${fileExtension || "jpg"}`,
|
||||
});
|
||||
|
||||
const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`;
|
||||
|
||||
console.log("url : ", url)
|
||||
console.log("formdata : ", formData)
|
||||
|
||||
// return
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
console.log("image response : ", response)
|
||||
if(response?.status === 200){
|
||||
fetchGroupDetails()
|
||||
ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT)
|
||||
}
|
||||
|
||||
} catch (error:any) {
|
||||
console.error("❌ Error updating group image:", error);
|
||||
Alert.alert("Error", error.message || "Could not update group image");
|
||||
} finally {
|
||||
setImageUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getProfileImage = () => {
|
||||
return selectedImage
|
||||
? selectedImage
|
||||
: groupDetails?.imageBlob
|
||||
? `data:image/jpeg;base64,${groupDetails.imageBlob}`
|
||||
: "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg";
|
||||
};
|
||||
// Helper function to get MIME type
|
||||
|
||||
|
||||
return (
|
||||
// Use AppView as the root container with flex: 1
|
||||
<AppView style={{ flex: 1, paddingHorizontal: 0 }}>
|
||||
@@ -330,6 +418,28 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={[styles.avatarContainer, { flexDirection: 'row', flex: 1, gap: 10 }]}>
|
||||
<View style={{}}>
|
||||
<Image
|
||||
// source={{ uri: groupDetails?.imageBlob ? groupDetails?.imageBlob : selectedImage || "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg" }}
|
||||
source={{ uri: getProfileImage() }}
|
||||
style={styles.avatar}
|
||||
onError={() => console.log("Error loading image")}
|
||||
/>
|
||||
<TouchableOpacity style={styles.cameraIcon} onPress={pickImage}>
|
||||
<Ionicons name="camera" size={18} color="#000" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={{justifyContent:'center'}}>
|
||||
<CustomButton
|
||||
title={imageUpdating ? "Updating..." : "Update Image"}
|
||||
onPress={handleUpdateImage}
|
||||
icon={imageUpdating ? undefined : "cloud-upload"}
|
||||
style={{paddingHorizontal:20}}
|
||||
buttonBackground={colors.pastelBlue}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{renderCard('Group Info', [
|
||||
{ field: 'groupName', label: 'Group Name', displayValue: () => groupDetails.groupName },
|
||||
{ field: 'description', label: 'Description', displayValue: () => groupDetails.description },
|
||||
@@ -407,6 +517,33 @@ const styles = StyleSheet.create({
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#E5E7EB',
|
||||
},
|
||||
|
||||
|
||||
avatarContainer: {
|
||||
position: "relative",
|
||||
marginVertical: 16,
|
||||
},
|
||||
avatar: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
borderWidth: 4,
|
||||
borderColor: "#e2e8f0",
|
||||
},
|
||||
cameraIcon: {
|
||||
position: "absolute",
|
||||
// right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: colors.pastelBlue,
|
||||
borderRadius: 20,
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderWidth: 3,
|
||||
borderColor: "#fff",
|
||||
elevation: 2,
|
||||
},
|
||||
headerTitle: { fontSize: 28, fontWeight: 'bold', color: '#111827' },
|
||||
card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10 },
|
||||
cardHeader: {
|
||||
|
||||
@@ -25,6 +25,8 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
|
||||
{state.routes.map((route, index) => {
|
||||
const { options } = descriptors[route.key];
|
||||
const label = options.tabBarLabel ?? options.title ?? route.name;
|
||||
|
||||
console.log("option : ", options)
|
||||
// A custom option to pass the icon name
|
||||
const iconName = options.tabBarIconName ?? 'help-circle';
|
||||
|
||||
@@ -66,7 +68,7 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
|
||||
color={isFocused ? '#4F46E5' : '#6B7280'}
|
||||
/>
|
||||
<Text style={[styles.tabLabel, { color: isFocused ? '#4F46E5' : '#6B7280' }]}>
|
||||
{label}
|
||||
{label as string}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
@@ -118,6 +120,7 @@ const ManagementTabNavigation: React.FC<Props> = ({ route }) => {
|
||||
tabBarLabel: 'Members',
|
||||
|
||||
}}
|
||||
|
||||
/>
|
||||
{/* You can easily add more tabs here in the future */}
|
||||
</Tab.Navigator>
|
||||
|
||||
@@ -445,8 +445,6 @@ const Profile = () => {
|
||||
icon={imageUpdating ? undefined : "cloud-upload"}
|
||||
style={styles.updateImageButton}
|
||||
buttonBackground={colors.pastelBlue}
|
||||
|
||||
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -20,3 +20,5 @@ export const GROUP_ALL_MEMBER = `${BASE_URL}fund/group/all/member`
|
||||
export const UPDATE_BANK_URL = `${BASE_URL}user/bank/update`;
|
||||
export const CONTRIBUTE = `${BASE_URL}payments/groups/`;
|
||||
export const TRANSACTION = `${BASE_URL}payments/my-transactions`;
|
||||
export const UPLOAD_GROUP_IMAGE = `${BASE_URL}fund/groups/`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user