diff --git a/package-lock.json b/package-lock.json index 7ec7f75..0887aee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "CircleCollect", "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", @@ -22,6 +24,7 @@ "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", @@ -2734,6 +2737,18 @@ "node": ">= 8" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native-community/cli": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.0.0.tgz", @@ -2967,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", @@ -3490,6 +3511,19 @@ "react-native-screens": ">= 4.0.0" } }, + "node_modules/@react-navigation/bottom-tabs/node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/@react-navigation/core": { "version": "7.12.4", "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.12.4.tgz", @@ -3537,6 +3571,19 @@ } } }, + "node_modules/@react-navigation/elements/node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/@react-navigation/native": { "version": "7.1.17", "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.17.tgz", @@ -3598,6 +3645,19 @@ "react-native-screens": ">= 4.0.0" } }, + "node_modules/@react-navigation/stack/node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4468,9 +4528,9 @@ } }, "node_modules/axios": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz", - "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==", + "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", @@ -5127,16 +5187,13 @@ "license": "MIT" }, "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "license": "MIT", "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" + "color-convert": "^1.9.3", + "color-string": "^1.6.0" } }, "node_modules/color-convert": { @@ -5167,6 +5224,21 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, "node_modules/colorette": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", @@ -7818,6 +7890,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8934,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": { @@ -9206,6 +9286,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9712,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", @@ -13111,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", @@ -13132,31 +13266,6 @@ "react-native-vector-icons": ">7.0.0" } }, - "node_modules/react-native-elements/node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/react-native-elements/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/react-native-elements/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, "node_modules/react-native-gesture-handler": { "version": "2.28.0", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", @@ -13236,31 +13345,6 @@ "react-native-safe-area-context": "*" } }, - "node_modules/react-native-paper/node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/react-native-paper/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/react-native-paper/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, "node_modules/react-native-ratings": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/react-native-ratings/-/react-native-ratings-8.0.4.tgz", @@ -13308,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", @@ -13446,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", @@ -14631,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", @@ -15198,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 12709bc..6a81609 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "test": "jest" }, "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", @@ -24,6 +26,7 @@ "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 index e423323..c2380e2 100644 --- a/src/assets/colors/colors.js +++ b/src/assets/colors/colors.js @@ -1,6 +1,6 @@ -// colors.js -export default { - ligtblue:'#98e2f4', +const colours = { + lightblue: '#98e2f4', + colorOne: '#83f5cc', primary: '#F4721E', secondary: '#FF9A62', textPrimary: '#333333', @@ -11,3 +11,5 @@ export default { 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/common/Custom.tsx b/src/components/common/Custom.tsx new file mode 100644 index 0000000..13a0021 --- /dev/null +++ b/src/components/common/Custom.tsx @@ -0,0 +1,175 @@ +import React from "react"; +import { + StyleSheet, + View, + Text, + TextInput, + TouchableOpacity, +} from "react-native"; +import { TextInputProps } from "react-native"; +import {Ionicons} from "@react-native-vector-icons/ionicons"; + +type CustomInputProps = TextInputProps & { + label: string; + value: string; + placeholder: string; + onChangeText: (text: string) => void; + icon?: string; +}; + +const CustomInput: React.FC = ({ + label, + value, + placeholder, + onChangeText, + icon, + ...rest +}) => ( + + {label} + + {icon && } + + + +); + +type CustomButtonProps = { + title: string; + onPress: () => void; + type?: "primary" | "secondary" | "danger" | "outline"; + icon?: string | null; + style?: object; +}; + +const CustomButton: React.FC = ({ + title, + onPress, + type = "primary", + icon, +}) => ( + + + {icon && } + + {title} + + + +); + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: "#f5f7fb" }, + loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center" }, + loadingText: { marginTop: 10, fontSize: 16, color: "#007AFF" }, + header: { + flexDirection: "row", + justifyContent: "space-between", + padding: 16, + paddingTop: 40, + backgroundColor: "#fff", + alignItems: "center", + borderBottomWidth: 1, + borderColor: "#eee", + }, + headerTitle: { fontSize: 20, fontWeight: "600", color: "#333" }, + profileCard: { + borderRadius: 16, + margin: 16, + backgroundColor: "#fff", + padding: 20, + shadowColor: "#000", + shadowOpacity: 0.1, + shadowRadius: 6, + elevation: 4, + }, + profileHeader: { alignItems: "center", marginBottom: 16 }, + avatar: { width: 100, height: 100, borderRadius: 50, marginBottom: 8 }, + cameraIcon: { + position: "absolute", + right: 110, + bottom: 10, + backgroundColor: "#007AFF", + borderRadius: 15, + width: 32, + height: 32, + justifyContent: "center", + alignItems: "center", + }, + detail: { + fontSize: 16, + marginBottom: 10, + color: "#444", + paddingLeft: 4, + }, + inputContainer: { marginBottom: 12 ,browRadius: 12}, + inputLabel: { fontSize: 14, fontWeight: "500", color: "#555"}, + inputWrapper: { + flexDirection: "row", + alignItems: "center", + position: "relative", + }, + inputIcon: { + position: "absolute", + left: 10, + zIndex: 1, + }, + input: { + flex: 1, + borderWidth: 1, + borderColor: "#ddd", + borderRadius: 20, + padding: 12, + paddingLeft: 12, + backgroundColor: "#fdfdfd", + color: "#000", + fontSize: 15, + }, + button: { + backgroundColor: "#007AFF", + paddingVertical: 12, + borderRadius: 12, + alignItems: "center", + marginVertical: 6, + shadowColor: "#007AFF", + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 3, + }, + buttonSecondary: { + backgroundColor: "#e5e5ea", + }, + buttonDanger: { + backgroundColor: "#ff3b30", + }, + buttonContent: { + flexDirection: "row", + alignItems: "center", + }, + buttonText: { color: "#fff", fontSize: 16, fontWeight: "600" }, + buttonTextSecondary: { color: "#333" }, + buttonTextDanger: { color: "#fff" }, +}); + +export { CustomInput, styles, CustomButton }; diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 8f0c0d1..a762f87 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -4,18 +4,19 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { View, Text, Button } from "react-native"; import LoginScreen from "../screens/LoginScreen"; import OtpVerificationScreen from "../screens/OtpVarificationScreen"; -import BottomTabs from "./BottomTabs"; import SplashScreen from "../screens/SplashScreen"; +import Profile from "../screens/profile/Profile"; import OrganizerRegisterScreen from "../screens/OrgnizerRagister"; import GroupCreation from "../screens/GroupCreation"; import GroupReview from "../screens/GroupReview"; +import BottomTabs from "./BottomTabs"; const Stack = createNativeStackNavigator(); - export type RootStackParamList = { +export type RootStackParamList = { Login: undefined; - OtpVarify:{ mobile: string }; - Home:undefined - HomeTabs:undefined + OtpVarify: { mobile: string }; + Home: undefined + HomeTabs: undefined splash: undefined OrganizerRegister:undefined Profile: undefined; @@ -35,13 +36,7 @@ function HomeScreen() { ); } -function ProfileScreen() { - return ( - - Profile Screen - - ); -} + export default function AppNavigator() { const linking = { @@ -55,12 +50,12 @@ export default function AppNavigator() { return ( - - - + + + + - - + diff --git a/src/navigation/BottomTabs.tsx b/src/navigation/BottomTabs.tsx index 25eeed2..8339caa 100644 --- a/src/navigation/BottomTabs.tsx +++ b/src/navigation/BottomTabs.tsx @@ -1,33 +1,46 @@ 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"; // ✅ corrected import +import { Ionicons } from "@react-native-vector-icons/ionicons"; import GroupScreen from "../screens/GroupScreen"; -import ProfileScreen from "../screens/ProfileScreen"; +import Profile from "../screens/profile/Profile"; const Tab = createBottomTabNavigator(); const TAB_CONFIG = { - activeColor: "#000", - inactiveColor: "#8e8e93", + activeColor: "#000", + inactiveColor: "#8e8e93", backgroundColor: "#fff", height: 70, iconSize: 26, }; +// 1. Define a color map for active tabs +const activeTabColors: { [key: string]: string } = { + Home: "#ff7675", // A soft red + Profile: "#74b9ff", // A nice blue + Group: "#55efc4", // Your theme's green + Settings: "#ffeaa7", // A gentle yellow +}; + +// 2. Define a text color map for contrasting icon colors +const activeIconColors: { [key: string]: string } = { + Home: "#333", + Profile: "#333", + Group: "#333", + Settings: "#e17055", +} + + function HomeScreen() { return ( - - 🏠 Home - + 🏠 Home ); } function SettingsScreen() { return ( - - ⚙️ Settings - + ⚙️ Settings ); } @@ -36,32 +49,41 @@ export default function BottomTabs() { ({ headerShown: false, - tabBarShowLabel: true, // ✅ no labels, only icons tabBarStyle: styles.tabBar, - - tabBarIcon: ({ color, focused }) => { - let iconName: string = "home-outline"; - 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"; + tabBarLabel: ({ focused, color }) => + !focused ? ( + {route.name} + ) : null, + + tabBarIcon: ({ focused }) => { + let iconName: string = "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"; + + // 3. Get the active background color and icon color from the maps + const activeBackgroundColor = activeTabColors[route.name]; + const activeIconColor = activeIconColors[route.name]; return ( ); @@ -69,7 +91,7 @@ export default function BottomTabs() { })} > - + @@ -95,7 +117,7 @@ const styles = StyleSheet.create({ right: 20, height: TAB_CONFIG.height, backgroundColor: TAB_CONFIG.backgroundColor, - borderRadius: 20 , + borderRadius: 20, shadowColor: "#000", shadowOpacity: 0.1, shadowOffset: { width: 0, height: 5 }, @@ -103,17 +125,25 @@ const styles = StyleSheet.create({ elevation: 10, paddingBottom: Platform.OS === "ios" ? 25 : 10, paddingTop: 10, - marginHorizontal:10 + marginHorizontal: 10, + }, + label: { + fontSize: 12, + marginBottom: 5, }, iconContainer: { justifyContent: "center", alignItems: "center", - width: 60, - height: 60, - marginTop:10, + width: 50, // Reduced size for a sleeker look + height: 50, // Reduced size + marginTop: 10, borderRadius: 15, }, activeIconContainer: { - backgroundColor: "#000", // active circle background + // 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..25092b9 100644 --- a/src/screens/GroupScreen.tsx +++ b/src/screens/GroupScreen.tsx @@ -1,46 +1,145 @@ 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"; + +// --- 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, + 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 +149,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/LoginScreen.tsx b/src/screens/LoginScreen.tsx index baab763..048ca04 100644 --- a/src/screens/LoginScreen.tsx +++ b/src/screens/LoginScreen.tsx @@ -27,9 +27,10 @@ const theme = { }; export default function LoginScreen() { + + const navigation = useNavigation>(); const [mobile, setMobile] = useState(""); - const navigation = useNavigation>(); - + const [loading, setLoading] = useState(false); // animations const fade = useRef(new Animated.Value(0)).current; @@ -43,17 +44,21 @@ export default function LoginScreen() { ]).start(); }, []); - const handleContinue = async () => { + const handleContinue = async () => { if (mobile.length === 10) { - console.log("Mobile submitted:", mobile); + setLoading(true); try { - const res = await axios.get(LOGIN_URL+mobile) - console.log('loginres',res.data) - if(res.data.statusCode === 200){ - navigation.navigate('OtpVarify', { mobile: mobile }) + console.log("Mobile submitted:", mobile); + const res = await axios.get(LOGIN_URL + mobile) + console.log('loginres', res.data) + if (res.data.statusCode === 200) { + navigation.navigate('OtpVarify', { mobile: mobile }) } } catch (error) { - + console.error("Login error:", error); + Alert.alert("Error", "Failed to send OTP. Please try again."); + }finally { + setLoading(false); } } else { Alert.alert("Please enter a valid 10-digit mobile number"); diff --git a/src/screens/OtpVarificationScreen.tsx b/src/screens/OtpVarificationScreen.tsx index 02a83e1..68d3cfd 100644 --- a/src/screens/OtpVarificationScreen.tsx +++ b/src/screens/OtpVarificationScreen.tsx @@ -14,6 +14,7 @@ import { KeyboardAvoidingView, StatusBar, Alert, + ToastAndroid, } from "react-native"; import { RootStackParamList } from "../navigation/AppNavigator"; import * as Keychain from "react-native-keychain"; @@ -84,13 +85,11 @@ export default function OtpVerificationScreen() { console.log("✅ OTP Verified:", res.data); if (res.data.statusCode === 200) { saveToken(res.data.data); - - navigation.navigate('HomeTabs') - - } + navigation.navigate('Home') + ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT);} } catch (err: any) { console.error("❌ OTP Verification Error:", err?.response?.data || err.message); - Alert.alert("Error", "Failed to verify OTP. Please try again."); + ToastAndroid.show(err.message || "Invalid OTP. Please try again.", ToastAndroid.SHORT); } }; @@ -100,16 +99,17 @@ export default function OtpVerificationScreen() { setTimer(30); // reset timer const res = await axios.get( - LOGIN_URL+mobile, + LOGIN_URL + mobile, { headers: { "Content-Type": "application/json", Accept: "*/*" } } ); console.log("📩 OTP Resent:", res.data); - - Alert.alert("OTP Sent", "A new OTP has been sent to your mobile."); + setCode(Array(CELL_COUNT).fill("")); + inputs.current[0]?.focus(); + ToastAndroid.show("OTP Sent", ToastAndroid.SHORT); } catch (err: any) { console.error("❌ Resend OTP Error:", err?.response?.data || err.message); - Alert.alert("Error", "Failed to resend OTP. Try again later."); + ToastAndroid.show("Failed to resend OTP. Try again later.", ToastAndroid.SHORT); } finally { setIsResending(false); } diff --git a/src/screens/ProfileScreen.tsx b/src/screens/ProfileScreen.tsx deleted file mode 100644 index d8a6ac0..0000000 --- a/src/screens/ProfileScreen.tsx +++ /dev/null @@ -1,770 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { - StyleSheet, - View, - Text, - ScrollView, - Image, - TouchableOpacity, - Alert, - ActivityIndicator, - StatusBar, - TextInput, - Platform, -} from "react-native"; -import * as Keychain from "react-native-keychain"; -import Ionicons from "react-native-vector-icons/Ionicons"; -import { launchImageLibrary, ImageLibraryOptions, Asset } from "react-native-image-picker"; -import { PROFILE_URL } from "../utils/ApiUrls"; - -type ButtonType = "primary" | "secondary" | "danger" | "outline"; - -type CustomButtonProps = { - title: string; - onPress: () => void; - type?: ButtonType; - icon?: string; - style?: any; - disabled?: boolean; -}; - -const CustomButton = ({ - title, - onPress, - type = "primary", - icon, - style, - disabled = false, -}: CustomButtonProps) => { - const getButtonStyle = () => { - switch (type) { - case "secondary": - return [styles.button, styles.buttonSecondary]; - case "danger": - return [styles.button, styles.buttonDanger]; - case "outline": - return [styles.button, styles.buttonOutline]; - default: - return styles.button; - } - }; - - const getTextStyle = () => { - switch (type) { - case "secondary": - return [styles.buttonText, styles.buttonTextSecondary]; - case "danger": - return [styles.buttonText, styles.buttonTextDanger]; - case "outline": - return [styles.buttonText, styles.buttonTextOutline]; - default: - return styles.buttonText; - } - }; - - const textColor = - Array.isArray(getTextStyle()) - ? getTextStyle().find((s: any) => s && typeof s === "object" && "color" in s)?.color || "#fff" - : (getTextStyle() as any).color || "#fff"; - - return ( - - {icon ? : null} - {title} - - ); -}; - -type CustomInputProps = { - label?: string; - placeholder?: string; - value?: string; - onChangeText?: (text: string) => void; - icon?: string; - [key: string]: any; -}; - -const CustomInput = ({ - label, - placeholder, - value, - onChangeText, - icon, - ...props -}: CustomInputProps) => { - return ( - - {label ? {label} : null} - - {icon ? : null} - - - - ); -}; - -type UserProfile = { - id: string; - userId: number; - firstName: string | null; - lastName: string | null; - mobile: string | null; - gender: string | null; - email: string | null; - username: string | null; - address: string | null; - bankName: string | null; - branchCode: string | null; - filePath: string | null; // local selection - bankAccountNumber: string | null; - imageBlob: string | null; // base64 from API - organizerStatus?: string; - role?: string; -}; - -const ProfileScreen = () => { - const [user, setUser] = useState(null); - const [fetching, setFetching] = useState(true); - const [editing, setEditing] = useState(false); - const [imageUpdating, setImageUpdating] = useState(false); - - const fetchUserProfile = async () => { - try { - const credentials = await Keychain.getGenericPassword(); - if (!credentials) { - Alert.alert("Error", "You are not logged in"); - return; - } - const token = credentials.password; - - setFetching(true); - const response = await fetch( - PROFILE_URL, - { - method: "GET", - headers: { - Accept: "application/json", - Authorization: `Bearer ${token}`, - }, - } - ); - - if (!response.ok) { - Alert.alert("Error", `Failed to fetch user profile (${response.status})`); - setFetching(false); - return; - } - - const result = await response.json(); - if (result.status === "success") { - setUser(result.data as UserProfile); - } else { - Alert.alert("Error", result.message || "Failed to fetch user profile"); - } - } catch (error) { - console.error("❌ Error fetching profile:", error); - Alert.alert("Error", "Could not connect to server"); - } finally { - setFetching(false); - } - }; - - const updateProfile = async () => { - try { - if (!user) return; - const credentials = await Keychain.getGenericPassword(); - if (!credentials) { - Alert.alert("Error", "You are not logged in"); - return; - } - const token = credentials.password; - - const response = await fetch( - "https://exotic-humpback-mistakenly.ngrok-free.app/api/user/profile/update", - { - method: "PUT", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ - address: user.address || null, - email: user.email || null, - firstName: user.firstName || null, - gender: user.gender || null, - lastName: user.lastName || null, - username: user.username || null, - }), - } - ); - - if (!response.ok) { - Alert.alert("Error", `Failed to update profile (${response.status})`); - return; - } - - const result = await response.json(); - if (result.status === "success") { - Alert.alert("Success", "Profile updated successfully"); - setEditing(false); - fetchUserProfile(); - } else { - Alert.alert("Error", result.message || "Failed to update profile"); - } - } catch (error) { - console.error("❌ Error updating profile:", error); - Alert.alert("Error", "Could not update profile"); - } - }; - - const pickImage = () => { - const options: ImageLibraryOptions = { - mediaType: "photo", - quality: 0.7, - selectionLimit: 1, - includeBase64: false, - }; - launchImageLibrary(options, (response) => { - if (response.didCancel) return; - if (response.errorCode) { - Alert.alert("Error", response.errorMessage || "Image picker error"); - return; - } - const asset: Asset | undefined = response.assets?.[0]; - if (!asset) return; - setUser((prev) => (prev ? { ...prev, filePath: asset.uri ?? null } : prev)); - }); - }; - - const handleUpdateImage = async () => { - try { - const credentials = await Keychain.getGenericPassword(); - if (!credentials) { - Alert.alert("Error", "You are not logged in"); - return; - } - const token = credentials.password; - - if (!user?.filePath) { - Alert.alert("Error", "Please select an image first"); - return; - } - - setImageUpdating(true); - - // Normalize uri for iOS if needed - const normalizedUri = - Platform.OS === "ios" ? user.filePath.replace("file://", "") : user.filePath; - - const formData = new FormData(); - // Best effort mime/name; servers often ignore name but it's helpful - const name = "profile.jpg"; - const type = "image/jpeg"; - - formData.append("file", { - uri: normalizedUri, - name, - type, - } as any); - - const response = await fetch( - "https://exotic-humpback-mistakenly.ngrok-free.app/api/user/update/userImg", - { - method: "PUT", - headers: { - Accept: "application/json", - Authorization: `Bearer ${token}`, - // Do NOT set Content-Type manually; let fetch add proper multipart boundary - }, - body: formData, - } - ); - - if (!response.ok) { - // Try to parse JSON; if fails, provide status - let message = `Failed to update profile image (${response.status})`; - try { - const err = await response.json(); - if (err?.message) message = err.message; - } catch {} - Alert.alert("Error", message); - return; - } - - const result = await response.json(); - if (result.status === "success") { - Alert.alert("Success", "Profile image updated successfully"); - - if (result.data?.imageBlob) { - setUser((prev) => - prev ? { ...prev, imageBlob: result.data.imageBlob, filePath: null } : prev - ); - } else { - setUser((prev) => prev ? { ...prev, filePath: user.filePath } : prev); - } - - fetchUserProfile(); - } else { - Alert.alert("Error", result.message || "Failed to update profile image"); - } - } catch (error) { - console.error("❌ Error updating profile image:", error); - Alert.alert("Error", "Could not update profile image"); - } finally { - setImageUpdating(false); - } - }; - - useEffect(() => { - fetchUserProfile(); - }, []); - - const getProfileImage = () => { - if (user?.imageBlob) { - return `data:image/jpeg;base64,${user.imageBlob}`; - } - if (user?.filePath) { - return user.filePath; - } - return "https://randomuser.me/api/portraits/men/1.jpg"; - }; - - if (fetching || !user) { - return ( - - - Loading profile... - - ); - } - - return ( - - - - - User Profile - - - - - - - - - - console.log("Error loading image")} - /> - - - - - - - - - {!editing ? ( - <> - - Personal Information - - - Become Organizer - - - - - - Name - - {(user.firstName || "") + " " + (user.lastName || "")} - - - - - - - - Email - {user.email || "Not provided"} - - - - - - - Mobile - {user.mobile || "Not provided"} - - - - - - - Username - {user.username || "-"} - - - - - - - Address - {user.address || "Not provided"} - - - - - - - Gender - {user.gender || "Not specified"} - - - - - - - Role - {user.role || "User"} - - - - - setEditing(true)} - icon="create" - style={styles.editButton} - /> - - ) : ( - <> - - Edit Profile - - - setUser((prev) => (prev ? { ...prev, firstName: text } : prev)) - } - icon="person" - /> - - - setUser((prev) => (prev ? { ...prev, lastName: text } : prev)) - } - icon="person" - /> - - - setUser((prev) => (prev ? { ...prev, email: text } : prev)) - } - icon="mail" - keyboardType="email-address" - autoCapitalize="none" - /> - - - setUser((prev) => (prev ? { ...prev, username: text } : prev)) - } - icon="at-circle" - autoCapitalize="none" - /> - - - setUser((prev) => (prev ? { ...prev, address: text } : prev)) - } - icon="location" - multiline - /> - - - setUser((prev) => (prev ? { ...prev, gender: text } : prev)) - } - icon="male-female" - /> - - - - - setEditing(false)} - icon="close" - style={styles.cancelButton} - /> - - - )} - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "#f8fafc", - }, - loadingContainer: { - flex: 1, - justifyContent: "center", - alignItems: "center", - backgroundColor: "#f8fafc", - }, - loadingText: { - marginTop: 10, - fontSize: 16, - color: "#64748b", - }, - header: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - padding: 16, - paddingTop: 50, - backgroundColor: "#6366f1", - borderBottomLeftRadius: 20, - borderBottomRightRadius: 20, - elevation: 4, - shadowColor: "#000", - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.2, - shadowRadius: 4, - }, - headerTitle: { - fontSize: 22, - fontWeight: "700", - color: "#fff", - }, - refreshButton: { - padding: 4, - }, - scrollContainer: { - flex: 1, - }, - profileCard: { - borderRadius: 20, - margin: 16, - backgroundColor: "#fff", - padding: 20, - shadowColor: "#000", - shadowOpacity: 0.1, - shadowRadius: 10, - shadowOffset: { width: 0, height: 4 }, - elevation: 5, - }, - profileImageSection: { - alignItems: "center", - marginBottom: 24, - paddingBottom: 20, - borderBottomWidth: 1, - borderBottomColor: "#f1f5f9", - }, - avatarContainer: { - position: "relative", - marginBottom: 16, - }, - avatar: { - width: 120, - height: 120, - borderRadius: 60, - borderWidth: 4, - borderColor: "#e2e8f0", - }, - cameraIcon: { - position: "absolute", - right: 0, - bottom: 0, - backgroundColor: "#6366f1", - borderRadius: 20, - width: 40, - height: 40, - justifyContent: "center", - alignItems: "center", - borderWidth: 3, - borderColor: "#fff", - elevation: 2, - }, - updateImageButton: { - width: "80%", - }, - detailsSection: { - marginBottom: 24, - }, - sectionTitle: { - fontSize: 18, - fontWeight: "700", - color: "#1e293b", - marginBottom: 16, - paddingBottom: 8, - borderBottomWidth: 1, - borderBottomColor: "#f1f5f9", - }, - detailRow: { - flexDirection: "row", - alignItems: "center", - marginBottom: 16, - paddingHorizontal: 8, - }, - detailIcon: { - marginRight: 12, - }, - detailLabel: { - fontSize: 12, - color: "#64748b", - marginBottom: 2, - }, - detailText: { - fontSize: 16, - color: "#334155", - fontWeight: "500", - }, - editButton: { - marginTop: 8, - }, - editSection: { - marginBottom: 20, - }, - editButtons: { - gap: 12, - }, - saveButton: { - marginTop: 8, - }, - cancelButton: { - marginTop: 4, - }, - inputContainer: { - marginBottom: 16, - }, - inputLabel: { - fontSize: 14, - fontWeight: "600", - color: "#374151", - marginBottom: 6, - }, - inputWrapper: { - flexDirection: "row", - alignItems: "center", - position: "relative", - }, - inputIcon: { - position: "absolute", - left: 12, - zIndex: 1, - }, - input: { - borderWidth: 1, - borderColor: "#d1d5db", - borderRadius: 12, - padding: 12, - backgroundColor: "#fff", - color: "#1f2937", - fontSize: 16, - flex: 1, - }, - inputWithIcon: { - paddingLeft: 40, - }, - button: { - backgroundColor: "#6366f1", - paddingVertical: 14, - borderRadius: 12, - alignItems: "center", - justifyContent: "center", - flexDirection: "row", - shadowColor: "#6366f1", - shadowOpacity: 0.3, - shadowRadius: 6, - shadowOffset: { width: 0, height: 3 }, - elevation: 4, - }, - buttonSecondary: { - backgroundColor: "#f1f5f9", - }, - buttonDanger: { - backgroundColor: "#ef4444", - }, - buttonOutline: { - backgroundColor: "transparent", - borderWidth: 1, - borderColor: "#6366f1", - }, - buttonText: { - color: "#fff", - fontSize: 16, - fontWeight: "600", - }, - buttonTextSecondary: { - color: "#334155", - }, - buttonTextDanger: { - color: "#fff", - }, - buttonTextOutline: { - color: "#6366f1", - }, - buttonIcon: { - marginRight: 8, - }, -}); - -export default ProfileScreen; diff --git a/src/screens/SplashScreen.tsx b/src/screens/SplashScreen.tsx index 1e4258e..541c028 100644 --- a/src/screens/SplashScreen.tsx +++ b/src/screens/SplashScreen.tsx @@ -15,7 +15,7 @@ const SplashScreen = () => { const creds = await Keychain.getGenericPassword(); if (creds) { console.log("🔑 Found token:", creds.password); - navigation.replace("HomeTabs"); + navigation.replace("Home"); // go to bottom tabs if logged in } else { navigation.replace("Login"); } diff --git a/src/screens/profile/Profile.tsx b/src/screens/profile/Profile.tsx new file mode 100644 index 0000000..1a30dec --- /dev/null +++ b/src/screens/profile/Profile.tsx @@ -0,0 +1,632 @@ +import React, { useState, useEffect } from "react"; +import { + StyleSheet, + View, + Text, + ScrollView, + Image, + TouchableOpacity, + Alert, + ActivityIndicator, + StatusBar, + TextInput, + KeyboardAvoidingView, + Platform, +} from "react-native"; +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"; + + + +type UserProfile = { + id: string; + userId: number; + firstName: string | null; + lastName: string | null; + mobile: string | null; + gender: string | null; + email: string | null; + username: string | null; + address: string | null; + bankName: string | null; + branchCode: string | null; + filePath: string | null; + bankAccountNumber: string | null; + imageBlob: string | null; + organizerStatus?: string; + role?: string; +}; + +const Profile = () => { + const [user, setUser] = useState(null); + const [fetching, setFetching] = useState(true); + const [editing, setEditing] = useState(false); + const [imageUpdating, setImageUpdating] = useState(false); + + const fetchUserProfile = async () => { + try { + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; + + setFetching(true); + const response = await fetch( + `${base_URL}user/profile/view`, + { + method: "GET", + headers: { + Accept: "*/*", + Authorization: `Bearer ${token}`, + }, + } + ); + + const result = await response.json(); + if (result.status === "success") { + setUser(result.data); + } else { + Alert.alert("Error", "Failed to fetch user profile"); + } + } catch (error) { + console.error("❌ Error fetching profile:", error); + Alert.alert("Error", "Could not connect to server"); + } finally { + setFetching(false); + } + }; + + const updateProfile = async () => { + try { + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; + + const response = await fetch( + `${base_URL}user/profile/update`, + { + method: "PUT", + headers: { + Accept: "*/*", + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + address: user?.address || null, + email: user?.email || null, + firstName: user?.firstName || null, + gender: user?.gender || null, + lastName: user?.lastName || null, + username: user?.username || null, + }), + } + ); + + const result = await response.json(); + if (result.status === "success") { + Alert.alert("Success", "Profile updated successfully"); + setEditing(false); + fetchUserProfile(); // reload updated data + } else { + Alert.alert("Error", "Failed to update profile"); + } + } catch (error) { + console.error("❌ Error updating profile:", error); + Alert.alert("Error", "Could not update profile"); + } + }; + + const pickImage = () => { + launchImageLibrary({ mediaType: "photo", quality: 0.5 }, (response) => { + if (response.didCancel) return; + if (response.errorCode) { + Alert.alert("Error", response.errorMessage || "Something went wrong"); + } else if (response.assets && response.assets.length > 0) { + const selectedImage = response.assets[0]; + setUser((prev) => + prev ? { ...prev, filePath: selectedImage.uri ?? null } : prev + ); + } + }); + }; + + const handleUpdateImage = async () => { + try { + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; + + if (!user?.filePath) { + Alert.alert("Error", "Please select an image first"); + return; + } + + setImageUpdating(true); + + const formData = new FormData(); + formData.append("file", { + uri: user.filePath, + type: "image/jpeg", + name: "profile.jpg", + } as any); + + const response = await fetch( + `${base_URL}user/update/userImg`, + { + method: "PUT", + headers: { + Accept: "application/json", + Authorization: `Bearer ${token}`, + }, + body: formData, + } + ); + + const result = await response.json(); + if (result.status === "success") { + Alert.alert("Success", "Profile image updated successfully"); + + // If API returns updated image, set it + if (result.data?.imageBlob) { + setUser((prev) => prev ? { ...prev, imageBlob: result.data.imageBlob, filePath: null } : prev); + } else { + // Otherwise, keep the local filePath until next fetch + setUser((prev) => prev ? { ...prev, filePath: user.filePath } : prev); + } + + // Refresh profile from API as well + fetchUserProfile(); + } else { + Alert.alert("Error", result.message || "Failed to update profile image"); + } + } catch (error) { + console.error("❌ Error updating profile image:", error); + Alert.alert("Error", "Could not update profile image"); + } finally { + setImageUpdating(false); + } + }; + + + useEffect(() => { + fetchUserProfile(); + }, []); + + console.log("user image :", user?.filePath); + const getProfileImage = () => { + if (user?.imageBlob) { + return `data:image/jpeg;base64,${user.imageBlob}`; + } + if (user?.filePath) { + return user.filePath; + } + return "https://randomuser.me/api/portraits/men/1.jpg"; + }; + + + if (fetching || !user) { + return ( + + + Loading profile... + + ); + } + + return ( + + + + {/* Header */} + + User Profile + + + + + + + {/* Profile Card */} + + {/* Profile Image Section */} + + + console.log("Error loading image")} + /> + + + + + + + + + {!editing ? ( + <> + {/* Profile Details */} + + Personal Information + + + Become Organizer + + + + + + Name + {user.firstName} {user.lastName} + + + + + + + Email + {user.email || "Not provided"} + + + + + + + Mobile + {user.mobile || "Not provided"} + + + + + + + Username + {user.username} + + + + + + + Address + {user.address || "Not provided"} + + + + + + + Gender + {user.gender || "Not specified"} + + + + + + + Role + {user.role || "User"} + + + + + setEditing(true)} + icon="create" + style={styles.editButton} + /> + + ) : ( + <> + {/* Edit Form */} + + Edit Profile + + setUser({ ...user, firstName: text })} + icon="person" + /> + + setUser({ ...user, lastName: text })} + icon="person" + /> + + setUser({ ...user, email: text })} + icon="mail" + keyboardType="email-address" + /> + + setUser({ ...user, username: text })} + icon="at" + /> + setUser({ ...user, address: text })} + icon="location" + multiline + /> + setUser({ ...user, gender: text })} + icon="male-female" + /> + + + + setEditing(false)} + icon="close" + style={styles.cancelButton} + /> + + + )} + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#f8fafc", + marginBottom: '30%', + }, + loadingContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + backgroundColor: "#f8fafc" + }, + loadingText: { + marginTop: 10, + fontSize: 16, + color: "#64748b" + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + padding: 16, + paddingTop: 50, + backgroundColor: "#6366f1", + borderBottomLeftRadius: 20, + borderBottomRightRadius: 20, + elevation: 4, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + }, + headerTitle: { + fontSize: 22, + fontWeight: "700", + color: "#fff" + }, + refreshButton: { + padding: 4, + }, + + profileCard: { + borderRadius: 20, + margin: 16, + backgroundColor: "#fff", + padding: 20, + shadowColor: "#000", + shadowOpacity: 0.1, + shadowRadius: 10, + shadowOffset: { width: 0, height: 4 }, + elevation: 5, + }, + profileImageSection: { + alignItems: "center", + marginBottom: 24, + paddingBottom: 20, + borderBottomWidth: 1, + borderBottomColor: "#f1f5f9", + }, + avatarContainer: { + position: "relative", + marginBottom: 16, + }, + avatar: { + width: 120, + height: 120, + borderRadius: 60, + borderWidth: 4, + borderColor: "#e2e8f0", + }, + cameraIcon: { + position: "absolute", + right: 0, + bottom: 0, + backgroundColor: "#6366f1", + borderRadius: 20, + width: 40, + height: 40, + justifyContent: "center", + alignItems: "center", + borderWidth: 3, + borderColor: "#fff", + elevation: 2, + }, + updateImageButton: { + width: "80%", + }, + detailsSection: { + marginBottom: 24, + }, + sectionTitle: { + fontSize: 18, + fontWeight: "700", + color: "#1e293b", + marginBottom: 16, + paddingBottom: 8, + borderBottomWidth: 1, + borderBottomColor: "#f1f5f9", + }, + detailRow: { + flexDirection: "row", + alignItems: "center", + marginBottom: 16, + paddingHorizontal: 8, + }, + detailIcon: { + marginRight: 12, + }, + detailLabel: { + fontSize: 12, + color: "#64748b", + marginBottom: 2, + }, + detailText: { + fontSize: 16, + color: "#334155", + fontWeight: "500", + }, + editButton: { + marginTop: 8, + }, + editSection: { + marginBottom: 20, + flex: 1, + }, + editButtons: { + gap: 12, + }, + saveButton: { + marginTop: 8, + }, + cancelButton: { + marginTop: 4, + }, + inputContainer: { + marginBottom: 16, + }, + inputLabel: { + fontSize: 14, + fontWeight: "600", + color: "#374151", + marginBottom: 6, + }, + inputWrapper: { + flexDirection: "row", + alignItems: "center", + position: "relative", + }, + inputIcon: { + position: "absolute", + left: 12, + zIndex: 1, + }, + input: { + borderWidth: 1, + borderColor: "#d1d5db", + borderRadius: 12, + padding: 12, + backgroundColor: "#fff", + color: "#1f2937", + fontSize: 16, + flex: 1, + }, + inputWithIcon: { + paddingLeft: 40, + }, + button: { + backgroundColor: "#6366f1", + paddingVertical: 14, + borderRadius: 12, + alignItems: "center", + justifyContent: "center", + flexDirection: "row", + shadowColor: "#6366f1", + shadowOpacity: 0.3, + shadowRadius: 6, + shadowOffset: { width: 0, height: 3 }, + elevation: 4, + }, + buttonSecondary: { + backgroundColor: "#f1f5f9", + }, + buttonDanger: { + backgroundColor: "#ef4444", + }, + buttonOutline: { + backgroundColor: "transparent", + borderWidth: 1, + borderColor: "#6366f1", + }, + buttonText: { + color: "#fff", + fontSize: 16, + fontWeight: "600", + }, + buttonTextSecondary: { + color: "#334155", + }, + buttonTextDanger: { + color: "#fff", + }, + buttonTextOutline: { + color: "#6366f1", + }, + buttonIcon: { + marginRight: 8, + }, +}); + +export default Profile; \ No newline at end of file