diff --git a/android/app/release/CircleCollect.apk b/android/app/release/CircleCollect.apk new file mode 100644 index 0000000..bfad9c0 Binary files /dev/null and b/android/app/release/CircleCollect.apk differ diff --git a/android/app/release/baselineProfiles/0/app-release.dm b/android/app/release/baselineProfiles/0/app-release.dm new file mode 100644 index 0000000..a8daac4 Binary files /dev/null and b/android/app/release/baselineProfiles/0/app-release.dm differ diff --git a/android/app/release/baselineProfiles/1/app-release.dm b/android/app/release/baselineProfiles/1/app-release.dm new file mode 100644 index 0000000..d8c7c1a Binary files /dev/null and b/android/app/release/baselineProfiles/1/app-release.dm differ diff --git a/android/app/release/output-metadata.json b/android/app/release/output-metadata.json new file mode 100644 index 0000000..08b0ec4 --- /dev/null +++ b/android/app/release/output-metadata.json @@ -0,0 +1,37 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.circlecollect", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File", + "baselineProfiles": [ + { + "minApi": 28, + "maxApi": 30, + "baselineProfiles": [ + "baselineProfiles/1/app-release.dm" + ] + }, + { + "minApi": 31, + "maxApi": 2147483647, + "baselineProfiles": [ + "baselineProfiles/0/app-release.dm" + ] + } + ], + "minSdkVersionForDexing": 24 +} \ No newline at end of file diff --git a/android/appKeyStore b/android/appKeyStore new file mode 100644 index 0000000..b483839 Binary files /dev/null and b/android/appKeyStore differ diff --git a/package-lock.json b/package-lock.json index 90e8877..0bb25eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "react-native-inset-shadow": "^1.0.3", "react-native-keychain": "^10.0.0", "react-native-linear-gradient": "^2.8.3", + "react-native-material-ripple": "^0.9.1", "react-native-paper": "^5.14.5", "react-native-reanimated": "^4.1.2", "react-native-safe-area-context": "^5.6.1", @@ -12932,6 +12933,19 @@ "react-native": "*" } }, + "node_modules/react-native-material-ripple": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/react-native-material-ripple/-/react-native-material-ripple-0.9.1.tgz", + "integrity": "sha512-dHjARdKoGD3LXwPh2TkURabywM5BfdpKc/AHplJTfBMalhVZ2bc7iJoJmNhNnmT/TZ+B3szL6uV1mO7Pv8zNeA==", + "license": "BSD-3-Clause", + "dependencies": { + "prop-types": "^15.5.10" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-paper": { "version": "5.14.5", "license": "MIT", diff --git a/package.json b/package.json index f618d1b..e43c628 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "react-native-inset-shadow": "^1.0.3", "react-native-keychain": "^10.0.0", "react-native-linear-gradient": "^2.8.3", + "react-native-material-ripple": "^0.9.1", "react-native-paper": "^5.14.5", "react-native-reanimated": "^4.1.2", "react-native-safe-area-context": "^5.6.1", diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index 076b9f9..9ce3fd0 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -33,12 +33,12 @@ const Header: React.FC = ({ return ( - {showBack && ( + {/* {showBack && ( - )} - {title} + )} */} + {title} {rightAction} ); diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx index 6690701..abaad1b 100644 --- a/src/screens/LoginScreen.tsx +++ b/src/screens/LoginScreen.tsx @@ -1,6 +1,4 @@ -// src/screens/LoginScreen.tsx - -import React, { useState, useRef, useEffect } from "react"; +import React, { useState, useRef, useEffect } from 'react'; import { View, Text, @@ -12,27 +10,34 @@ import { Animated, StatusBar, ActivityIndicator, -} from "react-native"; -import { LOGIN_URL } from "../utils/ApiUrls"; -import axios from "axios"; -import { useNavigation } from "@react-navigation/native"; -import type { StackNavigationProp } from "@react-navigation/stack"; -import { RootStackParamList } from "../navigation/AppNavigator"; -import LinearGradient from "react-native-linear-gradient"; + Dimensions, +} from 'react-native'; +import { LOGIN_URL } from '../utils/ApiUrls'; +import axios from 'axios'; +import { useNavigation } from '@react-navigation/native'; +import type { StackNavigationProp } from '@react-navigation/stack'; +import { RootStackParamList } from '../navigation/AppNavigator'; +import LinearGradient from 'react-native-linear-gradient'; import Ionicons from '@react-native-vector-icons/ionicons'; -import CustomAlert from "../components/CostomAlart"; -import colors from "../components/colors"; // Centralized colors from your file +import MaterialRipple from 'react-native-material-ripple'; // For the button ripple effect +import CustomAlert from '../components/CostomAlart'; +import colors from '../components/colors'; + +const { width } = Dimensions.get('window'); +const BLOCK_SIZE = width / 3; + +// Note: You will need to install react-native-material-ripple +// npm install react-native-material-ripple export default function LoginScreen() { const navigation = useNavigation>(); - const [mobile, setMobile] = useState(""); + const [mobile, setMobile] = useState(''); const [loading, setLoading] = useState(false); - const [alertInfo, setAlertInfo] = useState({ visible: false, - title: "", - message: "", - buttons: [] as { text: string; onPress: () => void; style?: "cancel" | "destructive" | "default" }[], + title: '', + message: '', + buttons: [] as { text: string; onPress: () => void; style?: 'cancel' | 'destructive' | 'default' }[], alertType: 'info' as 'info' | 'success' | 'error', }); @@ -50,12 +55,12 @@ export default function LoginScreen() { const showAlert = ( title: string, message: string, - buttons: { text: string; onPress: () => void; style?: "cancel" | "destructive" | "default" }[], - alertType: 'info' | 'success' | 'error' = 'info' + buttons: { text: string; onPress: () => void; style?: 'cancel' | 'destructive' | 'default' }[], + alertType: 'info' | 'success' | 'error' = 'info', ) => { setAlertInfo({ visible: true, title, message, buttons, alertType }); }; - + const hideAlert = () => { setAlertInfo({ ...alertInfo, visible: false }); }; @@ -64,68 +69,70 @@ export default function LoginScreen() { if (mobile.length === 10) { setLoading(true); try { - console.log("Login api call:"); - // FIX: Used the imported LOGIN_URL constant instead of a string literal const res = await axios.get(LOGIN_URL, { params: { mobile } }); - console.log("Login response:", res.data); if (res.data.statusCode === 200) { - navigation.navigate("OtpVarify", { mobile }); + navigation.navigate('OtpVarify', { mobile }); } else { - showAlert( - "Login Failed", - res.data.message || "Could not send OTP.", - [{ text: "Try Again", onPress: hideAlert }], - 'error' - ); - + showAlert('Login Failed', res.data.message || 'Could not send OTP.', [{ text: 'Try Again', onPress: hideAlert }], 'error'); } } catch (error: any) { - console.error("Login error:", error); - showAlert( - "An Error Occurred", - "Failed to send OTP. Please check your connection and try again.", - [{ text: "OK", onPress: hideAlert }], - 'error' - ); + showAlert('An Error Occurred', 'Failed to send OTP. Please check your connection and try again.', [{ text: 'OK', onPress: hideAlert }], 'error'); } finally { setLoading(false); } } else { - showAlert( - "Invalid Number", - "Please enter a valid 10-digit mobile number.", - [{ text: "Got it", onPress: hideAlert }], - 'info' - ); + showAlert('Invalid Number', 'Please enter a valid 10-digit mobile number.', [{ text: 'Got it', onPress: hideAlert }], 'info'); } }; + const colorsBlocks = [ + colors.pastelPink, + colors.pastelBlue, + colors.pastelGreen, + colors.pastelOrange, + colors.pastelBlue, + colors.pastelPink, + colors.pastelGreen, + colors.pastelOrange, + colors.pastelPink, + ]; + return ( - + + + + {/* Funky grid blocks background (Unchanged) */} + + {colorsBlocks.map((bgColor, index) => ( + + ))} + + - - - - - + + + + CircleCollect + Welcome Back! + + Let's get you signed in to your community. + + + - - CircleCollect - Welcome! - - Glad to see you again. Let's get you signed in to your community. - - - - - - - - + + + + + + + {loading ? ( + + ) : ( + + Continue + + + )} + + + + Your privacy is our priority. Your information is always secure. + + - - - - {loading ? ( - - ) : ( - Continue with Mobile - )} - - - Your privacy is our priority. Your information is always secure. - - - - + ); } const styles = StyleSheet.create({ - gradientContainer: { - flex: 1, - }, container: { flex: 1, + }, + gridContainer: { + ...StyleSheet.absoluteFillObject, + flexDirection: 'row', + flexWrap: 'wrap', + }, + gridBlock: { + width: BLOCK_SIZE, + height: BLOCK_SIZE, + margin: 2, + borderRadius: 16, + }, + elevatedBlock: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 5 }, + shadowOpacity: 0.15, + shadowRadius: 10, + elevation: 7, + }, + keyboardAvoidingContainer: { + flex: 1, + }, + contentContainer: { + flex: 1, + justifyContent: 'space-between', padding: 24, - justifyContent: "space-between", - }, - shape: { - position: 'absolute', - borderRadius: 1000, - opacity: 0.6, - }, - shape1: { - width: 400, - height: 400, - backgroundColor: colors.pastelPink, - top: -150, - left: -100, - }, - shape2: { - width: 300, - height: 300, - backgroundColor: colors.pastelBlue, - bottom: -100, - right: -120, - }, - shape3: { - width: 200, - height: 200, - backgroundColor: colors.pastelGreen, - bottom: '40%', - left: -80, - opacity: 0.5 }, topSection: { flex: 0.8, - justifyContent: "center", - alignItems: 'center' - }, - formSection: { - flex: 1, - justifyContent: 'flex-start' + justifyContent: 'center', + alignItems: 'center', }, brand: { - fontSize: 24, - fontWeight: "600", + fontSize: 28, + fontWeight: '700', color: colors.primary, - textAlign: "center", - letterSpacing: 1.5, + textAlign: 'center', + letterSpacing: 2, marginBottom: 16, + textShadowColor: 'rgba(0, 0, 0, 0.1)', + textShadowOffset: { width: 1, height: 1 }, + textShadowRadius: 2, }, title: { - fontSize: 38, - fontWeight: "bold", + fontSize: 40, + fontWeight: 'bold', color: colors.textDark, - textAlign: "center", - marginBottom: 12, + textAlign: 'center', + marginBottom: 14, }, subtitle: { fontSize: 16, - color: colors.textLight, - textAlign: "center", - lineHeight: 24, + color: colors.textDark, + textAlign: 'center', + lineHeight: 26, maxWidth: 320, }, - inputContainer: { - flexDirection: "row", - alignItems: "center", - backgroundColor: colors.white, - borderRadius: 16, - borderWidth: 1, - borderColor: colors.inputBorder, - paddingHorizontal: 16, - marginTop: 32, - shadowColor: colors.black, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.05, - shadowRadius: 10, - elevation: 3, + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.85)', + borderRadius: 36, + borderWidth: 2, + borderColor: colors.pastelPink, + paddingHorizontal: 20, + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 6 }, + shadowOpacity: 0.2, + shadowRadius: 14, + elevation: 8, }, inputIcon: { - marginRight: 12, + marginRight: 16, }, input: { flex: 1, + fontSize: 18, + fontWeight: '600', + color: colors.textLight, height: 58, - fontSize: 16, - color: colors.textDark, }, bottomSection: { - paddingBottom: Platform.OS === "ios" ? 20 : 10, - paddingHorizontal: 10 + alignItems: 'center', + paddingBottom: Platform.OS === 'ios' ? 20 : 10, }, button: { - backgroundColor: colors.primary, - paddingVertical: 18, + width: '100%', borderRadius: 30, - alignItems: "center", - justifyContent: "center", - flexDirection: "row", shadowColor: colors.primary, - shadowOffset: { width: 0, height: 8 }, - shadowOpacity: 0.3, - shadowRadius: 15, - elevation: 8, + shadowOffset: { width: 0, height: 10 }, + shadowOpacity: 0.4, + shadowRadius: 20, + elevation: 14, + marginTop: 20, + }, + buttonGradient: { + borderRadius: 30, + paddingVertical: 18, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, + buttonContent: { + flexDirection: 'row', + alignItems: 'center', }, buttonText: { color: colors.white, - fontWeight: "700", - fontSize: 16, + fontWeight: '800', + fontSize: 18, }, footerText: { - textAlign: "center", - color: colors.textLight, - fontSize: 12, - marginTop: 20, + fontSize: 14, + color: 'rgba(0,0,0,0.5)', + marginTop: 28, + textAlign: 'center', + maxWidth: 300, }, }); diff --git a/src/screens/OtpVarificationScreen.tsx b/src/screens/OtpVarificationScreen.tsx index d4580b4..66afa93 100644 --- a/src/screens/OtpVarificationScreen.tsx +++ b/src/screens/OtpVarificationScreen.tsx @@ -1,4 +1,4 @@ -import { RouteProp, useNavigation, useRoute } from "@react-navigation/native"; +import { CommonActions, RouteProp, useNavigation, useRoute } from "@react-navigation/native"; import axios from "axios"; import React, { useEffect, useRef, useState } from "react"; import { @@ -13,16 +13,18 @@ import { TextInputKeyPressEventData, KeyboardAvoidingView, StatusBar, + Dimensions, } from "react-native"; import { RootStackParamList } from "../navigation/AppNavigator"; import * as Keychain from "react-native-keychain"; import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls"; import colors from "../components/colors"; -import LinearGradient from "react-native-linear-gradient"; import CustomAlert from "../components/CostomAlart"; import { getFcmToken } from "../utils/firebase/firebaseServices"; const CELL_COUNT = 6; +const { width } = Dimensions.get('window'); +const BLOCK_SIZE = width / 3; type OtpVerifyRouteProp = RouteProp; interface AlertButton { @@ -53,21 +55,21 @@ export default function OtpVerificationScreen() { const route = useRoute(); const { mobile } = route.params; const navigation = useNavigation(); - + // Refs for auto-closing logic const successActionRef = useRef<(() => void) | null>(null); const successTimerRef = useRef(null); const fade = useRef(new Animated.Value(0)).current; - const slide = useRef(new Animated.Value(24)).current; + const slide = useRef(new Animated.Value(30)).current; const ctaScale = useRef(new Animated.Value(1)).current; useEffect(() => { Animated.parallel([ - Animated.timing(fade, { toValue: 1, duration: 600, useNativeDriver: true }), - Animated.timing(slide, { toValue: 0, duration: 600, useNativeDriver: true }), + Animated.timing(fade, { toValue: 1, duration: 800, useNativeDriver: true }), + Animated.spring(slide, { toValue: 0, friction: 5, tension: 40, useNativeDriver: true }), ]).start(); - }, [fade, slide]); + }, []); useEffect(() => { if (timer <= 0) return; @@ -77,8 +79,8 @@ export default function OtpVerificationScreen() { const hideAlert = () => { if (successTimerRef.current) { - clearTimeout(successTimerRef.current); - successTimerRef.current = null; + clearTimeout(successTimerRef.current); + successTimerRef.current = null; } setAlertConfig(prev => ({ ...prev, visible: false })); }; @@ -91,12 +93,12 @@ export default function OtpVerificationScreen() { autoCloseAction?: () => void ) => { setAlertConfig({ visible: true, title, message, buttons, alertType }); - + if (alertType === 'success' && autoCloseAction) { - successActionRef.current = autoCloseAction; - successTimerRef.current = setTimeout(() => { - successActionRef.current?.(); - }, 2000); // Auto-close after 2 seconds + successActionRef.current = autoCloseAction; + successTimerRef.current = setTimeout(() => { + successActionRef.current?.(); + }, 2000); } }; @@ -130,14 +132,18 @@ export default function OtpVerificationScreen() { await saveToken(jwt); await getFcmToken(res.data?.data?.userId); - let hasActionRun = false; - const successAction = () => { - if (hasActionRun) return; - hasActionRun = true; - hideAlert(); - navigation.replace("Home"); - }; + const successAction = () => { + if (hasActionRun) return; + hasActionRun = true; + hideAlert(); + navigation.dispatch( + CommonActions.reset({ + index: 0, + routes: [{ name: 'Home' }], + }) + ); +}; triggerAlert( 'Success!', @@ -162,20 +168,18 @@ export default function OtpVerificationScreen() { try { setIsResending(true); setTimer(30); - await axios.get(`${LOGIN_URL}?mobile=${mobile}`, { - headers: { "Content-Type": "application/json", Accept: "*/*" }, - }); - + await axios.get(LOGIN_URL, { params: { mobile } }) + setCode(Array(CELL_COUNT).fill("")); inputs.current[0]?.focus(); - + let hasActionRun = false; const successAction = () => { - if (hasActionRun) return; - hasActionRun = true; - hideAlert(); + if (hasActionRun) return; + hasActionRun = true; + hideAlert(); }; - + triggerAlert( 'OTP Sent', 'A new code has been sent to your mobile number.', @@ -233,73 +237,130 @@ export default function OtpVerificationScreen() { inputs.current[idx - 1]?.focus(); } }; + + const colorsBlocks = [ + colors.pastelPink, + colors.pastelBlue, + colors.pastelGreen, + colors.pastelOrange, + colors.pastelBlue, + colors.pastelPink, + colors.pastelGreen, + colors.pastelOrange, + colors.pastelPink, + ]; return ( - - - - + + + + + {colorsBlocks.map((bgColor, index) => ( + + ))} + + + + - - Verify Your Code 🔒 - We’ve sent a 6-digit verification code to +91 {mobile}. + + + Verify Your Code + We’ve sent a 6-digit verification code to +91 {mobile}. - - {Array.from({ length: CELL_COUNT }).map((_, i) => ( - { inputs.current[i] = r; }} - style={[styles.cell, code[i] ? styles.cellFilled : null]} - keyboardType="number-pad" - maxLength={i === 0 ? CELL_COUNT : 1} - value={code[i]} - onChangeText={(t) => (i === 0 ? handleFirstCellChange(t) : updateIndex(i, t))} - onKeyPress={(e) => handleKeyPress(i, e)} - placeholder="•" - placeholderTextColor="#BDBDBD" - textContentType="oneTimeCode" - /> - ))} + + {Array.from({ length: CELL_COUNT }).map((_, i) => ( + { inputs.current[i] = r; }} + style={[styles.cell, code[i] ? styles.cellFilled : null]} + keyboardType="number-pad" + maxLength={i === 0 ? CELL_COUNT : 1} + value={code[i]} + onChangeText={(t) => (i === 0 ? handleFirstCellChange(t) : updateIndex(i, t))} + onKeyPress={(e) => handleKeyPress(i, e)} + placeholder="•" + placeholderTextColor="#BDBDBD" + textContentType="oneTimeCode" + /> + ))} + + + + + Animated.spring(ctaScale, { toValue: 0.97, useNativeDriver: true }).start()} + onPressOut={() => Animated.spring(ctaScale, { toValue: 1, useNativeDriver: true }).start()} + onPress={() => onSubmit(code.join(""), mobile)} + style={styles.button} + > + Verify & Continue + + + {timer > 0 ? ( + Resend code in 00:{timer.toString().padStart(2, "0")} + ) : ( + + + {isResending ? "Resending..." : "Resend Code"} + + + )} - - - Animated.spring(ctaScale, { toValue: 0.97, useNativeDriver: true }).start()} - onPressOut={() => Animated.spring(ctaScale, { toValue: 1, useNativeDriver: true }).start()} - onPress={() => onSubmit(code.join(""), mobile)} - style={styles.button} - > - Verify & Continue - - - {timer > 0 ? ( - Resend code in 00:{timer.toString().padStart(2, "0")} - ) : ( - - - {isResending ? "Resending..." : "Resend Code"} - - - )} - - + ); } const styles = StyleSheet.create({ - gradientContainer: { flex: 1 }, - container: { flex: 1, padding: 24, justifyContent: "space-between" }, - topSection: { flex: 1, justifyContent: "center", alignItems: 'center' }, + container: { flex: 1 }, + gridContainer: { + ...StyleSheet.absoluteFillObject, + flexDirection: 'row', + flexWrap: 'wrap', + }, + gridBlock: { + width: BLOCK_SIZE, + height: BLOCK_SIZE, + margin: 2, + borderRadius: 16, + }, + elevatedBlock: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 5 }, + shadowOpacity: 0.15, + shadowRadius: 10, + elevation: 7, + }, + keyboardAvoidingContainer: { flex: 1, justifyContent: 'center' }, + contentPanel: { + marginHorizontal: 24, + backgroundColor: 'rgba(255, 255, 255, 0.95)', + borderRadius: 32, + padding: 28, + shadowColor: '#000', + shadowOffset: { width: 0, height: 10 }, + shadowOpacity: 0.14, + shadowRadius: 24, + elevation: 12, + }, + topSection: { alignItems: 'center', marginBottom: 24 }, title: { fontSize: 28, fontWeight: "bold", color: colors.textDark, textAlign: 'center', marginBottom: 12 }, subtitle: { fontSize: 16, color: colors.textLight, textAlign: 'center', marginBottom: 32, lineHeight: 24, maxWidth: 300 }, otpRow: { flexDirection: "row", justifyContent: "space-between", width: '100%', paddingHorizontal: 10 }, cell: { width: 48, height: 56, borderRadius: 12, borderWidth: 1.5, borderColor: colors.inputBorder, backgroundColor: colors.white, textAlign: "center", fontSize: 22, fontWeight: '700', color: colors.textDark, shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 8, elevation: 3 }, cellFilled: { borderColor: colors.primary, backgroundColor: '#F7F2FF' }, - bottomSection: { paddingBottom: Platform.OS === "ios" ? 20 : 10 }, - button: { backgroundColor: colors.primary, paddingVertical: 18, borderRadius: 30, alignItems: "center", justifyContent: "center", shadowColor: colors.primary, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.3, shadowRadius: 15, elevation: 8 }, + bottomSection: { paddingBottom: Platform.OS === "ios" ? 20 : 10, paddingTop: 10 }, + button: { backgroundColor: colors.pastelPink, paddingVertical: 18, borderRadius: 30, alignItems: "center", justifyContent: "center", shadowColor: colors.primary, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.3, shadowRadius: 15, elevation: 8 ,borderWidth:2, borderColor:colors.pastelPink}, buttonText: { color: colors.white, fontWeight: "700", fontSize: 16 }, footerText: { textAlign: "center", color: colors.textLight, fontSize: 14, marginTop: 20 }, }); diff --git a/src/screens/SplashScreen.tsx b/src/screens/SplashScreen.tsx index 8d9f920..d41038b 100644 --- a/src/screens/SplashScreen.tsx +++ b/src/screens/SplashScreen.tsx @@ -110,7 +110,7 @@ const SplashScreen = () => { Circle Collect - + ); }; diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx index 51dd386..0318a7f 100644 --- a/src/screens/home/Home.tsx +++ b/src/screens/home/Home.tsx @@ -60,6 +60,7 @@ const userActions: ActionItem[] = [ { id: "1", title: "Joined Groups", subtitle: "See your groups", action: "NAVIGATE_MANAGEMENT", tab: "Joined Groups", iconName: "person-outline", backOne: '#D7E9F7', backTwo: '#82AAE3' }, { id: "2", title: "Your Requests", subtitle: "Track your requests", action: "NAVIGATE_MANAGEMENT", tab: "Your Requests", iconName: "mail-open-outline", backOne: '#FFF4D2', backTwo: '#FFB344' }, { id: "3", title: "Transactions", subtitle: "View your history", action: "TRANSACTION", iconName: "receipt-outline", backOne: '#decbfcff', backTwo: '#B799FF' }, + { id: "4", title: "Create Group", subtitle: "Create a new group", action: "NAVIGATE_CREATE_GROUP", tab: "Requests", iconName: "people-outline", backOne: '#DFF6DD', backTwo: '#88D18A' }, ]; const Home: React.FC = () => { @@ -96,6 +97,7 @@ const Home: React.FC = () => { } else if (item.action === "TRANSACTION") { navigation.navigate("TransactionList" as never); } else if (item.action === "NAVIGATE_CREATE_GROUP") { + navigation.navigate("GroupCreation" as never); } else { ToastAndroid.show(`Unknown action: ${item.title}`, ToastAndroid.SHORT); diff --git a/src/screens/home/user-home/CycleDashboard.tsx b/src/screens/home/user-home/CycleDashboard.tsx index 0f4c8e9..4d0259e 100644 --- a/src/screens/home/user-home/CycleDashboard.tsx +++ b/src/screens/home/user-home/CycleDashboard.tsx @@ -164,7 +164,7 @@ const CycleDashboard = ({ data }: { data: CycleDashboardData }) => { Cycles - {data.currentCycle}/{data.totalCycles} + {data.currentCycle}/{data.totalParticipants} diff --git a/src/screens/home/user-home/GroupDetails.tsx b/src/screens/home/user-home/GroupDetails.tsx index f21de4a..11bf3da 100644 --- a/src/screens/home/user-home/GroupDetails.tsx +++ b/src/screens/home/user-home/GroupDetails.tsx @@ -12,16 +12,18 @@ import { import { useRoute } from "@react-navigation/native"; import AppView from "../../../components/AppView"; import colors from "../../../components/colors"; -import { Ionicons } from "@react-native-vector-icons/ionicons"; +import Ionicons from '@react-native-vector-icons/ionicons'; import Header from "../../../components/common/Header"; import { apiCall } from "../../../utils/ApiHelper"; import { BASE_URL, CONTRIBUTE, GROUP_ALL_MEMBER } from "../../../utils/ApiUrls"; import ContributionScreen from "../../ContributionScreen"; import CycleDashboard from "./CycleDashboard"; -const CustomDropdown = ({ items, selectedValue, onValueChange, placeholder = "Select an item" }:any) => { + +const CustomDropdown = ({ items, selectedValue, onValueChange, placeholder = "Select an item" }: any) => { const [open, setOpen] = useState(false); - const selectedLabel = items.find((item:any) => item.value === selectedValue)?.label || placeholder; + const selectedLabel = items.find((item: any) => item.value === selectedValue)?.label || placeholder; + return ( @@ -31,6 +33,7 @@ const CustomDropdown = ({ items, selectedValue, onValueChange, placeholder = "Se + { const route = useRoute(); + console.log(" route id : ", route) const [memberData, setMemberData] = useState([]); const [loading, setLoading] = useState(false); @@ -75,6 +80,8 @@ const GroupDetails = () => { const [cycleDashboardData, setCycleDashboardData] = useState(); const [selectedCycleNumber, setSelectedCycleNumber] = useState(null); const [cycleItems, setCycleItems] = useState([]); + const [selectedUser, setSelectedUser] = useState() as any + const group = { id: route.params.item?.id, @@ -86,14 +93,26 @@ const GroupDetails = () => { members: memberData, }; + + + const getPaymentStatus = async () => { + const response = await apiCall(`${BASE_URL}payments/group/${group.id || route?.params?.groupId}/payment/paid`, 'GET') as { status: string; data?: { currentCycle: number, allPaid: boolean, selectedUser: any }; message?: string }; + console.log("paymentStatus", response.data?.selectedUser) + if (response.status === 'success') { + setSelectedUser(response.data?.selectedUser); + } + } + + + const getCycleDashboard = async () => { try { const response = await apiCall(`${BASE_URL}payments/groups/${group.id || route?.params?.groupId}/cycle-dashboard`, "GET") as any; const responseData = response.data; setCycleDashboardData(responseData); - + console.log("cycledata", responseData) if (responseData?.cycles) { - const items = responseData.cycles.map(cycle => ({ + const items = responseData.cycles.map((cycle: any) => ({ label: `Cycle ${cycle.cycleNumber}`, value: cycle.cycleNumber })); @@ -107,6 +126,7 @@ const GroupDetails = () => { } }; + const fetchGroupDetails = async () => { try { const res = await apiCall(`${GROUP_ALL_MEMBER}?groupId=${group.id || route?.params?.groupId}`, "GET") as any; @@ -117,6 +137,7 @@ const GroupDetails = () => { } }; + const handleContribution = async () => { try { const res = await apiCall(`${CONTRIBUTE}${group.id || route?.params?.groupId}/calculate-contribution`, 'GET') as any; @@ -126,6 +147,7 @@ const GroupDetails = () => { } }; + const fetchAllData = useCallback(async () => { setLoading(true); await Promise.all([ @@ -136,22 +158,28 @@ const GroupDetails = () => { setLoading(false); }, [group.id || route?.params?.groupId]); + useEffect(() => { if (group.id || route?.params?.groupId) { fetchAllData(); + getPaymentStatus(); } }, [group.id || route?.params?.groupId, fetchAllData]); + const onRefresh = useCallback(async () => { setRefreshing(true); await fetchAllData(); + await getPaymentStatus(); setRefreshing(false); }, [fetchAllData]); + const confirmPayment = async () => { try { const res = await apiCall(`${BASE_URL}payments/contribute/${group?.id}?amount=${payable?.totalPayable}`, 'POST') as any; - if (res.status === 200 || res.status === 201) { + console.log('payment res ', res) + if (res.status === 200 || res.status === 201 || res.statusCode === 200) { ToastAndroid.show('Payment done!', ToastAndroid.SHORT); onRefresh(); } else { @@ -162,10 +190,19 @@ const GroupDetails = () => { } }; - const selectedCycleData = cycleDashboardData?.cycles.find(c => c.cycleNumber === selectedCycleNumber); + + const selectedCycleData = cycleDashboardData?.cycles.find((c: any) => c.cycleNumber === selectedCycleNumber); + return ( + {selectedUser?.firstName && ( + + + Winner: {`${selectedUser.firstName} ${selectedUser.lastName || ''}`} + + + )} @@ -175,21 +212,23 @@ const GroupDetails = () => {
{group.description || route.name} + - {group.members.length ?? 0} + {memberData.length ?? 0} Members - ₹{group.collected} + ₹{cycleDashboardData?.contributionAmount || group.collected} Contribution - + - ₹{group.goal} + ₹{cycleDashboardData?.contributionAmount * memberData.length || group.goal} Total Amount + {cycleItems.length > 0 && ( { placeholder="Select a Cycle" /> )} - + {selectedCycleData && cycleDashboardData && ( )} + Members - {group.status} + {cycleDashboardData?.status || group?.status} - {memberData.map((member:any) => ( + + {memberData.map((member: any) => ( @@ -221,14 +262,15 @@ const GroupDetails = () => { ))} - {/* Button at the bottom inside scroll (no absolute positioning) */} - {group.status === 'ACTIVE' && !payable?.hasPaid && ( + + {cycleDashboardData?.status === 'ACTIVE' && !payable?.hasPaid && ( setOpenModal(true)} style={styles.button}> Contribute )} + {openModal && ( { ); }; + export default GroupDetails; + const styles = StyleSheet.create({ dropdownWrapper: { marginHorizontal: 15, @@ -261,6 +305,10 @@ const styles = StyleSheet.create({ borderRadius: 8, backgroundColor: '#fff', }, + headerText: { + fontSize: 15, + color: colors.black, + }, modalOverlay: { flex: 1, justifyContent: 'center', @@ -335,7 +383,7 @@ const styles = StyleSheet.create({ statusText: { fontSize: 14, fontWeight: '600', - color: colors.Home, + color: colors.black, }, memberCard: { flexDirection: "row", @@ -373,4 +421,20 @@ const styles = StyleSheet.create({ scrollContent: { paddingBottom: 50, }, + floatingWinnerContainer: { + position: 'absolute', + bottom: 80, + alignSelf: 'center', + backgroundColor: colors.pastelBlue, + paddingHorizontal: 15, + paddingVertical: 8, + borderRadius: 20, + elevation: 5, + zIndex: 10, + }, + floatingWinnerText: { + color: colors.black, + fontWeight: 'bold', + fontSize: 16, + }, }); diff --git a/src/screens/management/EditGroupScreen.tsx b/src/screens/management/EditGroupScreen.tsx index a78a2e4..fe22591 100644 --- a/src/screens/management/EditGroupScreen.tsx +++ b/src/screens/management/EditGroupScreen.tsx @@ -32,6 +32,7 @@ import { StackNavigationProp } from '@react-navigation/stack'; import WinnerUser from './WinnerUser'; + // --- Type Definitions --- interface Participant { id: string; @@ -42,6 +43,7 @@ interface Participant { } + export interface GroupDetails { id: string; groupName: string; @@ -63,15 +65,16 @@ export interface GroupDetails { organizerId: string; totalParticipants: number; participants: Participant[]; - imageBlob: string + imageBlob: string; } + export interface EditGroupPayload { groupName: string; description: string; maxParticipants: number; - minParticipants?:number; + minParticipants?: number; contributionAmount: number; customCycleDays: number | null; cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM'; @@ -82,6 +85,7 @@ export interface EditGroupPayload { } + // Correct props for a screen inside a nested Tab Navigator type EditGroupScreenProps = CompositeScreenProps< BottomTabScreenProps, @@ -89,16 +93,20 @@ type EditGroupScreenProps = CompositeScreenProps< >; + type EditingCard = 'groupInfo' | 'financialRules' | 'configuration' | null; + type GroupManagementNavProp = StackNavigationProp; // --- Main Screen Component --- const EditGroupScreen: React.FC = ({ route }) => { const { groupId } = route?.params; - console.log("edit group screen groupId : ", route?.params) + + console.log("edit group screen groupId : ", route?.params) const navigation = useNavigation(); + const [editingCard, setEditingCard] = useState(null); const [groupDetails, setGroupDetails] = useState(null); const [editableData, setEditableData] = useState({ @@ -114,6 +122,7 @@ const EditGroupScreen: React.FC = ({ route }) => { lateFeeValue: null, }); + const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [starting, setStarting] = useState(false); @@ -141,6 +150,7 @@ const EditGroupScreen: React.FC = ({ route }) => { }; + const fetchGroupDetails = useCallback(async () => { setLoading(true); setError(null); @@ -148,6 +158,7 @@ const EditGroupScreen: React.FC = ({ route }) => { const response = await apiCall(`${BASE_URL}fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string }; console.log("groupDetails", response) + if (response.status === 'success' && response.data) { setGroupDetails(response.data); initializeEditableData(response.data); @@ -160,14 +171,6 @@ const EditGroupScreen: React.FC = ({ route }) => { setLoading(false); } }, [groupId]); - // const getPaymentStatus = async()=>{ - // const response = await apiCall(`${BASE_URL}payments/group/${groupId}/payment/paid`, 'GET') as { status: string; data?: { currentCycle: number ,status: boolean }; message?: string }; - // console.log("paymentStatus",response) - // if (response.status === 'success') { - // setPaymentStatus(response.data?.status || false); - // setCurrentCycle(response.data?.currentCycle || null); - // } - // } const getPaymentStatus = async () => { const response = await apiCall(`${BASE_URL}payments/group/${groupId}/payment/paid`, 'GET') as { status: string; data?: { currentCycle: number, allPaid: boolean, selectedUser: any }; message?: string }; @@ -179,16 +182,19 @@ const EditGroupScreen: React.FC = ({ route }) => { } } + console.log("groupid : ", groupId) useEffect(() => { getPaymentStatus() }, []) + useEffect(() => { fetchGroupDetails(); }, [fetchGroupDetails]); + const handleCancel = () => { if (groupDetails) { initializeEditableData(groupDetails); @@ -197,10 +203,10 @@ const EditGroupScreen: React.FC = ({ route }) => { }; + const handleSave = async () => { if (!groupDetails) return; - if (editableData.maxParticipants < groupDetails.totalParticipants) { ToastAndroid.show(`Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`, ToastAndroid.SHORT); return; @@ -208,8 +214,10 @@ const EditGroupScreen: React.FC = ({ route }) => { setSaving(true); try { - await apiCall(`${BASE_URL}fund/groups/${groupDetails.id}/edit`, 'PUT', editableData); - ToastAndroid.show('Group has been updated.', ToastAndroid.SHORT); + const res = await apiCall(`${BASE_URL}fund/groups/${groupDetails.id}/edit`, 'PUT', editableData); + console.log("Edit Group Response: ", res,editableData) + + Alert.alert('Success', 'Group has been updated.'); setEditingCard(null); await fetchGroupDetails(); } catch (e: any) { @@ -220,16 +228,19 @@ const EditGroupScreen: React.FC = ({ route }) => { }; + const handleDistribute = async () => { - navigation.navigate('RandomSelectUserScreen', { groupId , cycleNumber: currentCycle || 0 }); + navigation.navigate('RandomSelectUserScreen', { groupId, cycleNumber: currentCycle || 0 }); }; + const handleStartGroup = async () => { if (!groupDetails) return; + Alert.alert( "Confirm Start", "Are you sure you want to start this group? This action cannot be undone.", @@ -256,13 +267,13 @@ const EditGroupScreen: React.FC = ({ route }) => { ); }; + const handleInvite = async () => { if (!groupDetails) return; setInviting(true); try { const response = await apiCall(`${BASE_URL}invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string }; - const inviteLink = response.data; if (inviteLink) { @@ -284,12 +295,16 @@ const EditGroupScreen: React.FC = ({ route }) => { }; + const handleUpdateField = (field: keyof EditGroupPayload, value: any) => { setEditableData(prev => ({ ...prev, [field]: value })); }; - const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean, isEditable?: boolean) => { + // --- CORRECTED FUNCTION --- + // The 'isEditable' parameter was removed, and the condition now correctly + // uses 'isEditing' to show the input fields. + const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean) => { const commonInputProps = { style: styles.input, placeholderTextColor: "#9CA3AF" }; const getInputForField = () => { @@ -328,7 +343,7 @@ const EditGroupScreen: React.FC = ({ route }) => { return ( {label} - {isEditing && isEditable ? ( + {isEditing ? ( {getInputForField()} ) : ( {displayValue} @@ -337,9 +352,11 @@ const EditGroupScreen: React.FC = ({ route }) => { ); } + const renderCard = (title: string, fields: Array<{ field: keyof EditGroupPayload, label: string, displayValue: () => string | number }>, cardKey: EditingCard) => { const isEditingThisCard = editingCard === cardKey; - + // This isEditable check correctly controls the visibility of the "Update" button itself. + const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED'; return ( @@ -353,6 +370,7 @@ const EditGroupScreen: React.FC = ({ route }) => { {fields.map(({ field, label, displayValue }) => + // The call now correctly matches the updated renderField signature renderField(field, label, displayValue(), isEditingThisCard) )} @@ -370,15 +388,16 @@ const EditGroupScreen: React.FC = ({ route }) => { ); } + if (loading) return ; if (error) return {error}; if (!groupDetails) return null; - const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED'; const isInvitable = groupDetails.status === 'APPROVED'; const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants; + console.log(" myu status : ", groupDetails.status) const pickImage = () => { @@ -399,7 +418,6 @@ const EditGroupScreen: React.FC = ({ route }) => { const maxSizeKB = 1000; const maxSizeBytes = maxSizeKB * 1024; - // Size validation if (selectedImage.fileSize && selectedImage.fileSize > maxSizeBytes) { const currentSizeKB = (selectedImage.fileSize / 1024).toFixed(2); Alert.alert( @@ -409,23 +427,15 @@ const EditGroupScreen: React.FC = ({ route }) => { return; } - // Additional validation for required fields if (!selectedImage.uri) { ToastAndroid.show("Invalid image file", ToastAndroid.SHORT); return; } - - // console.log("✅ Image selected:", { - // uri: selectedImage.uri, - // size: (selectedImage?.fileSize / 1024).toFixed(2) + "KB", - // width: selectedImage.width, - // height: selectedImage.height - // }); - setSelectedImage(selectedImage.uri); }); }; + const handleUpdateImage = async () => { try { const credentials = await Keychain.getGenericPassword(); @@ -435,16 +445,18 @@ const EditGroupScreen: React.FC = ({ route }) => { } const token = credentials.password; + if (!selectedImage) { ToastAndroid.show("Please select an image first", ToastAndroid.SHORT); return; } + setImageUpdating(true); - // Get file extension and mime type dynamically const fileExtension = selectedImage.split('.').pop(); + const formData = new FormData(); formData.append("file", { uri: selectedImage, @@ -452,12 +464,13 @@ const EditGroupScreen: React.FC = ({ route }) => { name: `group_${groupId}_profile.${fileExtension || "jpg"}`, }); + const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`; + console.log("url : ", url) console.log("formdata : ", formData) - // return const response = await fetch(url, { method: "PUT", headers: { @@ -467,12 +480,14 @@ const EditGroupScreen: React.FC = ({ route }) => { body: formData, }); + console.log("image response : ", response) if (response?.status === 200) { fetchGroupDetails() ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT) } + } catch (error: any) { console.error("❌ Error updating group image:", error); } finally { @@ -480,6 +495,7 @@ const EditGroupScreen: React.FC = ({ route }) => { } }; + const getProfileImage = () => { return selectedImage ? selectedImage @@ -487,8 +503,6 @@ const EditGroupScreen: React.FC = ({ route }) => { ? `data:image/jpeg;base64,${groupDetails.imageBlob}` : "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg"; }; - // Helper function to get MIME type - return ( @@ -501,7 +515,6 @@ const EditGroupScreen: React.FC = ({ route }) => { )} - = ({ route }) => { console.log("Error loading image")} @@ -535,6 +547,7 @@ const EditGroupScreen: React.FC = ({ route }) => { ], 'groupInfo')} + {groupDetails.status === 'ACTIVE' ? renderCard('Financial Rules', [ { field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` }, @@ -551,12 +564,14 @@ const EditGroupScreen: React.FC = ({ route }) => { } + {renderCard('Configuration', [ { field: 'cycleType', label: 'Payment Cycle', displayValue: () => groupDetails.cycleType }, ...(groupDetails.cycleType === 'CUSTOM' ? [{ field: 'customCycleDays' as keyof EditGroupPayload, label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays} days` }] : []), { field: 'visibility', label: 'Visibility', displayValue: () => groupDetails.visibility }, ], 'configuration')} + Participants ({groupDetails.totalParticipants}) {groupDetails.participants.length > 0 ? ( @@ -577,7 +592,6 @@ const EditGroupScreen: React.FC = ({ route }) => { - {canStartGroup && ( = ({ route }) => { )} - {selectedUser?.cycleStatus !== "PENDING_PAYMENTS" && groupDetails.status === 'ACTIVE' && selectedUser?.cycleStatus === "PENDING_SELECTION" &&( + {selectedUser?.cycleStatus !== "PENDING_PAYMENTS" && groupDetails.status === 'ACTIVE' && selectedUser?.cycleStatus === "PENDING_SELECTION" && ( = ({ route }) => { } { - selectedUser?.selectedUser !== null && selectedUser?.cycleStatus === "READY_FOR_WITHDRAWAL" && + selectedUser?.selectedUser !== null && selectedUser?.cycleStatus === "READY_FOR_WITHDRAWAL" && = ({ route }) => { }; + const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F3F4F6' }, contentContainer: { padding: 16, paddingTop: 0, paddingBottom: 150 }, @@ -647,6 +662,7 @@ const styles = StyleSheet.create({ }, + avatarContainer: { position: "relative", marginVertical: 16, @@ -682,8 +698,8 @@ const styles = StyleSheet.create({ marginBottom: 12, }, cardTitle: { fontSize: 18, fontWeight: 'bold', color: '#1F2937' }, - editButton: { padding: 8,backgroundColor:colors.pastelOrange, borderRadius: 15,elevation:5 }, - editButtonText: { color: colors.black, fontSize: 15, fontWeight: 'bold' }, + editButton: { padding: 8, backgroundColor: colors.pastelOrange, borderRadius: 15, elevation: 5 }, + editButtonText: { color: colors.black, fontSize: 15, fontWeight: 'bold' }, fieldContainer: { borderBottomWidth: 1, borderBottomColor: '#F3F4F6', paddingBottom: 12, marginBottom: 12 }, infoLabel: { fontSize: 16, color: '#6B7280', marginBottom: 4 }, infoValue: { fontSize: 16, fontWeight: '600', color: '#111827', textTransform: 'capitalize' }, @@ -731,4 +747,5 @@ const styles = StyleSheet.create({ }); + export default EditGroupScreen; diff --git a/src/screens/management/Payments.tsx b/src/screens/management/Payments.tsx index 0b15242..eef0178 100644 --- a/src/screens/management/Payments.tsx +++ b/src/screens/management/Payments.tsx @@ -63,7 +63,7 @@ const Payments: React.FC = () => { // These are regular functions and do not call hooks inside them, which is correct. const getCycleData = async () => { try { - const response = await apiCall(`${API_BASE_URL}/groups/${groupId}/cycle-dashboard`, 'GET') as ApiResponse; + const response = await apiCall(`${BASE_URL}/payments/groups/${groupId}/cycle-dashboard`, 'GET') as ApiResponse; if (response && response.statusCode === 200) { setCycleData(response.data); } @@ -131,7 +131,7 @@ const Payments: React.FC = () => { } try { - const url = `${BASE_URL}groups/${groupId}/cycles/${paymentDetails.currentCycle}/waive/${currentParticipant.userId}?amountToWaive=${amountToWaive}`; + const url = `https://api.godemo.in/FundCollect/api/payments/groups/${groupId}/cycles/${paymentDetails.currentCycle}/waive/${currentParticipant.userId}?amountToWaive=${amountToWaive}`; const response = await apiCall(url, 'PUT', {}) as any; if (response && response.statusCode === 200) { @@ -248,8 +248,8 @@ const Payments: React.FC = () => { ₹{paymentDetails.contributionAmount} - Total Due - ₹{paymentDetails.totalDueWithLateFee} + Total Amount + ₹{paymentDetails.totalDueWithLateFee} Participants diff --git a/src/screens/management/RandomSelectUserScreen.tsx b/src/screens/management/RandomSelectUserScreen.tsx index 5625f60..cf57e1b 100644 --- a/src/screens/management/RandomSelectUserScreen.tsx +++ b/src/screens/management/RandomSelectUserScreen.tsx @@ -306,16 +306,9 @@ const styles = StyleSheet.create({ button: { paddingVertical: 16, paddingHorizontal: 50, - backgroundColor: '#4f46e5', + backgroundColor: colors.pastelOrange, borderRadius: 30, - elevation: 3, - shadowColor: "#4f46e5", - shadowOffset: { - width: 0, - height: 4, - }, - shadowOpacity: 0.3, - shadowRadius: 4.65, + elevation: 5, }, buttonDisabled: { backgroundColor: '#9ca3af', @@ -323,7 +316,7 @@ const styles = StyleSheet.create({ }, buttonText: { fontSize: 18, - color: '#fff', + color: colors.black, fontWeight: 'bold', }, winnerTextContainer: { diff --git a/src/screens/management/WinnerUser.tsx b/src/screens/management/WinnerUser.tsx index d69e7d2..3601d2b 100644 --- a/src/screens/management/WinnerUser.tsx +++ b/src/screens/management/WinnerUser.tsx @@ -4,6 +4,7 @@ import { Ionicons } from '@react-native-vector-icons/ionicons' import CustomModal from '../../components/common/CustomModal' import { apiCall } from '../../utils/ApiHelper' import { BASE_URL } from '../../utils/ApiUrls' +import colors from '../../components/colors' interface WinnerUserProps { data: { @@ -111,6 +112,7 @@ const WinnerUser: React.FC = ({ data, visible, onClose, groupId onClose={onClose} confirmText={canProceedToWithdrawal ? 'Proceed to Withdraw' : 'Close'} onConfirm={canProceedToWithdrawal ? onConfirm : onClose} + confirmButton={colors.pastelOrange} > diff --git a/src/screens/profile/Profile.tsx b/src/screens/profile/Profile.tsx index 4af1f0f..36d4824 100644 --- a/src/screens/profile/Profile.tsx +++ b/src/screens/profile/Profile.tsx @@ -375,7 +375,7 @@ const Profile = () => { User Profile confirmLogout(navigation)} style={styles.refreshButton}> - + {/* if (fetching || !user) { @@ -696,7 +696,7 @@ const styles = StyleSheet.create({ borderBottomRightRadius: 20, }, headerTitle: { - fontSize: 22, fontWeight: "600", color: colors.primary + fontSize: 22, fontWeight: "600", color: colors.black }, refreshButton: { padding: 4, diff --git a/src/utils/firebase/firebaseServices.ts b/src/utils/firebase/firebaseServices.ts index 2619b05..8f4422a 100644 --- a/src/utils/firebase/firebaseServices.ts +++ b/src/utils/firebase/firebaseServices.ts @@ -176,10 +176,7 @@ export const getFcmToken = async (userId: string) => { console.log('FCM Token:', token); // Send token to backend with user ID - await apiCall(STORE_FCM_TOKEN, 'POST', { - token, - userId - }); + await apiCall(`${STORE_FCM_TOKEN}${token}`, 'POST'); console.log('FCM Token sent to backend', token); return token;