themming and try to create awesome theme

This commit is contained in:
Jaanu423
2025-09-15 18:01:37 +05:30
15 changed files with 706 additions and 328 deletions
+15
View File
@@ -0,0 +1,15 @@
const colours = {
lightblue: '#98e2f4',
colorOne: '#83f5cc',
primary: '#F4721E',
secondary: '#FF9A62',
textPrimary: '#333333',
textSecondary: '#666666',
background: '#FFFFFF',
success: '#008e02',
error: '#FF3B30',
warning: '#FF9500',
info: '#007AFF',
};
export default colours;
+1 -1
View File
@@ -13,7 +13,7 @@ type CustomButtonProps = {
export default function CustomButton({
title,
onPress,
backgroundColor = "#6C63FF",
backgroundColor = "#000000ff",
textColor = "#fff",
loading = false,
style,
+9 -4
View File
@@ -1,10 +1,15 @@
// colors.ts
const colors = {
pastelPink: "#F8BBD0", // darker pink
pastelBlue: "#90CAF9", // darker blue
pastelGreen: "#A5D6A7", // darker green
pastelOrange: "#FFCC80", // darker orange
pastelPink: "#decbfcff", // darker pink
pastelBlue: "#D7E9F7", // darker blue
pastelGreen: "#DFF6DD", // darker green
pastelOrange: "#FFF4D2", // darker orange
// darker accent colors
Home: "#995dfaff", // Rose red for contrast
Profile: "#2D9CDB", // Deep sky blue
Group: "#27AE60", // Green for freshness
Settings: "#F39C12", // Amber for warmth
// common text + UI colors
textDark: "#212121",
textLight: "#555555",
+88 -51
View File
@@ -1,73 +1,91 @@
import React from "react";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { View, Text, StyleSheet, Platform } from "react-native";
import { Ionicons } from '@react-native-vector-icons/ionicons';
import { Ionicons, IoniconsIconName } from "@react-native-vector-icons/ionicons";
import GroupScreen from "../screens/GroupScreen";
import Profile from "../screens/profile/Profile";
import Home from "../screens/home/Home";
import colors from "../components/colors";
import Setting from "../screens/setting/Setting";
const Tab = createBottomTabNavigator();
const TAB_CONFIG = {
activeColor: "#eb87a3ff", // coffee-like orange
inactiveColor: "#000000ff",
backgroundColor: "#e3e9efff",
height: 80,
iconSize: 30,
labelSize: 12,
labelWeight: "600",
activeColor: "#000",
inactiveColor: "#8e8e93",
backgroundColor: "#fff",
height: 70,
iconSize: 26,
};
// Background (tab highlight) colors
const activeTabColors: { [key: string]: string } = {
Home: colors.pastelPink, // Soft pink
Profile: colors.pastelBlue, // Light sky blue
Group: colors.pastelGreen, // Gentle mint green
Settings: colors.pastelOrange, // Warm pastel yellow
};
// Icon/Text colors on active tabs
const activeIconColors: { [key: string]: string } = {
Home: colors.Home, // Rose red for contrast
Profile: colors.Profile, // Deep sky blue
Group: colors.Group, // Green for freshness
Settings: colors.Settings, // Amber for warmth
};
function SettingsScreen() {
return (
<View style={styles.screen}>
<Text style={styles.text}>⚙️ Settings</Text>
</View>
);
}
export default function BottomTabs() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
headerShown: false,
tabBarStyle: styles.tabBar,
tabBarActiveTintColor: TAB_CONFIG.activeColor,
tabBarInactiveTintColor: TAB_CONFIG.inactiveColor,
tabBarLabelStyle: {
fontSize: TAB_CONFIG.labelSize,
fontWeight: TAB_CONFIG.labelWeight as any,
marginBottom: Platform.OS === "ios" ? 0 : 5,
},
tabBarIcon: ({ color, focused }) => {
let iconName:
| "home"
| "home-outline"
| "person"
| "person-outline"
| "settings"
| "settings-outline"
| "people"
| "people-outline" = "home-outline";
tabBarLabel: ({ focused, color }) =>
!focused ? (
<Text style={[styles.label, { color }]}>{route.name}</Text>
) : null,
tabBarIcon: ({ focused }) => {
let iconName: IoniconsIconName = "home-outline";
// Determine icon based on route
if (route.name === "Home") iconName = focused ? "home" : "home-outline";
else if (route.name === "Profile")
iconName = focused ? "person" : "person-outline";
else if (route.name === "Settings")
iconName = focused ? "settings" : "settings-outline";
else if (route.name === "Group")
iconName = focused ? "people" : "people-outline";
else if (route.name === "Profile") iconName = focused ? "person" : "person-outline";
else if (route.name === "Settings") iconName = focused ? "settings" : "settings-outline";
else if (route.name === "Group") iconName = focused ? "people" : "people-outline";
return <Ionicons name={iconName} size={TAB_CONFIG.iconSize} color={color} />;
// 3. Get the active background color and icon color from the maps
const activeBackgroundColor = activeTabColors[route.name];
const activeIconColor = activeIconColors[route.name];
return (
<View
style={[
styles.iconContainer,
// 4. Apply the dynamic background color when focused
focused && [
styles.activeIconContainer,
{ backgroundColor: activeBackgroundColor },
],
]}
>
<Ionicons
name={iconName}
size={TAB_CONFIG.iconSize}
color={focused ? activeIconColor : TAB_CONFIG.inactiveColor}
/>
</View>
);
},
})}
>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Group" component={GroupScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
<Tab.Screen name="Settings" component={Setting} />
</Tab.Navigator>
);
}
@@ -77,7 +95,7 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#dae4ed",
backgroundColor: "#f5f7fa",
},
text: {
fontSize: 20,
@@ -86,19 +104,38 @@ const styles = StyleSheet.create({
},
tabBar: {
position: "absolute",
bottom: 0,
left: 0,
right: 0,
bottom: 20,
left: 20,
right: 20,
height: TAB_CONFIG.height,
backgroundColor: TAB_CONFIG.backgroundColor,
borderTopLeftRadius: 30,
borderTopRightRadius: 30,
borderRadius: 20,
shadowColor: "#000",
shadowOpacity: 0.10,
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.1,
shadowOffset: { width: 0, height: 5 },
shadowRadius: 8,
elevation: 20,
elevation: 10,
paddingBottom: Platform.OS === "ios" ? 25 : 10,
paddingTop: 10,
borderTopWidth: 0,
marginHorizontal: 10,
},
label: {
fontSize: 12,
marginBottom: 5,
},
iconContainer: {
justifyContent: "center",
alignItems: "center",
width: 50, // Reduced size for a sleeker look
height: 50, // Reduced size
marginTop: 10,
borderRadius: 15,
},
activeIconContainer: {
// The default active style, background color is now applied dynamically
elevation: 2,
shadowColor: '#000',
shadowOpacity: 0.1,
shadowRadius: 5,
},
});
+155 -155
View File
@@ -1,36 +1,104 @@
import { Picker } from "@react-native-picker/picker";
import React, { useState } from "react";
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
ScrollView,
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";
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<any>();
const [form, setForm] = useState({
contributionAmount: "",
contributionAmount: "4000",
customCycleDays: "",
cycleType: "CUSTOM",
cycleType: "MONTHLY",
description: "",
endDate: "",
groupName: "",
maxParticipants: "",
minParticipants: "",
startDate: "",
visibility: "PRIVATE",
fundDistribution: "SEQUENTIAL",
});
const [errors, setErrors] = useState<any>({}); // State for validation errors
const [isCalendarVisible, setCalendarVisible] = useState(false);
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 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.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.";
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleReview = () => {
if (validateForm()) {
navigation.navigate("GroupReview", { form });
} else {
Alert.alert("Incomplete Form", "Please fill out all required fields correctly.");
}
};
return (
@@ -39,166 +107,98 @@ export default function GroupCreationForm() {
behavior={Platform.OS === "ios" ? "padding" : undefined}
>
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
<Text style={styles.title}>Group Creation</Text>
{/* Group Info */}
<Text style={styles.label}>Group Name</Text>
<TextInput
style={styles.input}
placeholderTextColor={"#a3a3a3ff"}
placeholder="Enter your group name"
value={form.groupName}
onChangeText={(val) => handleChange("groupName", val)}
/>
<Text style={styles.label}>Description</Text>
<TextInput
style={[styles.input, { height: 80 }]}
placeholderTextColor={"#a3a3a3ff"}
placeholder="Enter a short description for the group"
value={form.description}
multiline
onChangeText={(val) => handleChange("description", val)}
/>
<Text style={styles.label}>Visibility</Text>
<View style={styles.pickerWrapper}>
<Picker
selectedValue={form.visibility}
onValueChange={(val) => handleChange("visibility", val)}
style={styles.picker}
>
<Picker.Item label="Private (Only invited can join)" value="PRIVATE" />
<Picker.Item label="Public (Anyone can find)" value="PUBLIC" />
</Picker>
</View>
{/* Participants */}
<Text style={styles.label}>Minimum Participants</Text>
<TextInput
style={styles.input}
placeholderTextColor={"#a3a3a3ff"}
placeholder="Enter minimum number of participants"
keyboardType="numeric"
value={form.minParticipants}
onChangeText={(val) => handleChange("minParticipants", val)}
/>
<Text style={styles.label}>Maximum Participants</Text>
<TextInput
style={styles.input}
placeholderTextColor={"#a3a3a3ff"}
placeholder="Enter maximum number of participants"
keyboardType="numeric"
value={form.maxParticipants}
onChangeText={(val) => handleChange("maxParticipants", val)}
/>
{/* Contribution */}
<Text style={styles.label}>Contribution Amount (₹)</Text>
<TextInput
style={styles.input}
placeholderTextColor={"#a3a3a3ff"}
placeholder="Enter contribution amount (₹)"
keyboardType="numeric"
value={form.contributionAmount}
onChangeText={(val) => handleChange("contributionAmount", val)}
/>
<Text style={styles.label}>Cycle Type</Text>
<View style={styles.pickerWrapper}>
<Picker
selectedValue={form.cycleType}
onValueChange={(val) => handleChange("cycleType", val)}
style={styles.picker}
>
<Picker.Item label="Custom (Set custom cycle days)" value="CUSTOM" />
<Picker.Item label="Monthly" value="MONTHLY" />
<Picker.Item label="Quarterly" value="QUARTERLY" />
<Picker.Item label="Half Yearly" value="HALF_YEARLY" />
<Picker.Item label="Yearly" value="YEARLY" />
<View style={styles.titleContainer}><Text style={styles.title}>Group Creation</Text></View>
</Picker>
<CustomInput label="Group Name" placeholder="Enter your group name" value={form.groupName} onChangeText={(val) => handleChange("groupName", val)} icon="people" error={errors.groupName} />
<CustomInput label="Description" placeholder="Enter a short description" value={form.description} onChangeText={(val) => handleChange("description", val)} multiline icon="document-text" error={errors.description} />
<Text style={styles.label}>Visibility</Text>
<View style={styles.toggleContainer}>
<TouchableOpacity style={[styles.toggleButton, form.visibility === "PRIVATE" && styles.selectedButton]} onPress={() => handleChange("visibility", "PRIVATE")}><Text style={[styles.toggleButtonText, form.visibility === "PRIVATE" && styles.selectedButtonText]}>Private</Text></TouchableOpacity>
<TouchableOpacity style={[styles.toggleButton, form.visibility === "PUBLIC" && styles.selectedButton]} onPress={() => handleChange("visibility", "PUBLIC")}><Text style={[styles.toggleButtonText, form.visibility === "PUBLIC" && styles.selectedButtonText]}>Public</Text></TouchableOpacity>
</View>
{form.cycleType === "CUSTOM" && (
<>
<Text style={styles.label}>Custom Cycle Days</Text>
<TextInput
style={styles.input}
placeholderTextColor={"#a3a3a3ff"}
placeholder="Enter number of days for custom cycle"
keyboardType="numeric"
value={form.customCycleDays}
onChangeText={(val) => handleChange("customCycleDays", val)}
/>
</>
)}
<Text style={styles.label}>Fund Distribution</Text>
<View style={styles.toggleContainer}>
{distributionTypes.map((type) => (<TouchableOpacity key={type} onPress={() => handleChange("fundDistribution", type)} style={[styles.toggleButton, form.fundDistribution === type && styles.selectedButton]}><Text style={[styles.toggleButtonText, form.fundDistribution === type && styles.selectedButtonText]}>{type.charAt(0) + type.slice(1).toLowerCase()}</Text></TouchableOpacity>))}
</View>
<View style={styles.participantsRow}>
<View style={styles.participantInput}><CustomInput label="Min" placeholder="Min" value={form.minParticipants} onChangeText={(val) => handleChange("minParticipants", val)} keyboardType="numeric" icon="person-add" error={errors.minParticipants} /></View>
<View style={styles.participantInput}><CustomInput label="Max" placeholder="Max" value={form.maxParticipants} onChangeText={(val) => handleChange("maxParticipants", val)} keyboardType="numeric" icon="people-circle" error={errors.maxParticipants} /></View>
</View>
<Text style={styles.label}>Contribution Amount (INR)</Text>
<View style={styles.presetContainer}>
{presetAmounts.map((amount) => (<TouchableOpacity key={amount} onPress={() => handlePresetAmount(amount)} style={[styles.presetButton, form.contributionAmount === amount.toString() && styles.presetSelected]}><Text style={[styles.presetButtonText, form.contributionAmount === amount.toString() && styles.presetSelectedText]}>{amount}</Text></TouchableOpacity>))}
</View>
<View style={styles.amountContainer}>
<Slider style={styles.slider} minimumValue={0} maximumValue={50000} step={100} value={parseInt(form.contributionAmount, 10) || 0} onValueChange={handleAmountChange} minimumTrackTintColor="#83f5cc" maximumTrackTintColor="#ddd" thumbTintColor="#62b59a" />
<View style={styles.amountInput}><CustomInput label="" placeholder="Amount" value={form.contributionAmount} onChangeText={(val) => handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" error={errors.contributionAmount} /></View>
</View>
<Text style={styles.label}>Cycle Type</Text>
<View style={styles.cycleTypeContainer}>
{cycleTypes.map((type) => (<TouchableOpacity key={type} onPress={() => handleChange("cycleType", type)} style={[styles.cycleTypeButton, form.cycleType === type && styles.cycleTypeSelected]}><Text style={[styles.cycleTypeButtonText, form.cycleType === type && styles.cycleTypeSelectedText]}>{type.charAt(0) + type.slice(1).toLowerCase()}</Text></TouchableOpacity>))}
</View>
{form.cycleType === "CUSTOM" && (<CustomInput label="Custom Cycle Days" placeholder="Enter number of days" value={form.customCycleDays} onChangeText={(val) => handleChange("customCycleDays", val)} keyboardType="numeric" icon="calendar" error={errors.customCycleDays} />)}
{/* Timeline */}
<Text style={styles.label}>Start Date</Text>
<TextInput
style={styles.input}
placeholder="YYYY-MM-DD (e.g. 2025-09-12)"
placeholderTextColor={"#a3a3a3ff"}
value={form.startDate}
onChangeText={(val) => handleChange("startDate", val)}
/>
<TouchableOpacity style={[styles.datePickerButton, errors.startDate && styles.errorBorder]} onPress={() => setCalendarVisible(true)}>
<Ionicons name="calendar-outline" size={20} color="#555" />
<Text style={styles.datePickerText}>{form.startDate || "Select a start date"}</Text>
</TouchableOpacity>
{errors.startDate && <Text style={styles.errorText}>{errors.startDate}</Text>}
<Text style={styles.label}>End Date</Text>
<TextInput
style={styles.input}
placeholderTextColor={"#a3a3a3ff"}
placeholder="YYYY-MM-DD (e.g. 2025-12-31)"
value={form.endDate}
onChangeText={(val) => handleChange("endDate", val)}
/>
{/* Next Button
<TouchableOpacity
onPress={() => navigation.navigate("GroupReview", { form })}
style={styles.nextBtn}
>
<Text style={styles.btnText}>➡ Review</Text>
</TouchableOpacity> */}
<CustomButton
title="➡ Review"
onPress={() => navigation.navigate("GroupReview", { form })}
/>
<CustomButton title="Review" onPress={handleReview} />
</ScrollView>
<Modal animationType="fade" transparent={true} visible={isCalendarVisible} onRequestClose={() => setCalendarVisible(false)}>
<TouchableOpacity style={styles.modalBackdrop} activeOpacity={1} onPressOut={() => setCalendarVisible(false)}>
<View style={styles.calendarWrapper}><Calendar onDayPress={handleDayPress} markedDates={{[form.startDate]: { selected: true, selectedColor: '#83f5cc' }}} theme={calendarTheme} /></View>
</TouchableOpacity>
</Modal>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: { flexGrow: 1, padding: 20 },
title: { fontSize: 22, fontWeight: "bold", marginBottom: 15, color: "#333" },
label: { fontSize: 14, fontWeight: "600", marginBottom: 5, color: "#444" },
input: {
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 12,
padding: 12,
marginBottom: 15,
fontSize: 16,
backgroundColor: "#fff",
elevation: 1,
container: { flexGrow: 1, padding: 20, backgroundColor: "#f5f6fa" },
titleContainer: { backgroundColor: "#83f5cc", paddingHorizontal: 18, 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" },
selectedButton: { backgroundColor: "#83f5cc", 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: '#83f5cc' },
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: '#83f5cc', 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
},
pickerWrapper: {
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 12,
marginBottom: 15,
backgroundColor: "#fff",
elevation: 1,
errorText: {
color: '#ff4d4d',
fontSize: 12,
marginBottom: 10,
marginLeft: 5,
},
picker: { height: 60, width: "100%" },
nextBtn: {
backgroundColor: "#eb87a3ff",
paddingVertical: 16,
borderRadius: 12,
alignItems: "center",
marginTop: 10,
},
btnText: { color: "#fff", fontWeight: "bold", fontSize: 16 },
});
+153 -56
View File
@@ -1,10 +1,10 @@
import React from "react";
import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView } from "react-native";
import axios from "axios";
import { GROUP_CREATION_URL } from "../utils/ApiUrls";
import App from "../../App";
import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from "react-native";
import {Ionicons} from "@react-native-vector-icons/ionicons";
import AppView from "../components/AppView";
import { apiCall } from "../utils/ApiHelper";
import { GROUP_CREATION_URL } from "../utils/ApiUrls";
import CustomButton from "../components/CustomButton";
export default function GroupReview({ route, navigation }: any) {
const { form } = route.params;
@@ -12,83 +12,180 @@ export default function GroupReview({ route, navigation }: any) {
const handleSubmit = async () => {
const payload = {
...form,
contributionAmount: Number(form.contributionAmount),
customCycleDays: Number(form.customCycleDays),
maxParticipants: Number(form.maxParticipants),
minParticipants: Number(form.minParticipants),
contributionAmount: Number(form.contributionAmount) || 0,
customCycleDays: Number(form.customCycleDays) || 0,
maxParticipants: Number(form.maxParticipants) || 0,
minParticipants: Number(form.minParticipants) || 0,
};
await apiCall(GROUP_CREATION_URL, "POST", payload);
};
const renderRow = (label: string, value: string | number) => (
<View style={styles.row}>
<Text style={styles.label}>{label}</Text>
<Text style={styles.value}>{value}</Text>
const TimelineSection = ({ icon, title, children }: { icon: any; title: string; children: React.ReactNode }) => (
<View style={styles.section}>
<View style={styles.timeline}>
<View style={styles.timelineIcon}>
<Ionicons name={icon} size={22} color="#fff" />
</View>
<View style={styles.timelineLine} />
</View>
<View style={styles.content}>
<Text style={styles.sectionTitle}>{title}</Text>
<View style={styles.card}>
{children}
</View>
</View>
</View>
);
const renderRow = (label: string, value: string | number) => {
if (!value && typeof value !== 'number') return null;
return (
<View style={styles.row}>
<Text style={styles.label}>{label}</Text>
<Text style={styles.value}>{value}</Text>
</View>
);
};
return (
<AppView>
<ScrollView contentContainerStyle={styles.container}>
<Text style={styles.title}>Review Your Group</Text>
<View style={styles.reviewBox}>
{renderRow("Group Name", form.groupName)}
{renderRow("Description", form.description)}
{renderRow("Min Participants", form.minParticipants)}
{renderRow("Max Participants", form.maxParticipants)}
{renderRow("Contribution", `₹${form.contributionAmount}`)}
{renderRow("Cycle Type", form.cycleType)}
{form.cycleType === "CUSTOM" && renderRow("Custom Cycle", `${form.customCycleDays} days`)}
{renderRow("Start Date", form.startDate)}
{renderRow("End Date", form.endDate)}
{renderRow("Visibility", form.visibility)}
</View>
<ScrollView contentContainerStyle={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>{form.groupName || "Group Review"}</Text>
<Text style={styles.subtitle}>Confirm the details to launch your group.</Text>
</View>
<TouchableOpacity onPress={handleSubmit} style={styles.submitBtn}>
<Text style={styles.btnText}> Submit</Text>
</TouchableOpacity>
</ScrollView>
<TimelineSection icon="information-circle-outline" title="Basic Information">
{renderRow("Name", form.groupName)}
{form.description ? (
<View style={styles.descriptionContainer}>
<Text style={styles.label}>Description</Text>
<Text style={styles.descriptionText}>{form.description}</Text>
</View>
) : null}
</TimelineSection>
<TimelineSection icon="shield-checkmark-outline" title="Rules & Privacy">
{renderRow("Visibility", form.visibility)}
{renderRow("Distribution", form.fundDistribution)}
{renderRow("Min Members", form.minParticipants)}
{renderRow("Max Members", form.maxParticipants)}
</TimelineSection>
<TimelineSection icon="wallet-outline" title="Financial Details">
{renderRow("Contribution", `${form.contributionAmount} INR`)}
{renderRow("Cycle", form.cycleType)}
{form.cycleType === "CUSTOM" && renderRow("Cycle Days", `${form.customCycleDays} days`)}
{renderRow("Start Date", form.startDate)}
</TimelineSection>
<CustomButton title="Create Group" onPress={handleSubmit} />
</ScrollView>
</AppView>
);
}
const styles = StyleSheet.create({
container: { flexGrow: 1, padding: 20, backgroundColor: "#f5f5f5" },
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 20,
color: "#222",
textAlign: "center",
container: {
padding: 20,
backgroundColor: "#f5f6fa",
},
reviewBox: {
padding: 18,
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 16,
backgroundColor: "#fff",
marginBottom: 25,
header: {
marginBottom: 30,
alignItems: 'flex-start',
},
title: {
fontSize: 28,
fontWeight: "bold",
color: "#333",
},
subtitle: {
fontSize: 16,
color: "#666",
marginTop: 2,
},
section: {
flexDirection: 'row',
marginBottom: 10,
},
timeline: {
alignItems: 'center',
width: 40,
},
timelineIcon: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#83f5cc',
justifyContent: 'center',
alignItems: 'center',
elevation: 3,
shadowColor: "#000",
shadowOpacity: 0.1,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 5,
},
timelineLine: {
flex: 1,
width: 2,
backgroundColor: '#e0e0e0',
},
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: "#eee",
borderBottomColor: '#f5f5f5',
},
label: {
fontSize: 15,
color: "#666",
fontWeight: "500",
},
value: {
fontSize: 15,
color: "#333",
fontWeight: "600",
textAlign: 'right',
textTransform: 'capitalize',
},
descriptionContainer: {
paddingVertical: 10,
},
descriptionText: {
fontSize: 15,
color: '#333',
marginTop: 4,
lineHeight: 22,
},
label: { fontSize: 16, fontWeight: "600", color: "#444" },
value: { fontSize: 16, fontWeight: "500", color: "#111" },
submitBtn: {
backgroundColor: "#008e02",
paddingVertical: 18,
borderRadius: 14,
backgroundColor: "#83f5cc",
flexDirection: 'row',
justifyContent: 'center',
alignItems: "center",
elevation: 2,
paddingVertical: 16,
borderRadius: 12,
elevation: 3,
marginTop: 20,
borderWidth: 1.5,
borderColor: '#62b59a',
},
btnText: {
color: "#333",
fontWeight: "bold",
fontSize: 18,
marginLeft: 10,
},
btnText: { color: "#fff", fontWeight: "bold", fontSize: 18 },
});
+148 -28
View File
@@ -1,46 +1,146 @@
import React, { useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import { FAB, Portal, Provider } from "react-native-paper";
import Tooltip from "react-native-walkthrough-tooltip";
import { View, Text, StyleSheet, FlatList, TouchableOpacity } 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 { StackNavigationProp } from "@react-navigation/stack";
import { RootStackParamList } from "../navigation/AppNavigator";
import AppView from "../components/AppView";
import colors from "../components/colors";
// --- 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 },
];
// --- Placeholder Group List Component ---
const GroupList = ({ data }: { data: any[] }) => {
if (data.length === 0) {
return <Text style={styles.emptyText}>No groups found.</Text>;
}
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>
)}
/>
);
};
export default function GroupScreen() {
const [tooltipVisible, setTooltipVisible] = useState(false);
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
const [activeTab, setActiveTab] = useState("My Groups");
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;
}
};
return (
<Provider>
<AppView backgroundColor={colors.pastelGreen} barStyle="dark-content">
<View style={styles.container}>
<Text style={styles.title}>Groups</Text>
<FAB
style={styles.fab}
icon={() => <MaterialIcons name="group-add" size={24} color="#eb87a3ff" />}
onPress={() => {
navigation.navigate('GroupCreation')
}}
/>
{/* --- 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>
))}
</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>
</Provider>
</AppView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#dae4ed",
padding: 16,
paddingTop: 40,
// padding: 16,
// paddingTop: 40,
// backgroundColor: '#f5f6fa',
},
title: {
fontSize: 22,
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",
@@ -50,12 +150,32 @@ const styles = StyleSheet.create({
height: 70,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#dae4ed",
borderRadius: 60,
backgroundColor: "#83f5cc",
borderRadius: 35,
elevation: 4,
},
tooltipText: {
color: "#fff",
// --- Group List Styles ---
groupCard: {
backgroundColor: '#fff',
padding: 16,
borderRadius: 12,
marginBottom: 12,
elevation: 1,
},
groupName: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
},
groupMembers: {
fontSize: 14,
fontWeight: "500",
color: '#777',
marginTop: 4,
},
emptyText: {
textAlign: 'center',
marginTop: 50,
fontSize: 16,
color: '#888',
},
});
+2 -2
View File
@@ -18,7 +18,7 @@ import {
} from "react-native";
import { RootStackParamList } from "../navigation/AppNavigator";
import * as Keychain from "react-native-keychain";
import { base_URL, LOGIN_URL } from "../utils/ApiUrls";
import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls";
import { StackNavigationProp } from "@react-navigation/stack";
const theme = {
@@ -77,7 +77,7 @@ export default function OtpVerificationScreen() {
try {
const payload = { mobile, otp };
const res = await axios.post(
`${base_URL}user/otp/verify`,
VARIFY_OTP_URL,
payload,
{ headers: { "Content-Type": "application/json", Accept: "*/*" } }
);
+4 -3
View File
@@ -2,12 +2,13 @@ import { StyleSheet, Text, View } from 'react-native'
import React from 'react'
import AppView from '../../components/AppView'
import UserProfileStatus from './UserProfileStatus'
import colors from '../../components/colors'
const Home = () => {
return (
<AppView>
<UserProfileStatus/>
</AppView>
<AppView backgroundColor={colors.pastelPink} barStyle="dark-content">
<UserProfileStatus/>
</AppView>
)
}
+4 -4
View File
@@ -9,8 +9,8 @@ import {
Alert,
} from "react-native";
import * as Keychain from "react-native-keychain";
import { base_URL } from "../../utils/ApiUrls";
import { useFocusEffect, useNavigation } from "@react-navigation/native";
import { BASE_URL } from "../../utils/ApiUrls";
export type ApiResponse = {
message: string;
@@ -43,7 +43,7 @@ const UserProfileStatus: React.FC = () => {
setLoading(true);
setError(null);
const response = await fetch(`${base_URL}user/profile/status`, {
const response = await fetch(`${BASE_URL}user/profile/status`, {
method: "GET",
headers: {
Accept: "*/*",
@@ -154,8 +154,8 @@ export default UserProfileStatus;
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: "#f5f7fb",
// padding: 16,
// backgroundColor: "#f5f7fb",
},
centered: {
flex: 1,
+15 -15
View File
@@ -16,12 +16,12 @@ import {
import * as Keychain from "react-native-keychain";
import { Ionicons } from "@react-native-vector-icons/ionicons";
import { launchImageLibrary } from "react-native-image-picker";
import { base_URL } from "../../utils/ApiUrls";
import { CustomButton, CustomInput } from "../../components/common/Custom";
import colors from "../../components/colors";
import SwitchNavigator from "./SwitchNavigator";
import { useFocusEffect } from "@react-navigation/native";
import { ApiResponse } from "../home/UserProfileStatus";
import { BASE_URL } from "../../utils/ApiUrls";
@@ -64,7 +64,7 @@ const Profile = () => {
setFetching(true);
const response = await fetch(
`${base_URL}user/profile/view`,
`${BASE_URL}user/profile/view`,
{
method: "GET",
headers: {
@@ -98,7 +98,7 @@ const Profile = () => {
const token = credentials.password;
const response = await fetch(
`${base_URL}user/profile/update`,
`${BASE_URL}user/profile/update`,
{
method: "PUT",
headers: {
@@ -169,7 +169,7 @@ const Profile = () => {
} as any);
const response = await fetch(
`${base_URL}user/update/userImg`,
`${BASE_URL}user/update/userImg`,
{
method: "PUT",
headers: {
@@ -216,7 +216,7 @@ const Profile = () => {
setLoading(true);
setError(null);
const response = await fetch(`${base_URL}user/profile/status`, {
const response = await fetch(`${BASE_URL}user/profile/status`, {
method: "GET",
headers: {
Accept: "*/*",
@@ -275,7 +275,7 @@ const Profile = () => {
return (
<View style={styles.container}>
<StatusBar backgroundColor="#6366f1" barStyle="light-content" />
<StatusBar barStyle="light-content" />
{/* Header */}
<View style={styles.header}>
@@ -298,7 +298,7 @@ const Profile = () => {
{/* Profile Image Section */}
{
statusData?.role && statusData?.organizerStatus ?
<Text style={{ color: "#64748b", marginBottom: 16 , textAlign:'center', fontSize:18}}>Your Role: {statusData.role} | Organizer Status: {statusData.organizerStatus}</Text>
<Text style={{ color: "#64748b", marginBottom: 16, textAlign: 'center', fontSize: 18 }}>Your Role: {statusData.role} | Organizer Status: {statusData.organizerStatus}</Text>
:
<SwitchNavigator role={statusData?.role || ''} status={statusData?.organizerStatus || ''} />}
<View style={styles.profileImageSection}>
@@ -481,7 +481,7 @@ const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.pastelBlue,
marginBottom: '30%',
paddingBottom: '30%',
},
loadingContainer: {
flex: 1,
@@ -500,19 +500,19 @@ const styles = StyleSheet.create({
alignItems: "center",
padding: 16,
paddingTop: 50,
backgroundColor: "#6366f1",
// backgroundColor: "#6366f1",
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
elevation: 4,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 4,
// elevation: 4,
// shadowColor: "#000",
// shadowOffset: { width: 0, height: 2 },
// shadowOpacity: 0.2,
// shadowRadius: 4,
},
headerTitle: {
fontSize: 22,
fontWeight: "700",
color: "#fff"
color: "#000"
},
refreshButton: {
padding: 4,
+16
View File
@@ -0,0 +1,16 @@
import { StyleSheet, Text, View } from 'react-native'
import React from 'react'
import AppView from '../../components/AppView'
import colors from '../../components/colors'
const Setting = () => {
return (
<AppView backgroundColor={colors.pastelOrange} barStyle="dark-content">
<Text>Setting</Text>
</AppView>
)
}
export default Setting
const styles = StyleSheet.create({})
+6 -3
View File
@@ -1,3 +1,6 @@
export const base_URL = "https://api.godemo.in/FundCollect/api/"
export const LOGIN_URL = `${base_URL}user/generate/otp?mobile=`
export const VARIFY_OTP_URL = `${base_URL}user/otp/verify`
export const BASE_URL = "https://api.godemo.in/FundCollect/api/"
export const LOGIN_URL = `${BASE_URL}user/generate/otp?mobile=`
export const VARIFY_OTP_URL = `${BASE_URL}user/otp/verify`
export const ORGRAGISTRATION = `${BASE_URL}organizer/registration/apply`
export const GROUP_CREATION_URL = `${BASE_URL}group/create`
export const PROFILE_URL =`${BASE_URL}user/profile/view`