Files
circle-collect/src/screens/management/EditGroupScreen.tsx
T

815 lines
33 KiB
TypeScript

import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Image,
ActivityIndicator,
ToastAndroid,
FlatList,
Modal,
Platform,
TouchableWithoutFeedback,
Keyboard,
TextInput,
ScrollView,
Dimensions,
KeyboardAvoidingView,
Share,
RefreshControl,
} from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { Picker } from '@react-native-picker/picker';
import Clipboard from '@react-native-clipboard/clipboard';
import { launchImageLibrary } from 'react-native-image-picker';
import * as Keychain from 'react-native-keychain';
import LucideIcon from '../../components/common/LucideIcon';
import { responsiveFontSize } from 'react-native-responsive-dimensions';
import AppView from '../../components/AppView';
import { useTheme } from '../../hooks/ui/useTheme';
import { useFetch } from '../../hooks/core/useFetch';
import { useUpdate } from '../../hooks/core/useUpdate';
import { useInvite } from '../../hooks/core/useInvite';
import { BASE_URL, UPLOAD_GROUP_IMAGE } from '../../utils/ApiUrls';
import FontsFamily from '../../constents/FontsFamily';
import SkeletonLoader from '../../components/common/SkeletonLoader';
import WinnerUser from './WinnerUser';
const { width } = Dimensions.get('window');
// --- TYPE DEFINITIONS ---
interface Participant {
id: string;
firstName: string;
lastName: string | null;
mobile: string;
role: 'ORGANIZER' | 'USER';
}
export interface EditGroupPayload {
groupName: string;
description: string;
maxParticipants: number;
minParticipants: number;
lateFeeMode: 'DAILY' | 'FIXED' | string;
lateFeeValue: number;
latePaymentCharge: number;
visibility: 'PUBLIC' | 'PRIVATE';
contributionAmount: number;
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM' | 'HALF_YEARLY' | 'YEARLY';
entryFee: number;
customCycleDays: number | null;
}
type EditSection = 'groupInfo' | 'financialRules' | 'configuration' | null;
const EditGroupScreen: React.FC = () => {
const theme = useTheme();
const styles = React.useMemo(() => getStyles(theme), [theme]);
const route = useRoute<any>();
const navigation = useNavigation<any>();
const { groupId } = route?.params || {};
// State
const [activeSection, setActiveSection] = useState<EditSection>(null);
const [editableData, setEditableData] = useState<EditGroupPayload | null>(null);
const [originalData, setOriginalData] = useState<EditGroupPayload | null>(null);
const [changedFields, setChangedFields] = useState<Set<keyof EditGroupPayload>>(new Set());
const [selectedImage, setSelectedImage] = useState<string | null>(null);
const [imageUpdating, setImageUpdating] = useState(false);
const [selectedUserModal, setSelectedUserModel] = useState(false);
const [isActivating, setIsActivating] = useState(false);
const [showInviteTooltip, setShowInviteTooltip] = useState(false);
// --- DATA FETCHING ---
const {
data: groupDetailsRes,
isLoading: isGroupLoading,
isRefetching: isRefetchingGroup,
refetch: refetchGroup,
} = useFetch<any>(
`${BASE_URL}fund/group/details?groupId=${groupId}`,
undefined,
{ enabled: !!groupId }
);
const groupDetails = groupDetailsRes?.data;
// Initialize data
useEffect(() => {
if (groupDetails) {
const newData: EditGroupPayload = {
groupName: groupDetails.groupName || '',
description: groupDetails.description || '',
maxParticipants: groupDetails.maxParticipants || 0,
minParticipants: groupDetails.minParticipants || 0,
contributionAmount: groupDetails.contributionAmount || 0,
customCycleDays: groupDetails.customCycleDays || 0,
cycleType: groupDetails.cycleType || 'MONTHLY',
latePaymentCharge: groupDetails.latePaymentCharge ?? 0,
visibility: groupDetails.visibility || 'PUBLIC',
lateFeeMode: groupDetails.lateFeeMode ?? null,
lateFeeValue: groupDetails.lateFeeValue ?? 0,
entryFee: groupDetails.entryFee ?? 0,
};
setOriginalData(newData);
setEditableData(newData);
setChangedFields(new Set());
if (groupDetails.status === 'APPROVED') {
setShowInviteTooltip(true);
const timer = setTimeout(() => setShowInviteTooltip(false), 5000);
return () => clearTimeout(timer);
}
}
// DEBUG: Log Token
const logToken = async () => {
try {
const credentials = await Keychain.getGenericPassword();
if (credentials) {
console.log("🔐 AUTH TOKEN:", credentials.password);
} else {
console.log("🔐 No credentials stored");
}
} catch (error) {
console.error("Error retrieving token:", error);
}
};
logToken();
}, [groupDetails]);
const groupStatusFromDetails = groupDetails?.status;
const { data: paymentStatusRes, refetch: refetchPaymentStatus } = useFetch<any>(
`${BASE_URL}payments/group/${groupId}/payment/paid`,
undefined,
{ enabled: !!groupId && (groupStatusFromDetails === 'ACTIVE' || groupStatusFromDetails === 'DELAY') }
);
const paymentStatus = paymentStatusRes?.data;
const { mutate: generateInvite, isPending: isInviting } = useInvite({
onSuccess: (res) => {
if (res.data) {
Clipboard.setString(res.data);
ToastAndroid.show('Invite link copied', ToastAndroid.SHORT);
Share.share({ message: `Join my group: ${res.data}` });
}
},
onError: (error: any) => {
ToastAndroid.show(error?.message || 'Failed to generate invite', ToastAndroid.SHORT);
}
});
// --- MUTATIONS ---
const { mutate: updateGroup, isPending: isSaving } = useUpdate<any, any>(
`${BASE_URL}fund/groups/${groupId}/edit`,
{
onSuccess: () => {
ToastAndroid.show('Group updated successfully', ToastAndroid.SHORT);
setActiveSection(null);
refetchGroup();
},
onError: (err: any) => {
ToastAndroid.show(err?.message || 'Failed to update group', ToastAndroid.SHORT);
},
}
);
const activateGroup = async () => {
setIsActivating(true);
try {
const credentials = await Keychain.getGenericPassword();
const token = credentials ? credentials.password : '';
const response = await fetch(`${BASE_URL}fund/group/start?groupId=${groupId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
ToastAndroid.show('Group activated successfully', ToastAndroid.SHORT);
refetchGroup();
} else {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || 'Activation failed');
}
} catch (error: any) {
ToastAndroid.show(error?.message || 'Activation failed', ToastAndroid.SHORT);
} finally {
setIsActivating(false);
}
};
// --- HANDLERS ---
const handleSave = () => {
if (!editableData || !groupDetails) return;
if (editableData.maxParticipants < groupDetails.totalParticipants) {
ToastAndroid.show('Capacity cannot be less than current participants', ToastAndroid.SHORT);
return;
}
// Construct payload strictly matching user requirements
const payloadData: EditGroupPayload = {
groupName: editableData.groupName,
description: editableData.description,
maxParticipants: Number(editableData.maxParticipants || 0),
minParticipants: Number(editableData.minParticipants || 0),
lateFeeMode: editableData.lateFeeMode || 'DAILY',
lateFeeValue: Number(editableData.lateFeeValue || 0),
latePaymentCharge: Number(editableData.latePaymentCharge || 0),
visibility: editableData.visibility || 'PUBLIC',
contributionAmount: Number(editableData.contributionAmount || 0),
cycleType: editableData.cycleType || 'MONTHLY',
entryFee: Number(editableData.entryFee || 0),
customCycleDays: editableData.cycleType === 'CUSTOM' ? Number(editableData.customCycleDays || 0) : null,
};
updateGroup(payloadData);
};
const [refreshing, setRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setRefreshing(true);
const promises = [refetchGroup()];
if (groupStatusFromDetails === 'ACTIVE') {
promises.push(refetchPaymentStatus());
}
Promise.all(promises).finally(() => setRefreshing(false));
}, [refetchGroup, refetchPaymentStatus, groupStatusFromDetails]);
const handleUpdateImage = async () => {
if (!selectedImage) return;
setImageUpdating(true);
try {
const credentials = await Keychain.getGenericPassword();
const token = credentials ? credentials.password : '';
const fileExtension = selectedImage.split('.').pop();
const formData = new FormData();
formData.append('file', {
uri: selectedImage,
type: 'image/jpeg',
name: `group_${groupId}.${fileExtension || 'jpg'}`,
} as any);
const response = await fetch(`${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`, {
method: 'PUT',
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${token}`,
},
body: formData,
});
if (response.status === 200) {
ToastAndroid.show('Image updated', ToastAndroid.SHORT);
refetchGroup();
setSelectedImage(null);
} else {
throw new Error('Upload failed');
}
} catch (e) {
ToastAndroid.show('Failed to upload image', ToastAndroid.SHORT);
} finally {
setImageUpdating(false);
}
};
const handleInvite = () => {
generateInvite(groupId);
};
const pickImage = () => {
launchImageLibrary({ mediaType: 'photo', quality: 0.5 }, (res) => {
if (res.assets?.[0]?.uri) setSelectedImage(res.assets[0].uri);
});
};
const handleDistribute = () => {
navigation.navigate('RandomSelectUserScreen', { groupId, cycleNumber: paymentStatus?.currentCycle || 0 });
};
const handleFieldChange = (field: keyof EditGroupPayload, value: any) => {
if (!editableData) return;
const newData = { ...editableData, [field]: value };
setEditableData(newData);
const newChangedFields = new Set(changedFields);
if (originalData && originalData[field] !== value) {
newChangedFields.add(field);
} else {
newChangedFields.delete(field);
}
setChangedFields(newChangedFields);
};
// --- RENDER HELPERS ---
const renderField = (field: keyof EditGroupPayload, label: string) => {
if (!editableData) return null;
const isNum = ['contributionAmount', 'latePaymentCharge', 'maxParticipants', 'customCycleDays', 'minParticipants', 'entryFee', 'lateFeeValue'].includes(field);
return (
<View style={styles.modalFieldContainer}>
<Text style={styles.modalLabel}>{label}</Text>
{field === 'cycleType' || field === 'visibility' || field === 'lateFeeMode' ? (
<View style={styles.chipContainer}>
{(field === 'cycleType'
? ['MONTHLY', 'QUARTERLY', 'HALF_YEARLY', 'YEARLY', 'CUSTOM']
: field === 'lateFeeMode'
? ['DAILY', 'MONTHLY']
: ['PUBLIC', 'PRIVATE']
).map((option) => (
<TouchableOpacity
key={option}
style={[
styles.chip,
editableData[field] === option && styles.activeChip,
]}
onPress={() => handleFieldChange(field, option)}
>
<Text
style={[
styles.chipText,
editableData[field] === option && styles.activeChipText,
]}
>
{option}
</Text>
</TouchableOpacity>
))}
</View>
) : (
<TextInput
style={styles.modalInput}
value={String(editableData[field] ?? '')}
placeholder={`Enter ${label}`}
placeholderTextColor="#999"
onChangeText={(text) => handleFieldChange(field, isNum ? Number(text.replace(/[^0-9.]/g, '')) : text)}
keyboardType={isNum ? 'decimal-pad' : 'default'}
multiline={field === 'description'}
numberOfLines={field === 'description' ? 3 : 1}
textAlignVertical={field === 'description' ? 'top' : 'center'}
/>
)}
</View>
);
};
// --- LIST SECTIONS ---
const SECTIONS = [
{ id: 'cover', type: 'cover' },
{ id: 'stats', type: 'stats' },
{ id: 'info', type: 'card', title: 'Group Info', section: 'groupInfo' as EditSection },
{ id: 'financial', type: 'card', title: 'Financial Rules', section: 'financialRules' as EditSection },
{ id: 'config', type: 'card', title: 'Configuration', section: 'configuration' as EditSection },
{ id: 'participants', type: 'participants' },
{ id: 'actions', type: 'actions' },
];
const renderItem = ({ item }: { item: any }) => {
if (!groupDetails) return null;
if (item.type === 'cover') {
const coverImageUri = selectedImage || (groupDetails.imageUrl ? groupDetails.imageUrl : `https://api.dicebear.com/7.x/shapes/png?seed=${encodeURIComponent(groupDetails.groupName)}`);
return (
<View style={styles.coverSection}>
<Image source={{ uri: coverImageUri }} style={styles.coverImage} />
<View style={styles.coverOverlay} />
<View style={styles.coverHeader}>
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.iconBtn}>
<LucideIcon name="arrow-back" size={24} color="#fff" />
</TouchableOpacity>
<Text style={styles.headerTitle}>Manage Group</Text>
{groupDetails.status === 'APPROVED' ? (
<View style={styles.inviteContainer}>
<TouchableOpacity onPress={handleInvite} style={styles.iconBtn}>
<LucideIcon name="person-add-outline" size={22} color="#ffffffff" />
</TouchableOpacity>
{showInviteTooltip && (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => setShowInviteTooltip(false)}
style={styles.inviteTooltip}
>
<Text style={styles.tooltipText}>Invite members</Text>
<View style={styles.tooltipArrow} />
</TouchableOpacity>
)}
</View>
) : (
<View style={{ width: 40 }} />
)}
</View>
<View style={styles.coverContent}>
<Text style={styles.groupNameTitle}>{groupDetails.groupName}</Text>
{groupDetails.description && (
<View style={styles.descContainer}>
<Text style={styles.descText} numberOfLines={2}>{groupDetails.description}</Text>
</View>
)}
<View style={styles.badgesRow}>
<View style={styles.badge}><Text style={styles.badgeText}>{groupDetails.status}</Text></View>
<View style={styles.badge}><Text style={styles.badgeText}>{groupDetails.visibility}</Text></View>
</View>
</View>
<TouchableOpacity onPress={pickImage} style={styles.cameraBtn}>
<LucideIcon name="camera" size={20} color={theme.colors.primary} />
</TouchableOpacity>
{selectedImage && (
<View style={styles.uploadFloatWrapper}>
<View style={[styles.uploadTooltip, { backgroundColor: theme.colors.primary }]}>
<Text style={[styles.uploadTooltipText, { color: theme.colors.onPrimary }]}>Upload Image</Text>
<View style={[styles.uploadTooltipArrow, { borderTopColor: theme.colors.primary }]} />
</View>
<TouchableOpacity onPress={handleUpdateImage} style={[styles.uploadFloatBtnInner, { backgroundColor: theme.colors.primary }]} disabled={imageUpdating}>
{imageUpdating ? <ActivityIndicator color={theme.colors.onPrimary} /> : <LucideIcon name="cloud-upload" size={24} color={theme.colors.onPrimary} />}
</TouchableOpacity>
</View>
)}
</View>
);
}
if (item.type === 'stats') {
return (
<View style={styles.statsContainer}>
<View style={styles.statBox}>
<Text style={styles.statValue}>{groupDetails.totalParticipants}</Text>
<Text style={styles.statLabel}>Participants</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statBox}>
<Text style={styles.statValue}>₹{groupDetails.contributionAmount}</Text>
<Text style={styles.statLabel}>Contribution</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statBox}>
<Text style={styles.statValue}>{groupDetails.cycleType}</Text>
<Text style={styles.statLabel}>Cycle</Text>
</View>
</View>
);
}
if (item.type === 'card') {
const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED';
const fields = item.section === 'groupInfo'
? ['groupName', 'description', 'visibility']
: item.section === 'financialRules'
? ['contributionAmount', 'entryFee', 'latePaymentCharge', 'lateFeeMode']
: ['maxParticipants', 'minParticipants', 'cycleType', ...(groupDetails.customCycleDays ? ['customCycleDays'] : [])];
const getDisplayValue = (field: string) => {
const val = groupDetails[field as keyof typeof groupDetails];
if (field.includes('Amount') || field.includes('Fee') || field.includes('Charge') || field.includes('Value')) return `₹${val ?? 0}`;
return String(val ?? '-');
};
return (
<TouchableOpacity
style={styles.cardContainer}
onPress={() => isEditable && setActiveSection(item.section)}
activeOpacity={isEditable ? 0.7 : 1}
>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}>{item.title}</Text>
{isEditable && <LucideIcon name="create-outline" size={20} color={theme.colors.primary} />}
</View>
<View style={styles.cardBody}>
{fields.map((f: string) => (
<Text key={f} style={styles.cardFieldText}>
<Text style={{ color: '#888' }}>{f.replace(/([A-Z])/g, ' $1').trim()}: </Text>
{getDisplayValue(f)}
</Text>
))}
</View>
</TouchableOpacity>
);
}
if (item.type === 'participants') {
return (
<View style={styles.cardContainer}>
<Text style={styles.cardTitle}>Participants ({groupDetails.participants.length})</Text>
{groupDetails.participants.slice(0, 3).map((p: any) => (
<View key={p.id} style={styles.participantRow}>
<View style={styles.avatarPlaceholder}><Text style={{ color: '#fff' }}>{p.firstName[0]}</Text></View>
<View style={{ flex: 1 }}>
<Text style={styles.pName}>{p.firstName} {p.lastName}</Text>
<Text style={styles.pRole}>{p.role}</Text>
</View>
</View>
))}
{groupDetails.participants.length > 3 && (
<TouchableOpacity style={styles.viewAllBtn}>
<Text style={styles.viewAllText}>View All Participants</Text>
</TouchableOpacity>
)}
</View>
);
}
if (item.type === 'actions') {
const isApproved = groupDetails.status === 'APPROVED';
const notEnoughParticipants = groupDetails.totalParticipants < groupDetails.minParticipants;
return (
<View style={styles.actionsContainer}>
{isApproved && notEnoughParticipants && (
<View style={styles.hintContainer}>
<LucideIcon name="information-circle-outline" size={20} color="#F59E0B" />
<Text style={styles.hintText}>
To start the group, a minimum of {groupDetails.minParticipants} participants are needed.
</Text>
</View>
)}
{paymentStatus?.cycleStatus === 'PENDING_SELECTION' && (
<TouchableOpacity onPress={handleDistribute} style={styles.primaryBtn}>
<Text style={styles.btnText}>Distribute Funds</Text>
</TouchableOpacity>
)}
{paymentStatus?.cycleStatus === 'READY_FOR_WITHDRAWAL' && paymentStatus?.selectedUser && (
<TouchableOpacity onPress={() => setSelectedUserModel(true)} style={styles.secondaryBtn}>
<Text style={styles.secondaryBtnText}>View Winner</Text>
</TouchableOpacity>
)}
</View>
);
}
return null;
};
if (isGroupLoading) {
return (
<AppView style={{ backgroundColor: theme.colors.background }}>
<View style={{ padding: 20, paddingTop: 60 }}>
<SkeletonLoader width="100%" height={200} style={{ borderRadius: 16, marginBottom: 20 }} />
<SkeletonLoader width="100%" height={100} style={{ borderRadius: 16, marginBottom: 20 }} />
<SkeletonLoader width="100%" height={150} style={{ borderRadius: 16, marginBottom: 20 }} />
</View>
</AppView>
);
}
if (!groupDetails) return null;
return (
<AppView paddingHorizontal={0} style={{ backgroundColor: theme.colors.background, flex: 1 }}>
<FlatList
data={SECTIONS}
renderItem={renderItem}
keyExtractor={item => item.id}
contentContainerStyle={{ paddingBottom: 150 }}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={[theme.colors.primary]} />
}
/>
{/* Floating Activate Button */}
{groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants && (
<View style={styles.floatingBtnContainer}>
<TouchableOpacity
onPress={() => activateGroup()}
style={styles.floatingActivateBtn}
disabled={isActivating}
>
{isActivating ? (
<ActivityIndicator color="#fff" />
) : (
<>
<LucideIcon name="power" size={24} color="#fff" style={{ marginRight: 8 }} />
<Text style={styles.floatingBtnText}>Activate Group</Text>
<LucideIcon name="arrow-forward" size={20} color="#fff" style={{ marginLeft: 8, opacity: 0.8 }} />
</>
)}
</TouchableOpacity>
</View>
)}
{/* --- EDIT MODAL --- */}
<Modal
visible={activeSection !== null}
animationType="slide"
transparent={true}
onRequestClose={() => setActiveSection(null)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContainer}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>
{activeSection === 'groupInfo' ? 'Group Information' :
activeSection === 'financialRules' ? 'Financial Rules' : 'Configuration'}
</Text>
<TouchableOpacity onPress={() => setActiveSection(null)} style={styles.closeBtn}>
<LucideIcon name="close" size={24} color="#333" />
</TouchableOpacity>
</View>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined} style={{ flex: 1 }}>
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 20 }}>
{activeSection === 'groupInfo' && (
<>
{renderField('groupName', 'Group Name')}
{renderField('description', 'Description')}
{renderField('visibility', 'Visibility')}
</>
)}
{activeSection === 'financialRules' && (
<>
{renderField('contributionAmount', 'Contribution Amount')}
{renderField('entryFee', 'Entry Fee')}
{renderField('latePaymentCharge', 'Late Payment Charge')}
{renderField('lateFeeMode', 'Late Fee Mode')}
</>
)}
{activeSection === 'configuration' && (
<>
{renderField('maxParticipants', 'Max Participants')}
{renderField('minParticipants', 'Min Participants')}
{renderField('cycleType', 'Payment Cycle')}
{editableData?.cycleType === 'CUSTOM' && renderField('customCycleDays', 'Cycle Days')}
</>
)}
</ScrollView>
</KeyboardAvoidingView>
<View style={styles.modalFooter}>
<TouchableOpacity onPress={handleSave} style={styles.saveBtn} disabled={isSaving}>
{isSaving ? <ActivityIndicator color="#fff" /> : <Text style={styles.saveBtnText}>Save Changes</Text>}
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
{selectedUserModal && (
<WinnerUser
groupId={groupId}
data={paymentStatus}
visible={selectedUserModal}
onClose={() => setSelectedUserModel(false)}
/>
)}
</AppView>
);
};
const getStyles = (theme: any) => StyleSheet.create({
// Cover
coverSection: { height: 280, position: 'relative' },
coverImage: { width: '100%', height: '100%', resizeMode: 'cover' },
coverOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.4)' },
coverHeader: { position: 'absolute', top: 30, left: 20, right: 20, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
iconBtn: { padding: 8, backgroundColor: 'rgba(255,255,255,0.2)', borderRadius: 20 },
headerTitle: { color: '#fff', fontSize: 18, fontFamily: FontsFamily.nunitoBold },
coverContent: { position: 'absolute', bottom: 30, left: 20, right: 20 },
groupNameTitle: { color: '#fff', fontSize: 24, fontFamily: FontsFamily.nunitoBold, marginBottom: 8 },
descContainer: { backgroundColor: 'rgba(255,255,255,0.15)', padding: 10, borderRadius: 8, borderLeftWidth: 3, borderLeftColor: '#fff', marginBottom: 12 },
descText: { color: '#eee', fontSize: 14, fontFamily: FontsFamily.nunitoRegular },
badgesRow: { flexDirection: 'row', gap: 10 },
badge: { backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12 },
badgeText: { color: '#fff', fontSize: 12, fontWeight: 'bold' },
cameraBtn: { position: 'absolute', bottom: -20, right: 30, width: 44, height: 44, borderRadius: 22, backgroundColor: '#fff', elevation: 4, justifyContent: 'center', alignItems: 'center' },
uploadFloatBtn: { position: 'absolute', bottom: 40, right: 30, width: 50, height: 50, borderRadius: 25, elevation: 5, justifyContent: 'center', alignItems: 'center' },
uploadFloatWrapper: { position: 'absolute', bottom: 40, right: 30, alignItems: 'center' },
uploadTooltip: { position: 'absolute', bottom: 58, right: 0, paddingHorizontal: 10, paddingVertical: 5, borderRadius: 8, elevation: 6, zIndex: 10, minWidth: 100, alignItems: 'center' },
uploadTooltipText: { fontSize: 12, fontFamily: FontsFamily.nunitoBold },
uploadTooltipArrow: { position: 'absolute', bottom: -5, width: 0, height: 0, borderLeftWidth: 6, borderRightWidth: 6, borderTopWidth: 6, borderLeftColor: 'transparent', borderRightColor: 'transparent' },
uploadFloatBtnInner: { width: 50, height: 50, borderRadius: 25, elevation: 5, justifyContent: 'center', alignItems: 'center' },
// Stats
statsContainer: { flexDirection: 'row', backgroundColor: theme.colors.card, margin: 20, marginTop: 30, borderRadius: 16, padding: 20, elevation: 2, justifyContent: 'space-between', alignItems: 'center' },
statBox: { alignItems: 'center', flex: 1 },
statValue: { fontSize: 18, fontFamily: FontsFamily.nunitoBold, color: theme.colors.textPrimary },
statLabel: { fontSize: 12, fontFamily: FontsFamily.nunitoRegular, color: theme.colors.textSecondary },
statDivider: { width: 1, height: 30, backgroundColor: '#eee' },
// Cards
cardContainer: { backgroundColor: theme.colors.card, marginHorizontal: 20, marginBottom: 16, borderRadius: 16, padding: 16, elevation: 2 },
cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: '#f0f0f0' },
cardTitle: { fontSize: 16, fontFamily: FontsFamily.nunitoBold, color: theme.colors.textPrimary },
cardBody: {},
cardFieldText: { fontSize: 14, color: theme.colors.textPrimary, marginBottom: 6, fontFamily: FontsFamily.nunitoMedium },
moreText: { fontSize: 12, color: theme.colors.textSecondary, marginTop: 4, fontStyle: 'italic' },
// Participants
participantRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 },
avatarPlaceholder: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#6366F1', justifyContent: 'center', alignItems: 'center', marginRight: 12 },
pName: { fontSize: 16, fontFamily: FontsFamily.nunitoBold, color: theme.colors.textPrimary },
pRole: { fontSize: 12, color: theme.colors.textSecondary },
viewAllBtn: { paddingVertical: 8, alignItems: 'center', borderTopWidth: 1, borderTopColor: '#f0f0f0', marginTop: 8 },
viewAllText: { color: theme.colors.primary, fontFamily: FontsFamily.nunitoBold },
// Actions
actionsContainer: { padding: 20 },
primaryBtn: { backgroundColor: theme.colors.primary, padding: 16, borderRadius: 12, alignItems: 'center', elevation: 2, marginBottom: 12 },
btnText: { color: theme.colors.white, fontSize: 16, fontFamily: FontsFamily.nunitoBold },
secondaryBtn: { backgroundColor: '#f0f0f0', padding: 16, borderRadius: 12, alignItems: 'center' },
secondaryBtnText: { color: theme.colors.textPrimary, fontSize: 16, fontFamily: FontsFamily.nunitoBold },
// Modal
modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' },
modalContainer: { backgroundColor: theme.colors.background, borderTopLeftRadius: 24, borderTopRightRadius: 24, height: '80%', padding: 20 },
modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 },
modalTitle: { fontSize: 20, fontFamily: FontsFamily.nunitoBold, color: theme.colors.textPrimary },
closeBtn: { padding: 8, backgroundColor: '#f5f5f5', borderRadius: 20 },
// New Styles
hintContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#FFFBEB', padding: 12, borderRadius: 12, marginBottom: 16, borderLeftWidth: 4, borderLeftColor: '#F59E0B' },
hintText: { color: '#B45309', fontSize: 13, fontFamily: FontsFamily.nunitoMedium, marginLeft: 8, flex: 1 },
floatingBtnContainer: {
position: 'absolute',
bottom: 90,
left: 20,
right: 20,
alignItems: 'center',
},
floatingActivateBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.colors.primary,
paddingVertical: 16,
paddingHorizontal: 24,
borderRadius: 16,
width: '100%',
elevation: 8,
shadowColor: theme.colors.primary,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
},
// Invite & Tooltip
inviteContainer: {
position: 'relative',
alignItems: 'center'
},
inviteTooltip: {
position: 'absolute',
top: 45,
right: -10,
backgroundColor: '#d5d5d5ff',
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 8,
width: 140,
zIndex: 100,
elevation: 10,
},
tooltipText: {
color: '#494949ff',
fontSize: 11,
fontFamily: FontsFamily.nunitoSemiBold,
textAlign: 'center',
},
tooltipArrow: {
position: 'absolute',
top: -6,
right: 20,
width: 0,
height: 0,
borderLeftWidth: 6,
borderRightWidth: 6,
borderBottomWidth: 6,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: '#334155',
},
floatingBtnText: {
color: '#fff',
fontSize: 18,
fontFamily: FontsFamily.nunitoBold,
letterSpacing: 0.5,
},
modalContent: { flex: 1 },
modalFieldContainer: { marginBottom: 16 },
modalLabel: { fontSize: 14, color: theme.colors.textSecondary, marginBottom: 8, fontFamily: FontsFamily.nunitoBold },
modalInput: { borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 12, padding: 14, fontSize: 16, color: theme.colors.textPrimary, backgroundColor: '#f9f9f9', fontFamily: FontsFamily.nunitoMedium },
chipContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
chip: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 20, backgroundColor: '#f0f0f0', borderWidth: 1, borderColor: '#e0e0e0' },
activeChip: { backgroundColor: theme.colors.primary, borderColor: theme.colors.primary },
chipText: { fontSize: 14, fontFamily: FontsFamily.nunitoMedium, color: theme.colors.textSecondary },
activeChipText: { color: theme.colors.white, fontFamily: FontsFamily.nunitoBold },
modalFooter: { borderTopWidth: 1, borderTopColor: '#eee', paddingTop: 16 },
saveBtn: { backgroundColor: theme.colors.primary, padding: 16, borderRadius: 12, alignItems: 'center' },
saveBtnText: { color: theme.colors.white, fontSize: 16, fontFamily: FontsFamily.nunitoBold }
});
export default EditGroupScreen;