manage group

This commit is contained in:
Abin Toppo
2025-09-18 13:45:39 +05:30
26 changed files with 2044 additions and 501 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
{ {
"postman.settings.dotenv-detection-notification-visibility": false "postman.settings.dotenv-detection-notification-visibility": false,
"java.compile.nullAnalysis.mode": "automatic"
} }
+11 -2
View File
@@ -1,9 +1,18 @@
/** /**
* @format * @format
*/ */
import React from 'react';
import { AppRegistry } from 'react-native'; import { AppRegistry } from 'react-native';
import App from './App'; import App from './App';
import { name as appName } from './app.json'; import { name as appName } from './app.json';
import { Provider } from 'react-redux';
import { store } from './src/redux/store';
AppRegistry.registerComponent(appName, () => App); // ✅ Wrapper with Provider
const ReduxWrapper = () => (
<Provider store={store}>
<App />
</Provider>
);
AppRegistry.registerComponent(appName, () => ReduxWrapper);
+237 -165
View File
@@ -8,7 +8,6 @@
"name": "CircleCollect", "name": "CircleCollect",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-community/slider": "^5.0.1", "@react-native-community/slider": "^5.0.1",
"@react-native-picker/picker": "^2.11.2", "@react-native-picker/picker": "^2.11.2",
"@react-native-vector-icons/ionicons": "^12.3.0", "@react-native-vector-icons/ionicons": "^12.3.0",
@@ -19,12 +18,14 @@
"@react-navigation/native": "^7.1.17", "@react-navigation/native": "^7.1.17",
"@react-navigation/native-stack": "^7.3.26", "@react-navigation/native-stack": "^7.3.26",
"@react-navigation/stack": "^7.4.8", "@react-navigation/stack": "^7.4.8",
"@reduxjs/toolkit": "^2.9.0",
"axios": "^1.12.2", "axios": "^1.12.2",
"install": "^0.13.0", "install": "^0.13.0",
"npm": "^11.6.0", "npm": "^11.6.0",
"react": "19.1.0", "react": "19.1.0",
"react-native": "0.81.1", "react-native": "0.81.1",
"react-native-calendars": "^1.1313.0", "react-native-calendars": "^1.1313.0",
"react-native-dialog": "^9.3.0",
"react-native-elements": "^3.4.3", "react-native-elements": "^3.4.3",
"react-native-image-picker": "^8.2.1", "react-native-image-picker": "^8.2.1",
"react-native-keychain": "^10.0.0", "react-native-keychain": "^10.0.0",
@@ -32,7 +33,8 @@
"react-native-paper": "^5.14.5", "react-native-paper": "^5.14.5",
"react-native-safe-area-context": "^5.6.1", "react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.16.0", "react-native-screens": "^4.16.0",
"react-native-walkthrough-tooltip": "^1.6.0" "react-native-walkthrough-tooltip": "^1.6.0",
"react-redux": "^9.2.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
@@ -2680,9 +2682,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.30", "version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@@ -2737,18 +2739,6 @@
"node": ">= 8" "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": { "node_modules/@react-native-community/cli": {
"version": "20.0.0", "version": "20.0.0",
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.0.0.tgz", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.0.0.tgz",
@@ -3511,19 +3501,6 @@
"react-native-screens": ">= 4.0.0" "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": { "node_modules/@react-navigation/core": {
"version": "7.12.4", "version": "7.12.4",
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.12.4.tgz", "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.12.4.tgz",
@@ -3542,12 +3519,6 @@
"react": ">= 18.2.0" "react": ">= 18.2.0"
} }
}, },
"node_modules/@react-navigation/core/node_modules/react-is": {
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
"integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
"license": "MIT"
},
"node_modules/@react-navigation/elements": { "node_modules/@react-navigation/elements": {
"version": "2.6.4", "version": "2.6.4",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.6.4.tgz", "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.6.4.tgz",
@@ -3571,19 +3542,6 @@
} }
} }
}, },
"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": { "node_modules/@react-navigation/native": {
"version": "7.1.17", "version": "7.1.17",
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.17.tgz", "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.17.tgz",
@@ -3645,17 +3603,30 @@
"react-native-screens": ">= 4.0.0" "react-native-screens": ">= 4.0.0"
} }
}, },
"node_modules/@react-navigation/stack/node_modules/color": { "node_modules/@reduxjs/toolkit": {
"version": "4.2.3", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-convert": "^2.0.1", "@standard-schema/spec": "^1.0.0",
"color-string": "^1.9.0" "@standard-schema/utils": "^0.3.0",
"immer": "^10.0.3",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
}, },
"engines": { "peerDependencies": {
"node": ">=12.5.0" "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
} }
}, },
"node_modules/@sideway/address": { "node_modules/@sideway/address": {
@@ -3706,6 +3677,18 @@
"@sinonjs/commons": "^3.0.0" "@sinonjs/commons": "^3.0.0"
} }
}, },
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
"license": "MIT"
},
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -3806,18 +3789,18 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.3.1", "version": "24.5.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.1.tgz",
"integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==", "integrity": "sha512-/SQdmUP2xa+1rdx7VwB9yPq8PaKej8TD5cQ+XfKDPWWC+VDJU4rvVVagXqKUzhKjtFoNA8rXDJAkCxQPAe00+Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~7.10.0" "undici-types": "~7.12.0"
} }
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.1.12", "version": "19.1.13",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
@@ -3865,6 +3848,12 @@
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "17.0.33", "version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@@ -4735,6 +4724,15 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/baseline-browser-mapping": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz",
"integrity": "sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/bl": { "node_modules/bl": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
@@ -4793,7 +4791,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
@@ -4812,9 +4809,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.25.4", "version": "4.26.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz",
"integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -4831,9 +4828,10 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001737", "baseline-browser-mapping": "^2.8.3",
"electron-to-chromium": "^1.5.211", "caniuse-lite": "^1.0.30001741",
"node-releases": "^2.0.19", "electron-to-chromium": "^1.5.218",
"node-releases": "^2.0.21",
"update-browserslist-db": "^1.1.3" "update-browserslist-db": "^1.1.3"
}, },
"bin": { "bin": {
@@ -4995,9 +4993,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001741", "version": "1.0.30001743",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz",
"integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -5187,13 +5185,16 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/color": { "node_modules/color": {
"version": "3.2.1", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-convert": "^1.9.3", "color-convert": "^2.0.1",
"color-string": "^1.6.0" "color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
} }
}, },
"node_modules/color-convert": { "node_modules/color-convert": {
@@ -5224,21 +5225,6 @@
"simple-swizzle": "^0.2.2" "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": { "node_modules/colorette": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
@@ -5522,9 +5508,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.1", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -5732,9 +5718,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.214", "version": "1.5.220",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.220.tgz",
"integrity": "sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==", "integrity": "sha512-TWXijEwR1ggr4BdAKrb1nMNqYLTx1/4aD1fkeZU+FVJGTKu53/T7UyHKXlqEX3Ub02csyHePbHmkvnrjcaYzMA==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/emittery": { "node_modules/emittery": {
@@ -5789,9 +5775,9 @@
} }
}, },
"node_modules/error-ex": { "node_modules/error-ex": {
"version": "1.3.2", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
@@ -7479,6 +7465,16 @@
"node": ">=16.x" "node": ">=16.x"
} }
}, },
"node_modules/immer": {
"version": "10.1.3",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz",
"integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -7890,15 +7886,6 @@
"node": ">=8" "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": { "node_modules/is-regex": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -8091,7 +8078,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/istanbul-lib-coverage": { "node_modules/istanbul-lib-coverage": {
@@ -8848,7 +8834,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
@@ -9286,18 +9271,6 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT" "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": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -9780,7 +9753,6 @@
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
@@ -9872,9 +9844,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.20", "version": "2.0.21",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.20.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
"integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==", "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-stream-zip": { "node_modules/node-stream-zip": {
@@ -12930,6 +12902,12 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/pretty-format/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/promise": { "node_modules/promise": {
"version": "8.3.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz",
@@ -13151,9 +13129,9 @@
} }
}, },
"node_modules/react-is": { "node_modules/react-is": {
"version": "18.3.1", "version": "19.1.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-native": { "node_modules/react-native": {
@@ -13245,6 +13223,15 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-dialog": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/react-native-dialog/-/react-native-dialog-9.3.0.tgz",
"integrity": "sha512-JEOJY/0AzTM9grIl0BL8o/IJPIJru7k5MPj9POTE9RRezUEtgn0YSvCpTlBtG0obWgOdzg2otMz1OQvMXS0wMQ==",
"license": "MIT",
"peerDependencies": {
"react-native": ">=0.63.0"
}
},
"node_modules/react-native-elements": { "node_modules/react-native-elements": {
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz", "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz",
@@ -13266,6 +13253,31 @@
"react-native-vector-icons": ">7.0.0" "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": { "node_modules/react-native-gesture-handler": {
"version": "2.28.0", "version": "2.28.0",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz",
@@ -13345,6 +13357,31 @@
"react-native-safe-area-context": "*" "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": { "node_modules/react-native-ratings": {
"version": "8.0.4", "version": "8.0.4",
"resolved": "https://registry.npmjs.org/react-native-ratings/-/react-native-ratings-8.0.4.tgz", "resolved": "https://registry.npmjs.org/react-native-ratings/-/react-native-ratings-8.0.4.tgz",
@@ -13491,6 +13528,29 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.14.2", "version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -13514,13 +13574,6 @@
"react": "^19.1.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-test-renderer/node_modules/react-is": {
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
"integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
"dev": true,
"license": "MIT"
},
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "3.6.2", "version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -13551,6 +13604,21 @@
"react-native": ">= 0.30.0" "react-native": ">= 0.30.0"
} }
}, },
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"license": "MIT",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -13582,9 +13650,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/regenerate-unicode-properties": { "node_modules/regenerate-unicode-properties": {
"version": "10.2.0", "version": "10.2.2",
"resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
"integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -13622,18 +13690,18 @@
} }
}, },
"node_modules/regexpu-core": { "node_modules/regexpu-core": {
"version": "6.2.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.3.1.tgz",
"integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "integrity": "sha512-DzcswPr252wEr7Qz8AyAVbfyBDKLoYp6eRA1We2Fa9qirRFSdtkP5sHr3yglDKy2BbA0fd2T+j/CUSKes3FeVQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"regenerate": "^1.4.2", "regenerate": "^1.4.2",
"regenerate-unicode-properties": "^10.2.0", "regenerate-unicode-properties": "^10.2.2",
"regjsgen": "^0.8.0", "regjsgen": "^0.8.0",
"regjsparser": "^0.12.0", "regjsparser": "^0.12.0",
"unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-ecmascript": "^2.0.0",
"unicode-match-property-value-ecmascript": "^2.1.0" "unicode-match-property-value-ecmascript": "^2.2.1"
}, },
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -13688,6 +13756,12 @@
"devOptional": true, "devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.10", "version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -14188,18 +14262,18 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/simple-swizzle": { "node_modules/simple-swizzle": {
"version": "0.2.2", "version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-arrayish": "^0.3.1" "is-arrayish": "^0.3.1"
} }
}, },
"node_modules/simple-swizzle/node_modules/is-arrayish": { "node_modules/simple-swizzle/node_modules/is-arrayish": {
"version": "0.3.2", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/sisteransi": { "node_modules/sisteransi": {
@@ -14687,7 +14761,6 @@
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/throat": { "node_modules/throat": {
@@ -14926,9 +14999,9 @@
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.10.0", "version": "7.12.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/unicode-canonical-property-names-ecmascript": { "node_modules/unicode-canonical-property-names-ecmascript": {
@@ -14956,9 +15029,9 @@
} }
}, },
"node_modules/unicode-match-property-value-ecmascript": { "node_modules/unicode-match-property-value-ecmascript": {
"version": "2.2.0", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
"integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -14966,9 +15039,9 @@
} }
}, },
"node_modules/unicode-property-aliases-ecmascript": { "node_modules/unicode-property-aliases-ecmascript": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
"integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -15146,7 +15219,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
+4 -2
View File
@@ -10,7 +10,6 @@
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-community/slider": "^5.0.1", "@react-native-community/slider": "^5.0.1",
"@react-native-picker/picker": "^2.11.2", "@react-native-picker/picker": "^2.11.2",
"@react-native-vector-icons/ionicons": "^12.3.0", "@react-native-vector-icons/ionicons": "^12.3.0",
@@ -21,12 +20,14 @@
"@react-navigation/native": "^7.1.17", "@react-navigation/native": "^7.1.17",
"@react-navigation/native-stack": "^7.3.26", "@react-navigation/native-stack": "^7.3.26",
"@react-navigation/stack": "^7.4.8", "@react-navigation/stack": "^7.4.8",
"@reduxjs/toolkit": "^2.9.0",
"axios": "^1.12.2", "axios": "^1.12.2",
"install": "^0.13.0", "install": "^0.13.0",
"npm": "^11.6.0", "npm": "^11.6.0",
"react": "19.1.0", "react": "19.1.0",
"react-native": "0.81.1", "react-native": "0.81.1",
"react-native-calendars": "^1.1313.0", "react-native-calendars": "^1.1313.0",
"react-native-dialog": "^9.3.0",
"react-native-elements": "^3.4.3", "react-native-elements": "^3.4.3",
"react-native-image-picker": "^8.2.1", "react-native-image-picker": "^8.2.1",
"react-native-keychain": "^10.0.0", "react-native-keychain": "^10.0.0",
@@ -34,7 +35,8 @@
"react-native-paper": "^5.14.5", "react-native-paper": "^5.14.5",
"react-native-safe-area-context": "^5.6.1", "react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.16.0", "react-native-screens": "^4.16.0",
"react-native-walkthrough-tooltip": "^1.6.0" "react-native-walkthrough-tooltip": "^1.6.0",
"react-redux": "^9.2.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
+1 -1
View File
@@ -15,7 +15,7 @@ const colors = {
textLight: "#555555", textLight: "#555555",
white: "#FFFFFF", white: "#FFFFFF",
black: "#000000", black: "#000000",
primary: "#007AFF", primary: "#a57ae9ff",
}; };
export default colors; export default colors;
+1 -1
View File
@@ -63,7 +63,7 @@ const CustomButton: React.FC<CustomButtonProps> = ({
? colors.pastelOrange ? colors.pastelOrange
: colors.primary); : colors.primary);
const textColor = type === "secondary" ? colors.textDark : colors.white; const textColor = type === "secondary" ? colors.textDark : '#000';
return ( return (
<TouchableOpacity <TouchableOpacity
+3
View File
@@ -11,6 +11,7 @@ import GroupCreation from "../screens/GroupCreation";
import GroupReview from "../screens/GroupReview"; import GroupReview from "../screens/GroupReview";
import BottomTabs from "./BottomTabs"; import BottomTabs from "./BottomTabs";
import BecomeOrg from "../screens/profile/BecomeOrg"; import BecomeOrg from "../screens/profile/BecomeOrg";
import EditGroupScreen from "../screens/management/EditGroupScreen";
const Stack = createNativeStackNavigator(); const Stack = createNativeStackNavigator();
export type RootStackParamList = { export type RootStackParamList = {
@@ -23,6 +24,7 @@ export type RootStackParamList = {
Profile: undefined; Profile: undefined;
GroupCreation: undefined; GroupCreation: undefined;
GroupReview: undefined; GroupReview: undefined;
EditGroup: { groupId: string}
}; };
@@ -60,6 +62,7 @@ export default function AppNavigator() {
<Stack.Screen name="GroupCreation" component={GroupCreation} options={{ headerShown: false }} /> <Stack.Screen name="GroupCreation" component={GroupCreation} options={{ headerShown: false }} />
<Stack.Screen name="GroupReview" component={GroupReview} options={{ headerShown: false }} /> <Stack.Screen name="GroupReview" component={GroupReview} options={{ headerShown: false }} />
<Stack.Screen name="BecomeOrg" component={BecomeOrg} options={{ headerShown: false }} /> <Stack.Screen name="BecomeOrg" component={BecomeOrg} options={{ headerShown: false }} />
<Stack.Screen name="EditGroup" component={EditGroupScreen} options={{ headerShown: false }} />
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
); );
+13
View File
@@ -0,0 +1,13 @@
// store.ts
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice"; // adjust the path
export const store = configureStore({
reducer: {
user: userReducer, // register user slice here
},
});
// ✅ Types for TypeScript
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
+70
View File
@@ -0,0 +1,70 @@
import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
export interface UserState {
id: string;
userId: number | null;
username: string;
firstName: string;
lastName: string;
gender: string;
mobile: string;
email: string;
address: string;
bankAccountNumber: string;
bankName: string;
branchCode: string;
profilePicture: string;
filePath: string;
imageBlob: string;
role: string;
organizerId: string;
organizerStatus: string;
profileStatus: boolean;
}
const initialState: UserState = {
id: "",
userId: null,
username: "",
firstName: "",
lastName: "",
gender: "",
mobile: "",
email: "",
address: "",
bankAccountNumber: "",
bankName: "",
branchCode: "",
profilePicture: "",
filePath: "",
imageBlob: "",
role: "",
organizerId: "",
organizerStatus: "",
profileStatus: false,
};
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {
// ✅ set all user details at once (e.g. after login)
setUserData: (state, action: PayloadAction<UserState>) => {
return { ...action.payload };
},
// ✅ update only selected fields (e.g. profile update)
updateUserData: (state, action: PayloadAction<Partial<UserState>>) => {
return { ...state, ...action.payload };
},
// ✅ clear user data (e.g. on logout)
clearUser: () => initialState,
},
});
// Action creators
export const { setUserData, updateUserData, clearUser } = userSlice.actions;
export default userSlice.reducer;
+6 -5
View File
@@ -16,6 +16,7 @@ import { CustomInput } from "../components/common/Custom";
import CustomButton from "../components/CustomButton"; import CustomButton from "../components/CustomButton";
import { Calendar } from "react-native-calendars"; import { Calendar } from "react-native-calendars";
import { Ionicons } from "@react-native-vector-icons/ionicons"; import { Ionicons } from "@react-native-vector-icons/ionicons";
import colors from "../components/colors";
const calendarTheme = { const calendarTheme = {
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
@@ -133,7 +134,7 @@ export default function GroupCreationForm() {
{presetAmounts.map((amount) => (<TouchableOpacity key={amount} onPress={() => handlePresetAmount(amount)} style={[styles.presetButton, form.contributionAmount === amount.toString() && styles.presetSelected]}><Text style={[styles.presetButtonText, form.contributionAmount === amount.toString() && styles.presetSelectedText]}>{amount}</Text></TouchableOpacity>))} {presetAmounts.map((amount) => (<TouchableOpacity key={amount} onPress={() => handlePresetAmount(amount)} style={[styles.presetButton, form.contributionAmount === amount.toString() && styles.presetSelected]}><Text style={[styles.presetButtonText, form.contributionAmount === amount.toString() && styles.presetSelectedText]}>{amount}</Text></TouchableOpacity>))}
</View> </View>
<View style={styles.amountContainer}> <View style={styles.amountContainer}>
<Slider style={styles.slider} minimumValue={0} maximumValue={50000} step={100} value={parseInt(form.contributionAmount, 10) || 0} onValueChange={handleAmountChange} minimumTrackTintColor="#83f5cc" maximumTrackTintColor="#ddd" thumbTintColor="#62b59a" /> <Slider style={styles.slider} minimumValue={0} maximumValue={50000} step={100} value={parseInt(form.contributionAmount, 10) || 0} onValueChange={handleAmountChange} minimumTrackTintColor={colors.pastelGreen} maximumTrackTintColor="#ddd" thumbTintColor="#62b59a" />
<View style={styles.amountInput}><CustomInput label="" placeholder="Amount" value={form.contributionAmount} onChangeText={(val) => handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" /></View> <View style={styles.amountInput}><CustomInput label="" placeholder="Amount" value={form.contributionAmount} onChangeText={(val) => handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" /></View>
</View> </View>
@@ -164,19 +165,19 @@ export default function GroupCreationForm() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flexGrow: 1, padding: 20, backgroundColor: "#f5f6fa" }, container: { flexGrow: 1, padding: 20, backgroundColor: "#f5f6fa" },
titleContainer: { backgroundColor: "#83f5cc", paddingHorizontal: 18, paddingVertical: 2, borderRadius: 15, alignSelf: "flex-start", marginBottom: 30 }, titleContainer: { paddingVertical: 2, borderRadius: 15, alignSelf: "flex-start", marginBottom: 30 },
title: { fontSize: 22, fontWeight: "bold", color: "#333" }, title: { fontSize: 22, fontWeight: "bold", color: "#333" },
label: { fontSize: 14, fontWeight: "600", marginBottom: 10, color: "#444" }, label: { fontSize: 14, fontWeight: "600", marginBottom: 10, color: "#444" },
toggleContainer: { flexDirection: "row", gap: 10, marginBottom: 15 }, toggleContainer: { flexDirection: "row", gap: 10, marginBottom: 15 },
toggleButton: { flex: 1, paddingVertical: 12, borderWidth: 1, borderColor: "#ddd", borderRadius: 12, alignItems: "center", backgroundColor: "#fff" }, toggleButton: { flex: 1, paddingVertical: 12, borderWidth: 1, borderColor: "#ddd", borderRadius: 12, alignItems: "center", backgroundColor: "#fff" },
selectedButton: { backgroundColor: "#83f5cc", borderColor: "#62b59a" }, selectedButton: { backgroundColor: colors.pastelGreen, borderColor: "#62b59a" },
toggleButtonText: { color: "#444", fontWeight: "600" }, toggleButtonText: { color: "#444", fontWeight: "600" },
selectedButtonText: { color: "#333" }, selectedButtonText: { color: "#333" },
participantsRow: { flexDirection: "row", justifyContent: "space-between", alignItems: 'flex-start', marginBottom: 10 }, participantsRow: { flexDirection: "row", justifyContent: "space-between", alignItems: 'flex-start', marginBottom: 10 },
participantInput: { flex: 0.48 }, participantInput: { flex: 0.48 },
presetContainer: { flexDirection: 'row', justifyContent: 'flex-start', gap: 10, marginBottom: 10 }, presetContainer: { flexDirection: 'row', justifyContent: 'flex-start', gap: 10, marginBottom: 10 },
presetButton: { paddingVertical: 8, paddingHorizontal: 15, backgroundColor: '#ddd', borderRadius: 20 }, presetButton: { paddingVertical: 8, paddingHorizontal: 15, backgroundColor: '#ddd', borderRadius: 20 },
presetSelected: { backgroundColor: '#83f5cc' }, presetSelected: { backgroundColor: colors.pastelGreen, },
presetButtonText: { color: '#555', fontWeight: '600' }, presetButtonText: { color: '#555', fontWeight: '600' },
presetSelectedText: { color: '#333' }, presetSelectedText: { color: '#333' },
amountContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 15, }, amountContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 15, },
@@ -184,7 +185,7 @@ const styles = StyleSheet.create({
amountInput: { width: 120, marginLeft: 10 }, amountInput: { width: 120, marginLeft: 10 },
cycleTypeContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 20 }, cycleTypeContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 20 },
cycleTypeButton: { paddingVertical: 10, paddingHorizontal: 15, backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 20, elevation: 1 }, cycleTypeButton: { paddingVertical: 10, paddingHorizontal: 15, backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 20, elevation: 1 },
cycleTypeSelected: { backgroundColor: '#83f5cc', borderColor: '#62b59a' }, cycleTypeSelected: { backgroundColor: colors.pastelGreen, borderColor: '#62b59a' },
cycleTypeButtonText: { color: '#444', fontWeight: '600', textTransform: 'capitalize' }, cycleTypeButtonText: { color: '#444', fontWeight: '600', textTransform: 'capitalize' },
cycleTypeSelectedText: { color: '#333' }, cycleTypeSelectedText: { color: '#333' },
datePickerButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 12, paddingVertical: 15, paddingHorizontal: 15, marginBottom: 5, }, datePickerButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 12, paddingVertical: 15, paddingHorizontal: 15, marginBottom: 5, },
+5 -4
View File
@@ -4,6 +4,7 @@ import { Ionicons } from "@react-native-vector-icons/ionicons";
import AppView from "../components/AppView"; import AppView from "../components/AppView";
import { apiCall } from "../utils/ApiHelper"; import { apiCall } from "../utils/ApiHelper";
import { GROUP_CREATION_URL } from "../utils/ApiUrls"; import { GROUP_CREATION_URL } from "../utils/ApiUrls";
import colors from "../components/colors";
// --- Simplified Dialog Component --- // --- Simplified Dialog Component ---
const ThemedDialog = ({ visible, isSuccess, onClose }: { visible: boolean; isSuccess: boolean; onClose: () => void; }) => { const ThemedDialog = ({ visible, isSuccess, onClose }: { visible: boolean; isSuccess: boolean; onClose: () => void; }) => {
@@ -114,7 +115,7 @@ export default function GroupReview({ route, navigation }: any) {
<TimelineSection icon="wallet-outline" title="Financial Details">{renderRow("Contribution", `${form.contributionAmount} INR`)}{renderRow("Cycle", form.cycleType)}{form.cycleType === "CUSTOM" && renderRow("Cycle Days", `${form.customCycleDays} days`)}{renderRow("Start Date", form.startDate)}</TimelineSection> <TimelineSection icon="wallet-outline" title="Financial Details">{renderRow("Contribution", `${form.contributionAmount} INR`)}{renderRow("Cycle", form.cycleType)}{form.cycleType === "CUSTOM" && renderRow("Cycle Days", `${form.customCycleDays} days`)}{renderRow("Start Date", form.startDate)}</TimelineSection>
<TouchableOpacity onPress={handleSubmit} style={styles.submitBtn} disabled={isLoading}> <TouchableOpacity onPress={handleSubmit} style={styles.submitBtn} disabled={isLoading}>
{isLoading ? <ActivityIndicator color="#333" /> : ( <> <Ionicons name="rocket-outline" size={22} color="#333" /> <Text style={styles.btnText}>Launch Group</Text> </> )} {isLoading ? <ActivityIndicator color="#333" /> : ( <> <Ionicons name="rocket-outline" size={22} color="#333" /> <Text style={styles.btnText}>Create Group</Text> </> )}
</TouchableOpacity> </TouchableOpacity>
</ScrollView> </ScrollView>
@@ -136,7 +137,7 @@ const styles = StyleSheet.create({
subtitle: { fontSize: 16, color: "#666", marginTop: 2 }, subtitle: { fontSize: 16, color: "#666", marginTop: 2 },
section: { flexDirection: 'row', marginBottom: 10 }, section: { flexDirection: 'row', marginBottom: 10 },
timeline: { alignItems: 'center', width: 40 }, timeline: { alignItems: 'center', width: 40 },
timelineIcon: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#83f5cc', justifyContent: 'center', alignItems: 'center', elevation: 3 }, timelineIcon: { width: 40, height: 40, borderRadius: 20, backgroundColor: colors.pastelGreen, justifyContent: 'center', alignItems: 'center', elevation: 3 },
timelineLine: { flex: 1, width: 2, backgroundColor: '#e0e0e0' }, timelineLine: { flex: 1, width: 2, backgroundColor: '#e0e0e0' },
content: { flex: 1, paddingLeft: 10, paddingBottom: 20 }, content: { flex: 1, paddingLeft: 10, paddingBottom: 20 },
sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#444', marginBottom: 10 }, sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#444', marginBottom: 10 },
@@ -146,12 +147,12 @@ const styles = StyleSheet.create({
value: { fontSize: 15, color: "#333", fontWeight: "600", textAlign: 'right', textTransform: 'capitalize' }, value: { fontSize: 15, color: "#333", fontWeight: "600", textAlign: 'right', textTransform: 'capitalize' },
descriptionContainer: { paddingVertical: 10 }, descriptionContainer: { paddingVertical: 10 },
descriptionText: { fontSize: 15, color: '#333', marginTop: 4, lineHeight: 22 }, descriptionText: { fontSize: 15, color: '#333', marginTop: 4, lineHeight: 22 },
submitBtn: { backgroundColor: "#83f5cc", flexDirection: 'row', justifyContent: 'center', alignItems: "center", paddingVertical: 16, borderRadius: 12, elevation: 3, marginTop: 20, borderWidth: 1.5, borderColor: '#62b59a' }, submitBtn: { backgroundColor: colors.pastelGreen, flexDirection: 'row', justifyContent: 'center', alignItems: "center", paddingVertical: 16, borderRadius: 12, elevation: 3, marginTop: 20, borderWidth: 1.5, borderColor: '#62b59a' },
btnText: { color: "#333", fontWeight: "bold", fontSize: 18, marginLeft: 10 }, btnText: { color: "#333", fontWeight: "bold", fontSize: 18, marginLeft: 10 },
modalBackdrop: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center' }, modalBackdrop: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center' },
dialogBox: { width: '85%', backgroundColor: 'white', borderRadius: 16, padding: 25, alignItems: 'center', elevation: 10 }, dialogBox: { width: '85%', backgroundColor: 'white', borderRadius: 16, padding: 25, alignItems: 'center', elevation: 10 },
dialogTitle: { fontSize: 22, fontWeight: 'bold', color: '#333', marginTop: 15, marginBottom: 5 }, dialogTitle: { fontSize: 22, fontWeight: 'bold', color: '#333', marginTop: 15, marginBottom: 5 },
dialogMessage: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 25 }, dialogMessage: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 25 },
dialogButton: { backgroundColor: '#83f5cc', paddingVertical: 12, paddingHorizontal: 40, borderRadius: 10 }, dialogButton: { backgroundColor: colors.pastelGreen, paddingVertical: 12, paddingHorizontal: 40, borderRadius: 10 },
dialogButtonText: { color: '#333', fontWeight: 'bold', fontSize: 16 }, dialogButtonText: { color: '#333', fontWeight: 'bold', fontSize: 16 },
}); });
+7 -2
View File
@@ -191,7 +191,7 @@ export default function GroupScreen() {
}; };
const renderContent = () => { const renderContent = () => {
let data ; let data: Group[];
switch (activeTab) { switch (activeTab) {
case "My Groups": data = myGroups; break; case "My Groups": data = myGroups; break;
case "Joined": data = joinedGroups; break; case "Joined": data = joinedGroups; break;
@@ -214,7 +214,12 @@ export default function GroupScreen() {
renderItem={({ item }) => ( renderItem={({ item }) => (
<GroupCard <GroupCard
item={item} item={item}
onJoin={activeTab === 'Public' ? handleJoinGroup : () => { }} onJoin={() => {
console.log('mmmm')
if (activeTab === 'Public') {
handleJoinGroup(item.id, 'currentUserId');
}
}}
onView={handleViewGroup} onView={handleViewGroup}
isGrid={isGridView} isGrid={isGridView}
/> />
+20 -24
View File
@@ -10,24 +10,29 @@ import {
Platform, Platform,
Animated, Animated,
StatusBar, StatusBar,
ActivityIndicator,
} from "react-native"; } from "react-native";
import { LOGIN_URL } from "../utils/ApiUrls"; import { LOGIN_URL } from "../utils/ApiUrls";
import axios from "axios"; import axios from "axios";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import type { StackNavigationProp } from '@react-navigation/stack'; import type { StackNavigationProp } from "@react-navigation/stack";
import { RootStackParamList } from "../navigation/AppNavigator"; import { RootStackParamList } from "../navigation/AppNavigator";
import { CustomButton, CustomInput } from "../components/common/Custom";
import colors from "../components/colors";
const theme = { const theme = {
primary: "#111111", primary: "#111111",
accent: "#FF7A00", accent: colors.primary,
background: "#F9F9F9", background: "#F9F9F9",
surface: "#FFFFFF",
text: "#1A1A1A", text: "#1A1A1A",
subText: "#6B6B6B", subText: "#6B6B6B",
outline: "#E5E5E5", outline: "#E5E5E5",
error: "#E53935",
success: "#2E7D32",
}; };
export default function LoginScreen() { export default function LoginScreen() {
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>(); const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
const [mobile, setMobile] = useState(""); const [mobile, setMobile] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -48,16 +53,14 @@ export default function LoginScreen() {
if (mobile.length === 10) { if (mobile.length === 10) {
setLoading(true); setLoading(true);
try { try {
console.log("Mobile submitted:", mobile); const res = await axios.get(LOGIN_URL + mobile);
const res = await axios.get(LOGIN_URL + mobile)
console.log('loginres', res.data)
if (res.data.statusCode === 200) { if (res.data.statusCode === 200) {
navigation.navigate('OtpVarify', { mobile: mobile }) navigation.navigate("OtpVarify", { mobile });
} }
} catch (error) { } catch (error) {
console.error("Login error:", error); console.error("Login error:", error);
Alert.alert("Error", "Failed to send OTP. Please try again."); Alert.alert("Error", "Failed to send OTP. Please try again.");
}finally { } finally {
setLoading(false); setLoading(false);
} }
} else { } else {
@@ -102,15 +105,14 @@ export default function LoginScreen() {
{ opacity: fade, transform: [{ translateY: slide }] }, { opacity: fade, transform: [{ translateY: slide }] },
]} ]}
> >
<Text style={styles.inputLabel}>Mobile Number</Text> <CustomInput
<TextInput label="Mobile Number"
style={styles.input}
placeholder="Enter Mobile Number" placeholder="Enter Mobile Number"
placeholderTextColor={theme.subText}
keyboardType="phone-pad"
maxLength={10}
value={mobile} value={mobile}
onChangeText={setMobile} onChangeText={setMobile}
keyboardType="phone-pad"
maxLength={10}
icon="call-outline"
/> />
</Animated.View> </Animated.View>
</View> </View>
@@ -119,14 +121,8 @@ export default function LoginScreen() {
<Animated.View <Animated.View
style={[styles.bottomSection, { transform: [{ scale: ctaScale }] }]} style={[styles.bottomSection, { transform: [{ scale: ctaScale }] }]}
> >
<TouchableOpacity <CustomButton title={loading ? "Loading..." : "Continue"} onPress={handleContinue} icon="arrow-forward" />
style={styles.button}
onPress={handleContinue}
onPressIn={onPressIn}
onPressOut={onPressOut}
>
<Text style={styles.buttonText}>Continue</Text>
</TouchableOpacity>
<Text style={styles.footerText}> <Text style={styles.footerText}>
By continuing, you agree to our{" "} By continuing, you agree to our{" "}
<Text style={{ color: theme.accent, fontWeight: "600" }}> <Text style={{ color: theme.accent, fontWeight: "600" }}>
@@ -142,7 +138,7 @@ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: theme.background, padding: 20 }, container: { flex: 1, backgroundColor: theme.background, padding: 20 },
topSection: { flex: 1, justifyContent: "center" }, topSection: { flex: 1, justifyContent: "center" },
brand: { brand: {
fontSize: 20, fontSize: 30,
fontWeight: "700", fontWeight: "700",
color: theme.accent, color: theme.accent,
marginBottom: 24, marginBottom: 24,
@@ -166,7 +162,7 @@ const styles = StyleSheet.create({
color: theme.text, color: theme.text,
}, },
input: { input: {
backgroundColor: "#fff", backgroundColor: theme.surface,
borderRadius: 14, borderRadius: 14,
padding: 14, padding: 14,
fontSize: 16, fontSize: 16,
+4 -3
View File
@@ -20,10 +20,11 @@ import { RootStackParamList } from "../navigation/AppNavigator";
import * as Keychain from "react-native-keychain"; import * as Keychain from "react-native-keychain";
import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls"; import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls";
import { StackNavigationProp } from "@react-navigation/stack"; import { StackNavigationProp } from "@react-navigation/stack";
import colors from "../components/colors";
const theme = { const theme = {
primary: "#111111", primary: "#111111",
accent: "#FF7A00", accent: colors.primary,
background: "#F9F9F9", background: "#F9F9F9",
text: "#1A1A1A", text: "#1A1A1A",
subText: "#6B6B6B", subText: "#6B6B6B",
@@ -245,13 +246,13 @@ const styles = StyleSheet.create({
cellFilled: { borderColor: "#D1D1D1" }, cellFilled: { borderColor: "#D1D1D1" },
bottomSection: { marginBottom: 30 }, bottomSection: { marginBottom: 30 },
button: { button: {
backgroundColor: theme.primary, backgroundColor: colors.primary,
paddingVertical: 18, paddingVertical: 18,
borderRadius: 14, borderRadius: 14,
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
buttonText: { color: "#fff", fontWeight: "700", fontSize: 16 }, buttonText: { color: "#000", fontWeight: "700", fontSize: 16 },
footerText: { footerText: {
textAlign: "center", textAlign: "center",
color: theme.subText, color: theme.subText,
+70 -4
View File
@@ -1,13 +1,75 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { View, Text, StyleSheet, ActivityIndicator, Animated } from "react-native"; import { View, Text, StyleSheet, ActivityIndicator, Animated, Alert } from "react-native";
import * as Keychain from "react-native-keychain"; import * as Keychain from "react-native-keychain";
import { StackNavigationProp } from "@react-navigation/stack"; import { StackNavigationProp } from "@react-navigation/stack";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { RootStackParamList } from "../navigation/AppNavigator"; // adjust path import { RootStackParamList } from "../navigation/AppNavigator"; // adjust path
import { BASE_URL } from "../utils/ApiUrls";
import { useDispatch, useSelector } from "react-redux";
import { setUserData } from "../redux/userSlice";
import colors from "../components/colors";
const SplashScreen = () => { const SplashScreen = () => {
const dispatch = useDispatch();
const user = useSelector((state: any) => state.user);
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>(); const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
const fadeAnim = useRef(new Animated.Value(1)).current; // opacity starts at 1 const fadeAnim = useRef(new Animated.Value(1)).current;
const fetchUserProfile = 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/view`,
{
method: "GET",
headers: {
Accept: "*/*",
Authorization: `Bearer ${token}`,
},
}
);
const result = await response.json();
if (result.status === "success") {
dispatch(setUserData({
id: result?.data.id,
userId: result?.data.userId,
username: result?.data.username,
firstName: result?.data.firstName,
lastName: result?.data.lastName,
gender: result?.data.gender,
mobile: result?.data.mobile,
email: result?.data.email,
address: result?.data.address,
bankAccountNumber: result?.data.bankAccountNumber,
bankName: result?.data.bankName,
branchCode: result?.data.branchCode,
profilePicture: result?.data.filePath, // or keep as `filePath`
filePath: result?.data.filePath,
imageBlob: result?.data.imageBlob,
role: result?.data.role,
organizerId: result?.data.organizerId,
organizerStatus: result?.data.organizerStatus,
profileStatus: true, // set true once profile is fetched
}));
console.log("splash user data:", 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");
}
};
console.log("redux user", user);
useEffect(() => { useEffect(() => {
const checkLogin = async () => { const checkLogin = async () => {
@@ -36,12 +98,16 @@ const SplashScreen = () => {
}); });
}, [navigation, fadeAnim]); }, [navigation, fadeAnim]);
useEffect(() => {
fetchUserProfile();
}, [])
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Animated.Text style={[styles.title, { opacity: fadeAnim }]}> <Animated.Text style={[styles.title, { opacity: fadeAnim }]}>
Circle Collect Circle Collect
</Animated.Text> </Animated.Text>
<ActivityIndicator size="large" color="#FF7A00" style={{ marginTop: 20 }} /> <ActivityIndicator size="large" color={colors.Home} style={{ marginTop: 20 }} />
</View> </View>
); );
}; };
@@ -58,7 +124,7 @@ const styles = StyleSheet.create({
title: { title: {
fontSize: 28, fontSize: 28,
fontWeight: "800", fontWeight: "800",
color: "#FF7A00", color: colors.Home,
letterSpacing: 1, letterSpacing: 1,
}, },
}); });
+74
View File
@@ -0,0 +1,74 @@
import React, { useState } from "react";
import { View, Text, TextInput, Button, Modal, StyleSheet } from "react-native";
const DeclineModal = ({ visible, onClose, onSubmit }: {
visible: boolean;
onClose: () => void;
onSubmit: (reason: string) => void;
}) => {
const [reason, setReason] = useState("");
return (
<Modal
transparent
animationType="fade"
visible={visible}
onRequestClose={onClose}
>
<View style={styles.overlay}>
<View style={styles.container}>
<Text style={styles.title}>Decline Request</Text>
<TextInput
style={styles.input}
placeholder="Enter reason"
value={reason}
onChangeText={setReason}
/>
<View style={styles.actions}>
<Button title="Cancel" onPress={onClose} />
<Button
title="Decline"
onPress={() => {
onSubmit(reason);
setReason(""); // reset after submit
}}
color="red"
/>
</View>
</View>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0,0,0,0.5)",
},
container: {
width: "80%",
backgroundColor: "white",
padding: 20,
borderRadius: 10,
},
title: {
fontSize: 18,
marginBottom: 10,
},
input: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 5,
padding: 8,
marginBottom: 15,
},
actions: {
flexDirection: "row",
justifyContent: "space-between",
},
});
export default DeclineModal;
+192
View File
@@ -0,0 +1,192 @@
import React, { useState, useEffect } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
TextInput,
FlatList,
ActivityIndicator,
LayoutAnimation,
UIManager,
Platform,
Alert,
} from "react-native";
import { Ionicons } from "@react-native-vector-icons/ionicons";
import colors from "../../components/colors";
import * as keychain from "react-native-keychain";
import { BASE_URL } from "../../utils/ApiUrls";
// Enable LayoutAnimation for Android
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
const GroupCard = ({ item }: { item: any }) => {
const [expanded, setExpanded] = useState(false);
const toggleExpand = () => {
LayoutAnimation.easeInEaseOut();
setExpanded(!expanded);
};
const initial = item.groupName ? item.groupName.charAt(0).toUpperCase() : "?";
return (
<View style={styles.groupCard}>
<View style={styles.groupLeft}>
<View style={styles.groupAvatar}>
<Text style={styles.avatarText}>{initial}</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={styles.groupName}>{item.groupName}</Text>
<Text style={styles.groupDescription}>{item.description}</Text>
<Text style={styles.groupMeta}>
{item.visibility} • {item.cycleType}
</Text>
</View>
<TouchableOpacity onPress={toggleExpand}>
<Ionicons
name={expanded ? "chevron-up-outline" : "chevron-forward-outline"}
size={22}
color={colors.textLight}
/>
</TouchableOpacity>
</View>
{expanded && (
<View style={styles.detailsBox}>
{Object.entries(item).map(([key, value]) => (
<Text key={key} style={styles.detailText}>
<Text style={{ fontWeight: "600" }}>{key}:</Text>{" "}
{Array.isArray(value) ? value.join("-") : String(value)}
</Text>
))}
</View>
)}
</View>
);
};
// ✅ Group List with Search
const GroupList = () => {
const [groups, setGroups] = useState<any[]>([]);
const [filteredGroups, setFilteredGroups] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState("");
useEffect(() => {
const fetchGroups = async () => {
const credentials = await keychain.getGenericPassword();
if (!credentials) {
Alert.alert("Error", "You are not logged in");
return;
}
const token = credentials.password;
try {
const res = await fetch(`${BASE_URL}fund/getAll/my/groups`, {
headers: { Accept: "*/*", Authorization: `Bearer ${token}` },
});
console.log("my groups response", res);
const data = await res.json();
setGroups(data.data || []);
setFilteredGroups(data.data || []);
} catch (e) {
console.error("API error", e);
} finally {
setLoading(false);
}
};
fetchGroups();
}, []);
useEffect(() => {
if (search.trim() === "") {
setFilteredGroups(groups);
} else {
setFilteredGroups(
groups.filter((g) =>
g.groupName.toLowerCase().includes(search.toLowerCase())
)
);
}
}, [search, groups]);
if (loading) return <ActivityIndicator size="large" color={colors.Home} />;
return (
<>
<View style={styles.searchContainer}>
<Ionicons name="search" size={20} color={colors.textLight} />
<TextInput
placeholder="Search groups..."
placeholderTextColor={colors.textLight}
style={styles.searchInput}
value={search}
onChangeText={setSearch}
/>
</View>
<FlatList
data={filteredGroups}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <GroupCard item={item} />}
ListEmptyComponent={
<Text style={styles.emptyText}>No groups found</Text>
}
/>
</>
);
};
export default GroupList;
const styles = StyleSheet.create({
searchContainer: {
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.white,
borderRadius: 12,
paddingHorizontal: 12,
marginVertical: 10,
height: 45,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 5,
elevation: 2,
},
searchInput: { flex: 1, marginLeft: 8, color: colors.textDark },
groupCard: {
backgroundColor: colors.white,
padding: 14,
borderRadius: 14,
marginBottom: 12,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 2,
},
groupLeft: { flexDirection: "row", alignItems: "center" },
groupAvatar: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: colors.pastelPink,
justifyContent: "center",
alignItems: "center",
marginRight: 12,
},
avatarText: { fontSize: 18, fontWeight: "700", color: colors.Home },
groupName: { fontSize: 16, fontWeight: "600", color: colors.textDark },
groupDescription: { fontSize: 13, color: colors.textLight, marginTop: 2 },
groupMeta: { fontSize: 12, color: colors.Home, marginTop: 2 },
detailsBox: {
marginTop: 10,
padding: 10,
borderRadius: 8,
backgroundColor: "#f9f9f9",
},
detailText: { fontSize: 12, color: colors.textDark, marginBottom: 4 },
emptyText: { textAlign: "center", color: colors.textLight, marginTop: 20 },
centered: { flex: 1, justifyContent: "center", alignItems: "center" },
sectionTitle: { fontSize: 18, fontWeight: "600", color: colors.textDark },
});
+126
View File
@@ -0,0 +1,126 @@
import React, { useState, useEffect } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
} from "react-native";
import colors from "../../components/colors";
import GroupList from "./GroupList";
import RequestList from "./RequestList";
// ✅ Tab Layout
const GroupsTabs = () => {
const [activeTab, setActiveTab] = useState("MyGroups");
const renderContent = () => {
switch (activeTab) {
case "MyGroups":
return <GroupList />;
case "Requests":
return <RequestList />;
default:
return null;
}
};
return (
<>
{/* Tabs */}
<View style={styles.tabs}>
{["MyGroups", "Requests"].map((tab) => (
<TouchableOpacity
key={tab}
style={[
styles.tabButton,
activeTab === tab && styles.activeTabButton,
]}
onPress={() => setActiveTab(tab)}
>
<Text
style={[
styles.tabText,
activeTab === tab && styles.activeTabText,
]}
>
{tab === "MyGroups" ? "My Groups" : tab}
</Text>
</TouchableOpacity>
))}
</View>
{/* Content */}
{renderContent()}
</>
);
};
export default GroupsTabs;
const styles = StyleSheet.create({
tabs: {
flexDirection: "row",
justifyContent: "space-around",
marginVertical: 10,
borderBottomWidth: 1,
borderBottomColor: "#ddd",
},
tabButton: { paddingVertical: 10, flex: 1, alignItems: "center" },
tabText: { fontSize: 16, color: colors.textLight },
activeTabButton: { borderBottomWidth: 2, borderBottomColor: colors.Home },
activeTabText: { color: colors.Home, fontWeight: "600" },
searchContainer: {
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.white,
borderRadius: 12,
paddingHorizontal: 12,
marginVertical: 10,
height: 45,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 5,
elevation: 2,
},
searchInput: { flex: 1, marginLeft: 8, color: colors.textDark },
groupCard: {
backgroundColor: colors.white,
padding: 14,
borderRadius: 14,
marginBottom: 12,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 2,
},
groupLeft: { flexDirection: "row", alignItems: "center" },
groupAvatar: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: colors.Home + "33",
justifyContent: "center",
alignItems: "center",
marginRight: 12,
},
avatarText: { fontSize: 18, fontWeight: "700", color: colors.Home },
groupName: { fontSize: 16, fontWeight: "600", color: colors.textDark },
groupDescription: { fontSize: 13, color: colors.textLight, marginTop: 2 },
groupMeta: { fontSize: 12, color: colors.Home, marginTop: 2 },
detailsBox: {
marginTop: 10,
padding: 10,
borderRadius: 8,
backgroundColor: "#f9f9f9",
},
detailText: { fontSize: 12, color: colors.textDark, marginBottom: 4 },
emptyText: { textAlign: "center", color: colors.textLight, marginTop: 20 },
centered: { flex: 1, justifyContent: "center", alignItems: "center" },
sectionTitle: { fontSize: 18, fontWeight: "600", color: colors.textDark },
});
+39 -141
View File
@@ -1,82 +1,51 @@
import React from "react"; import React, { useEffect, useState } from "react";
import { import {
StyleSheet, StyleSheet,
Text, Text,
View, View,
TextInput,
TouchableOpacity, TouchableOpacity,
FlatList, UIManager,
Platform,
Image, Image,
} from "react-native"; } from "react-native";
import AppView from "../../components/AppView"; import AppView from "../../components/AppView";
import UserProfileStatus from "./UserProfileStatus"; import UserProfileStatus from "./UserProfileStatus";
import colors from "../../components/colors"; import colors from "../../components/colors";
import {Ionicons} from "@react-native-vector-icons/ionicons"; import { Ionicons } from "@react-native-vector-icons/ionicons";
import GroupsTabs from "./GroupsTabs";
const dummyChats = [ import { useSelector } from "react-redux";
{ id: "1", name: "Amit Sharma", lastMessage: "Sent ₹500", avatar: "https://i.pravatar.cc/100?img=1" }, import { useNavigation } from "@react-navigation/native";
{ id: "2", name: "Sneha Patel", lastMessage: "Requested ₹200", avatar: "https://i.pravatar.cc/100?img=2" }, // Enable animation for Android
{ id: "3", name: "Group Savings", lastMessage: "Target reached 🎉", avatar: "https://i.pravatar.cc/100?img=3" }, if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
]; UIManager.setLayoutAnimationEnabledExperimental(true);
}
const Home = () => { const Home = () => {
const user = useSelector((state: any) => state.user);
const navigation = useNavigation();
console.log("redux user data in Home:", user);
return ( return (
<AppView barStyle="dark-content"> <AppView barStyle="dark-content" style={styles.container}>
<View style={styles.container}> {/* Header */}
{/* Header */} <View style={styles.header}>
<View style={styles.header}> <Text style={styles.greeting}>Hello, {user.firstName} {user.lastName}👋</Text>
<Text style={styles.greeting}>Hello, Pooja 👋</Text> <TouchableOpacity onPress={() => navigation.navigate("Profile" as never)}>
<TouchableOpacity> {/* <Ionicons
<Ionicons name="person-circle-outline" size={40} color={colors.primary} /> name="person-circle-outline"
</TouchableOpacity> size={40}
</View> color={colors.primary}
/> */}
{/* Status */} <Image
<UserProfileStatus /> source={
user.imageBlob
{/* Search Bar */} && { uri: user.imageBlob ? `data:image/jpeg;base64,${user.imageBlob}` : user.profilePicture }
<View style={styles.searchContainer}> }
<Ionicons name="search" size={20} color={colors.textLight} /> style={{ width: 40, height: 40, borderRadius: 20, borderColor: colors.primary, borderWidth: 2 }}
<TextInput
placeholder="Search chats or funds..."
placeholderTextColor={colors.textLight}
style={styles.searchInput}
/> />
</View> </TouchableOpacity>
{/* Quick Actions */}
<View style={styles.quickActions}>
<TouchableOpacity style={styles.actionButton}>
<Ionicons name="send-outline" size={22} color={colors.primary} />
<Text style={styles.actionText}>Send</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton}>
<Ionicons name="cash-outline" size={22} color={colors.primary} />
<Text style={styles.actionText}>Request</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton}>
<Ionicons name="time-outline" size={22} color={colors.primary} />
<Text style={styles.actionText}>History</Text>
</TouchableOpacity>
</View>
{/* Recent Chats / Transactions */}
<Text style={styles.sectionTitle}>Recent Activity</Text>
<FlatList
data={dummyChats}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<TouchableOpacity style={styles.chatItem}>
<Image source={{ uri: item.avatar }} style={styles.avatar} />
<View style={styles.chatInfo}>
<Text style={styles.chatName}>{item.name}</Text>
<Text style={styles.chatMessage}>{item.lastMessage}</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} color={colors.textLight} />
</TouchableOpacity>
)}
/>
</View> </View>
{/* Status */}
<UserProfileStatus />
<GroupsTabs />
</AppView> </AppView>
); );
}; };
@@ -85,8 +54,7 @@ export default Home;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, paddingBottom: '25%',
// padding: 16,
}, },
header: { header: {
flexDirection: "row", flexDirection: "row",
@@ -94,77 +62,7 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
marginBottom: 12, marginBottom: 12,
}, },
greeting: { greeting: { fontSize: 22, fontWeight: "600", color: colors.Home },
fontSize: 22,
fontWeight: "600",
color: colors.textDark, });
},
searchContainer: {
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.white,
borderRadius: 12,
paddingHorizontal: 12,
marginVertical: 10,
height: 45,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 5,
elevation: 2,
},
searchInput: {
flex: 1,
marginLeft: 8,
color: colors.textDark,
},
quickActions: {
flexDirection: "row",
justifyContent: "space-between",
marginVertical: 16,
},
actionButton: {
alignItems: "center",
flex: 1,
},
actionText: {
marginTop: 6,
fontSize: 14,
color: colors.textDark,
},
sectionTitle: {
fontSize: 18,
fontWeight: "600",
marginVertical: 10,
color: colors.textDark,
},
chatItem: {
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.white,
padding: 12,
borderRadius: 12,
marginBottom: 10,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 1,
},
avatar: {
width: 45,
height: 45,
borderRadius: 22,
marginRight: 12,
},
chatInfo: {
flex: 1,
},
chatName: {
fontSize: 16,
fontWeight: "500",
color: colors.textDark,
},
chatMessage: {
fontSize: 14,
color: colors.textLight,
},
});
+606
View File
@@ -0,0 +1,606 @@
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
RefreshControl,
Alert,
ActivityIndicator,
TextInput
} from 'react-native';
import { Ionicons } from '@react-native-vector-icons/ionicons';
import colors from '../../components/colors';
import DeclineModal from './DeclineModal';
// Define TypeScript interfaces
interface GroupRequest {
requestId: string;
groupName: string;
groupId: string;
userId: number;
userName: string;
mobile: string;
status: string;
}
interface ApiResponse {
message: string;
status: string;
statusCode: number;
data: GroupRequest[];
}
const RequestList: React.FC = () => {
const [requests, setRequests] = useState<GroupRequest[]>([]);
const [filteredRequests, setFilteredRequests] = useState<GroupRequest[]>([]);
const [refreshing, setRefreshing] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState<string>('');
const [processingRequest, setProcessingRequest] = useState<string | null>(null);
const [showDeclineModal, setShowDeclineModal] = useState<{modal: boolean, requestId: string | null}>({modal: false, requestId: null});
const [selectedRequestId, setSelectedRequestId] = useState<string | null>(null);
// Fetch data from API
const fetchRequests = async (): Promise<void> => {
try {
setRefreshing(true);
setError(null);
const response = await fetch(
"https://api.godemo.in/FundCollect/api/fund/group/pending/request",
{
method: "GET",
headers: {
accept: "*/*",
Authorization: "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDkxMjEzLCJleHAiOjE3NTgxNzc2MTN9.T3BvU7nl8fnjwwVobIWRmSFFxQzOjtyIct1Ba0i9lIU"
}
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: ApiResponse = await response.json();
if (data.status === "success") {
setRequests(data.data);
setFilteredRequests(data.data);
} else {
throw new Error("Failed to fetch requests");
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
setError(errorMessage);
console.error("API Error:", error);
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchRequests();
}, []);
// Filter requests based on search query
useEffect(() => {
if (searchQuery.trim() === '') {
setFilteredRequests(requests);
} else {
const query = searchQuery.toLowerCase();
const filtered = requests.filter(request =>
request.groupName.toLowerCase().includes(query) ||
request.userName.toLowerCase().includes(query) ||
request.mobile.includes(query) ||
request.status.toLowerCase().includes(query)
);
setFilteredRequests(filtered);
}
}, [searchQuery, requests]);
const handleAccept = async (requestId: string): Promise<void> => {
Alert.alert(
"Accept Request",
"Are you sure you want to accept this request?",
[
{ text: "Cancel", style: "cancel" },
{
text: "Accept",
onPress: async () => {
try {
setProcessingRequest(requestId);
// Make API call to approve the request
const response = await fetch(
`https://api.godemo.in/FundCollect/api/fund/requests/${requestId}/approve`,
{
method: "PUT",
headers: {
"accept": "*/*",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDkxMjEzLCJleHAiOjE3NTgxNzc2MTN9.T3BvU7nl8fnjwwVobIWRmSFFxQzOjtyIct1Ba0i9lIU"
}
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Parse response
const data = await response.json();
if (data.status === "success") {
// Remove the accepted request from the list
const updatedRequests = requests.filter(req => req.requestId !== requestId);
setRequests(updatedRequests);
Alert.alert("Success", data.message || "Request accepted successfully");
fetchRequests();
} else {
throw new Error(data.message || "Failed to accept request");
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
Alert.alert("Error", `Failed to accept request: ${errorMessage}`);
console.error("Accept Error:", error);
} finally {
setProcessingRequest(null);
}
}
}
]
);
};
const handleDeclineConfirm = async (requestedId: string | null, reason: string) => {
setShowDeclineModal({modal: false, requestId: null});
try {
setProcessingRequest(requestedId);
// Make API call to decline the request
const response = await fetch(
`https://api.godemo.in/FundCollect/api/fund/requests/${requestedId}/reject?reason=/${reason}`,
{
method: "PUT",
headers: {
"accept": "*/*",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDkxMjEzLCJleHAiOjE3NTgxNzc2MTN9.T3BvU7nl8fnjwwVobIWRmSFFxQzOjtyIct1Ba0i9lIU"
}
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Parse response
const data = await response.json();
if (data.status === "success") {
// Remove the declined request from the list
const updatedRequests = requests.filter(req => req.requestId !== selectedRequestId);
setRequests(updatedRequests);
Alert.alert("Success", data.message || "Request declined successfully");
fetchRequests();
} else {
throw new Error(data.message || "Failed to decline request");
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
Alert.alert("Error", `Failed to decline request: ${errorMessage}`);
console.error("Decline Error:", error);
} finally {
setProcessingRequest(null);
}// call your API
};
const onRefresh = (): void => {
fetchRequests();
};
const getInitials = (name: string): string => {
return name
.split(' ')
.map(word => word.charAt(0))
.join('')
.toUpperCase()
.substring(0, 2);
};
const clearSearch = (): void => {
setSearchQuery('');
};
if (loading) {
return (
<View style={styles.centerContainer}>
<ActivityIndicator size="large" color="#3498db" />
<Text style={styles.loadingText}>Loading requests...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.centerContainer}>
<Ionicons name="alert-circle" size={64} color="#e74c3c" />
<Text style={styles.errorText}>Error loading requests</Text>
<Text style={styles.errorSubText}>{error}</Text>
<TouchableOpacity style={styles.retryButton} onPress={fetchRequests}>
<Text style={styles.retryButtonText}>Try Again</Text>
</TouchableOpacity>
</View>
);
}
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerTitle}>Pending Requests</Text>
<Text style={styles.headerSubtitle}>
{filteredRequests.length} pending group request{filteredRequests.length !== 1 ? 's' : ''}
{searchQuery ? ` matching "${searchQuery}"` : ''}
</Text>
</View>
{/* Search Bar */}
<View style={styles.searchContainer}>
<Ionicons name="search" size={20} color="#95a5a6" style={styles.searchIcon} />
<TextInput
style={styles.searchInput}
placeholder="Search by group, name, phone or status..."
value={searchQuery}
onChangeText={setSearchQuery}
placeholderTextColor="#95a5a6"
/>
{searchQuery ? (
<TouchableOpacity onPress={clearSearch} style={styles.clearButton}>
<Ionicons name="close-circle" size={20} color="#95a5a6" />
</TouchableOpacity>
) : null}
</View>
<ScrollView
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
contentContainerStyle={filteredRequests.length === 0 ? styles.emptyScrollView : undefined}
>
{filteredRequests.length === 0 ? (
<View style={styles.emptyState}>
{searchQuery ? (
<>
<Ionicons name="search" size={64} color="#ccc" />
<Text style={styles.emptyStateText}>No matching requests</Text>
<Text style={styles.emptyStateSubtext}>
No requests found for "{searchQuery}"
</Text>
<TouchableOpacity onPress={clearSearch} style={styles.clearSearchButton}>
<Text style={styles.clearSearchText}>Clear Search</Text>
</TouchableOpacity>
</>
) : (
<>
<Ionicons name="checkmark-done-circle" size={64} color="#ccc" />
<Text style={styles.emptyStateText}>No pending requests</Text>
<Text style={styles.emptyStateSubtext}>
You're all caught up! There are no pending group requests.
</Text>
</>
)}
</View>
) : (
filteredRequests.map((request) => (
<View key={request.requestId} style={styles.requestCard}>
<View style={styles.cardHeader}>
<View style={styles.groupInfo}>
<View style={styles.avatar}>
<Text style={styles.avatarText}>
{getInitials(request.groupName)}
</Text>
</View>
<View>
<Text style={styles.groupName}>{request.groupName}</Text>
<Text style={styles.requestText}>Group Join Request</Text>
</View>
</View>
<View style={[
styles.statusBadge,
request.status === 'PENDING' ? styles.pendingBadge : styles.otherBadge
]}>
<Text style={styles.statusText}>{request.status}</Text>
</View>
</View>
<View style={styles.divider} />
<View style={styles.userInfo}>
<Ionicons name="person" size={16} color="#666" />
<Text style={styles.userName}>{request.userName}</Text>
</View>
<View style={styles.userInfo}>
<Ionicons name="call" size={16} color="#666" />
<Text style={styles.userDetail}>{request.mobile}</Text>
</View>
<View style={styles.actions}>
<TouchableOpacity
style={[styles.button, styles.declineButton]}
onPress={() => setShowDeclineModal({modal:true, requestId: request.requestId})}
disabled={processingRequest === request.requestId}
>
{processingRequest === request.requestId ? (
<ActivityIndicator size="small" color="#e74c3c" />
) : (
<Text style={styles.declineButtonText}>Decline</Text>
)}
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.acceptButton]}
onPress={() => handleAccept(request.requestId)}
disabled={processingRequest === request.requestId}
>
{processingRequest === request.requestId ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Text style={styles.acceptButtonText}>Accept</Text>
)}
</TouchableOpacity>
</View>
</View>
))
)}
</ScrollView>
{showDeclineModal.modal && (
<DeclineModal
visible={showDeclineModal.modal}
onClose={() => setShowDeclineModal({modal: false, requestId: null})}
onSubmit={(reason) => handleDeclineConfirm(showDeclineModal.requestId, reason)}
/>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingBottom: '100%',
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f7fb',
padding: 20,
},
loadingText: {
fontSize: 16,
color: '#666',
marginTop: 12,
},
errorText: {
fontSize: 18,
fontWeight: '600',
color: '#e74c3c',
marginTop: 16,
marginBottom: 8,
},
errorSubText: {
fontSize: 14,
color: '#7f8c8d',
textAlign: 'center',
marginBottom: 20,
},
retryButton: {
backgroundColor: colors.pastelPink,
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 8,
},
retryButtonText: {
color: '#fff',
fontWeight: '600',
},
header: {
// backgroundColor: '#fff',
// // padding: 20,
// borderBottomWidth: 1,
// borderBottomColor: '#eee',
// shadowColor: '#000',
// shadowOffset: { width: 0, height: 2 },
// shadowOpacity: 0.1,
// shadowRadius: 3,
// elevation: 3,
},
headerTitle: {
fontSize: 24,
fontWeight: 'bold',
color: '#2c3e50',
marginBottom: 5,
},
headerSubtitle: {
fontSize: 14,
color: '#7f8c8d',
},
// Search styles
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
marginVertical: 16,
marginBottom: 8,
borderRadius: 10,
paddingHorizontal: 12,
height: 50,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2,
},
searchIcon: {
marginRight: 8,
},
searchInput: {
flex: 1,
height: '100%',
fontSize: 16,
color: '#2c3e50',
padding: 0,
},
clearButton: {
padding: 4,
},
clearSearchButton: {
marginTop: 16,
paddingVertical: 8,
paddingHorizontal: 16,
backgroundColor: '#ecf0f1',
borderRadius: 6,
},
clearSearchText: {
color: '#7f8c8d',
fontWeight: '500',
},
emptyScrollView: {
flexGrow: 1,
justifyContent: 'center',
},
requestCard: {
backgroundColor: '#fff',
borderRadius: 12,
marginBottom: 16,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
groupInfo: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
avatar: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: colors.pastelPink,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
avatarText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
groupName: {
fontSize: 18,
fontWeight: '600',
color: '#2c3e50',
},
requestText: {
fontSize: 14,
color: '#7f8c8d',
marginTop: 2,
},
statusBadge: {
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 12,
},
pendingBadge: {
backgroundColor: '#fff4e6',
},
otherBadge: {
backgroundColor: '#e8f5e9',
},
statusText: {
fontSize: 12,
fontWeight: '600',
},
divider: {
height: 1,
backgroundColor: '#ecf0f1',
marginVertical: 12,
},
userInfo: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
userName: {
fontSize: 16,
color: '#2c3e50',
marginLeft: 8,
},
userDetail: {
fontSize: 14,
color: '#7f8c8d',
marginLeft: 8,
},
actions: {
flexDirection: 'row',
justifyContent: 'flex-end',
marginTop: 16,
},
button: {
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 8,
marginLeft: 12,
minWidth: 100,
alignItems: 'center',
},
acceptButton: {
backgroundColor: '#2ecc71',
},
declineButton: {
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#e74c3c',
},
acceptButtonText: {
color: '#fff',
fontWeight: '600',
},
declineButtonText: {
color: '#e74c3c',
fontWeight: '600',
},
emptyState: {
alignItems: 'center',
justifyContent: 'center',
padding: 40,
},
emptyStateText: {
fontSize: 18,
fontWeight: '600',
color: '#7f8c8d',
marginTop: 16,
marginBottom: 8,
},
emptyStateSubtext: {
fontSize: 14,
color: '#bdc3c7',
textAlign: 'center',
},
});
export default RequestList;
+1 -1
View File
@@ -159,7 +159,7 @@ export default UserProfileStatus;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, // flex: 1,
// padding: 16, // padding: 16,
// backgroundColor: "#f5f7fb", // backgroundColor: "#f5f7fb",
}, },
+310
View File
@@ -0,0 +1,310 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import React, { useState, useEffect } from 'react';
import {
View,
Text,
TextInput,
StyleSheet,
ScrollView,
TouchableOpacity,
Alert,
ActivityIndicator,
} from 'react-native';
import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path
import { apiCall } from '../../utils/ApiHelper'; // Adjust path
import AppView from '../../components/AppView';
// --- Type Definitions ---
interface Participant {
id: string;
firstName: string;
lastName: string | null;
mobile: string;
role: 'ORGANIZER' | 'USER';
}
export interface GroupDetails {
id: string;
groupName: string;
description: string;
startDate: [number, number, number];
endDate: [number, number, number] | null;
contributionAmount: number;
maxParticipants: number;
latePaymentCharge: number | null;
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
visibility: 'PUBLIC' | 'PRIVATE';
organizerFirstName: string;
organizerLastName: string;
customCycleDays: number;
status: string;
totalParticipants: number;
participants: Participant[];
}
export interface EditGroupPayload {
groupName: string;
description: string;
visibility: 'PUBLIC' | 'PRIVATE';
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
contributionAmount: number;
maxParticipants: number;
latePaymentCharge: number;
customCycleDays: number;
startDate: string;
endDate: string | null;
}
type EditGroupScreenProps = NativeStackScreenProps<RootStackParamList, 'EditGroup'>;
// --- Helper Components for Read-Only View ---
const InfoRow: React.FC<{ label: string; value: string | number; isStatus?: boolean }> = ({ label, value, isStatus = false }) => (
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>{label}</Text>
<Text
style={[
styles.infoValue,
isStatus && { color: value === 'ACTIVE' ? '#059669' : '#D97706' } // Green for Active, Amber for others
]}
>
{value}
</Text>
</View>
);
const ParticipantRow: React.FC<{ participant: Participant }> = ({ participant }) => (
<View style={styles.participantRow}>
<View>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={styles.participantName}>{`${participant.firstName} ${participant.lastName || ''}`.trim()}</Text>
{participant.role === 'ORGANIZER' && (
<View style={styles.organizerBadge}>
<Text style={styles.organizerBadgeText}>Organizer</Text>
</View>
)}
</View>
<Text style={styles.participantMobile}>{participant.mobile}</Text>
</View>
</View>
);
// --- Main Screen Component ---
const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route, navigation }) => {
const { groupId } = route.params;
// --- State Management ---
const [isEditing, setIsEditing] = useState(false);
const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
// --- Form State ---
const [groupName, setGroupName] = useState('');
const [description, setDescription] = useState('');
const [contributionAmount, setContributionAmount] = useState('');
const [maxParticipants, setMaxParticipants] = useState('');
const [latePaymentCharge, setLatePaymentCharge] = useState('');
// --- Data Fetching ---
const fetchGroupDetails = async () => {
if (!groupId) {
setError("Group ID not provided.");
setLoading(false);
return;
}
setLoading(true);
try {
const response = await apiCall(
`https://api.godemo.in/FundCollect/api/fund/group/details?groupId=${groupId}`,
'GET',
) as { status: string; data?: GroupDetails; message?: string };
if (response.status === 'success' && response.data) {
setGroupDetails(response.data);
// Populate form state immediately
const data = response.data;
setGroupName(data.groupName);
setDescription(data.description);
setContributionAmount(data.contributionAmount.toString());
setMaxParticipants(data.maxParticipants.toString());
setLatePaymentCharge((data.latePaymentCharge || 0).toString());
} else {
throw new Error(response.message || 'Failed to fetch group data.');
}
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchGroupDetails();
}, [groupId]);
// --- Action Handlers ---
const handleCancelEdit = () => {
// Re-populate state from original details to discard any changes
if (groupDetails) {
setGroupName(groupDetails.groupName);
setDescription(groupDetails.description);
setContributionAmount(groupDetails.contributionAmount.toString());
setMaxParticipants(groupDetails.maxParticipants.toString());
setLatePaymentCharge((groupDetails.latePaymentCharge || 0).toString());
}
setIsEditing(false);
};
const handleSaveChanges = async () => {
if (!groupDetails) return;
setSaving(true);
const payload: EditGroupPayload = {
groupName,
description,
visibility: groupDetails.visibility, // Assuming these are not editable
cycleType: groupDetails.cycleType,
contributionAmount: parseFloat(contributionAmount) || 0,
maxParticipants: parseInt(maxParticipants, 10) || 0,
latePaymentCharge: parseFloat(latePaymentCharge) || 0,
customCycleDays: groupDetails.customCycleDays,
startDate: new Date(groupDetails.startDate.join('-')).toISOString().split('T')[0],
endDate: groupDetails.endDate ? new Date(groupDetails.endDate.join('-')).toISOString().split('T')[0] : null,
};
try {
const response = await apiCall(
`https://api.godemo.in/FundCollect/api/fund/groups/${groupDetails.id}/edit`, 'PUT', payload
) as { status: string; message?: string };
if (response.status === 'success') {
Alert.alert('Success', 'Group details have been updated.');
setIsEditing(false);
await fetchGroupDetails(); // Refresh data to show updated view
} else {
throw new Error(response.message || 'Failed to save changes.');
}
} catch (e: any) {
Alert.alert('Save Error', e.message);
} finally {
setSaving(false);
}
};
// --- Render Logic ---
if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color="#4F46E5" /></View>;
if (error) return <View style={styles.centered}><Text style={styles.errorText}>{error}</Text></View>;
if (!groupDetails) return null;
return (
<AppView paddingHorizontal={0}>
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
<View style={styles.header}>
<Text style={styles.headerTitle}>Group Overview</Text>
{!isEditing && groupDetails.status !== 'ACTIVE' ? (
<TouchableOpacity style={styles.editButton} onPress={() => setIsEditing(true)}>
<Text style={styles.editButtonText}>Edit</Text>
</TouchableOpacity>
) : isEditing ? (
<TouchableOpacity onPress={handleCancelEdit}>
<Text style={styles.cancelButtonText}>Cancel</Text>
</TouchableOpacity>
) : null}
</View>
{/* --- Main Details Card --- */}
<View style={styles.card}>
{isEditing ? (
<>
<Text style={styles.inputLabel}>Group Name</Text>
<TextInput style={styles.input} value={groupName} onChangeText={setGroupName} />
<Text style={styles.inputLabel}>Description</Text>
<TextInput style={[styles.input, styles.textArea]} value={description} onChangeText={setDescription} multiline />
</>
) : (
<>
<Text style={styles.groupNameView}>{groupDetails.groupName}</Text>
<Text style={styles.descriptionView}>{groupDetails.description}</Text>
</>
)}
<View style={styles.divider} />
<View style={styles.row}>
<View style={styles.column}>
<Text style={styles.inputLabel}>Contribution</Text>
<Text style={styles.dataText}>{`₹${groupDetails.contributionAmount.toLocaleString('en-IN')}`}</Text>
</View>
<View style={styles.column}>
<Text style={styles.inputLabel}>Cycle</Text>
<Text style={styles.dataText}>{groupDetails.cycleType}</Text>
</View>
</View>
</View>
{/* --- Participants Card --- */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants}/{groupDetails.maxParticipants})</Text>
{groupDetails.participants.map(p => <ParticipantRow key={`${p.id}-${p.mobile}`} participant={p} />)}
</View>
{/* --- Other Details Card (Always Read-Only) --- */}
<View style={styles.card}>
<InfoRow label="Group Status" value={groupDetails.status} isStatus />
<InfoRow label="Visibility" value={groupDetails.visibility} />
<InfoRow label="Start Date" value={new Date(groupDetails.startDate.join('-')).toLocaleDateString()} />
</View>
{isEditing && (
<TouchableOpacity style={styles.saveButton} onPress={handleSaveChanges} disabled={saving}>
{saving ? <ActivityIndicator color="#FFFFFF" /> : <Text style={styles.saveButtonText}>Save Changes</Text>}
</TouchableOpacity>
)}
</ScrollView>
</AppView>
);
};
// --- Styles ---
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F3F4F6' },
contentContainer: { paddingVertical: 20, paddingHorizontal: 16 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F3F4F6' },
errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' },
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 },
headerTitle: { fontSize: 26, fontWeight: 'bold', color: '#111827' },
editButton: { backgroundColor: '#E0E7FF', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20 },
editButtonText: { color: '#4338CA', fontSize: 14, fontWeight: '600' },
cancelButtonText: { fontSize: 16, color: '#6B7280', fontWeight: '500' },
card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, elevation: 1, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10, shadowOffset: { width: 0, height: 4 } },
cardTitle: { fontSize: 18, fontWeight: '600', color: '#1F2937', marginBottom: 10 },
groupNameView: { fontSize: 22, fontWeight: '700', color: '#1F2937', marginBottom: 8 },
descriptionView: { fontSize: 16, color: '#6B7280', lineHeight: 24 },
divider: { height: 1, backgroundColor: '#E5E7EB', marginVertical: 20 },
row: { flexDirection: 'row', gap: 20 },
column: { flex: 1 },
inputLabel: { fontSize: 14, color: '#6B7280', marginBottom: 8, fontWeight: '500' },
input: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, paddingHorizontal: 14, paddingVertical: 12, fontSize: 16, color: '#111827', marginBottom: 16 },
textArea: { height: 80, textAlignVertical: 'top' },
dataText: { fontSize: 18, fontWeight: '600', color: '#111827' },
infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' },
infoLabel: { fontSize: 16, color: '#6B7280' },
infoValue: { fontSize: 16, color: '#1F2937', fontWeight: '600' },
participantRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' },
participantName: { fontSize: 16, fontWeight: '600', color: '#111827' },
participantMobile: { fontSize: 14, color: '#6B7280', marginTop: 4 },
organizerBadge: { backgroundColor: '#FEF3C7', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 12, marginLeft: 10 },
organizerBadgeText: { color: '#92400E', fontSize: 12, fontWeight: '500' },
saveButton: { backgroundColor: '#4F46E5', borderRadius: 12, paddingVertical: 16, alignItems: 'center', marginTop: 10, elevation: 2 },
saveButtonText: { color: '#FFFFFF', fontSize: 16, fontWeight: '600' },
});
export default EditGroupScreen;
+5 -1
View File
@@ -49,6 +49,10 @@ const ManagementScreen: FC = () => {
apiCall(DECLINE_REQUEST_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>, // Assuming this is also needed apiCall(DECLINE_REQUEST_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>, // Assuming this is also needed
apiCall(REQUESTS_URL, "GET") as Promise<ApiResponse<JoinRequest[]>>, apiCall(REQUESTS_URL, "GET") as Promise<ApiResponse<JoinRequest[]>>,
]); ]);
console.log("My Groups:", myGroupsRes.data);
console.log("Joined Groups:", joinedGroupsRes.data);
console.log("Requests:", requestsRes.data);
setMyGroups(myGroupsRes.data || []); setMyGroups(myGroupsRes.data || []);
setJoinedGroups(joinedGroupsRes.data || []); setJoinedGroups(joinedGroupsRes.data || []);
setRequests(requestsRes.data || []); setRequests(requestsRes.data || []);
@@ -91,7 +95,7 @@ const ManagementScreen: FC = () => {
const handleApprove = (req: JoinRequest) => handleAction(APPROVE_REQUEST_URL+req.requestId+"/approve", { requestId: req.requestId }, `${req.userName} has been added.`, "Failed to approve request.","Approve"); const handleApprove = (req: JoinRequest) => handleAction(APPROVE_REQUEST_URL+req.requestId+"/approve", { requestId: req.requestId }, `${req.userName} has been added.`, "Failed to approve request.","Approve");
const handleDecline = (req: JoinRequest) => handleAction(DECLINE_REQUEST_URL+req.requestId+"/reject?reason=/"+" ", { requestId: req.requestId }, `Request from ${req.userName} has been declined.`, "Failed to decline request.","Decline"); const handleDecline = (req: JoinRequest) => handleAction(DECLINE_REQUEST_URL+req.requestId+"/reject?reason=/"+" ", { requestId: req.requestId }, `Request from ${req.userName} has been declined.`, "Failed to decline request.","Decline");
const handleManageGroup = (group: MyGroup) => Alert.alert("Manage Group", `Navigating to manage ${group.groupName}`); const handleManageGroup = (group: MyGroup) => navigation.navigate('EditGroup', { groupId: group.id });
const renderContent = () => { const renderContent = () => {
if (error) { if (error) {
+191 -143
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback} from "react";
import { import {
StyleSheet, StyleSheet,
View, View,
@@ -9,20 +9,22 @@ import {
Alert, Alert,
ActivityIndicator, ActivityIndicator,
StatusBar, StatusBar,
TextInput,
KeyboardAvoidingView, KeyboardAvoidingView,
Platform, Platform,
RefreshControl,
} from "react-native"; } from "react-native";
import * as Keychain from "react-native-keychain"; import * as Keychain from "react-native-keychain";
import { Ionicons } from "@react-native-vector-icons/ionicons"; // Assuming you use expo-cli or have this package import { Ionicons } from "@react-native-vector-icons/ionicons";
import { launchImageLibrary } from "react-native-image-picker"; import { launchImageLibrary } from "react-native-image-picker";
import { CustomButton, CustomInput } from "../../components/common/Custom"; import { CustomButton, CustomInput } from "../../components/common/Custom";
import colors from "../../components/colors"; import colors from "../../components/colors";
import SwitchNavigator from "./SwitchNavigator"; import SwitchNavigator from "./SwitchNavigator";
import { CommonActions, useFocusEffect, useNavigation } from "@react-navigation/native"; import { useFocusEffect } from "@react-navigation/native";
import { ApiResponse } from "../home/UserProfileStatus"; import { ApiResponse } from "../home/UserProfileStatus";
import { BASE_URL } from "../../utils/ApiUrls"; import { BASE_URL } from "../../utils/ApiUrls";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AppView from "../../components/AppView";
type UserProfile = { type UserProfile = {
id: string; id: string;
@@ -49,12 +51,10 @@ const Profile = () => {
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const [imageUpdating, setImageUpdating] = useState(false); const [imageUpdating, setImageUpdating] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false); // State for pull-to-refresh
const [statusData, setStatusData] = useState<null | { profileStatus: boolean; organizerStatus: string; role: string }>(null); const [statusData, setStatusData] = useState<null | { profileStatus: boolean; organizerStatus: string; role: string }>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const navigation = useNavigation<any>();
const fetchUserProfile = async (isRefreshing = false) => { const fetchUserProfile = async () => {
try { try {
const credentials = await Keychain.getGenericPassword(); const credentials = await Keychain.getGenericPassword();
if (!credentials) { if (!credentials) {
@@ -63,19 +63,20 @@ const Profile = () => {
} }
const token = credentials.password; const token = credentials.password;
if (!isRefreshing) { setFetching(true);
setFetching(true); const response = await fetch(
} `${BASE_URL}user/profile/view`,
{
const response = await fetch(`${BASE_URL}user/profile/view`, { method: "GET",
method: "GET", headers: {
headers: { Accept: "*/*",
Accept: "*/*", Authorization: `Bearer ${token}`,
Authorization: `Bearer ${token}`, },
}, }
}); );
const result = await response.json(); const result = await response.json();
console.log("👤 Profile Response:", result);
if (result.status === "success") { if (result.status === "success") {
setUser(result.data); setUser(result.data);
} else { } else {
@@ -85,10 +86,7 @@ const Profile = () => {
console.error("❌ Error fetching profile:", error); console.error("❌ Error fetching profile:", error);
Alert.alert("Error", "Could not connect to server"); Alert.alert("Error", "Could not connect to server");
} finally { } finally {
if (!isRefreshing) { setFetching(false);
setFetching(false);
}
setRefreshing(false);
} }
}; };
@@ -101,22 +99,25 @@ const Profile = () => {
} }
const token = credentials.password; const token = credentials.password;
const response = await fetch(`${BASE_URL}user/profile/update`, { const response = await fetch(
method: "PUT", `${BASE_URL}user/profile/update`,
headers: { {
Accept: "*/*", method: "PUT",
"Content-Type": "application/json", headers: {
Authorization: `Bearer ${token}`, Accept: "*/*",
}, "Content-Type": "application/json",
body: JSON.stringify({ Authorization: `Bearer ${token}`,
address: user?.address || null, },
email: user?.email || null, body: JSON.stringify({
firstName: user?.firstName || null, address: user?.address || null,
gender: user?.gender || null, email: user?.email || null,
lastName: user?.lastName || null, firstName: user?.firstName || null,
username: user?.username || null, gender: user?.gender || null,
}), lastName: user?.lastName || null,
}); username: user?.username || null,
}),
}
);
const result = await response.json(); const result = await response.json();
if (result.status === "success") { if (result.status === "success") {
@@ -169,23 +170,31 @@ const Profile = () => {
name: "profile.jpg", name: "profile.jpg",
} as any); } as any);
const response = await fetch(`${BASE_URL}user/update/userImg`, { const response = await fetch(
method: "PUT", `${BASE_URL}user/update/userImg`,
headers: { {
Accept: "application/json", method: "PUT",
Authorization: `Bearer ${token}`, headers: {
}, Accept: "application/json",
body: formData, Authorization: `Bearer ${token}`,
}); },
body: formData,
}
);
const result = await response.json(); const result = await response.json();
if (result.status === "success") { if (result.status === "success") {
Alert.alert("Success", "Profile image updated successfully"); Alert.alert("Success", "Profile image updated successfully");
// If API returns updated image, set it
if (result.data?.imageBlob) { if (result.data?.imageBlob) {
setUser((prev) => prev ? { ...prev, imageBlob: result.data.imageBlob, filePath: null } : prev); setUser((prev) => prev ? { ...prev, imageBlob: result.data.imageBlob, filePath: null } : prev);
} else { } else {
// Otherwise, keep the local filePath until next fetch
setUser((prev) => prev ? { ...prev, filePath: user.filePath } : prev); setUser((prev) => prev ? { ...prev, filePath: user.filePath } : prev);
} }
// Refresh profile from API as well
fetchUserProfile(); fetchUserProfile();
} else { } else {
Alert.alert("Error", result.message || "Failed to update profile image"); Alert.alert("Error", result.message || "Failed to update profile image");
@@ -212,15 +221,17 @@ const Profile = () => {
const response = await fetch(`${BASE_URL}user/profile/status`, { const response = await fetch(`${BASE_URL}user/profile/status`, {
method: "GET", method: "GET",
headers: { headers: {
Accept: "*/*", Accept: "/",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
}); });
const json: ApiResponse = await response.json(); const json: ApiResponse = await response.json();
console.log("👤 Profile Status Response:", json); console.log("👤 Profile Status Response:", json);
if (json.status === "success") { if (json.status === "success") {
setStatusData(json.data); setStatusData(json.data);
} else { } else {
setError(json.message || "Failed to fetch status"); setError(json.message || "Failed to fetch status");
} }
@@ -231,41 +242,7 @@ const Profile = () => {
} }
}; };
const handleLogout = async () => {
Alert.alert(
"Confirm Logout",
"Are you sure you want to log out?",
[
{ text: "Cancel", style: "cancel" },
{
text: "Logout",
style: "destructive",
onPress: async () => {
try {
await AsyncStorage.clear();
await Keychain.resetGenericPassword();
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: "Splash" }],
})
);
} catch (error) {
console.log("Logout error:", error);
Alert.alert("Error", "Could not log out. Please try again.");
}
},
},
]
);
};
const onRefresh = useCallback(() => {
setRefreshing(true);
fetchUserProfile(true).then(() => {
userStatus();
});
}, []);
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -274,9 +251,11 @@ const Profile = () => {
); );
useEffect(() => { useEffect(() => {
userStatus();
fetchUserProfile(); fetchUserProfile();
}, []); }, []);
console.log("user image :", user?.filePath);
const getProfileImage = () => { const getProfileImage = () => {
if (user?.imageBlob) { if (user?.imageBlob) {
return `data:image/jpeg;base64,${user.imageBlob}`; return `data:image/jpeg;base64,${user.imageBlob}`;
@@ -287,31 +266,25 @@ const Profile = () => {
return "https://randomuser.me/api/portraits/men/1.jpg"; return "https://randomuser.me/api/portraits/men/1.jpg";
}; };
if (fetching || !user) { if (fetching || !user) {
return ( return (
<View style={styles.loadingContainer}> <View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={colors.Profile} /> <ActivityIndicator size="large" color={colors.pastelBlue} />
<Text style={styles.loadingText}>Loading profile...</Text> <Text style={styles.loadingText}>Loading profile...</Text>
</View> </View>
); );
} }
return ( return (
<View style={styles.container}> <AppView>
<StatusBar barStyle="light-content" /> {/* Header */}
<View style={styles.header}> <View style={styles.header}>
<Text style={styles.headerTitle}>User Profile</Text> <Text style={styles.headerTitle}>User Profile</Text>
<View style={styles.headerIcons}> <TouchableOpacity onPress={fetchUserProfile} style={styles.refreshButton}>
<TouchableOpacity onPress={() => fetchUserProfile(true)} style={styles.refreshButton}> <Ionicons name="refresh" size={24} color={'#000'} />
<Ionicons name="refresh" size={24} color={colors.Profile} /> </TouchableOpacity>
</TouchableOpacity>
<TouchableOpacity onPress={handleLogout} style={styles.logoutButton}>
<Ionicons name="log-out-outline" size={26} color={"#ef4444"} />
</TouchableOpacity>
</View>
</View> </View>
<KeyboardAvoidingView <KeyboardAvoidingView
style={{ flex: 1 }} style={{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : undefined} behavior={Platform.OS === "ios" ? "padding" : undefined}
@@ -319,23 +292,16 @@ const Profile = () => {
<ScrollView <ScrollView
contentContainerStyle={{ flexGrow: 1 }} contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled" keyboardShouldPersistTaps="handled"
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={[colors.Profile]}
tintColor={colors.Profile}
/>
}
> >
{/* Profile Card */}
<View style={styles.profileCard}> <View style={styles.profileCard}>
{statusData?.role && statusData?.organizerStatus ? ( {/* Profile Image Section */}
<Text style={{ color: "#64748b", marginBottom: 16, textAlign: 'center', fontSize: 18 }}> {/* {
Your Role: {statusData.role} | Organizer Status: {statusData.organizerStatus} statusData?.role && statusData?.organizerStatus ?
</Text> <Text style={{ color: "#64748b", marginBottom: 16, textAlign: 'center', fontSize: 18 }}>Your Role: {statusData.role} | Organizer Status: {statusData.organizerStatus}</Text>
) : ( :
<SwitchNavigator role={statusData?.role || ''} status={statusData?.organizerStatus || ''} /> <SwitchNavigator role={statusData?.role || ''} status={statusData?.organizerStatus || ''} />} */}
)}
<View style={styles.profileImageSection}> <View style={styles.profileImageSection}>
<View style={styles.avatarContainer}> <View style={styles.avatarContainer}>
<Image <Image
@@ -344,84 +310,101 @@ const Profile = () => {
onError={() => console.log("Error loading image")} onError={() => console.log("Error loading image")}
/> />
<TouchableOpacity style={styles.cameraIcon} onPress={pickImage}> <TouchableOpacity style={styles.cameraIcon} onPress={pickImage}>
<Ionicons name="camera" size={18} color="white" /> <Ionicons name="camera" size={18} color="#000" />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<CustomButton <CustomButton
title={imageUpdating ? "Updating..." : "Update Image"} title={imageUpdating ? "Updating..." : "Update Image"}
onPress={handleUpdateImage} onPress={handleUpdateImage}
icon={imageUpdating ? undefined : "cloud-upload"} icon={imageUpdating ? undefined : "cloud-upload"}
style={styles.updateImageButton} style={styles.updateImageButton}
buttonBackground={colors.Profile} buttonBackground={colors.pastelBlue}
/> />
</View> </View>
{!editing ? ( {!editing ? (
<> <>
{/* Profile Details */}
<View style={styles.detailsSection}> <View style={styles.detailsSection}>
<Text style={styles.sectionTitle}>Personal Information</Text> <Text style={styles.sectionTitle}>Personal Information</Text>
<View style={{ marginBottom: 16 }}>
<Text style={{ color: "#64748b", marginBottom: 16 }}>Become Organizer</Text>
</View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Ionicons name="person" size={20} color={colors.Profile} style={styles.detailIcon} /> <Ionicons name="person" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
<View> <View>
<Text style={styles.detailLabel}>Name</Text> <Text style={styles.detailLabel}>Name</Text>
<Text style={styles.detailText}>{user.firstName} {user.lastName}</Text> <Text style={styles.detailText}>{user.firstName} {user.lastName}</Text>
</View> </View>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Ionicons name="mail" size={20} color={colors.Profile} style={styles.detailIcon} /> <Ionicons name="mail" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
<View> <View>
<Text style={styles.detailLabel}>Email</Text> <Text style={styles.detailLabel}>Email</Text>
<Text style={styles.detailText}>{user.email || "Not provided"}</Text> <Text style={styles.detailText}>{user.email || "Not provided"}</Text>
</View> </View>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Ionicons name="call" size={20} color={colors.Profile} style={styles.detailIcon} /> <Ionicons name="call" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
<View> <View>
<Text style={styles.detailLabel}>Mobile</Text> <Text style={styles.detailLabel}>Mobile</Text>
<Text style={styles.detailText}>{user.mobile || "Not provided"}</Text> <Text style={styles.detailText}>{user.mobile || "Not provided"}</Text>
</View> </View>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Ionicons name="at-circle" size={20} color={colors.Profile} style={styles.detailIcon} /> <Ionicons name="at-circle" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
<View> <View>
<Text style={styles.detailLabel}>Username</Text> <Text style={styles.detailLabel}>Username</Text>
<Text style={styles.detailText}>{user.username}</Text> <Text style={styles.detailText}>{user.username}</Text>
</View> </View>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Ionicons name="location" size={20} color={colors.Profile} style={styles.detailIcon} /> <Ionicons name="location" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
<View> <View>
<Text style={styles.detailLabel}>Address</Text> <Text style={styles.detailLabel}>Address</Text>
<Text style={styles.detailText}>{user.address || "Not provided"}</Text> <Text style={styles.detailText}>{user.address || "Not provided"}</Text>
</View> </View>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Ionicons name="male-female" size={20} color={colors.Profile} style={styles.detailIcon} /> <Ionicons name="male-female" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
<View> <View>
<Text style={styles.detailLabel}>Gender</Text> <Text style={styles.detailLabel}>Gender</Text>
<Text style={styles.detailText}>{user.gender || "Not specified"}</Text> <Text style={styles.detailText}>{user.gender || "Not specified"}</Text>
</View> </View>
</View> </View>
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Ionicons name="key" size={20} color={colors.Profile} style={styles.detailIcon} /> <Ionicons name="key" size={20} color={colors.pastelBlue} style={styles.detailIcon} />
<View> <View>
<Text style={styles.detailLabel}>Role</Text> <Text style={styles.detailLabel}>Role</Text>
<Text style={styles.detailText}>{user.role || "User"}</Text> <Text style={styles.detailText}>{user.role || "User"}</Text>
</View> </View>
</View> </View>
</View> </View>
<CustomButton <CustomButton
title="Edit Profile" title="Edit Profile"
onPress={() => setEditing(true)} onPress={() => setEditing(true)}
icon="create" icon="create"
style={styles.editButton} style={styles.editButton}
buttonBackground={colors.Profile} buttonBackground={colors.pastelBlue}
/> />
</> </>
) : ( ) : (
<> <>
{/* Edit Form */}
<View style={styles.editSection}> <View style={styles.editSection}>
<Text style={styles.sectionTitle}>Edit Profile</Text> <Text style={styles.sectionTitle}>Edit Profile</Text>
<CustomInput <CustomInput
label="First Name" label="First Name"
placeholder="Enter first name" placeholder="Enter first name"
@@ -429,6 +412,7 @@ const Profile = () => {
onChangeText={(text) => setUser({ ...user, firstName: text })} onChangeText={(text) => setUser({ ...user, firstName: text })}
icon="person" icon="person"
/> />
<CustomInput <CustomInput
label="Last Name" label="Last Name"
placeholder="Enter last name" placeholder="Enter last name"
@@ -436,6 +420,7 @@ const Profile = () => {
onChangeText={(text) => setUser({ ...user, lastName: text })} onChangeText={(text) => setUser({ ...user, lastName: text })}
icon="person" icon="person"
/> />
<CustomInput <CustomInput
label="Email" label="Email"
placeholder="Enter email" placeholder="Enter email"
@@ -444,6 +429,7 @@ const Profile = () => {
icon="mail" icon="mail"
keyboardType="email-address" keyboardType="email-address"
/> />
<CustomInput <CustomInput
label="Username" label="Username"
placeholder="Enter username" placeholder="Enter username"
@@ -488,59 +474,61 @@ const Profile = () => {
</View> </View>
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
</View> </AppView>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
// backgroundColor: colors.pastelBlue,
paddingBottom: '30%', paddingBottom: '30%',
}, },
loadingContainer: { loadingContainer: {
flex: 1, flex: 1,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
backgroundColor: "#f8fafc", backgroundColor: "#f8fafc"
}, },
loadingText: { loadingText: {
marginTop: 10, marginTop: 10,
fontSize: 16, fontSize: 16,
color: "#64748b", color: "#64748b"
}, },
header: { header: {
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
padding: 16, padding: 16,
paddingTop: 50, // paddingTop: 50,
// backgroundColor: "#6366f1",
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
// elevation: 4,
// shadowColor: "#000",
// shadowOffset: { width: 0, height: 2 },
// shadowOpacity: 0.2,
// shadowRadius: 4,
}, },
headerTitle: { headerTitle: {
fontSize: 22, fontSize: 22,
fontWeight: "700", fontWeight: "700",
color: "#000", color: "#000"
},
headerIcons: {
flexDirection: 'row',
alignItems: 'center',
gap: 16,
}, },
refreshButton: { refreshButton: {
padding: 4, padding: 4,
}, },
logoutButton: {
padding: 4,
},
profileCard: { profileCard: {
borderRadius: 20, borderRadius: 20,
margin: 16, margin: 16,
backgroundColor: "#fff", // backgroundColor: "#fff",
padding: 20, // padding: 20,
shadowColor: "#000", // shadowColor: "#000",
shadowOpacity: 0.1, // shadowOpacity: 0.1,
shadowRadius: 10, // shadowRadius: 10,
shadowOffset: { width: 0, height: 4 }, // shadowOffset: { width: 0, height: 4 },
elevation: 5, // elevation: 5,
}, },
profileImageSection: { profileImageSection: {
alignItems: "center", alignItems: "center",
@@ -564,7 +552,7 @@ const styles = StyleSheet.create({
position: "absolute", position: "absolute",
right: 0, right: 0,
bottom: 0, bottom: 0,
backgroundColor: "#6366f1", backgroundColor: colors.pastelBlue,
borderRadius: 20, borderRadius: 20,
width: 40, width: 40,
height: 40, height: 40,
@@ -615,6 +603,7 @@ const styles = StyleSheet.create({
}, },
editSection: { editSection: {
marginBottom: 20, marginBottom: 20,
flex: 1,
}, },
editButtons: { editButtons: {
gap: 12, gap: 12,
@@ -625,7 +614,66 @@ const styles = StyleSheet.create({
cancelButton: { cancelButton: {
marginTop: 4, marginTop: 4,
}, },
// Add any other styles you might need 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,
},
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; export default Profile;
+45
View File
@@ -36,3 +36,48 @@ export interface JoinRequest {
status: "PENDING"; status: "PENDING";
userAvatar?: string; userAvatar?: string;
} }
// src/types.ts
// Type for the data received from the GET group details API
export interface GroupDetails {
id: string;
groupName: string;
description: string;
startDate: [number, number, number];
endDate: [number, number, number] | null;
contributionAmount: number;
minParticipants: number;
maxParticipants: number;
portalCharge: number | null;
latePaymentCharge: number | null;
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
customCycleDays: number;
withdrawalMethod: 'RANDOM' | 'SEQUENTIAL';
status: 'PENDING' | 'ACTIVE' | 'COMPLETED';
visibility: 'PUBLIC' | 'PRIVATE';
organizerId: string;
organizerFirstName: string;
organizerLastName: string;
organizerMobile: string;
organizerEmail: string;
organizerRole: 'ORGANIZER';
organizerUserId: number;
isActive: boolean;
}
// Type for the payload sent to the edit group API
export interface EditGroupPayload {
groupName: string;
description: string;
visibility: 'PUBLIC' | 'PRIVATE';
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
contributionAmount: number;
maxParticipants: number;
latePaymentCharge: number;
customCycleDays: number;
startDate: string; // "YYYY-MM-DD"
endDate: string | null; // "YYYY-MM-DD"
}
+1 -1
View File
@@ -6,7 +6,7 @@ export const GROUP_CREATION_URL = `${BASE_URL}fund/groups/{organizerId}`
export const PROFILE_URL =`${BASE_URL}user/profile/view` export const PROFILE_URL =`${BASE_URL}user/profile/view`
export const PUBLIC_GROUPS_URL =`${BASE_URL}fund/user/public` export const PUBLIC_GROUPS_URL =`${BASE_URL}fund/user/public`
export const JOIN_GROUP_URL = `${BASE_URL}fund`; export const JOIN_GROUP_URL = `${BASE_URL}fund`;
export const MY_GROUPS_URL =`https://api.godemo.in/FundCollect/api/fund/getAll/my/groups` export const MY_GROUPS_URL =`https://api.godemo.in/FundCollect/api/fund/my/join/groups`
export const REQUESTS_URL =`https://api.godemo.in/FundCollect/api/fund/group/pending/request` export const REQUESTS_URL =`https://api.godemo.in/FundCollect/api/fund/group/pending/request`
//https://api.godemo.in/FundCollect/api/fund/groups/2a80cdaa-8532-4b8a-87f0-9c1023d18a7f/join/64 //https://api.godemo.in/FundCollect/api/fund/groups/2a80cdaa-8532-4b8a-87f0-9c1023d18a7f/join/64