diff --git a/package-lock.json b/package-lock.json index d4efc6e..a0b731d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", + "@react-native-community/slider": "^5.0.1", "@react-native-picker/picker": "^2.11.2", "@react-native-vector-icons/ionicons": "^12.3.0", "@react-native-vector-icons/material-icons": "^12.3.0", @@ -18,11 +19,12 @@ "@react-navigation/native": "^7.1.17", "@react-navigation/native-stack": "^7.3.26", "@react-navigation/stack": "^7.4.8", - "axios": "^1.11.0", + "axios": "^1.12.2", "install": "^0.13.0", "npm": "^11.6.0", "react": "19.1.0", "react-native": "0.81.1", + "react-native-calendars": "^1.1313.0", "react-native-elements": "^3.4.3", "react-native-image-picker": "^8.2.1", "react-native-keychain": "^10.0.0", @@ -2980,6 +2982,12 @@ "node": ">=10" } }, + "node_modules/@react-native-community/slider": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-5.0.1.tgz", + "integrity": "sha512-K3JRWkIW4wQ79YJ6+BPZzp1SamoikxfPRw7Yw4B4PElEQmqZFrmH9M5LxvIo460/3QSrZF/wCgi3qizJt7g/iw==", + "license": "MIT" + }, "node_modules/@react-native-picker/picker": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.2.tgz", @@ -4520,9 +4528,9 @@ } }, "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -9007,7 +9015,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "devOptional": true, "license": "MIT" }, "node_modules/lodash.isequal": { @@ -9797,6 +9804,16 @@ "node": ">=10" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -13196,6 +13213,38 @@ } } }, + "node_modules/react-native-calendars": { + "version": "1.1313.0", + "resolved": "https://registry.npmjs.org/react-native-calendars/-/react-native-calendars-1.1313.0.tgz", + "integrity": "sha512-YQ7Vg57rBRVymolamYDTxZ0lPOELTDHQbTukTWdxR47aRBYJwKI6ocRbwcY5gYgyDwNgJS4uLGu5AvmYS74LYQ==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.1", + "lodash": "^4.17.15", + "memoize-one": "^5.2.1", + "prop-types": "^15.5.10", + "react-native-safe-area-context": "4.5.0", + "react-native-swipe-gestures": "^1.0.5", + "recyclerlistview": "^4.0.0", + "xdate": "^0.8.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "moment": "^2.29.4" + } + }, + "node_modules/react-native-calendars/node_modules/react-native-safe-area-context": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.5.0.tgz", + "integrity": "sha512-0WORnk9SkREGUg2V7jHZbuN5x4vcxj/1B0QOcXJjdYWrzZHgLcUzYWWIUecUPJh747Mwjt/42RZDOaFn3L8kPQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-elements": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz", @@ -13343,6 +13392,12 @@ "react-native": "*" } }, + "node_modules/react-native-swipe-gestures": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz", + "integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw==", + "license": "MIT" + }, "node_modules/react-native-vector-icons": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.3.0.tgz", @@ -13481,6 +13536,21 @@ "node": ">= 6" } }, + "node_modules/recyclerlistview": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.3.tgz", + "integrity": "sha512-STR/wj/FyT8EMsBzzhZ1l2goYirMkIgfV3gYEPxI3Kf3lOnu6f7Dryhyw7/IkQrgX5xtTcDrZMqytvteH9rL3g==", + "license": "Apache-2.0", + "dependencies": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + }, + "peerDependencies": { + "react": ">= 15.2.1", + "react-native": ">= 0.30.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -14666,6 +14736,12 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==", + "license": "ISC" + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -15233,6 +15309,12 @@ "async-limiter": "~1.0.0" } }, + "node_modules/xdate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/xdate/-/xdate-0.8.3.tgz", + "integrity": "sha512-1NhJWPJwN+VjbkACT9XHbQK4o6exeSVtS2CxhMPwUE7xQakoEFTlwra9YcqV/uHQVyeEUYoYC46VGDJ+etnIiw==", + "license": "(MIT OR GPL-2.0)" + }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", diff --git a/package.json b/package.json index e30f84f..dbdff86 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", + "@react-native-community/slider": "^5.0.1", "@react-native-picker/picker": "^2.11.2", "@react-native-vector-icons/ionicons": "^12.3.0", "@react-native-vector-icons/material-icons": "^12.3.0", @@ -20,11 +21,12 @@ "@react-navigation/native": "^7.1.17", "@react-navigation/native-stack": "^7.3.26", "@react-navigation/stack": "^7.4.8", - "axios": "^1.11.0", + "axios": "^1.12.2", "install": "^0.13.0", "npm": "^11.6.0", "react": "19.1.0", "react-native": "0.81.1", + "react-native-calendars": "^1.1313.0", "react-native-elements": "^3.4.3", "react-native-image-picker": "^8.2.1", "react-native-keychain": "^10.0.0", diff --git a/src/assets/colors/colors.js b/src/assets/colors/colors.js new file mode 100644 index 0000000..c2380e2 --- /dev/null +++ b/src/assets/colors/colors.js @@ -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; diff --git a/src/components/CustomButton.tsx b/src/components/CustomButton.tsx index 185fb67..63b7b7f 100644 --- a/src/components/CustomButton.tsx +++ b/src/components/CustomButton.tsx @@ -13,7 +13,7 @@ type CustomButtonProps = { export default function CustomButton({ title, onPress, - backgroundColor = "#6C63FF", + backgroundColor = "#000000ff", textColor = "#fff", loading = false, style, diff --git a/src/components/colors.ts b/src/components/colors.ts index 6da0717..0206238 100644 --- a/src/components/colors.ts +++ b/src/components/colors.ts @@ -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", diff --git a/src/navigation/BottomTabs.tsx b/src/navigation/BottomTabs.tsx index d66a3e8..2f276c0 100644 --- a/src/navigation/BottomTabs.tsx +++ b/src/navigation/BottomTabs.tsx @@ -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 ( - - ⚙️ Settings - - ); -} - export default function BottomTabs() { return ( ({ 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 ? ( + {route.name} + ) : 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 ; + // 3. Get the active background color and icon color from the maps + const activeBackgroundColor = activeTabColors[route.name]; + const activeIconColor = activeIconColors[route.name]; + + return ( + + + + ); }, })} > - + ); } @@ -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, }, }); diff --git a/src/screens/GroupCreation.tsx b/src/screens/GroupCreation.tsx index 30b79cd..0425d37 100644 --- a/src/screens/GroupCreation.tsx +++ b/src/screens/GroupCreation.tsx @@ -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(); - 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({}); // 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} > - Group Creation - - {/* Group Info */} - Group Name - handleChange("groupName", val)} - /> - - Description - handleChange("description", val)} - /> - - Visibility - - handleChange("visibility", val)} - style={styles.picker} - > - - - - - - {/* Participants */} - Minimum Participants - handleChange("minParticipants", val)} - /> - - Maximum Participants - handleChange("maxParticipants", val)} - /> - - {/* Contribution */} - Contribution Amount (₹) - handleChange("contributionAmount", val)} - /> - - Cycle Type - - handleChange("cycleType", val)} - style={styles.picker} - > - - - - - + Group Creation - + 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 - {form.cycleType === "CUSTOM" && ( - <> - Custom Cycle Days - handleChange("customCycleDays", val)} - /> - - )} + Fund Distribution + + {distributionTypes.map((type) => ( handleChange("fundDistribution", type)} style={[styles.toggleButton, form.fundDistribution === type && styles.selectedButton]}>{type.charAt(0) + type.slice(1).toLowerCase()}))} + + + + 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" error={errors.contributionAmount} /> + + + 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" error={errors.customCycleDays} />)} - {/* Timeline */} Start Date - handleChange("startDate", val)} - /> + setCalendarVisible(true)}> + + {form.startDate || "Select a start date"} + + {errors.startDate && {errors.startDate}} - End Date - handleChange("endDate", val)} - /> - - {/* Next Button - navigation.navigate("GroupReview", { form })} - style={styles.nextBtn} - > - ➡ Review - */} - navigation.navigate("GroupReview", { form })} - /> + + + setCalendarVisible(false)}> + setCalendarVisible(false)}> + + + ); } 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 }, }); diff --git a/src/screens/GroupReview.tsx b/src/screens/GroupReview.tsx index 40f3673..242ac46 100644 --- a/src/screens/GroupReview.tsx +++ b/src/screens/GroupReview.tsx @@ -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) => ( - - {label} - {value} + 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} + + ); + }; + return ( - - Review Your Group - - {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)} - + + + {form.groupName || "Group Review"} + Confirm the details to launch your group. + - - Submit - - + + {renderRow("Name", form.groupName)} + {form.description ? ( + + Description + {form.description} + + ) : null} + + + + {renderRow("Visibility", form.visibility)} + {renderRow("Distribution", form.fundDistribution)} + {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)} + + + ); } 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 }, }); diff --git a/src/screens/GroupScreen.tsx b/src/screens/GroupScreen.tsx index d9b370e..20be00f 100644 --- a/src/screens/GroupScreen.tsx +++ b/src/screens/GroupScreen.tsx @@ -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 No groups found.; + } + + return ( + item.id} + renderItem={({ item }) => ( + + {item.name} + {item.members} members + + )} + /> + ); +}; export default function GroupScreen() { - const [tooltipVisible, setTooltipVisible] = useState(false); - const navigation = useNavigation>(); + const [activeTab, setActiveTab] = useState("My Groups"); + const navigation = useNavigation>(); + + const renderContent = () => { + switch (activeTab) { + case "My Groups": + return ; + case "Joined": + return ; + case "Public": + return ; + default: + return null; + } + }; + return ( - + - Groups - - - } - onPress={() => { - navigation.navigate('GroupCreation') - }} - /> - - + {/* --- Header and Segmented Control --- */} + Your Groups + + {["My Groups", "Joined", "Public"].map((tab) => ( + setActiveTab(tab)} + > + + {tab} + + + ))} + + + {/* --- Dynamic Content Area --- */} + + {renderContent()} + + + } + onPress={() => navigation.navigate('GroupCreation')} + /> - + ); } 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', }, }); diff --git a/src/screens/OtpVarificationScreen.tsx b/src/screens/OtpVarificationScreen.tsx index 968dfdf..f3e2ebc 100644 --- a/src/screens/OtpVarificationScreen.tsx +++ b/src/screens/OtpVarificationScreen.tsx @@ -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: "*/*" } } ); diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx index fc94e21..83bdb99 100644 --- a/src/screens/home/Home.tsx +++ b/src/screens/home/Home.tsx @@ -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 ( - - - + + + ) } diff --git a/src/screens/home/UserProfileStatus.tsx b/src/screens/home/UserProfileStatus.tsx index 334030b..2e00de8 100644 --- a/src/screens/home/UserProfileStatus.tsx +++ b/src/screens/home/UserProfileStatus.tsx @@ -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, diff --git a/src/screens/profile/Profile.tsx b/src/screens/profile/Profile.tsx index 5805f9e..6399bd9 100644 --- a/src/screens/profile/Profile.tsx +++ b/src/screens/profile/Profile.tsx @@ -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 ( - + {/* Header */} @@ -298,7 +298,7 @@ const Profile = () => { {/* Profile Image Section */} { statusData?.role && statusData?.organizerStatus ? - Your Role: {statusData.role} | Organizer Status: {statusData.organizerStatus} + Your Role: {statusData.role} | Organizer Status: {statusData.organizerStatus} : } @@ -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, diff --git a/src/screens/setting/Setting.tsx b/src/screens/setting/Setting.tsx new file mode 100644 index 0000000..397133b --- /dev/null +++ b/src/screens/setting/Setting.tsx @@ -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 ( + + Setting + + ) +} + +export default Setting + +const styles = StyleSheet.create({}) \ No newline at end of file diff --git a/src/utils/ApiUrls.tsx b/src/utils/ApiUrls.tsx index 6ff1acf..2de0e95 100644 --- a/src/utils/ApiUrls.tsx +++ b/src/utils/ApiUrls.tsx @@ -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`