From 82b121b99396f0b6241f35c82679d5f13939e3e2 Mon Sep 17 00:00:00 2001 From: Abin Toppo Date: Fri, 19 Sep 2025 15:59:31 +0530 Subject: [PATCH 1/2] errors are there --- src/components/common/Custom.tsx | 2 + src/screens/GroupCreation.tsx | 285 ++++++++++++++++--------------- src/screens/GroupReview.tsx | 170 ++++++++++-------- src/utils/HelperFunction.tsx | 1 + 4 files changed, 248 insertions(+), 210 deletions(-) diff --git a/src/components/common/Custom.tsx b/src/components/common/Custom.tsx index ae3f3ec..fcd837e 100644 --- a/src/components/common/Custom.tsx +++ b/src/components/common/Custom.tsx @@ -14,12 +14,14 @@ import colors from "../colors"; type CustomInputProps = TextInputProps & { label?: string; icon?: IoniconsIconName; + error:string }; const CustomInput: React.FC = ({ label, icon, style, + error, ...rest }) => { return ( diff --git a/src/screens/GroupCreation.tsx b/src/screens/GroupCreation.tsx index 7fb6308..227fb68 100644 --- a/src/screens/GroupCreation.tsx +++ b/src/screens/GroupCreation.tsx @@ -7,97 +7,68 @@ import { KeyboardAvoidingView, Platform, TouchableOpacity, - Modal, Alert, } from "react-native"; -import Slider from "@react-native-community/slider"; import { useNavigation } from "@react-navigation/native"; import { CustomInput } from "../components/common/Custom"; import CustomButton from "../components/CustomButton"; -import { Calendar } from "react-native-calendars"; -import { Ionicons } from "@react-native-vector-icons/ionicons"; import colors from "../components/colors"; -const calendarTheme = { - backgroundColor: '#ffffff', - calendarBackground: '#ffffff', - textSectionTitleColor: '#62b59a', - selectedDayTextColor: '#333', - todayTextColor: '#62b59a', - dayTextColor: '#333', - textDisabledColor: '#d9e1e8', - arrowColor: '#62b59a', - monthTextColor: '#333', - textDayFontWeight: '500', - textMonthFontWeight: 'bold', - textDayHeaderFontWeight: '600', - textDayFontSize: 16, - textMonthFontSize: 18, - textDayHeaderFontSize: 14, -}; - export default function GroupCreationForm() { const navigation = useNavigation(); const [form, setForm] = useState({ - contributionAmount: "4000", - customCycleDays: "", - cycleType: "MONTHLY", - description: "", groupName: "", - maxParticipants: "", - minParticipants: "", - startDate: "", + description: "", visibility: "PRIVATE", - withdrawalMethod: "SEQUENTIAL", + withdrawalMethod: "RANDOM", + minParticipants: "", + maxParticipants: "", + contributionAmount: "", + cycleType: "MONTHLY", + customCycleDays: "", + lateFeeMode: "DAILY", + lateFeeValue: "", }); - const [errors, setErrors] = useState({}); // State for validation errors - const [isCalendarVisible, setCalendarVisible] = useState(false); + const [errors, setErrors] = useState({}); const handleChange = (key: string, value: string) => { setForm({ ...form, [key]: value }); - // Clear error for the field when user starts typing if (errors[key]) { setErrors({ ...errors, [key]: null }); } }; - const handleAmountChange = (value: number) => { - setForm({ ...form, contributionAmount: Math.round(value).toString() }); - }; - - const handleDayPress = (day: any) => { - setForm({ ...form, startDate: day.dateString }); - setCalendarVisible(false); - setErrors({ ...errors, startDate: null }); - }; - - const presetAmounts = [2000, 4000, 8000]; const cycleTypes = ["CUSTOM", "MONTHLY", "QUARTERLY", "YEARLY"]; const distributionTypes = ["SEQUENTIAL", "RANDOM"]; - - const handlePresetAmount = (amount: number) => { - setForm({ ...form, contributionAmount: amount.toString() }); - }; + const lateFeeModes = ["DAILY", "FIXED"]; const validateForm = () => { const newErrors: any = {}; if (!form.groupName.trim()) newErrors.groupName = "Group name is required."; if (!form.description.trim()) newErrors.description = "Description is required."; - if (!form.minParticipants || Number(form.minParticipants) <= 0) newErrors.minParticipants = "Min participants must be greater than 0."; + if (!form.minParticipants || Number(form.minParticipants) <= 1) newErrors.minParticipants = "Min participants must be at least 2."; if (!form.maxParticipants || Number(form.maxParticipants) < Number(form.minParticipants)) newErrors.maxParticipants = "Max must be greater than or equal to min."; if (!form.contributionAmount || Number(form.contributionAmount) <= 0) newErrors.contributionAmount = "Amount must be greater than 0."; if (form.cycleType === "CUSTOM" && (!form.customCycleDays || Number(form.customCycleDays) <= 0)) newErrors.customCycleDays = "Custom days must be greater than 0."; - if (!form.startDate) newErrors.startDate = "Start date is required."; - + if (form.lateFeeMode && (!form.lateFeeValue || Number(form.lateFeeValue) <= 0)) newErrors.lateFeeValue = "Late fee value is required."; + setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleReview = () => { if (validateForm()) { - console.log("------",form) - navigation.navigate("GroupReview", { form }); + const payload = { + ...form, + contributionAmount: Number(form.contributionAmount), + customCycleDays: Number(form.customCycleDays) || 0, + lateFeeValue: Number(form.lateFeeValue) || 0, + maxParticipants: Number(form.maxParticipants), + minParticipants: Number(form.minParticipants), + }; + console.log("Reviewing Payload:", payload); + navigation.navigate("GroupReview", { form: payload }); } else { Alert.alert("Incomplete Form", "Please fill out all required fields correctly."); } @@ -106,101 +77,145 @@ export default function GroupCreationForm() { return ( - Group Creation + Create New Group - handleChange("groupName", val)} icon="people" /> - handleChange("description", val)} multiline icon="document-text" error={errors.description} /> + handleChange("groupName", val)} icon="people" error={errors.groupName} /> + handleChange("description", val)} multiline icon="document-text" error={errors.description} /> - Visibility - - handleChange("visibility", "PRIVATE")}>Private - handleChange("visibility", "PUBLIC")}>Public - +
+ + handleChange("visibility", val)} + /> + + + handleChange("withdrawalMethod", val)} + /> + +
- Fund Distribution - - {distributionTypes.map((type) => ( handleChange("withdrawalMethod", type)} style={[styles.toggleButton, form.withdrawalMethod === type && styles.selectedButton]}>{type.charAt(0) + type.slice(1).toLowerCase()}))} - - - - handleChange("minParticipants", val)} keyboardType="numeric" icon="person-add" /> - handleChange("maxParticipants", val)} keyboardType="numeric" icon="people-circle" /> - +
+ + handleChange("minParticipants", val)} keyboardType="numeric" icon="person-add" error={errors.minParticipants} /> + handleChange("maxParticipants", val)} keyboardType="numeric" icon="people-circle" error={errors.maxParticipants} /> + +
- Contribution Amount (INR) - - {presetAmounts.map((amount) => ( handlePresetAmount(amount)} style={[styles.presetButton, form.contributionAmount === amount.toString() && styles.presetSelected]}>{amount}))} - - - - handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" /> - - - Cycle Type - - {cycleTypes.map((type) => ( handleChange("cycleType", type)} style={[styles.cycleTypeButton, form.cycleType === type && styles.cycleTypeSelected]}>{type.charAt(0) + type.slice(1).toLowerCase()}))} - - {form.cycleType === "CUSTOM" && ( handleChange("customCycleDays", val)} keyboardType="numeric" icon="calendar" />)} +
+ handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" error={errors.contributionAmount} /> + + handleChange("cycleType", val)} + isWrap={true} + /> + + {form.cycleType === "CUSTOM" && ( handleChange("customCycleDays", val)} keyboardType="numeric" icon="calendar" error={errors.customCycleDays} />)} +
- Start Date - setCalendarVisible(true)}> - - {form.startDate || "Select a start date"} - - {errors.startDate && {errors.startDate}} +
+ + handleChange("lateFeeMode", val)} + /> + + handleChange("lateFeeValue", val)} keyboardType="numeric" icon="alert-circle" error={errors.lateFeeValue} /> +
- +
- - setCalendarVisible(false)}> - setCalendarVisible(false)}> - - -
); } +// --- Helper Components for a cleaner layout --- + +const Section: React.FC<{ title: string, children: React.ReactNode }> = ({ title, children }) => ( + + {title} + {children} + +); + +const SettingRow: React.FC<{ label: string, children: React.ReactNode }> = ({ label, children }) => ( + + {label} + {children} + +); + +const ToggleButtons: React.FC<{ options: string[], selectedValue: string, onSelect: (value: string) => void, isWrap?: boolean }> = ({ options, selectedValue, onSelect, isWrap = false }) => ( + + {options.map((option) => ( + onSelect(option)} + style={[styles.toggleButton, selectedValue === option && styles.selectedButton, isWrap && {flex: 0, paddingHorizontal: 15}]} + > + + {option.charAt(0) + option.slice(1).toLowerCase()} + + + ))} + +); + const styles = StyleSheet.create({ - container: { flexGrow: 1, padding: 20, backgroundColor: "#f5f6fa" }, - titleContainer: { paddingVertical: 2, borderRadius: 15, alignSelf: "flex-start", marginBottom: 30 }, - title: { fontSize: 22, fontWeight: "bold", color: "#333" }, - label: { fontSize: 14, fontWeight: "600", marginBottom: 10, color: "#444" }, - toggleContainer: { flexDirection: "row", gap: 10, marginBottom: 15 }, - toggleButton: { flex: 1, paddingVertical: 12, borderWidth: 1, borderColor: "#ddd", borderRadius: 12, alignItems: "center", backgroundColor: "#fff" }, + container: { flexGrow: 1, padding: 16, backgroundColor: "#f5f6fa" }, + titleContainer: { marginBottom: 20, paddingHorizontal: 4 }, + title: { fontSize: 28, fontWeight: "bold", color: "#1F2937" }, + + section: { + backgroundColor: '#fff', + borderRadius: 12, + padding: 16, + marginBottom: 16, + elevation: 1, + shadowColor: '#4A5568', + shadowOpacity: 0.06, + shadowRadius: 10, + }, + sectionTitle: { + fontSize: 18, + fontWeight: '600', + color: '#374151', + marginBottom: 16, + borderBottomWidth: 1, + borderBottomColor: '#F3F4F6', + paddingBottom: 10, + }, + + settingRow: { + marginBottom: 16, + }, + + label: { fontSize: 14, fontWeight: "600", marginBottom: 8, color: "#4B5563" }, + + toggleContainer: { flexDirection: "row", gap: 10 }, + toggleButton: { + flex: 1, + paddingVertical: 12, + borderWidth: 1, + borderColor: "#D1D5DB", + borderRadius: 8, + alignItems: "center", + backgroundColor: "#F9FAFB" + }, selectedButton: { backgroundColor: colors.pastelGreen, borderColor: "#62b59a" }, - toggleButtonText: { color: "#444", fontWeight: "600" }, - selectedButtonText: { color: "#333" }, - participantsRow: { flexDirection: "row", justifyContent: "space-between", alignItems: 'flex-start', marginBottom: 10 }, - participantInput: { flex: 0.48 }, - presetContainer: { flexDirection: 'row', justifyContent: 'flex-start', gap: 10, marginBottom: 10 }, - presetButton: { paddingVertical: 8, paddingHorizontal: 15, backgroundColor: '#ddd', borderRadius: 20 }, - presetSelected: { backgroundColor: colors.pastelGreen, }, - presetButtonText: { color: '#555', fontWeight: '600' }, - presetSelectedText: { color: '#333' }, - amountContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 15, }, - slider: { flex: 1 }, - amountInput: { width: 120, marginLeft: 10 }, - cycleTypeContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 20 }, - cycleTypeButton: { paddingVertical: 10, paddingHorizontal: 15, backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 20, elevation: 1 }, - cycleTypeSelected: { backgroundColor: colors.pastelGreen, borderColor: '#62b59a' }, - cycleTypeButtonText: { color: '#444', fontWeight: '600', textTransform: 'capitalize' }, - cycleTypeSelectedText: { color: '#333' }, - datePickerButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 12, paddingVertical: 15, paddingHorizontal: 15, marginBottom: 5, }, - datePickerText: { marginLeft: 10, fontSize: 16, color: '#333' }, - modalBackdrop: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0, 0, 0, 0.5)' }, - calendarWrapper: { width: '90%', backgroundColor: 'white', borderRadius: 16, paddingVertical: 10, elevation: 5, shadowColor: '#000', shadowOpacity: 0.2, shadowRadius: 10, }, - // --- ERROR STYLES --- - errorBorder: { - borderColor: '#ff4d4d', // Red border for fields with errors - }, - errorText: { - color: '#ff4d4d', - fontSize: 12, - marginBottom: 10, - marginLeft: 5, - }, + toggleButtonText: { color: "#374151", fontWeight: "600", textTransform: 'capitalize' }, + selectedButtonText: { color: "#1F2937" }, + + participantsRow: { flexDirection: "row", gap: 15 }, + participantInput: { flex: 1 }, }); diff --git a/src/screens/GroupReview.tsx b/src/screens/GroupReview.tsx index a0f91dd..30d0bd2 100644 --- a/src/screens/GroupReview.tsx +++ b/src/screens/GroupReview.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Modal, ActivityIndicator } from "react-native"; import { Ionicons } from "@react-native-vector-icons/ionicons"; import AppView from "../components/AppView"; @@ -6,42 +6,34 @@ import { apiCall } from "../utils/ApiHelper"; import { GROUP_CREATION_URL } from "../utils/ApiUrls"; import colors from "../components/colors"; -// --- Simplified Dialog Component --- -const ThemedDialog = ({ visible, isSuccess, onClose }: { visible: boolean; isSuccess: boolean; onClose: () => void; }) => { +// --- Themed Dialog Component --- +const ThemedDialog = ({ visible, isSuccess, message, onClose, onNavigate }: { visible: boolean; isSuccess: boolean; message: string; onClose: () => void; onNavigate: () => void; }) => { if (!visible) return null; - const dialogConfig = { - success: { - icon: "checkmark-circle" as const, - color: "#2ecc71", - title: "Success!", - message: "Your group has been created.", - buttonText: "Awesome!", - }, - failure: { - icon: "close-circle" as const, - color: "#e74c3c", - title: "Oops!", - message: "Something went wrong. Please try again.", - buttonText: "Try Again", - }, + const config = isSuccess ? { + icon: "checkmark-circle" as const, + color: "#2ecc71", + title: "Success!", + buttonText: "Go Home", + action: onNavigate, + } : { + icon: "close-circle" as const, + color: "#e74c3c", + title: "Oops!", + buttonText: "Try Again", + action: onClose, }; - const config = isSuccess ? dialogConfig.success : dialogConfig.failure; - return ( - + {config.title} - {config.message} - {/* Only show button for failure case */} - {!isSuccess && ( - - {config.buttonText} - - )} + {message} + + {config.buttonText} + @@ -51,56 +43,56 @@ const ThemedDialog = ({ visible, isSuccess, onClose }: { visible: boolean; isSuc export default function GroupReview({ route, navigation }: any) { const { form } = route.params; const [isLoading, setIsLoading] = useState(false); - // --- Simplified State --- const [isSuccess, setIsSuccess] = useState(false); const [modalVisible, setModalVisible] = useState(false); + const [feedbackMessage, setFeedbackMessage] = useState(""); - // --- Automatic Navigation Effect --- - useEffect(() => { - // If the modal is visible and the operation was a success... - if (modalVisible && isSuccess) { - // ...wait for 2 seconds, then navigate. - const timer = setTimeout(() => { - setModalVisible(false); // Close the dialog - navigation.reset({ // Reset the navigation stack - index: 0, - routes: [{ name: 'Home',}], - }); - }, 2000); - - return () => clearTimeout(timer); // Cleanup timer on unmount - } - }, [modalVisible, isSuccess, navigation]); + const handleNavigateHome = () => { + setModalVisible(false); + navigation.reset({ index: 0, routes: [{ name: 'Home' }] }); + }; const handleSubmit = async () => { setIsLoading(true); try { - const payload = { - ...form, - contributionAmount: Number(form.contributionAmount) || 0, - customCycleDays: Number(form.customCycleDays) || 0, - maxParticipants: Number(form.maxParticipants) || 0, - minParticipants: Number(form.minParticipants) || 0, - }; - console.log("payload:", payload); - const res = await apiCall(GROUP_CREATION_URL, "POST", payload) as { statusCode?: number }; - + const res = await apiCall(GROUP_CREATION_URL, "POST", form) as { statusCode?: number, message?: string }; if (res?.statusCode === 201) { setIsSuccess(true); + setFeedbackMessage(res.message || "Your group has been created successfully."); } else { - throw new Error("Failed to create group."); + throw new Error(res?.message || "Failed to create the group."); } - } catch (error) { + } catch (error: any) { setIsSuccess(false); + setFeedbackMessage(error.message || "An unexpected error occurred."); } finally { setIsLoading(false); - setModalVisible(true); // Show the dialog for both success and failure + setModalVisible(true); } }; - // --- UI COMPONENTS --- - const TimelineSection = ({ icon, title, children }: { icon: any; title: string; children: React.ReactNode }) => ( {title} {children} ); - const renderRow = (label: string, value: string | number) => { if (!value && typeof value !== 'number') return null; return ( {label} {value} ); }; + const TimelineSection: React.FC<{ icon: any; title: string; children: React.ReactNode }> = ({ icon, title, children }) => ( + + + + + + + {title} + {children} + + + ); + + const renderRow = (label: string, value: string | number | null) => { + if (value === null || value === undefined || value === "") return null; + return ( + + {label} + {value} + + ); + }; return ( @@ -110,32 +102,60 @@ export default function GroupReview({ route, navigation }: any) { Confirm the details to launch your group. - {/* --- Timeline Sections --- */} - {renderRow("Name", form.groupName)}{form.description ? ( Description {form.description} ) : null} - {renderRow("Visibility", form.visibility)}{renderRow("Distribution", form.withdrawalMethod)}{renderRow("Min Members", form.minParticipants)}{renderRow("Max Members", form.maxParticipants)} - {renderRow("Contribution", `${form.contributionAmount} INR`)}{renderRow("Cycle", form.cycleType)}{form.cycleType === "CUSTOM" && renderRow("Cycle Days", `${form.customCycleDays} days`)}{renderRow("Start Date", form.startDate)} + + {renderRow("Name", form.groupName)} + {form.description ? ( + + Description + {form.description} + + ) : null} + + + + {renderRow("Visibility", form.visibility)} + {renderRow("Distribution", form.withdrawalMethod)} + {renderRow("Min Members", form.minParticipants)} + {renderRow("Max Members", form.maxParticipants)} + + + + {renderRow("Contribution", `₹${form.contributionAmount}`)} + {renderRow("Cycle", form.cycleType)} + {form.cycleType === "CUSTOM" && renderRow("Cycle Days", `${form.customCycleDays} days`)} + + + + {renderRow("Fee Mode", form.lateFeeMode)} + {renderRow("Fee Value", `₹${form.lateFeeValue}`)} + - {isLoading ? : ( <> Create Group )} + {isLoading ? : ( + <> + + Create Group + + )} - {/* --- Simplified Dialog Call --- */} setModalVisible(false)} + onNavigate={handleNavigateHome} /> ); } const styles = StyleSheet.create({ - // ... (All previous styles remain the same) container: { padding: 20, backgroundColor: "#f5f6fa" }, - header: { marginBottom: 30, alignItems: 'flex-start' }, + header: { marginBottom: 30 }, title: { fontSize: 28, fontWeight: "bold", color: "#333" }, - subtitle: { fontSize: 16, color: "#666", marginTop: 2 }, + subtitle: { fontSize: 16, color: "#666", marginTop: 4 }, section: { flexDirection: 'row', marginBottom: 10 }, timeline: { alignItems: 'center', width: 40 }, timelineIcon: { width: 40, height: 40, borderRadius: 20, backgroundColor: colors.pastelGreen, justifyContent: 'center', alignItems: 'center', elevation: 3 }, @@ -143,17 +163,17 @@ const styles = StyleSheet.create({ content: { flex: 1, paddingLeft: 10, paddingBottom: 20 }, sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#444', marginBottom: 10 }, card: { backgroundColor: "#fff", borderRadius: 12, padding: 15, elevation: 2 }, - row: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#f5f5f5' }, + row: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#f5f5f5' }, label: { fontSize: 15, color: "#666", fontWeight: "500" }, - value: { fontSize: 15, color: "#333", fontWeight: "600", textAlign: 'right', textTransform: 'capitalize' }, + value: { fontSize: 15, color: "#333", fontWeight: "600", textTransform: 'capitalize' }, descriptionContainer: { paddingVertical: 10 }, descriptionText: { fontSize: 15, color: '#333', marginTop: 4, lineHeight: 22 }, - submitBtn: { backgroundColor: colors.pastelGreen, flexDirection: 'row', justifyContent: 'center', alignItems: "center", paddingVertical: 16, borderRadius: 12, elevation: 3, marginTop: 20, borderWidth: 1.5, borderColor: '#62b59a' }, + submitBtn: { backgroundColor: colors.pastelGreen, flexDirection: 'row', justifyContent: 'center', alignItems: "center", paddingVertical: 16, borderRadius: 12, marginTop: 20 }, btnText: { color: "#333", fontWeight: "bold", fontSize: 18, marginLeft: 10 }, modalBackdrop: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center' }, dialogBox: { width: '85%', backgroundColor: 'white', borderRadius: 16, padding: 25, alignItems: 'center', elevation: 10 }, - dialogTitle: { fontSize: 22, fontWeight: 'bold', color: '#333', marginTop: 15, marginBottom: 5 }, + dialogTitle: { fontSize: 22, fontWeight: 'bold', color: '#333', marginTop: 15, marginBottom: 10 }, dialogMessage: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 25 }, - dialogButton: { backgroundColor: colors.pastelGreen, paddingVertical: 12, paddingHorizontal: 40, borderRadius: 10 }, + dialogButton: { backgroundColor: '#f0f0f0', paddingVertical: 12, paddingHorizontal: 40, borderRadius: 10 }, dialogButtonText: { color: '#333', fontWeight: 'bold', fontSize: 16 }, }); diff --git a/src/utils/HelperFunction.tsx b/src/utils/HelperFunction.tsx index 563798f..2279126 100644 --- a/src/utils/HelperFunction.tsx +++ b/src/utils/HelperFunction.tsx @@ -28,6 +28,7 @@ export const fetchUserProfile = async () => { ); const result = await response.json(); + console.log("user api data",result) if (result.status === "success") { dispatch(setUserData({ id: result?.data.id, From 5a9e80f56f90fec0e345160992652b683fdff5cd Mon Sep 17 00:00:00 2001 From: Abin Toppo Date: Mon, 22 Sep 2025 11:00:42 +0530 Subject: [PATCH 2/2] manage groups --- src/components/JoinedGroupCard.tsx | 209 ++++++++- src/navigation/AppNavigator.tsx | 2 +- src/screens/management/EditGroupScreen.tsx | 465 +++++++++++--------- src/screens/management/ManagementScreen.tsx | 49 ++- 4 files changed, 502 insertions(+), 223 deletions(-) diff --git a/src/components/JoinedGroupCard.tsx b/src/components/JoinedGroupCard.tsx index ba68137..08383b0 100644 --- a/src/components/JoinedGroupCard.tsx +++ b/src/components/JoinedGroupCard.tsx @@ -2,42 +2,207 @@ import React, { FC } from 'react'; import { View, Text, StyleSheet } from 'react-native'; -import { JoinedGroup } from '../types'; + +// Update the type to match the full data structure +interface JoinedGroup { + id: string; + groupName: string; + description: string; + visibility: 'PUBLIC' | 'PRIVATE'; + createdByUserId: number; + createdByName: string; // Renamed from createdBy for clarity + startDate: [number, number, number] | null; + endDate: [number, number, number] | null; + minParticipants: number; + maxParticipants: number; + currentParticipants: number; + contributionAmount: number; + cycleType: 'MONTHLY' | 'WEEKLY' | 'QUARTERLY' | 'CUSTOM'; + status: 'ACTIVE' | 'PENDING' | 'COMPLETED'; +} interface JoinedGroupCardProps { item: JoinedGroup; } -export const JoinedGroupCard: FC = ({ item }) => ( - - - {item.groupName} - Created by {item.createdBy} +// Helper function to format dates +const formatDate = (dateArray: [number, number, number] | null): string => { + if (!dateArray) return 'N/A'; + const date = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]); + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); +}; + +// Helper to determine status color +const getStatusStyle = (status: JoinedGroup['status']) => { + switch (status) { + case 'ACTIVE': + return { + backgroundColor: '#D1FAE5', // Green tint + textColor: '#065F46', // Dark Green + }; + case 'PENDING': + return { + backgroundColor: '#FEF3C7', // Yellow tint + textColor: '#92400E', // Dark Yellow/Brown + }; + case 'COMPLETED': + return { + backgroundColor: '#E5E7EB', // Gray tint + textColor: '#4B5563', // Dark Gray + }; + default: + return { + backgroundColor: '#E5E7EB', + textColor: '#4B5563', + }; + } +}; + +export const JoinedGroupCard: FC = ({ item }) => { + const progress = (item.currentParticipants / item.maxParticipants) * 100; + const statusStyle = getStatusStyle(item.status); + + return ( + + {/* Header */} + + {item.groupName} + + {item.status} + + + Created by {item.createdByName} + + {/* Financial Info */} + + + Contribution + ₹{item.contributionAmount.toLocaleString('en-IN')} + + + Cycle + {item.cycleType} + + + + {/* Participants Progress */} + + + Participants + {item.currentParticipants} / {item.maxParticipants} + + + + + + + {/* Dates */} + + + {formatDate(item.startDate)} - {formatDate(item.endDate)} + + - -); + ); +}; const styles = StyleSheet.create({ card: { - backgroundColor: '#fff', - borderRadius: 12, - padding: 16, - marginBottom: 12, + backgroundColor: '#FFFFFF', + borderRadius: 16, + padding: 20, + marginBottom: 16, + shadowColor: '#374151', + shadowOpacity: 0.08, + shadowRadius: 10, + shadowOffset: { width: 0, height: 4 }, + elevation: 3, + }, + header: { flexDirection: 'row', - alignItems: 'center', justifyContent: 'space-between', - elevation: 2, - shadowColor: '#000', - shadowOpacity: 0.05, - shadowRadius: 4, + alignItems: 'flex-start', + marginBottom: 4, }, - cardTitle: { - fontSize: 17, + groupName: { + fontSize: 20, fontWeight: 'bold', - color: '#343a40', + color: '#1F2937', + flexShrink: 1, }, - cardSubtitle: { + createdBy: { fontSize: 14, - color: '#6c757d', + color: '#6B7280', + marginBottom: 16, + }, + statusBadge: { + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 12, + }, + statusText: { + fontSize: 12, + fontWeight: '600', + textTransform: 'uppercase', + }, + infoRow: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: 16, + borderTopWidth: 1, + borderBottomWidth: 1, + borderColor: '#F3F4F6', + marginBottom: 16, + }, + infoItem: { + alignItems: 'flex-start', + }, + infoLabel: { + fontSize: 14, + color: '#6B7280', + marginBottom: 4, + }, + infoValue: { + fontSize: 16, + fontWeight: '600', + color: '#111827', + textTransform: 'capitalize', + }, + progressContainer: { + marginBottom: 16, + }, + progressLabels: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 8, + }, + participantText: { + fontSize: 14, + color: '#6B7280', + }, + participantCount: { + fontSize: 14, + fontWeight: '600', + color: '#111827', + }, + progressBarBackground: { + height: 8, + backgroundColor: '#E5E7EB', + borderRadius: 4, + overflow: 'hidden', + }, + progressBarFill: { + height: '100%', + backgroundColor: '#4F46E5', + borderRadius: 4, + }, + footer: { + alignItems: 'center', + marginTop: 8, + }, + dateText: { + fontSize: 14, + color: '#4B5563', + fontWeight: '500', }, }); diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 7390d57..7d05f0e 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -64,7 +64,7 @@ export default function AppNavigator() { - {/* */} + ); diff --git a/src/screens/management/EditGroupScreen.tsx b/src/screens/management/EditGroupScreen.tsx index b0964fe..1478ab9 100644 --- a/src/screens/management/EditGroupScreen.tsx +++ b/src/screens/management/EditGroupScreen.tsx @@ -13,6 +13,7 @@ import { import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path import { apiCall } from '../../utils/ApiHelper'; // Adjust path import AppView from '../../components/AppView'; +import { Picker } from '@react-native-picker/picker'; // --- Type Definitions --- interface Participant { @@ -27,216 +28,284 @@ export interface GroupDetails { id: string; groupName: string; description: string; - startDate: [number, number, number]; + startDate: [number, number, number] | null; endDate: [number, number, number] | null; contributionAmount: number; minParticipants: number; maxParticipants: number; + portalCharge: number | null; latePaymentCharge: number | null; cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM'; - status: 'PENDING' | 'ACTIVE' | 'COMPLETED'; + customCycleDays: number; + withdrawalMethod: 'RANDOM' | 'SEQUENTIAL'; + status: 'PENDING' | 'ACTIVE' | 'COMPLETED' | 'APPROVED'; visibility: 'PUBLIC' | 'PRIVATE'; + organizerId: string; totalParticipants: number; participants: Participant[]; } export interface EditGroupPayload { - groupName?: string; - description?: string; - maxParticipants?: number; + groupName: string; + description: string; + maxParticipants: number; + contributionAmount: number; + customCycleDays: number | null; + cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM'; + latePaymentCharge: number; + visibility: 'PUBLIC' | 'PRIVATE'; } type EditGroupScreenProps = NativeStackScreenProps; -type EditingSection = 'details' | 'financials' | null; - -// --- Helper Components --- -const InfoRow: React.FC<{ label: string; value: string | number | null; isStatus?: boolean }> = ({ label, value, isStatus = false }) => ( - - {label} - - {value ?? 'N/A'} - - -); - -const ParticipantRow: React.FC<{ participant: Participant }> = ({ participant }) => ( - - - - {`${participant.firstName} ${participant.lastName || ''}`.trim()} - {participant.role === 'ORGANIZER' && ( - Organizer - )} - - {participant.mobile} - - -); - -const FormInput: React.FC<{ label: string; value: string; onChangeText: (text: string) => void; multiline?: boolean; keyboardType?: 'default' | 'numeric' }> = -({ label, value, onChangeText, multiline = false, keyboardType = 'default' }) => ( - - {label} - - -); +type EditingField = keyof EditGroupPayload | null; // --- Main Screen Component --- const EditGroupScreen: React.FC = ({ route }) => { - const { groupId } = route.params; + const { groupId } = route.params; - const [editingSection, setEditingSection] = useState(null); - const [groupDetails, setGroupDetails] = useState(null); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [error, setError] = useState(null); + const [editingField, setEditingField] = useState(null); + const [groupDetails, setGroupDetails] = useState(null); + const [editableData, setEditableData] = useState({ + groupName: '', + description: '', + maxParticipants: 0, + contributionAmount: 0, + customCycleDays: null, + cycleType: 'MONTHLY', + latePaymentCharge: 0, + visibility: 'PRIVATE', + }); + + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [starting, setStarting] = useState(false); + const [error, setError] = useState(null); - const [groupName, setGroupName] = useState(''); - const [description, setDescription] = useState(''); - const [maxParticipants, setMaxParticipants] = useState(''); + const fetchGroupDetails = useCallback(async () => { + setLoading(true); + setError(null); + try { + const response = await apiCall(`https://api.godemo.in/FundCollect/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string }; + if (response.status === 'success' && response.data) { + setGroupDetails(response.data); + setEditableData({ + groupName: response.data.groupName, + description: response.data.description, + maxParticipants: response.data.maxParticipants, + contributionAmount: response.data.contributionAmount, + customCycleDays: response.data.customCycleDays, + cycleType: response.data.cycleType, + latePaymentCharge: response.data.latePaymentCharge ?? 0, + visibility: response.data.visibility, + }); + } else { + throw new Error(response.message || 'Failed to fetch group data.'); + } + } catch (e: any) { + setError(e.message); + } finally { + setLoading(false); + } + }, [groupId]); - const fetchGroupDetails = useCallback(async () => { - setLoading(true); - setError(null); - try { - const response = await apiCall(`https://api.godemo.in/FundCollect/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string }; - if (response.status === 'success' && response.data) { - setGroupDetails(response.data); - setGroupName(response.data.groupName); - setDescription(response.data.description); - setMaxParticipants(response.data.maxParticipants.toString()); - } else { - throw new Error(response.message || 'Failed to fetch group data.'); - } - } catch (e: any) { - setError(e.message); - } finally { - setLoading(false); + useEffect(() => { + fetchGroupDetails(); + }, [fetchGroupDetails]); + + const handleCancel = () => { + if (groupDetails) { + setEditableData({ + groupName: groupDetails.groupName, + description: groupDetails.description, + maxParticipants: groupDetails.maxParticipants, + contributionAmount: groupDetails.contributionAmount, + customCycleDays: groupDetails.customCycleDays, + cycleType: groupDetails.cycleType, + latePaymentCharge: groupDetails.latePaymentCharge ?? 0, + visibility: groupDetails.visibility, + }); + } + setEditingField(null); + }; + + const handleSave = async () => { + if (!groupDetails || !editingField) return; + + if (editingField === 'maxParticipants') { + const newMaxParticipants = Number(editableData.maxParticipants); + if (newMaxParticipants < groupDetails.totalParticipants) { + Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`); + return; } - }, [groupId]); - - useEffect(() => { - fetchGroupDetails(); - }, [fetchGroupDetails]); - - const handleCancel = () => { - if (groupDetails) { - setGroupName(groupDetails.groupName); - setDescription(groupDetails.description); - setMaxParticipants(groupDetails.maxParticipants.toString()); - } - setEditingSection(null); - }; - - const handleSave = async (section: EditingSection) => { - if (!groupDetails || !section) return; - - let payload: EditGroupPayload = {}; - if (section === 'details') { - payload = { groupName, description }; - } else if (section === 'financials') { - const newMaxParticipants = parseInt(maxParticipants, 10); - if (newMaxParticipants < groupDetails.totalParticipants) { - Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`); - return; - } - payload = { maxParticipants: newMaxParticipants }; - } - - setSaving(true); - try { - await apiCall(`https://api.godemo.in/FundCollect/api/fund/groups/${groupDetails.id}/edit`, 'PUT', payload); - Alert.alert('Success', 'Group has been updated.'); - setEditingSection(null); - await fetchGroupDetails(); - } catch (e: any) { - Alert.alert('Save Error', e.message || 'An unexpected error occurred.'); - } finally { - setSaving(false); - } - }; + } - const CardActionButtons: React.FC<{ section: EditingSection }> = ({ section }) => ( - - - Cancel - - handleSave(section)} disabled={saving}> - {saving ? : Save} - + setSaving(true); + try { + await apiCall(`https://api.godemo.in/FundCollect/api/fund/groups/${groupDetails.id}/edit`, 'PUT', editableData); + Alert.alert('Success', 'Group has been updated.'); + setEditingField(null); + await fetchGroupDetails(); + } catch (e: any) { + Alert.alert('Save Error', e.message || 'An unexpected error occurred.'); + } finally { + setSaving(false); + } + }; + + const handleStartGroup = async () => { + if (!groupDetails) return; + + Alert.alert( + "Confirm Start", + "Are you sure you want to start this group? This action cannot be undone.", + [ + { text: "Cancel", style: "cancel" }, + { + text: "Start", + onPress: async () => { + setStarting(true); + try { + await apiCall(`https://api.godemo.in/FundCollect/api/fund/group/start?groupId=${groupDetails.id}`, 'GET'); + Alert.alert('Success', 'Group has been started successfully.'); + await fetchGroupDetails(); + } catch (e: any) { + Alert.alert('Start Error', e.message || 'An unexpected error occurred.'); + } finally { + setStarting(false); + } + }, + style: 'destructive' + } + ] + ); + }; + + const handleUpdateField = (field: keyof EditGroupPayload, value: any) => { + setEditableData(prev => ({ ...prev, [field]: value })); + }; + + if (loading) return ; + if (error) return {error}; + if (!groupDetails) return null; + + const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED'; + const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants; + + const renderInputField = (field: EditingField) => { + if (!field || !editableData) return null; + + const commonProps = { style: styles.input, placeholderTextColor: "#9CA3AF" }; + + switch(field) { + case 'groupName': + return handleUpdateField('groupName', text)} />; + case 'description': + return handleUpdateField('description', text)} multiline />; + case 'contributionAmount': + case 'maxParticipants': + case 'latePaymentCharge': + case 'customCycleDays': + return handleUpdateField(field, text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />; + case 'cycleType': + return handleUpdateField('cycleType', itemValue)} style={styles.picker}> + {['MONTHLY', 'WEEKLY', 'QUARTERLY', 'CUSTOM'].map(c => )} + ; + case 'visibility': + return handleUpdateField('visibility', itemValue)} style={styles.picker}> + + + ; + default: return null; + } + } + + const renderRow = (field: EditingField, label: string, displayValue: string | number) => { + return ( + + + {label} + {editingField !== field && ( + + {displayValue} + {isEditable && setEditingField(field)} style={styles.editButton}> + ✎ + } + + )} + + {editingField === field && isEditable && ( + + {renderInputField(field)} + + + Cancel + + + {saving ? : Save} + + + + )} - ); + ) + } - if (loading) return ; - if (error) return {error}; - if (!groupDetails) return null; + return ( + + + Edit Group - const isEditable = groupDetails.status === 'PENDING'; + {canStartGroup && ( + + Ready to Start? + This group has met the minimum participant requirement. + + {starting ? : Start Group} + + + )} - return ( - - - Group Overview + + Group Info + {renderRow('groupName', 'Group Name', groupDetails.groupName)} + {renderRow('description', 'Description', groupDetails.description)} + - - - Group Details - {isEditable && editingSection !== 'details' && ( - setEditingSection('details')}>Edit - )} + + Financial Rules + {renderRow('contributionAmount', 'Contribution', `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}`)} + {renderRow('maxParticipants', 'Max Participants', `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}`)} + {renderRow('latePaymentCharge', 'Late Fee', `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}`)} + + + + Configuration + {renderRow('cycleType', 'Payment Cycle', groupDetails.cycleType)} + {(editableData.cycleType === 'CUSTOM' || groupDetails.cycleType === 'CUSTOM') && renderRow('customCycleDays', 'Custom Days', `${editableData.customCycleDays ?? groupDetails.customCycleDays} days`)} + {renderRow('visibility', 'Visibility', groupDetails.visibility)} + + + + Participants ({groupDetails.totalParticipants}) + {groupDetails.participants.length > 0 ? ( + groupDetails.participants.map(p => ( + + + + {`${p.firstName} ${p.lastName || ''}`.trim()} + {p.role === 'ORGANIZER' && Organizer} + + {p.mobile} + - {editingSection === 'details' ? ( - <> - - - - - ) : ( - <> - {groupDetails.groupName} - {groupDetails.description} - - )} - - - - - Financial Rules - {isEditable && editingSection !== 'financials' && ( - setEditingSection('financials')}>Edit - )} - - - {editingSection === 'financials' ? ( - <> - - - - ) : ( - - )} - - - - - - Participants ({groupDetails.totalParticipants}) - {groupDetails.participants.length > 0 ? ( - groupDetails.participants.map(p => ) - ) : ( - No participants have joined yet. - )} - - - - ); + )) + ) : ( + No participants yet. + )} + + + + ); }; const styles = StyleSheet.create({ @@ -247,33 +316,35 @@ const styles = StyleSheet.create({ headerTitle: { fontSize: 28, fontWeight: 'bold', color: '#111827', marginBottom: 20 }, card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10 }, - cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderBottomWidth: 1, borderBottomColor: '#E5E7EB', paddingBottom: 10, marginBottom: 16 }, - cardTitle: { fontSize: 18, fontWeight: 'bold', color: '#1F2937' }, - editButtonText: { color: '#4F46E5', fontSize: 15, fontWeight: '600' }, + cardTitle: { fontSize: 18, fontWeight: 'bold', color: '#1F2937', marginBottom: 12, borderBottomWidth: 1, borderBottomColor: '#E5E7EB', paddingBottom: 10 }, - groupNameView: { fontSize: 22, fontWeight: '700', color: '#1F2937', marginBottom: 8 }, - descriptionView: { fontSize: 16, color: '#6B7280', lineHeight: 24 }, + editButton: { padding: 8, marginLeft: 8 }, + editButtonText: { color: '#4F46E5', fontSize: 20, fontWeight: 'bold' }, - inputContainer: { marginBottom: 16 }, - inputLabel: { fontSize: 14, color: '#6B7280', marginBottom: 8, fontWeight: '500' }, - input: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, padding: 14, fontSize: 16 }, - textArea: { height: 100, textAlignVertical: 'top' }, - - infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' }, + fieldContainer: { borderBottomWidth: 1, borderBottomColor: '#F3F4F6', paddingVertical: 12 }, + infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, infoLabel: { fontSize: 16, color: '#6B7280' }, - infoValue: { fontSize: 16, fontWeight: '600', textTransform: 'capitalize' }, - - participantRow: { paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' }, + infoValue: { fontSize: 16, fontWeight: '600', color: '#111827', textTransform: 'capitalize' }, + valueContainer: { flexDirection: 'row', alignItems: 'center' }, + + editingContainer: { marginTop: 12 }, + input: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, padding: 14, fontSize: 16, color: '#111827' }, + textArea: { height: 100, textAlignVertical: 'top' }, + picker: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, padding: 0, margin:0 }, + + participantRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' }, participantName: { fontSize: 16, fontWeight: '600', color: '#111827' }, participantMobile: { fontSize: 14, color: '#6B7280', marginTop: 4 }, organizerBadge: { backgroundColor: '#FEF3C7', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 12, marginLeft: 10 }, organizerBadgeText: { color: '#92400E', fontSize: 12, fontWeight: '500' }, noParticipantsText: { fontSize: 15, color: '#6B7280', textAlign: 'center', paddingVertical: 20 }, - cardActions: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 20, borderTopWidth: 1, borderTopColor: '#E5E7EB', paddingTop: 16 }, - button: { borderRadius: 10, paddingVertical: 10, paddingHorizontal: 20, marginLeft: 10, minWidth: 80, alignItems: 'center' }, + cardActions: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 20, paddingTop: 16, borderTopWidth: 1, borderTopColor: '#E5E7EB' }, + button: { borderRadius: 10, paddingVertical: 12, paddingHorizontal: 20, marginLeft: 10, minWidth: 80, alignItems: 'center', justifyContent: 'center' }, saveButton: { backgroundColor: '#4F46E5' }, cancelButton: { backgroundColor: '#E5E7EB' }, + startButton: { backgroundColor: '#22c55e', marginTop: 10 }, + startGroupText: { fontSize: 15, color: '#6B7280', lineHeight: 22, marginBottom: 10}, buttonText: { color: '#FFFFFF', fontSize: 15, fontWeight: '600' }, }); diff --git a/src/screens/management/ManagementScreen.tsx b/src/screens/management/ManagementScreen.tsx index 17232f2..f3421ca 100644 --- a/src/screens/management/ManagementScreen.tsx +++ b/src/screens/management/ManagementScreen.tsx @@ -18,7 +18,7 @@ import { APPROVE_REQUEST_URL, BASE_URL, DECLINE_REQUEST_URL, GET_JOINED_GROUP_UR import colors from "../../components/colors"; import AppView from "../../components/AppView"; import { apiCall } from "../../utils/ApiHelper"; - +import * as Keychain from "react-native-keychain"; import { MyGroup, JoinedGroup, JoinRequest, ApiResponse } from '../../types'; import { MyGroupCard } from "../../components/MyGroupCard"; import { JoinedGroupCard } from "../../components/JoinedGroupCard"; @@ -38,14 +38,57 @@ const ManagementScreen: FC = () => { const [requests, setRequests] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const userRole = user.role; + const [userRole,setUserRole] = useState(null) + const userStatus = async () => { + try { + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; + + setLoading(true); + setError(null); + const response = await fetch(`${BASE_URL}user/profile/status`, { + method: "GET", + headers: { + Accept: "*/*", + Authorization: `Bearer ${token}`, + }, + }); + + const json = await response.json(); + + console.log("👤 Profile Status Response:", json.data.role); + if (json.status === "success") { + setUserRole(json.data.role); + + if (json.data.profileStatus === false) { + + } + } else { + setError(json.message || "Failed to fetch status"); + } + } catch (err) { + setError("Something went wrong. Please try again."); + } finally { + setLoading(false); + } + }; + + useFocusEffect( + useCallback(() => { + userStatus(); + }, []) + ); - console.log(" abin user : ", user) const fetchData = useCallback(async () => { if (loading) return; setLoading(true); setError(null); try { + console.log(" abin user : ", userRole) if (userRole === "ORGANIZER") { // Fetch all data concurrently const [myGroupsRes, joinedGroupsRes, requestsRes] = await Promise.all([