image handle in whole project
This commit is contained in:
+109
-77
@@ -8,11 +8,15 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Alert,
|
Alert,
|
||||||
|
Image,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import { CustomInput } from "../components/common/Custom";
|
import { CustomInput } from "../components/common/Custom";
|
||||||
import CustomButton from "../components/CustomButton";
|
import CustomButton from "../components/CustomButton";
|
||||||
import colors from "../components/colors";
|
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() {
|
export default function GroupCreationForm() {
|
||||||
const navigation = useNavigation<any>();
|
const navigation = useNavigation<any>();
|
||||||
@@ -31,6 +35,7 @@ export default function GroupCreationForm() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [errors, setErrors] = useState<any>({});
|
const [errors, setErrors] = useState<any>({});
|
||||||
|
const [selectedImage, setSelectedImage] = useState()
|
||||||
|
|
||||||
const handleChange = (key: string, value: string) => {
|
const handleChange = (key: string, value: string) => {
|
||||||
setForm({ ...form, [key]: value });
|
setForm({ ...form, [key]: value });
|
||||||
@@ -60,12 +65,12 @@ export default function GroupCreationForm() {
|
|||||||
const handleReview = () => {
|
const handleReview = () => {
|
||||||
if (validateForm()) {
|
if (validateForm()) {
|
||||||
const payload = {
|
const payload = {
|
||||||
...form,
|
...form,
|
||||||
contributionAmount: Number(form.contributionAmount),
|
contributionAmount: Number(form.contributionAmount),
|
||||||
customCycleDays: Number(form.customCycleDays) || 0,
|
customCycleDays: Number(form.customCycleDays) || 0,
|
||||||
lateFeeValue: Number(form.lateFeeValue) || 0,
|
lateFeeValue: Number(form.lateFeeValue) || 0,
|
||||||
maxParticipants: Number(form.maxParticipants),
|
maxParticipants: Number(form.maxParticipants),
|
||||||
minParticipants: Number(form.minParticipants),
|
minParticipants: Number(form.minParticipants),
|
||||||
};
|
};
|
||||||
console.log("Reviewing Payload:", payload);
|
console.log("Reviewing Payload:", payload);
|
||||||
navigation.navigate("GroupReview", { form: payload });
|
navigation.navigate("GroupReview", { form: payload });
|
||||||
@@ -74,66 +79,68 @@ export default function GroupCreationForm() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={{ flex: 1, backgroundColor: "#f5f6fa", paddingTop: Platform.OS === "android" ? 40 : 0 }}
|
style={{ flex: 1, backgroundColor: "#f5f6fa", paddingTop: Platform.OS === "android" ? 40 : 0 }}
|
||||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
>
|
>
|
||||||
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
|
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
|
||||||
<View style={styles.titleContainer}><Text style={styles.title}>Create New Group</Text></View>
|
<View style={styles.titleContainer}><Text style={styles.title}>Create New Group</Text></View>
|
||||||
|
|
||||||
<CustomInput label="Group Name" placeholder="e.g., 'Family Savings'" value={form.groupName} onChangeText={(val) => handleChange("groupName", val)} icon="people" error={errors.groupName} />
|
<CustomInput label="Group Name" placeholder="e.g., 'Family Savings'" value={form.groupName} onChangeText={(val) => handleChange("groupName", val)} icon="people" error={errors.groupName} />
|
||||||
<CustomInput label="Description" placeholder="e.g., 'Monthly fund for family needs'" value={form.description} onChangeText={(val) => handleChange("description", val)} multiline icon="document-text" error={errors.description} />
|
<CustomInput label="Description" placeholder="e.g., 'Monthly fund for family needs'" value={form.description} onChangeText={(val) => handleChange("description", val)} multiline icon="document-text" error={errors.description} />
|
||||||
|
|
||||||
<Section title="Group Settings">
|
<Section title="Group Settings">
|
||||||
<SettingRow label="Visibility">
|
<SettingRow label="Visibility">
|
||||||
<ToggleButtons
|
<ToggleButtons
|
||||||
options={["PRIVATE", "PUBLIC"]}
|
options={["PRIVATE", "PUBLIC"]}
|
||||||
selectedValue={form.visibility}
|
selectedValue={form.visibility}
|
||||||
onSelect={(val) => handleChange("visibility", val)}
|
onSelect={(val) => handleChange("visibility", val)}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow label="Fund Distribution">
|
<SettingRow label="Fund Distribution">
|
||||||
<ToggleButtons
|
<ToggleButtons
|
||||||
options={distributionTypes}
|
options={distributionTypes}
|
||||||
selectedValue={form.withdrawalMethod}
|
selectedValue={form.withdrawalMethod}
|
||||||
onSelect={(val) => handleChange("withdrawalMethod", val)}
|
onSelect={(val) => handleChange("withdrawalMethod", val)}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Participant Rules">
|
<Section title="Participant Rules">
|
||||||
<View style={styles.participantsRow}>
|
<View style={styles.participantsRow}>
|
||||||
<View style={styles.participantInput}><CustomInput label="Min" placeholder="e.g., 5" value={form.minParticipants} onChangeText={(val) => handleChange("minParticipants", val)} keyboardType="numeric" icon="person-add" error={errors.minParticipants} /></View>
|
<View style={styles.participantInput}><CustomInput label="Min" placeholder="e.g., 5" value={form.minParticipants} onChangeText={(val) => handleChange("minParticipants", val)} keyboardType="numeric" icon="person-add" error={errors.minParticipants} /></View>
|
||||||
<View style={styles.participantInput}><CustomInput label="Max" placeholder="e.g., 10" value={form.maxParticipants} onChangeText={(val) => handleChange("maxParticipants", val)} keyboardType="numeric" icon="people-circle" error={errors.maxParticipants} /></View>
|
<View style={styles.participantInput}><CustomInput label="Max" placeholder="e.g., 10" value={form.maxParticipants} onChangeText={(val) => handleChange("maxParticipants", val)} keyboardType="numeric" icon="people-circle" error={errors.maxParticipants} /></View>
|
||||||
</View>
|
</View>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Financial Details">
|
<Section title="Financial Details">
|
||||||
<CustomInput label="Contribution Amount (INR)" placeholder="e.g., 5000" value={form.contributionAmount} onChangeText={(val) => handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" error={errors.contributionAmount} />
|
<CustomInput label="Contribution Amount (INR)" placeholder="e.g., 5000" value={form.contributionAmount} onChangeText={(val) => handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" error={errors.contributionAmount} />
|
||||||
<SettingRow label="Payment Cycle">
|
<SettingRow label="Payment Cycle">
|
||||||
<ToggleButtons
|
<ToggleButtons
|
||||||
options={cycleTypes}
|
options={cycleTypes}
|
||||||
selectedValue={form.cycleType}
|
selectedValue={form.cycleType}
|
||||||
onSelect={(val) => handleChange("cycleType", val)}
|
onSelect={(val) => handleChange("cycleType", val)}
|
||||||
isWrap={true}
|
isWrap={true}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
{form.cycleType === "CUSTOM" && (<CustomInput label="Custom Cycle (Days)" placeholder="Enter number of days" value={form.customCycleDays} onChangeText={(val) => handleChange("customCycleDays", val)} keyboardType="numeric" icon="calendar" error={errors.customCycleDays} />)}
|
{form.cycleType === "CUSTOM" && (<CustomInput label="Custom Cycle (Days)" placeholder="Enter number of days" value={form.customCycleDays} onChangeText={(val) => handleChange("customCycleDays", val)} keyboardType="numeric" icon="calendar" error={errors.customCycleDays} />)}
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Late Fee Policy">
|
<Section title="Late Fee Policy">
|
||||||
<SettingRow label="Fee Mode">
|
<SettingRow label="Fee Mode">
|
||||||
<ToggleButtons
|
<ToggleButtons
|
||||||
options={lateFeeModes}
|
options={lateFeeModes}
|
||||||
selectedValue={form.lateFeeMode}
|
selectedValue={form.lateFeeMode}
|
||||||
onSelect={(val) => handleChange("lateFeeMode", val)}
|
onSelect={(val) => handleChange("lateFeeMode", val)}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<CustomInput label="Late Fee Value (INR)" placeholder="e.g., 50" value={form.lateFeeValue} onChangeText={(val) => handleChange("lateFeeValue", val)} keyboardType="numeric" icon="alert-circle" error={errors.lateFeeValue} />
|
<CustomInput label="Late Fee Value (INR)" placeholder="e.g., 50" value={form.lateFeeValue} onChangeText={(val) => handleChange("lateFeeValue", val)} keyboardType="numeric" icon="alert-circle" error={errors.lateFeeValue} />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<CustomButton title="Review Group" onPress={handleReview} style={{marginTop: 20}} />
|
<CustomButton title="Review Group" onPress={handleReview} style={{ marginTop: 20 }} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
@@ -142,40 +149,40 @@ export default function GroupCreationForm() {
|
|||||||
// --- Helper Components for a cleaner layout ---
|
// --- Helper Components for a cleaner layout ---
|
||||||
|
|
||||||
const Section: React.FC<{ title: string, children: React.ReactNode }> = ({ title, children }) => (
|
const Section: React.FC<{ title: string, children: React.ReactNode }> = ({ title, children }) => (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>{title}</Text>
|
<Text style={styles.sectionTitle}>{title}</Text>
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const SettingRow: React.FC<{ label: string, children: React.ReactNode }> = ({ label, children }) => (
|
const SettingRow: React.FC<{ label: string, children: React.ReactNode }> = ({ label, children }) => (
|
||||||
<View style={styles.settingRow}>
|
<View style={styles.settingRow}>
|
||||||
<Text style={styles.label}>{label}</Text>
|
<Text style={styles.label}>{label}</Text>
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ToggleButtons: React.FC<{ options: string[], selectedValue: string, onSelect: (value: string) => void, isWrap?: boolean }> = ({ options, selectedValue, onSelect, isWrap = false }) => (
|
const ToggleButtons: React.FC<{ options: string[], selectedValue: string, onSelect: (value: string) => void, isWrap?: boolean }> = ({ options, selectedValue, onSelect, isWrap = false }) => (
|
||||||
<View style={[styles.toggleContainer, isWrap && { flexWrap: 'wrap' }]}>
|
<View style={[styles.toggleContainer, isWrap && { flexWrap: 'wrap' }]}>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={option}
|
key={option}
|
||||||
onPress={() => onSelect(option)}
|
onPress={() => onSelect(option)}
|
||||||
style={[styles.toggleButton, selectedValue === option && styles.selectedButton, isWrap && {flex: 0, paddingHorizontal: 15}]}
|
style={[styles.toggleButton, selectedValue === option && styles.selectedButton, isWrap && { flex: 0, paddingHorizontal: 15 }]}
|
||||||
>
|
>
|
||||||
<Text style={[styles.toggleButtonText, selectedValue === option && styles.selectedButtonText]}>
|
<Text style={[styles.toggleButtonText, selectedValue === option && styles.selectedButtonText]}>
|
||||||
{option.charAt(0) + option.slice(1).toLowerCase()}
|
{option.charAt(0) + option.slice(1).toLowerCase()}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { flexGrow: 1, padding: 16, backgroundColor: "#f5f6fa" },
|
container: { flexGrow: 1, padding: 16, backgroundColor: "#f5f6fa" },
|
||||||
titleContainer: { marginBottom: 20, paddingHorizontal: 4 },
|
titleContainer: { marginBottom: 20, paddingHorizontal: 4 },
|
||||||
title: { fontSize: 28, fontWeight: "bold", color: "#1F2937" },
|
title: { fontSize: 28, fontWeight: "bold", color: "#1F2937" },
|
||||||
|
|
||||||
section: {
|
section: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
@@ -196,26 +203,51 @@ const styles = StyleSheet.create({
|
|||||||
paddingBottom: 10,
|
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: {
|
settingRow: {
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
},
|
},
|
||||||
|
|
||||||
label: { fontSize: 14, fontWeight: "600", marginBottom: 8, color: "#4B5563" },
|
label: { fontSize: 14, fontWeight: "600", marginBottom: 8, color: "#4B5563" },
|
||||||
|
|
||||||
toggleContainer: { flexDirection: "row", gap: 10 },
|
toggleContainer: { flexDirection: "row", gap: 10 },
|
||||||
toggleButton: {
|
toggleButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: "#D1D5DB",
|
borderColor: "#D1D5DB",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
backgroundColor: "#F9FAFB"
|
backgroundColor: "#F9FAFB"
|
||||||
},
|
},
|
||||||
selectedButton: { backgroundColor: colors.pastelGreen, borderColor: "#62b59a" },
|
selectedButton: { backgroundColor: colors.pastelGreen, borderColor: "#62b59a" },
|
||||||
toggleButtonText: { color: "#374151", fontWeight: "600", textTransform: 'capitalize' },
|
toggleButtonText: { color: "#374151", fontWeight: "600", textTransform: 'capitalize' },
|
||||||
selectedButtonText: { color: "#1F2937" },
|
selectedButtonText: { color: "#1F2937" },
|
||||||
|
|
||||||
participantsRow: { flexDirection: "row", gap: 15 },
|
participantsRow: { flexDirection: "row", gap: 15 },
|
||||||
participantInput: { flex: 1 },
|
participantInput: { flex: 1 },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Share,
|
Share,
|
||||||
|
Image,
|
||||||
|
ToastAndroid,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path
|
import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path
|
||||||
import { apiCall } from '../../utils/ApiHelper'; // 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 { CompositeScreenProps } from '@react-navigation/native';
|
||||||
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
|
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
|
||||||
import { ManagementTabParamList } from './ManagementTabNavigation'; // Adjust path
|
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 ---
|
// --- Type Definitions ---
|
||||||
interface Participant {
|
interface Participant {
|
||||||
@@ -51,6 +58,7 @@ export interface GroupDetails {
|
|||||||
organizerId: string;
|
organizerId: string;
|
||||||
totalParticipants: number;
|
totalParticipants: number;
|
||||||
participants: Participant[];
|
participants: Participant[];
|
||||||
|
imageBlob:string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditGroupPayload {
|
export interface EditGroupPayload {
|
||||||
@@ -92,12 +100,14 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
lateFeeMode: null,
|
lateFeeMode: null,
|
||||||
lateFeeValue: null,
|
lateFeeValue: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [starting, setStarting] = useState(false);
|
const [starting, setStarting] = useState(false);
|
||||||
const [inviting, setInviting] = useState(false);
|
const [inviting, setInviting] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
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)
|
// ... (All your handler functions: initializeEditableData, fetchGroupDetails, handleCancel, handleSave, etc. remain the same)
|
||||||
const initializeEditableData = (data: GroupDetails) => {
|
const initializeEditableData = (data: GroupDetails) => {
|
||||||
@@ -120,7 +130,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string };
|
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string };
|
||||||
console.log("groupDetails",response)
|
console.log("groupDetails", response)
|
||||||
if (response.status === 'success' && response.data) {
|
if (response.status === 'success' && response.data) {
|
||||||
setGroupDetails(response.data);
|
setGroupDetails(response.data);
|
||||||
initializeEditableData(response.data);
|
initializeEditableData(response.data);
|
||||||
@@ -140,7 +150,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
if (groupDetails) {
|
if (groupDetails) {
|
||||||
initializeEditableData(groupDetails);
|
initializeEditableData(groupDetails);
|
||||||
}
|
}
|
||||||
setEditingCard(null);
|
setEditingCard(null);
|
||||||
};
|
};
|
||||||
@@ -149,10 +159,10 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
if (!groupDetails) return;
|
if (!groupDetails) return;
|
||||||
|
|
||||||
if (editableData.maxParticipants < groupDetails.totalParticipants) {
|
if (editableData.maxParticipants < groupDetails.totalParticipants) {
|
||||||
Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`);
|
Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/groups/${groupDetails.id}/edit`, 'PUT', editableData);
|
await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/groups/${groupDetails.id}/edit`, 'PUT', editableData);
|
||||||
@@ -193,30 +203,30 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInvite = async () => {
|
const handleInvite = async () => {
|
||||||
if (!groupDetails) return;
|
if (!groupDetails) return;
|
||||||
setInviting(true);
|
setInviting(true);
|
||||||
try {
|
try {
|
||||||
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string };
|
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string };
|
||||||
|
|
||||||
const inviteLink = response.data;
|
const inviteLink = response.data;
|
||||||
|
|
||||||
if (inviteLink) {
|
if (inviteLink) {
|
||||||
Clipboard.setString(inviteLink);
|
Clipboard.setString(inviteLink);
|
||||||
Alert.alert('Copied!', 'Invitation link copied to clipboard.');
|
Alert.alert('Copied!', 'Invitation link copied to clipboard.');
|
||||||
await Share.share({
|
await Share.share({
|
||||||
message: `Join my group on CircleCollect! ${inviteLink}`,
|
message: `Join my group on CircleCollect! ${inviteLink}`,
|
||||||
url: inviteLink,
|
url: inviteLink,
|
||||||
title: 'CircleCollect Group Invitation'
|
title: 'CircleCollect Group Invitation'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Could not generate invitation link.');
|
throw new Error('Could not generate invitation link.');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
Alert.alert('Error', error.message || 'Failed to generate invitation.');
|
Alert.alert('Error', error.message || 'Failed to generate invitation.');
|
||||||
} finally {
|
} finally {
|
||||||
setInviting(false);
|
setInviting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -227,81 +237,81 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
// ... (renderField and renderCard functions also remain the same)
|
// ... (renderField and renderCard functions also remain the same)
|
||||||
const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean) => {
|
const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean) => {
|
||||||
const commonInputProps = { style: styles.input, placeholderTextColor: "#9CA3AF" };
|
const commonInputProps = { style: styles.input, placeholderTextColor: "#9CA3AF" };
|
||||||
|
|
||||||
const getInputForField = () => {
|
const getInputForField = () => {
|
||||||
switch(field) {
|
switch (field) {
|
||||||
case 'groupName':
|
case 'groupName':
|
||||||
return <TextInput {...commonInputProps} value={editableData.groupName} onChangeText={(text) => handleUpdateField('groupName', text)} />;
|
return <TextInput {...commonInputProps} value={editableData.groupName} onChangeText={(text) => handleUpdateField('groupName', text)} />;
|
||||||
case 'description':
|
case 'description':
|
||||||
return <TextInput {...commonInputProps} style={[styles.input, styles.textArea]} value={editableData.description} onChangeText={(text) => handleUpdateField('description', text)} multiline />;
|
return <TextInput {...commonInputProps} style={[styles.input, styles.textArea]} value={editableData.description} onChangeText={(text) => handleUpdateField('description', text)} multiline />;
|
||||||
case 'contributionAmount':
|
case 'contributionAmount':
|
||||||
return <TextInput {...commonInputProps} value={String(editableData.contributionAmount ?? '')} onChangeText={(text) => handleUpdateField('contributionAmount', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
return <TextInput {...commonInputProps} value={String(editableData.contributionAmount ?? '')} onChangeText={(text) => handleUpdateField('contributionAmount', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||||
case 'maxParticipants':
|
case 'maxParticipants':
|
||||||
return <TextInput {...commonInputProps} value={String(editableData.maxParticipants ?? '')} onChangeText={(text) => handleUpdateField('maxParticipants', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
return <TextInput {...commonInputProps} value={String(editableData.maxParticipants ?? '')} onChangeText={(text) => handleUpdateField('maxParticipants', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||||
case 'latePaymentCharge':
|
case 'latePaymentCharge':
|
||||||
return <TextInput {...commonInputProps} value={String(editableData.latePaymentCharge ?? '')} onChangeText={(text) => handleUpdateField('latePaymentCharge', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
return <TextInput {...commonInputProps} value={String(editableData.latePaymentCharge ?? '')} onChangeText={(text) => handleUpdateField('latePaymentCharge', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||||
case 'cycleType':
|
case 'cycleType':
|
||||||
return (
|
return (
|
||||||
<Picker selectedValue={editableData.cycleType} onValueChange={(itemValue) => handleUpdateField('cycleType', itemValue)} style={styles.picker}>
|
<Picker selectedValue={editableData.cycleType} onValueChange={(itemValue) => handleUpdateField('cycleType', itemValue)} style={styles.picker}>
|
||||||
{['MONTHLY', 'WEEKLY', 'QUARTERLY', 'CUSTOM'].map(c => <Picker.Item key={c} label={c} value={c} />)}
|
{['MONTHLY', 'WEEKLY', 'QUARTERLY', 'CUSTOM'].map(c => <Picker.Item key={c} label={c} value={c} />)}
|
||||||
</Picker>
|
</Picker>
|
||||||
);
|
);
|
||||||
case 'customCycleDays':
|
case 'customCycleDays':
|
||||||
if (editableData.cycleType !== 'CUSTOM') return null;
|
if (editableData.cycleType !== 'CUSTOM') return null;
|
||||||
return <TextInput {...commonInputProps} value={String(editableData.customCycleDays ?? '')} onChangeText={(text) => handleUpdateField('customCycleDays', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
return <TextInput {...commonInputProps} value={String(editableData.customCycleDays ?? '')} onChangeText={(text) => handleUpdateField('customCycleDays', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||||
case 'visibility':
|
case 'visibility':
|
||||||
return (
|
return (
|
||||||
<Picker selectedValue={editableData.visibility} onValueChange={(itemValue) => handleUpdateField('visibility', itemValue)} style={styles.picker}>
|
<Picker selectedValue={editableData.visibility} onValueChange={(itemValue) => handleUpdateField('visibility', itemValue)} style={styles.picker}>
|
||||||
<Picker.Item label="PUBLIC" value="PUBLIC" />
|
<Picker.Item label="PUBLIC" value="PUBLIC" />
|
||||||
<Picker.Item label="PRIVATE" value="PRIVATE" />
|
<Picker.Item label="PRIVATE" value="PRIVATE" />
|
||||||
</Picker>
|
</Picker>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.fieldContainer}>
|
<View style={styles.fieldContainer}>
|
||||||
<Text style={styles.infoLabel}>{label}</Text>
|
<Text style={styles.infoLabel}>{label}</Text>
|
||||||
{isEditing && isEditable ? (
|
{isEditing && isEditable ? (
|
||||||
<View style={{marginTop: 8}}>{getInputForField()}</View>
|
<View style={{ marginTop: 8 }}>{getInputForField()}</View>
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.infoValue}>{displayValue}</Text>
|
<Text style={styles.infoValue}>{displayValue}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderCard = (title: string, fields: Array<{ field: keyof EditGroupPayload, label: string, displayValue: () => string | number }>, cardKey: EditingCard) => {
|
const renderCard = (title: string, fields: Array<{ field: keyof EditGroupPayload, label: string, displayValue: () => string | number }>, cardKey: EditingCard) => {
|
||||||
const isEditingThisCard = editingCard === cardKey;
|
const isEditingThisCard = editingCard === cardKey;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
<View style={styles.cardHeader}>
|
<View style={styles.cardHeader}>
|
||||||
<Text style={styles.cardTitle}>{title}</Text>
|
<Text style={styles.cardTitle}>{title}</Text>
|
||||||
{isEditable && !editingCard && (
|
{isEditable && !editingCard && (
|
||||||
<TouchableOpacity onPress={() => setEditingCard(cardKey)} style={styles.editButton}>
|
<TouchableOpacity onPress={() => setEditingCard(cardKey)} style={styles.editButton}>
|
||||||
<Text style={styles.editButtonText}>✎</Text>
|
<Text style={styles.editButtonText}>✎</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
|
||||||
|
|
||||||
{fields.map(({ field, label, displayValue }) =>
|
|
||||||
renderField(field, label, displayValue(), isEditingThisCard)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isEditingThisCard && (
|
|
||||||
<View style={styles.cardActions}>
|
|
||||||
<TouchableOpacity style={[styles.button, styles.cancelButton]} onPress={handleCancel} disabled={saving}>
|
|
||||||
<Text style={[styles.buttonText, { color: '#374151' }]}>Cancel</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={[styles.button, styles.saveButton]} onPress={handleSave} disabled={saving}>
|
|
||||||
{saving ? <ActivityIndicator color="#FFFFFF" size="small" /> : <Text style={styles.buttonText}>Save</Text>}
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{fields.map(({ field, label, displayValue }) =>
|
||||||
|
renderField(field, label, displayValue(), isEditingThisCard)
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isEditingThisCard && (
|
||||||
|
<View style={styles.cardActions}>
|
||||||
|
<TouchableOpacity style={[styles.button, styles.cancelButton]} onPress={handleCancel} disabled={saving}>
|
||||||
|
<Text style={[styles.buttonText, { color: '#374151' }]}>Cancel</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={[styles.button, styles.saveButton]} onPress={handleSave} disabled={saving}>
|
||||||
|
{saving ? <ActivityIndicator color="#FFFFFF" size="small" /> : <Text style={styles.buttonText}>Save</Text>}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +323,84 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
const isInvitable = groupDetails.status === 'APPROVED';
|
const isInvitable = groupDetails.status === 'APPROVED';
|
||||||
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
|
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 (
|
return (
|
||||||
// Use AppView as the root container with flex: 1
|
// Use AppView as the root container with flex: 1
|
||||||
<AppView style={{ flex: 1, paddingHorizontal: 0 }}>
|
<AppView style={{ flex: 1, paddingHorizontal: 0 }}>
|
||||||
@@ -330,42 +418,64 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
contentContainerStyle={styles.contentContainer}
|
contentContainerStyle={styles.contentContainer}
|
||||||
showsVerticalScrollIndicator={false}
|
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', [
|
{renderCard('Group Info', [
|
||||||
{ field: 'groupName', label: 'Group Name', displayValue: () => groupDetails.groupName },
|
{ field: 'groupName', label: 'Group Name', displayValue: () => groupDetails.groupName },
|
||||||
{ field: 'description', label: 'Description', displayValue: () => groupDetails.description },
|
{ field: 'description', label: 'Description', displayValue: () => groupDetails.description },
|
||||||
], 'groupInfo')}
|
], 'groupInfo')}
|
||||||
|
|
||||||
{renderCard('Financial Rules', [
|
{renderCard('Financial Rules', [
|
||||||
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
|
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
|
||||||
{ field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` },
|
{ field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` },
|
||||||
{ field: 'minParticipants', label: 'Min Participants', displayValue: () => `${groupDetails.minParticipants}` },
|
{ field: 'minParticipants', label: 'Min Participants', displayValue: () => `${groupDetails.minParticipants}` },
|
||||||
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
|
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
|
||||||
], 'financialRules')}
|
], 'financialRules')}
|
||||||
|
|
||||||
{renderCard('Configuration', [
|
{renderCard('Configuration', [
|
||||||
{ field: 'cycleType', label: 'Payment Cycle', displayValue: () => groupDetails.cycleType },
|
{ field: 'cycleType', label: 'Payment Cycle', displayValue: () => groupDetails.cycleType },
|
||||||
{ field: 'customCycleDays', label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays ?? ''} days` },
|
{ field: 'customCycleDays', label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays ?? ''} days` },
|
||||||
{ field: 'visibility', label: 'Visibility', displayValue: () => groupDetails.visibility },
|
{ field: 'visibility', label: 'Visibility', displayValue: () => groupDetails.visibility },
|
||||||
], 'configuration')}
|
], 'configuration')}
|
||||||
|
|
||||||
{/* Participants Card ... */}
|
{/* Participants Card ... */}
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants})</Text>
|
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants})</Text>
|
||||||
{groupDetails.participants.length > 0 ? (
|
{groupDetails.participants.length > 0 ? (
|
||||||
groupDetails.participants.map(p => (
|
groupDetails.participants.map(p => (
|
||||||
<View key={p.id} style={styles.participantRow}>
|
<View key={p.id} style={styles.participantRow}>
|
||||||
<View>
|
<View>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<Text style={styles.participantName}>{`${p.firstName} ${p.lastName || ''}`.trim()}</Text>
|
<Text style={styles.participantName}>{`${p.firstName} ${p.lastName || ''}`.trim()}</Text>
|
||||||
{p.role === 'ORGANIZER' && <View style={styles.organizerBadge}><Text style={styles.organizerBadgeText}>Organizer</Text></View>}
|
{p.role === 'ORGANIZER' && <View style={styles.organizerBadge}><Text style={styles.organizerBadgeText}>Organizer</Text></View>}
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.participantMobile}>{p.mobile}</Text>
|
<Text style={styles.participantMobile}>{p.mobile}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.noParticipantsText}>No participants yet.</Text>
|
<Text style={styles.noParticipantsText}>No participants yet.</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
@@ -394,7 +504,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { flex: 1, backgroundColor: '#F3F4F6' },
|
container: { flex: 1, backgroundColor: '#F3F4F6' },
|
||||||
// Added more paddingBottom to ensure content doesn't hide behind the button AND the tab bar
|
// Added more paddingBottom to ensure content doesn't hide behind the button AND the tab bar
|
||||||
contentContainer: { padding: 16, paddingTop: 0, paddingBottom: 150 },
|
contentContainer: { padding: 16, paddingTop: 0, paddingBottom: 150 },
|
||||||
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||||
errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' },
|
errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' },
|
||||||
headerContainer: {
|
headerContainer: {
|
||||||
@@ -407,6 +517,33 @@ const styles = StyleSheet.create({
|
|||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: '#E5E7EB',
|
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' },
|
headerTitle: { fontSize: 28, fontWeight: 'bold', color: '#111827' },
|
||||||
card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10 },
|
card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10 },
|
||||||
cardHeader: {
|
cardHeader: {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
|
|||||||
{state.routes.map((route, index) => {
|
{state.routes.map((route, index) => {
|
||||||
const { options } = descriptors[route.key];
|
const { options } = descriptors[route.key];
|
||||||
const label = options.tabBarLabel ?? options.title ?? route.name;
|
const label = options.tabBarLabel ?? options.title ?? route.name;
|
||||||
|
|
||||||
|
console.log("option : ", options)
|
||||||
// A custom option to pass the icon name
|
// A custom option to pass the icon name
|
||||||
const iconName = options.tabBarIconName ?? 'help-circle';
|
const iconName = options.tabBarIconName ?? 'help-circle';
|
||||||
|
|
||||||
@@ -66,7 +68,7 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
|
|||||||
color={isFocused ? '#4F46E5' : '#6B7280'}
|
color={isFocused ? '#4F46E5' : '#6B7280'}
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.tabLabel, { color: isFocused ? '#4F46E5' : '#6B7280' }]}>
|
<Text style={[styles.tabLabel, { color: isFocused ? '#4F46E5' : '#6B7280' }]}>
|
||||||
{label}
|
{label as string}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
@@ -118,6 +120,7 @@ const ManagementTabNavigation: React.FC<Props> = ({ route }) => {
|
|||||||
tabBarLabel: 'Members',
|
tabBarLabel: 'Members',
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
{/* You can easily add more tabs here in the future */}
|
{/* You can easily add more tabs here in the future */}
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
|
|||||||
@@ -445,8 +445,6 @@ const Profile = () => {
|
|||||||
icon={imageUpdating ? undefined : "cloud-upload"}
|
icon={imageUpdating ? undefined : "cloud-upload"}
|
||||||
style={styles.updateImageButton}
|
style={styles.updateImageButton}
|
||||||
buttonBackground={colors.pastelBlue}
|
buttonBackground={colors.pastelBlue}
|
||||||
|
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</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 UPDATE_BANK_URL = `${BASE_URL}user/bank/update`;
|
||||||
export const CONTRIBUTE = `${BASE_URL}payments/groups/`;
|
export const CONTRIBUTE = `${BASE_URL}payments/groups/`;
|
||||||
export const TRANSACTION = `${BASE_URL}payments/my-transactions`;
|
export const TRANSACTION = `${BASE_URL}payments/my-transactions`;
|
||||||
|
export const UPLOAD_GROUP_IMAGE = `${BASE_URL}fund/groups/`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user