dummy home screen
This commit is contained in:
+238
-155
@@ -1,181 +1,264 @@
|
||||
import React, { useState } from "react";
|
||||
import { View, Text, StyleSheet, FlatList, TouchableOpacity } from "react-native";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
FlatList,
|
||||
TouchableOpacity,
|
||||
TextInput,
|
||||
Dimensions,
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
ScrollView,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
import { FAB } from "react-native-paper";
|
||||
import { MaterialIcons } from '@react-native-vector-icons/material-icons';
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
import { MaterialIcons } from "@react-native-vector-icons/material-icons";
|
||||
import { useNavigation, useFocusEffect } from "@react-navigation/native";
|
||||
import { StackNavigationProp } from "@react-navigation/stack";
|
||||
import { RootStackParamList } from "../navigation/AppNavigator";
|
||||
import AppView from "../components/AppView";
|
||||
import colors from "../components/colors";
|
||||
import { apiCall } from "../utils/ApiHelper";
|
||||
import { JOIN_GROUP_URL } from "../utils/ApiUrls";
|
||||
|
||||
// --- Placeholder Data ---
|
||||
const myGroupsData = [
|
||||
{ id: '1', name: 'Family Savings', members: 5 },
|
||||
{ id: '2', name: 'Office Lunch Club', members: 12 },
|
||||
];
|
||||
const joinedGroupsData = [
|
||||
{ id: '3', name: 'Apartment Committee', members: 8 },
|
||||
];
|
||||
const publicGroupsData = [
|
||||
{ id: '4', name: 'City Runners', members: 50 },
|
||||
{ id: '5', name: 'Book Readers', members: 25 },
|
||||
];
|
||||
const { width } = Dimensions.get('window');
|
||||
const PUBLIC_GROUPS_URL = "https://api.godemo.in/FundCollect/api/fund/user/public";
|
||||
|
||||
// --- Placeholder Group List Component ---
|
||||
const GroupList = ({ data }: { data: any[] }) => {
|
||||
if (data.length === 0) {
|
||||
return <Text style={styles.emptyText}>No groups found.</Text>;
|
||||
}
|
||||
|
||||
// --- Types ---
|
||||
interface Group {
|
||||
id: string;
|
||||
groupName: string;
|
||||
description: string;
|
||||
maxParticipants: number;
|
||||
currentParticipants: number;
|
||||
contributionAmount: number;
|
||||
cycleType: string;
|
||||
createdByName: string;
|
||||
}
|
||||
|
||||
// --- UI Components ---
|
||||
const GroupCard = ({ item, onJoin, onView, isGrid }: { item: Group; onJoin: (groupId: string) => void; onView: (group: Group) => void; isGrid?: boolean; }) => {
|
||||
const isFull = item.currentParticipants >= item.maxParticipants;
|
||||
const cardStyle = isGrid ? styles.gridCard : styles.listCard;
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={data}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.groupCard}>
|
||||
<Text style={styles.groupName}>{item.name}</Text>
|
||||
<Text style={styles.groupMembers}>{item.members} members</Text>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
<View style={cardStyle}>
|
||||
<Text style={styles.groupName} numberOfLines={1}>{item.groupName}</Text>
|
||||
<Text style={styles.groupDescription} numberOfLines={isGrid ? 2 : 1}>{item.description}</Text>
|
||||
<View style={styles.groupMeta}>
|
||||
<Text style={styles.groupMetaText}>{item.currentParticipants}/{item.maxParticipants} Members</Text>
|
||||
<Text style={styles.groupMetaText}>{item.cycleType}</Text>
|
||||
</View>
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableOpacity style={styles.viewButton} onPress={() => onView(item)}>
|
||||
<Text style={styles.viewButtonText}>View</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.joinButton, isFull && styles.joinButtonDisabled]}
|
||||
onPress={() => onJoin(item.id)}
|
||||
disabled={isFull}
|
||||
>
|
||||
<Text style={styles.joinButtonText}>{isFull ? "Full" : "Join"}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const FeaturedGroupCard = ({ item }: { item: Group }) => (
|
||||
<View style={styles.featuredCard}>
|
||||
<Image source={{ uri: `https://i.pravatar.cc/150?u=${item.id}` }} style={styles.featuredImage} />
|
||||
<View style={styles.featuredContent}>
|
||||
<Text style={styles.featuredName} numberOfLines={1}>{item.groupName}</Text>
|
||||
<Text style={styles.featuredCreator}>by {item.createdByName}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default function GroupScreen() {
|
||||
const [activeTab, setActiveTab] = useState("My Groups");
|
||||
const [activeTab, setActiveTab] = useState("Public");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isGridView, setIsGridView] = useState(false);
|
||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
|
||||
|
||||
const renderContent = () => {
|
||||
switch (activeTab) {
|
||||
case "My Groups":
|
||||
return <GroupList data={myGroupsData} />;
|
||||
case "Joined":
|
||||
return <GroupList data={joinedGroupsData} />;
|
||||
case "Public":
|
||||
return <GroupList data={publicGroupsData} />;
|
||||
default:
|
||||
return null;
|
||||
const [myGroups, setMyGroups] = useState<Group[]>([]);
|
||||
const [joinedGroups, setJoinedGroups] = useState<Group[]>([]);
|
||||
const [publicGroups, setPublicGroups] = useState<Group[]>([]);
|
||||
const [featuredGroups, setFeaturedGroups] = useState<Group[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchGroups = async () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await apiCall(PUBLIC_GROUPS_URL, "GET") as { data?: Group[] };
|
||||
if (res && res.data) {
|
||||
setPublicGroups(res.data);
|
||||
setFeaturedGroups(res.data.slice(0, 5));
|
||||
} else {
|
||||
throw new Error("No data received");
|
||||
}
|
||||
} catch (e) {
|
||||
setError("Failed to fetch groups. Please try again.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useFocusEffect(React.useCallback(() => { fetchGroups(); }, []));
|
||||
|
||||
const filterData = (data: Group[]) => {
|
||||
if (!searchQuery.trim()) return data;
|
||||
return data.filter(g =>
|
||||
g.groupName.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
const handleJoinGroup = async (groupId: string, userId: string) => {
|
||||
console.log("Joining group:", groupId, "User:", userId);
|
||||
try {
|
||||
const url = `${JOIN_GROUP_URL}/groups/${groupId}/join/0`;
|
||||
console.log("Joining group URL:", url);
|
||||
const res = await apiCall(url, "POST") as { statusCode?: number; message?: string };
|
||||
|
||||
console.log("JOIN_GROUP_URL response:", res);
|
||||
|
||||
if (res.statusCode === 200) {
|
||||
Alert.alert("Success", "You requested to join the group.");
|
||||
} else {
|
||||
Alert.alert("Error", res.message || "Failed to join group.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Join Group Error:", error);
|
||||
Alert.alert("Error", "An unexpected error occurred.");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleViewGroup = (group: Group) => {
|
||||
// navigation.navigate('GroupDetails', { group });
|
||||
Alert.alert("View Group", `Viewing details for ${group.groupName}`);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
let data ;
|
||||
switch (activeTab) {
|
||||
case "My Groups": data = myGroups; break;
|
||||
case "Joined": data = joinedGroups; break;
|
||||
case "Public": data = publicGroups; break;
|
||||
default: data = [];
|
||||
}
|
||||
|
||||
if (loading && !publicGroups.length) return <ActivityIndicator size="large" color="#62b59a" style={{ marginTop: 40 }} />;
|
||||
if (error) return <Text style={styles.emptyText}>{error}</Text>;
|
||||
|
||||
const filteredData = filterData(data);
|
||||
if (filteredData.length === 0 && !loading) return <Text style={styles.emptyText}>No groups found.</Text>;
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={filteredData}
|
||||
key={isGridView ? 'grid' : 'list'}
|
||||
numColumns={isGridView ? 2 : 1}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<GroupCard
|
||||
item={item}
|
||||
onJoin={activeTab === 'Public' ? handleJoinGroup : () => { }}
|
||||
onView={handleViewGroup}
|
||||
isGrid={isGridView}
|
||||
/>
|
||||
)}
|
||||
columnWrapperStyle={isGridView ? { justifyContent: 'space-between' } : null}
|
||||
contentContainerStyle={{ paddingBottom: 150 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AppView barStyle="dark-content">
|
||||
<View style={styles.container}>
|
||||
{/* --- Header and Segmented Control --- */}
|
||||
<Text style={styles.title}>Your Groups</Text>
|
||||
<View style={styles.segmentedControl}>
|
||||
{["My Groups", "Joined", "Public"].map((tab) => (
|
||||
<TouchableOpacity
|
||||
key={tab}
|
||||
style={[
|
||||
styles.segmentButton,
|
||||
activeTab === tab && styles.segmentButtonActive,
|
||||
]}
|
||||
onPress={() => setActiveTab(tab)}
|
||||
>
|
||||
<Text style={[
|
||||
styles.segmentText,
|
||||
activeTab === tab && styles.segmentTextActive,
|
||||
]}>
|
||||
{tab}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
<AppView paddingHorizontal={0} style={styles.container}>
|
||||
{/* Fixed Header */}
|
||||
<View style={styles.headerContainer}>
|
||||
<Text style={styles.title}>Explore Groups</Text>
|
||||
<View style={styles.searchContainer}>
|
||||
<Ionicons name="search" size={20} color="#888" style={styles.searchIcon} />
|
||||
<TextInput style={styles.searchInput} placeholder="Search for groups..." placeholderTextColor="#888" value={searchQuery} onChangeText={setSearchQuery} />
|
||||
</View>
|
||||
|
||||
{/* --- Dynamic Content Area --- */}
|
||||
<View style={styles.contentArea}>
|
||||
{renderContent()}
|
||||
</View>
|
||||
|
||||
<FAB
|
||||
style={styles.fab}
|
||||
icon={() => <MaterialIcons name="group-add" size={24} color="#333" />}
|
||||
onPress={() => navigation.navigate('GroupCreation')}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Scrollable Content */}
|
||||
<ScrollView style={styles.container}>
|
||||
<Text style={styles.sectionTitle}>Featured</Text>
|
||||
<FlatList
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={featuredGroups}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => <FeaturedGroupCard item={item} />}
|
||||
style={styles.featuredList}
|
||||
ListEmptyComponent={!loading && featuredGroups.length === 0 ? <Text style={styles.emptyFeaturedText}>No featured groups.</Text> : null}
|
||||
/>
|
||||
|
||||
<View style={styles.tabHeader}>
|
||||
<View style={styles.segmentedControl}>
|
||||
{["My Groups", "Joined", "Public"].map((tab) => (
|
||||
<TouchableOpacity key={tab} style={[styles.segmentButton, activeTab === tab && styles.segmentButtonActive]} onPress={() => setActiveTab(tab)}>
|
||||
<Text style={[styles.segmentText, activeTab === tab && styles.segmentTextActive]}>{tab}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setIsGridView(!isGridView)} style={styles.layoutToggleButton}>
|
||||
<Ionicons name={isGridView ? "list" : "grid"} size={22} color="#555" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* The list now renders inside a simple View, as FlatList handles its own scroll */}
|
||||
<View style={styles.contentArea}>{renderContent()}</View>
|
||||
</ScrollView>
|
||||
<FAB style={styles.fab} icon={() => <MaterialIcons name="group-add" size={24} color="#333" />} onPress={() => navigation.navigate('GroupCreation')} />
|
||||
</AppView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
// padding: 16,
|
||||
// paddingTop: 40,
|
||||
// backgroundColor: '#f5f6fa',
|
||||
},
|
||||
title: {
|
||||
fontSize: 26,
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
marginBottom: 20,
|
||||
},
|
||||
segmentedControl: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#e0e0e0',
|
||||
borderRadius: 10,
|
||||
padding: 4,
|
||||
marginBottom: 20,
|
||||
},
|
||||
segmentButton: {
|
||||
flex: 1,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
segmentButtonActive: {
|
||||
backgroundColor: '#fff',
|
||||
elevation: 2,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 5,
|
||||
},
|
||||
segmentText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#555',
|
||||
},
|
||||
segmentTextActive: {
|
||||
color: '#333',
|
||||
},
|
||||
contentArea: {
|
||||
flex: 1,
|
||||
},
|
||||
fab: {
|
||||
position: "absolute",
|
||||
right: 20,
|
||||
bottom: 90,
|
||||
width: 70,
|
||||
height: 70,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#83f5cc",
|
||||
borderRadius: 35,
|
||||
elevation: 4,
|
||||
},
|
||||
// --- Group List Styles ---
|
||||
groupCard: {
|
||||
backgroundColor: '#fff',
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
marginBottom: 12,
|
||||
elevation: 1,
|
||||
},
|
||||
groupName: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
},
|
||||
groupMembers: {
|
||||
fontSize: 14,
|
||||
color: '#777',
|
||||
marginTop: 4,
|
||||
},
|
||||
emptyText: {
|
||||
textAlign: 'center',
|
||||
marginTop: 50,
|
||||
fontSize: 16,
|
||||
color: '#888',
|
||||
},
|
||||
container: { flex: 1, backgroundColor: '#f5f6fa' },
|
||||
headerContainer: { backgroundColor: '#f5f6fa', paddingTop: 40, paddingHorizontal: 16 },
|
||||
title: { fontSize: 28, fontWeight: "bold", color: "#222", marginBottom: 10 },
|
||||
searchContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 12, paddingHorizontal: 10, elevation: 2, marginBottom: 20 },
|
||||
searchIcon: { marginRight: 8 },
|
||||
searchInput: { flex: 1, height: 45, fontSize: 16, color: '#333' },
|
||||
sectionTitle: { fontSize: 20, fontWeight: 'bold', color: '#333', marginBottom: 12, paddingHorizontal: 16 },
|
||||
featuredList: { paddingLeft: 16, marginBottom: 20 },
|
||||
featuredCard: { width: width * 0.45, marginRight: 15, backgroundColor: '#fff', borderRadius: 12, elevation: 3, overflow: 'hidden' },
|
||||
featuredImage: { width: '100%', height: 100 },
|
||||
featuredContent: { padding: 10 },
|
||||
featuredName: { fontSize: 14, fontWeight: 'bold', color: '#333' },
|
||||
featuredCreator: { fontSize: 12, color: '#777', marginTop: 2 },
|
||||
tabHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, marginBottom: 16 },
|
||||
segmentedControl: { flex: 1, flexDirection: 'row', backgroundColor: '#e9ecef', borderRadius: 10, padding: 4, marginRight: 10 },
|
||||
segmentButton: { flex: 1, paddingVertical: 10, borderRadius: 8, alignItems: 'center' },
|
||||
segmentButtonActive: { backgroundColor: '#fff', elevation: 2, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 5 },
|
||||
segmentText: { fontSize: 14, fontWeight: '600', color: '#555' },
|
||||
segmentTextActive: { color: '#333' },
|
||||
layoutToggleButton: { padding: 8 },
|
||||
contentArea: { paddingHorizontal: 16 },
|
||||
fab: { position: "absolute", right: 20, bottom: 90, width: 60, height: 60, justifyContent: "center", alignItems: "center", backgroundColor: "#83f5cc", borderRadius: 30, elevation: 4 },
|
||||
listCard: { backgroundColor: '#fff', padding: 16, borderRadius: 12, marginBottom: 12, elevation: 1 },
|
||||
gridCard: { backgroundColor: '#fff', padding: 12, borderRadius: 12, marginBottom: 12, elevation: 1, width: (width - 48) / 2 },
|
||||
groupName: { fontSize: 16, fontWeight: 'bold', color: '#333' },
|
||||
groupDescription: { fontSize: 14, color: '#666', marginVertical: 4 },
|
||||
groupMeta: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 8, borderTopWidth: 1, borderTopColor: '#f0f0f0', paddingTop: 8, marginBottom: 10 },
|
||||
groupMetaText: { fontSize: 12, color: '#888', fontWeight: '500', textTransform: 'capitalize' },
|
||||
emptyText: { textAlign: 'center', marginTop: 40, fontSize: 16, color: '#888' },
|
||||
emptyFeaturedText: { marginLeft: 20, color: '#888' },
|
||||
buttonContainer: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 8 },
|
||||
viewButton: { flex: 1, backgroundColor: '#e9ecef', paddingVertical: 10, borderRadius: 8, alignItems: 'center', marginRight: 5 },
|
||||
viewButtonText: { color: '#333', fontWeight: 'bold', fontSize: 14 },
|
||||
joinButton: { flex: 1, backgroundColor: '#83f5cc', paddingVertical: 10, borderRadius: 8, alignItems: 'center', marginLeft: 5 },
|
||||
joinButtonDisabled: { backgroundColor: '#bdc3c7' },
|
||||
joinButtonText: { color: '#333', fontWeight: 'bold', fontSize: 14 },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user